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