blob: bd6552fe805f610e7ec0c73da1d788f13a071cb1 [file] [log] [blame]
Scott Baker2c0ebda2019-05-06 16:55:47 -07001/*
2 * Portions copyright 2019-present Open Networking Foundation
3 * Original copyright 2019-present Ciena Corporation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package commands
18
19import (
Scott Bakerc328cf12019-05-28 16:03:12 -070020 "context"
Scott Baker2c0ebda2019-05-06 16:55:47 -070021 "fmt"
Scott Baker2c0ebda2019-05-06 16:55:47 -070022 flags "github.com/jessevdk/go-flags"
23 "github.com/jhump/protoreflect/dynamic"
Scott Baker2c0ebda2019-05-06 16:55:47 -070024 "strings"
Scott Bakerc328cf12019-05-28 16:03:12 -070025 "time"
Scott Baker2c0ebda2019-05-06 16:55:47 -070026)
27
Scott Baker175cb402019-05-17 16:13:06 -070028const (
Scott Baker8b8a2b52019-05-21 08:56:28 -070029 DEFAULT_CREATE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker175cb402019-05-17 16:13:06 -070030 DEFAULT_DELETE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
31 DEFAULT_UPDATE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker8b8a2b52019-05-21 08:56:28 -070032 DEFAULT_SYNC_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker175cb402019-05-17 16:13:06 -070033)
34
Scott Baker63ce82e2019-05-15 09:01:42 -070035type ModelNameString string
36
Scott Baker2c0ebda2019-05-06 16:55:47 -070037type ModelList struct {
Scott Bakera00418a2019-06-03 16:15:28 -070038 ListOutputOptions
Scott Baker5201c0b2019-05-15 15:35:56 -070039 ShowHidden bool `long:"showhidden" description:"Show hidden fields in default output"`
40 ShowFeedback bool `long:"showfeedback" description:"Show feedback fields in default output"`
41 ShowBookkeeping bool `long:"showbookkeeping" description:"Show bookkeeping fields in default output"`
Scott Baker175cb402019-05-17 16:13:06 -070042 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
Scott Baker6cf525a2019-05-09 12:25:08 -070043 Args struct {
Scott Baker63ce82e2019-05-15 09:01:42 -070044 ModelName ModelNameString
Scott Baker2c0ebda2019-05-06 16:55:47 -070045 } `positional-args:"yes" required:"yes"`
46}
47
Scott Baker5281d002019-05-16 10:45:26 -070048type ModelUpdate struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070049 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070050 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
51 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
52 SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
53 SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
54 Sync bool `long:"sync" description:"Synchronize before returning"`
55 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
56 Args struct {
Scott Baker5281d002019-05-16 10:45:26 -070057 ModelName ModelNameString
58 } `positional-args:"yes" required:"yes"`
59 IDArgs struct {
60 ID []int32
61 } `positional-args:"yes" required:"no"`
Scott Baker6cf525a2019-05-09 12:25:08 -070062}
63
Scott Baker175cb402019-05-17 16:13:06 -070064type ModelDelete struct {
65 OutputOptions
Scott Baker8b8a2b52019-05-21 08:56:28 -070066 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
67 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
68 Args struct {
69 ModelName ModelNameString
70 } `positional-args:"yes" required:"yes"`
71 IDArgs struct {
72 ID []int32
73 } `positional-args:"yes" required:"no"`
74}
75
76type ModelCreate struct {
77 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070078 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output"`
79 SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
80 SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
81 Sync bool `long:"sync" description:"Synchronize before returning"`
82 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
83 Args struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -070084 ModelName ModelNameString
85 } `positional-args:"yes" required:"yes"`
86}
87
88type ModelSync struct {
89 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070090 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
91 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
92 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for synchronization"`
93 Args struct {
Scott Baker175cb402019-05-17 16:13:06 -070094 ModelName ModelNameString
95 } `positional-args:"yes" required:"yes"`
96 IDArgs struct {
97 ID []int32
98 } `positional-args:"yes" required:"no"`
99}
100
Scott Baker2c0ebda2019-05-06 16:55:47 -0700101type ModelOpts struct {
Scott Baker5281d002019-05-16 10:45:26 -0700102 List ModelList `command:"list"`
103 Update ModelUpdate `command:"update"`
Scott Baker175cb402019-05-17 16:13:06 -0700104 Delete ModelDelete `command:"delete"`
Scott Baker8b8a2b52019-05-21 08:56:28 -0700105 Create ModelCreate `command:"create"`
106 Sync ModelSync `command:"sync"`
Scott Baker175cb402019-05-17 16:13:06 -0700107}
108
109type ModelStatusOutputRow struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700110 Id interface{} `json:"id"`
111 Message string `json:"message"`
Scott Baker175cb402019-05-17 16:13:06 -0700112}
113
114type ModelStatusOutput struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700115 Rows []ModelStatusOutputRow
116 Unbuffered bool
Scott Baker2c0ebda2019-05-06 16:55:47 -0700117}
118
119var modelOpts = ModelOpts{}
120
121func RegisterModelCommands(parser *flags.Parser) {
122 parser.AddCommand("model", "model commands", "Commands to query and manipulate XOS models", &modelOpts)
123}
124
Scott Baker8b8a2b52019-05-21 08:56:28 -0700125// Initialize ModelStatusOutput structure, creating a row for each model that will be output
126func InitModelStatusOutput(unbuffered bool, count int) ModelStatusOutput {
127 return ModelStatusOutput{Rows: make([]ModelStatusOutputRow, count), Unbuffered: unbuffered}
Scott Baker175cb402019-05-17 16:13:06 -0700128}
129
Scott Baker8b8a2b52019-05-21 08:56:28 -0700130// Update model status output row for the model
131// If unbuffered is set then we will output directly to the console. Regardless of the unbuffered
132// setting, we always update the row, as callers may check that row for status.
133// Args:
134// output - ModelStatusOutput struct to update
135// i - index of row to update
136// id - id of model, <nil> if no model exists
137// status - status text to set if there is no error
138// errror - if non-nil, then apply error text instead of status text
139// final - true if successful status should be reported, false if successful status is yet to come
140
141func UpdateModelStatusOutput(output *ModelStatusOutput, i int, id interface{}, status string, err error, final bool) {
Scott Baker175cb402019-05-17 16:13:06 -0700142 if err != nil {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700143 if output.Unbuffered {
144 fmt.Printf("%v: %s\n", id, HumanReadableError(err))
Scott Baker175cb402019-05-17 16:13:06 -0700145 }
Scott Baker8b8a2b52019-05-21 08:56:28 -0700146 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: HumanReadableError(err)}
Scott Baker175cb402019-05-17 16:13:06 -0700147 } else {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700148 if output.Unbuffered && final {
Scott Baker175cb402019-05-17 16:13:06 -0700149 fmt.Println(id)
Scott Baker175cb402019-05-17 16:13:06 -0700150 }
Scott Baker8b8a2b52019-05-21 08:56:28 -0700151 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: status}
Scott Baker175cb402019-05-17 16:13:06 -0700152 }
153}
154
Scott Baker6cf525a2019-05-09 12:25:08 -0700155func (options *ModelList) Execute(args []string) error {
156 conn, descriptor, err := InitReflectionClient()
157 if err != nil {
158 return err
159 }
160
161 defer conn.Close()
162
Scott Baker63ce82e2019-05-15 09:01:42 -0700163 err = CheckModelName(descriptor, string(options.Args.ModelName))
Scott Baker6cf525a2019-05-09 12:25:08 -0700164 if err != nil {
165 return err
166 }
167
Scott Baker5281d002019-05-16 10:45:26 -0700168 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700169 if err != nil {
170 return err
171 }
172
Scott Bakerc328cf12019-05-28 16:03:12 -0700173 models, err := ListOrFilterModels(context.Background(), conn, descriptor, string(options.Args.ModelName), queries)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700174 if err != nil {
175 return err
176 }
177
178 field_names := make(map[string]bool)
Scott Baker5201c0b2019-05-15 15:35:56 -0700179 data := make([]map[string]interface{}, len(models))
180 for i, val := range models {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700181 data[i] = make(map[string]interface{})
182 for _, field_desc := range val.GetKnownFields() {
183 field_name := field_desc.GetName()
Scott Baker2c0ebda2019-05-06 16:55:47 -0700184
185 isGuiHidden := strings.Contains(field_desc.GetFieldOptions().String(), "1005:1")
186 isFeedback := strings.Contains(field_desc.GetFieldOptions().String(), "1006:1")
187 isBookkeeping := strings.Contains(field_desc.GetFieldOptions().String(), "1007:1")
188
189 if isGuiHidden && (!options.ShowHidden) {
190 continue
191 }
192
193 if isFeedback && (!options.ShowFeedback) {
194 continue
195 }
196
Scott Baker6cf525a2019-05-09 12:25:08 -0700197 if isBookkeeping && (!options.ShowBookkeeping) {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700198 continue
199 }
200
201 if field_desc.IsRepeated() {
202 continue
203 }
204
Scott Baker6cf525a2019-05-09 12:25:08 -0700205 data[i][field_name] = val.GetFieldByName(field_name)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700206
207 field_names[field_name] = true
208 }
209 }
210
211 var default_format strings.Builder
212 default_format.WriteString("table")
213 first := true
214 for field_name, _ := range field_names {
215 if first {
216 fmt.Fprintf(&default_format, "{{ .%s }}", field_name)
217 first = false
218 } else {
219 fmt.Fprintf(&default_format, "\t{{ .%s }}", field_name)
220 }
221 }
222
Scott Bakera00418a2019-06-03 16:15:28 -0700223 FormatAndGenerateListOutput(&options.ListOutputOptions, default_format.String(), "{{.id}}", data)
Scott Baker5281d002019-05-16 10:45:26 -0700224
225 return nil
226}
227
228func (options *ModelUpdate) Execute(args []string) error {
229 conn, descriptor, err := InitReflectionClient()
230 if err != nil {
231 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700232 }
233
Scott Baker5281d002019-05-16 10:45:26 -0700234 defer conn.Close()
235
236 err = CheckModelName(descriptor, string(options.Args.ModelName))
237 if err != nil {
238 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700239 }
240
Scott Baker5281d002019-05-16 10:45:26 -0700241 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
242 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
243 return fmt.Errorf("Use either an ID or a --filter to specify which models to update")
244 }
245
246 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
247 if err != nil {
248 return err
249 }
250
251 updates, err := CommaSeparatedQueryToMap(options.SetFields, true)
252 if err != nil {
253 return err
254 }
255
256 modelName := string(options.Args.ModelName)
257
258 var models []*dynamic.Message
259
260 if len(options.IDArgs.ID) > 0 {
261 models = make([]*dynamic.Message, len(options.IDArgs.ID))
262 for i, id := range options.IDArgs.ID {
Scott Bakerc328cf12019-05-28 16:03:12 -0700263 models[i], err = GetModel(context.Background(), conn, descriptor, modelName, id)
Scott Baker5281d002019-05-16 10:45:26 -0700264 if err != nil {
265 return err
266 }
267 }
268 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700269 models, err = ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700270 if err != nil {
271 return err
272 }
273 }
274
275 if len(models) == 0 {
276 return fmt.Errorf("Filter matches no objects")
277 } else if len(models) > 1 {
278 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
279 return fmt.Errorf("Aborted by user")
280 }
281 }
282
283 fields := make(map[string]interface{})
284
285 if len(options.SetJSON) > 0 {
286 fields["_json"] = []byte(options.SetJSON)
287 }
288
289 for fieldName, value := range updates {
290 value = value[1:]
291 proto_value, err := TypeConvert(descriptor, modelName, fieldName, value)
292 if err != nil {
293 return err
294 }
295 fields[fieldName] = proto_value
296 }
297
Scott Baker8b8a2b52019-05-21 08:56:28 -0700298 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(models))
Scott Baker175cb402019-05-17 16:13:06 -0700299 for i, model := range models {
300 id := model.GetFieldByName("id").(int32)
301 fields["id"] = id
302 err := UpdateModel(conn, descriptor, modelName, fields)
303
Scott Baker8b8a2b52019-05-21 08:56:28 -0700304 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Updated", err, !options.Sync)
Scott Baker5281d002019-05-16 10:45:26 -0700305 }
306
Scott Baker8b8a2b52019-05-21 08:56:28 -0700307 if options.Sync {
Scott Bakerc328cf12019-05-28 16:03:12 -0700308 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
309 defer cancel()
Scott Baker8b8a2b52019-05-21 08:56:28 -0700310 for i, model := range models {
311 id := model.GetFieldByName("id").(int32)
312 if modelStatusOutput.Rows[i].Message == "Updated" {
313 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700314 conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
Scott Baker8b8a2b52019-05-21 08:56:28 -0700315 conditional_printf(!options.Quiet, "\n")
316 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
317 }
318 }
319 }
320
321 if !options.Unbuffered {
322 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_UPDATE_FORMAT, DEFAULT_UPDATE_FORMAT, modelStatusOutput.Rows)
Scott Baker175cb402019-05-17 16:13:06 -0700323 }
324
325 return nil
326}
327
328func (options *ModelDelete) Execute(args []string) error {
329 conn, descriptor, err := InitReflectionClient()
330 if err != nil {
331 return err
332 }
333
334 defer conn.Close()
335
336 err = CheckModelName(descriptor, string(options.Args.ModelName))
337 if err != nil {
338 return err
339 }
340
341 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
342 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700343 return fmt.Errorf("Use either an ID or a --filter to specify which models to delete")
Scott Baker175cb402019-05-17 16:13:06 -0700344 }
345
346 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
347 if err != nil {
348 return err
349 }
350
351 modelName := string(options.Args.ModelName)
352
353 var ids []int32
354
355 if len(options.IDArgs.ID) > 0 {
356 ids = options.IDArgs.ID
357 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700358 models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker175cb402019-05-17 16:13:06 -0700359 if err != nil {
360 return err
361 }
362 ids = make([]int32, len(models))
363 for i, model := range models {
364 ids[i] = model.GetFieldByName("id").(int32)
365 }
366 if len(ids) == 0 {
367 return fmt.Errorf("Filter matches no objects")
368 } else if len(ids) > 1 {
369 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
370 return fmt.Errorf("Aborted by user")
371 }
372 }
373 }
374
Scott Baker8b8a2b52019-05-21 08:56:28 -0700375 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(ids))
Scott Baker175cb402019-05-17 16:13:06 -0700376 for i, id := range ids {
377 err = DeleteModel(conn, descriptor, modelName, id)
Scott Baker8b8a2b52019-05-21 08:56:28 -0700378 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Deleted", err, true)
Scott Baker175cb402019-05-17 16:13:06 -0700379 }
380
Scott Baker8b8a2b52019-05-21 08:56:28 -0700381 if !options.Unbuffered {
382 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_DELETE_FORMAT, DEFAULT_DELETE_FORMAT, modelStatusOutput.Rows)
383 }
384
385 return nil
386}
387
388func (options *ModelCreate) Execute(args []string) error {
389 conn, descriptor, err := InitReflectionClient()
390 if err != nil {
391 return err
392 }
393
394 defer conn.Close()
395
396 err = CheckModelName(descriptor, string(options.Args.ModelName))
397 if err != nil {
398 return err
399 }
400
401 updates, err := CommaSeparatedQueryToMap(options.SetFields, true)
402 if err != nil {
403 return err
404 }
405
406 modelName := string(options.Args.ModelName)
407
408 fields := make(map[string]interface{})
409
410 if len(options.SetJSON) > 0 {
411 fields["_json"] = []byte(options.SetJSON)
412 }
413
414 for fieldName, value := range updates {
415 value = value[1:]
416 proto_value, err := TypeConvert(descriptor, modelName, fieldName, value)
417 if err != nil {
418 return err
419 }
420 fields[fieldName] = proto_value
421 }
422
423 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, 1)
424
425 err = CreateModel(conn, descriptor, modelName, fields)
426 UpdateModelStatusOutput(&modelStatusOutput, 0, fields["id"], "Created", err, !options.Sync)
427
428 if options.Sync {
Scott Bakerc328cf12019-05-28 16:03:12 -0700429 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
430 defer cancel()
Scott Baker8b8a2b52019-05-21 08:56:28 -0700431 if modelStatusOutput.Rows[0].Message == "Created" {
432 id := fields["id"].(int32)
433 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700434 conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
Scott Baker8b8a2b52019-05-21 08:56:28 -0700435 conditional_printf(!options.Quiet, "\n")
436 UpdateModelStatusOutput(&modelStatusOutput, 0, id, "Enacted", err, true)
437 }
438 }
439
440 if !options.Unbuffered {
441 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_CREATE_FORMAT, DEFAULT_CREATE_FORMAT, modelStatusOutput.Rows)
442 }
443
444 return nil
445}
446
447func (options *ModelSync) Execute(args []string) error {
448 conn, descriptor, err := InitReflectionClient()
449 if err != nil {
450 return err
451 }
452
453 defer conn.Close()
454
455 err = CheckModelName(descriptor, string(options.Args.ModelName))
456 if err != nil {
457 return err
458 }
459
460 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
461 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
462 return fmt.Errorf("Use either an ID or a --filter to specify which models to sync")
463 }
464
465 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
466 if err != nil {
467 return err
468 }
469
470 modelName := string(options.Args.ModelName)
471
472 var ids []int32
473
474 if len(options.IDArgs.ID) > 0 {
475 ids = options.IDArgs.ID
476 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700477 models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker8b8a2b52019-05-21 08:56:28 -0700478 if err != nil {
479 return err
480 }
481 ids = make([]int32, len(models))
482 for i, model := range models {
483 ids[i] = model.GetFieldByName("id").(int32)
484 }
485 if len(ids) == 0 {
486 return fmt.Errorf("Filter matches no objects")
487 } else if len(ids) > 1 {
488 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
489 return fmt.Errorf("Aborted by user")
490 }
491 }
492 }
493
Scott Bakerc328cf12019-05-28 16:03:12 -0700494 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
495 defer cancel()
496
Scott Baker8b8a2b52019-05-21 08:56:28 -0700497 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(ids))
498 for i, id := range ids {
499 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700500 conn, _, err = GetModelWithRetry(ctx, conn, descriptor, modelName, id, GM_UNTIL_ENACTED|Ternary_uint32(options.Quiet, GM_QUIET, 0))
Scott Baker8b8a2b52019-05-21 08:56:28 -0700501 conditional_printf(!options.Quiet, "\n")
502 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
503 }
504
505 if !options.Unbuffered {
506 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_SYNC_FORMAT, DEFAULT_SYNC_FORMAT, modelStatusOutput.Rows)
Scott Baker175cb402019-05-17 16:13:06 -0700507 }
Scott Baker5281d002019-05-16 10:45:26 -0700508
Scott Baker2c0ebda2019-05-06 16:55:47 -0700509 return nil
510}
Scott Baker63ce82e2019-05-15 09:01:42 -0700511
512func (modelName *ModelNameString) Complete(match string) []flags.Completion {
513 conn, descriptor, err := InitReflectionClient()
514 if err != nil {
515 return nil
516 }
517
518 defer conn.Close()
519
520 models, err := GetModelNames(descriptor)
521 if err != nil {
522 return nil
523 }
524
525 list := make([]flags.Completion, 0)
526 for k := range models {
527 if strings.HasPrefix(k, match) {
528 list = append(list, flags.Completion{Item: k})
529 }
530 }
531
532 return list
533}