@@ -22,7 +22,9 @@ import (
22
22
"strconv"
23
23
"time"
24
24
25
+ "github.com/nats-io/jwt/v2"
25
26
au "github.com/nats-io/natscli/internal/auth"
27
+ "github.com/nats-io/natscli/internal/util"
26
28
iu "github.com/nats-io/natscli/internal/util"
27
29
28
30
"github.com/AlecAivazis/survey/v2"
@@ -101,6 +103,10 @@ type authAccountCommand struct {
101
103
prefix string
102
104
tags []string
103
105
rmTags []string
106
+ mapSource string
107
+ mapTarget string
108
+ mapWeight uint
109
+ inputFile string
104
110
}
105
111
106
112
func configureAuthAccountCommand (auth commandHost ) {
@@ -278,6 +284,30 @@ func configureAuthAccountCommand(auth commandHost) {
278
284
skrm .Flag ("key" , "The key to remove" ).StringVar (& c .skRole )
279
285
skrm .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
280
286
skrm .Flag ("force" , "Removes without prompting" ).Short ('f' ).UnNegatableBoolVar (& c .force )
287
+
288
+ mappings := acct .Command ("mappings" , "Manage account level subject mapping and partitioning" ).Alias ("m" )
289
+
290
+ mappingsaAdd := mappings .Command ("add" , "Add a new mapping" ).Alias ("new" ).Alias ("a" ).Action (c .mappingAddAction )
291
+ mappingsaAdd .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
292
+ mappingsaAdd .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
293
+ mappingsaAdd .Arg ("target" , "The target subject of the mapping" ).StringVar (& c .mapTarget )
294
+ mappingsaAdd .Arg ("weight" , "The weight (%) of the mappingmapping" ).Default ("100" ).UintVar (& c .mapWeight )
295
+ mappingsaAdd .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
296
+ mappingsaAdd .Flag ("config" , "JWT file to read configuration from" ).ExistingFileVar (& c .inputFile )
297
+
298
+ mappingsls := mappings .Command ("ls" , "List mappings" ).Alias ("list" ).Action (c .mappingListAction )
299
+ mappingsls .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
300
+ mappingsls .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
301
+
302
+ mappingsrm := mappings .Command ("rm" , "Remove a mapping" ).Action (c .mappingRmAction )
303
+ mappingsrm .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
304
+ mappingsrm .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
305
+ mappingsrm .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
306
+
307
+ mappingsinfo := mappings .Command ("info" , "Show information about a mapping" ).Alias ("i" ).Alias ("show" ).Alias ("view" ).Action (c .mappingInfoAction )
308
+ mappingsinfo .Arg ("account" , "Account to create the mappings on" ).StringVar (& c .accountName )
309
+ mappingsinfo .Arg ("source" , "The source subject of the mapping" ).StringVar (& c .mapSource )
310
+ mappingsinfo .Flag ("operator" , "Operator to act on" ).StringVar (& c .operatorName )
281
311
}
282
312
283
313
func (c * authAccountCommand ) selectAccount (pick bool ) (* ab.AuthImpl , ab.Operator , ab.Account , error ) {
@@ -1084,3 +1114,231 @@ func (c *authAccountCommand) validTiers(acct ab.Account) []int8 {
1084
1114
1085
1115
return tiers
1086
1116
}
1117
+
1118
+ func (c * authAccountCommand ) loadJwt () (* jwt.AccountClaims , error ) {
1119
+ if c .inputFile != "" {
1120
+ f , err := os .ReadFile (c .inputFile )
1121
+ if err != nil {
1122
+ return nil , err
1123
+ }
1124
+
1125
+ claims , err := jwt .DecodeAccountClaims (string (f ))
1126
+ if err != nil {
1127
+ return nil , fmt .Errorf ("failed to decode JWT: %w" , err )
1128
+ }
1129
+ return claims , nil
1130
+ }
1131
+ return nil , nil
1132
+
1133
+ }
1134
+
1135
+ func (c * authAccountCommand ) parseJwtMappings (mappings map [string ][]ab.Mapping , jwtMappings jwt.Mapping ) {
1136
+ for subject , weightedMappings := range jwtMappings {
1137
+ mappings [string (subject )] = []ab.Mapping {}
1138
+ for _ , m := range weightedMappings {
1139
+ mappings [string (subject )] = append (mappings [string (subject )], ab.Mapping {Weight : m .Weight , Subject : string (m .Subject ), Cluster : m .Cluster })
1140
+ }
1141
+ }
1142
+ }
1143
+
1144
+ func (c * authAccountCommand ) mappingAddAction (_ * fisk.ParseContext ) error {
1145
+ mappings := map [string ][]ab.Mapping {}
1146
+ if c .inputFile != "" {
1147
+ cfg , err := c .loadJwt ()
1148
+ if err != nil {
1149
+ return err
1150
+ }
1151
+ c .accountName = cfg .Name
1152
+ c .parseJwtMappings (mappings , cfg .Mappings )
1153
+ }
1154
+
1155
+ auth , _ , acct , err := c .selectAccount (true )
1156
+ if err != nil {
1157
+ return err
1158
+ }
1159
+
1160
+ if c .inputFile == "" {
1161
+ if c .mapSource == "" {
1162
+ err := iu .AskOne (& survey.Input {
1163
+ Message : "Source subject" ,
1164
+ Help : "The source subject of the mapping" ,
1165
+ }, & c .mapSource , survey .WithValidator (survey .Required ))
1166
+ if err != nil {
1167
+ return err
1168
+ }
1169
+ }
1170
+
1171
+ if c .mapTarget == "" {
1172
+ err := iu .AskOne (& survey.Input {
1173
+ Message : "Target subject" ,
1174
+ Help : "The target subject of the mapping" ,
1175
+ }, & c .mapTarget , survey .WithValidator (survey .Required ))
1176
+ if err != nil {
1177
+ return err
1178
+ }
1179
+ }
1180
+
1181
+ mapping := ab.Mapping {Subject : c .mapTarget , Weight : uint8 (c .mapWeight )}
1182
+ // check if there are mappings already set for the source
1183
+ currentMappings := acct .SubjectMappings ().Get (c .mapSource )
1184
+ if len (currentMappings ) > 0 {
1185
+ // Check that we don't overwrite the current mapping
1186
+ for _ , m := range currentMappings {
1187
+ if m .Subject == c .mapTarget {
1188
+ return fmt .Errorf ("mapping %s -> %s already exists" , c .mapSource , c .mapTarget )
1189
+ }
1190
+ }
1191
+ }
1192
+ currentMappings = append (currentMappings , mapping )
1193
+ mappings [c .mapSource ] = currentMappings
1194
+ }
1195
+
1196
+ for subject , m := range mappings {
1197
+ err = acct .SubjectMappings ().Set (subject , m ... )
1198
+ if err != nil {
1199
+ return err
1200
+ }
1201
+ }
1202
+
1203
+ err = auth .Commit ()
1204
+ if err != nil {
1205
+ return err
1206
+ }
1207
+
1208
+ return c .fShowMappings (os .Stdout , mappings )
1209
+ }
1210
+
1211
+ func (c * authAccountCommand ) mappingInfoAction (_ * fisk.ParseContext ) error {
1212
+ _ , _ , acct , err := c .selectAccount (true )
1213
+ if err != nil {
1214
+ return err
1215
+ }
1216
+
1217
+ accountMappings := c .getActiveMappings (acct )
1218
+ if len (accountMappings ) == 0 {
1219
+ fmt .Println ("No mappings defined" )
1220
+ return nil
1221
+ }
1222
+
1223
+ err = iu .AskOne (& survey.Select {
1224
+ Message : "Select a mapping to inspect" ,
1225
+ Options : accountMappings ,
1226
+ PageSize : iu .SelectPageSize (len (accountMappings )),
1227
+ }, & c .mapSource )
1228
+ if err != nil {
1229
+ return err
1230
+ }
1231
+
1232
+ mappings := map [string ][]ab.Mapping {
1233
+ c .mapSource : acct .SubjectMappings ().Get (c .mapSource ),
1234
+ }
1235
+
1236
+ return c .fShowMappings (os .Stdout , mappings )
1237
+ }
1238
+
1239
+ func (c * authAccountCommand ) mappingListAction (_ * fisk.ParseContext ) error {
1240
+ _ , _ , acct , err := c .selectAccount (true )
1241
+ if err != nil {
1242
+ return err
1243
+ }
1244
+
1245
+ mappings := acct .SubjectMappings ().List ()
1246
+ if len (mappings ) == 0 {
1247
+ fmt .Println ("No mappings defined" )
1248
+ return nil
1249
+ }
1250
+
1251
+ tbl := util .NewTableWriter (opts (), "Subject mappings for account %s" , acct .Name ())
1252
+ tbl .AddHeaders ("Source Subject" , "Target Subject" , "Weight" , "Cluster" )
1253
+
1254
+ for _ , fromMapping := range acct .SubjectMappings ().List () {
1255
+ subjectMaps := acct .SubjectMappings ().Get (fromMapping )
1256
+ for _ , m := range subjectMaps {
1257
+ tbl .AddRow (fromMapping , m .Subject , m .Weight , m .Cluster )
1258
+ }
1259
+ }
1260
+
1261
+ fmt .Println (tbl .Render ())
1262
+ return nil
1263
+ }
1264
+
1265
+ func (c * authAccountCommand ) mappingRmAction (_ * fisk.ParseContext ) error {
1266
+ auth , _ , acct , err := c .selectAccount (true )
1267
+ if err != nil {
1268
+ return err
1269
+ }
1270
+
1271
+ mappings := c .getActiveMappings (acct )
1272
+ if len (mappings ) == 0 {
1273
+ fmt .Println ("No mappings defined" )
1274
+ return nil
1275
+ }
1276
+
1277
+ if c .mapSource == "" {
1278
+ err = iu .AskOne (& survey.Select {
1279
+ Message : "Select a mapping to delete" ,
1280
+ Options : mappings ,
1281
+ PageSize : iu .SelectPageSize (len (mappings )),
1282
+ }, & c .mapSource )
1283
+ if err != nil {
1284
+ return err
1285
+ }
1286
+ }
1287
+
1288
+ err = acct .SubjectMappings ().Delete (c .mapSource )
1289
+ if err != nil {
1290
+ return err
1291
+ }
1292
+
1293
+ err = auth .Commit ()
1294
+ if err != nil {
1295
+ return err
1296
+ }
1297
+
1298
+ fmt .Printf ("Deleted mapping {%s}\n " , c .mapSource )
1299
+ return nil
1300
+ }
1301
+
1302
+ func (c * authAccountCommand ) getActiveMappings (acct ab.Account ) []string {
1303
+ accountMappings := []string {}
1304
+ for _ , m := range acct .SubjectMappings ().List () {
1305
+ if len (acct .SubjectMappings ().Get (m )) > 0 {
1306
+ accountMappings = append (accountMappings , m )
1307
+ }
1308
+ }
1309
+ return accountMappings
1310
+ }
1311
+
1312
+ func (c * authAccountCommand ) fShowMappings (w io.Writer , mappings map [string ][]ab.Mapping ) error {
1313
+ out , err := c .showMappings (mappings )
1314
+ if err != nil {
1315
+ return err
1316
+ }
1317
+
1318
+ _ , err = fmt .Fprintln (w , out )
1319
+ return err
1320
+ }
1321
+
1322
+ func (c * authAccountCommand ) showMappings (mappings map [string ][]ab.Mapping ) (string , error ) {
1323
+ cols := newColumns ("Subject mappings" )
1324
+ cols .AddSectionTitle ("Configuration" )
1325
+ for source , m := range mappings {
1326
+ // Remove when delete is fixed
1327
+ if len (m ) > 0 {
1328
+ totalWeight := 0
1329
+ for _ , wm := range m {
1330
+ cols .AddRow ("Source" , source )
1331
+ cols .AddRow ("Target" , wm .Subject )
1332
+ cols .AddRow ("Weight" , wm .Weight )
1333
+ cols .AddRow ("Cluster" , wm .Cluster )
1334
+ cols .AddRow ("" , "" )
1335
+ totalWeight += int (wm .Weight )
1336
+ }
1337
+ cols .AddRow ("Total weight:" , totalWeight )
1338
+ cols .AddRow ("" , "" )
1339
+ }
1340
+
1341
+ }
1342
+
1343
+ return cols .Render ()
1344
+ }
0 commit comments