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