blob: 81acb18c2daf952a4d0c9f3ce58488b298495623 [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 Bakerea815fc2019-06-05 13:25:44 -070024 "sort"
Scott Baker2c0ebda2019-05-06 16:55:47 -070025 "strings"
Scott Bakerc328cf12019-05-28 16:03:12 -070026 "time"
Scott Baker2c0ebda2019-05-06 16:55:47 -070027)
28
Scott Baker175cb402019-05-17 16:13:06 -070029const (
Scott Baker8b8a2b52019-05-21 08:56:28 -070030 DEFAULT_CREATE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker175cb402019-05-17 16:13:06 -070031 DEFAULT_DELETE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
32 DEFAULT_UPDATE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker8b8a2b52019-05-21 08:56:28 -070033 DEFAULT_SYNC_FORMAT = "table{{ .Id }}\t{{ .Message }}"
Scott Baker175cb402019-05-17 16:13:06 -070034)
35
Scott Baker63ce82e2019-05-15 09:01:42 -070036type ModelNameString string
37
Scott Baker2c0ebda2019-05-06 16:55:47 -070038type ModelList struct {
Scott Bakera00418a2019-06-03 16:15:28 -070039 ListOutputOptions
Scott Baker5201c0b2019-05-15 15:35:56 -070040 ShowHidden bool `long:"showhidden" description:"Show hidden fields in default output"`
41 ShowFeedback bool `long:"showfeedback" description:"Show feedback fields in default output"`
42 ShowBookkeeping bool `long:"showbookkeeping" description:"Show bookkeeping fields in default output"`
Scott Baker175cb402019-05-17 16:13:06 -070043 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
Scott Baker6cf525a2019-05-09 12:25:08 -070044 Args struct {
Scott Baker63ce82e2019-05-15 09:01:42 -070045 ModelName ModelNameString
Scott Baker2c0ebda2019-05-06 16:55:47 -070046 } `positional-args:"yes" required:"yes"`
47}
48
Scott Baker5281d002019-05-16 10:45:26 -070049type ModelUpdate struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070050 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070051 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
52 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
53 SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
54 SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
55 Sync bool `long:"sync" description:"Synchronize before returning"`
56 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
57 Args struct {
Scott Baker5281d002019-05-16 10:45:26 -070058 ModelName ModelNameString
59 } `positional-args:"yes" required:"yes"`
60 IDArgs struct {
61 ID []int32
62 } `positional-args:"yes" required:"no"`
Scott Baker6cf525a2019-05-09 12:25:08 -070063}
64
Scott Baker175cb402019-05-17 16:13:06 -070065type ModelDelete struct {
66 OutputOptions
Scott Baker8b8a2b52019-05-21 08:56:28 -070067 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
68 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
69 Args struct {
70 ModelName ModelNameString
71 } `positional-args:"yes" required:"yes"`
72 IDArgs struct {
73 ID []int32
74 } `positional-args:"yes" required:"no"`
75}
76
77type ModelCreate struct {
78 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070079 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output"`
80 SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
81 SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
82 Sync bool `long:"sync" description:"Synchronize before returning"`
83 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for --sync option"`
84 Args struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -070085 ModelName ModelNameString
86 } `positional-args:"yes" required:"yes"`
87}
88
89type ModelSync struct {
90 OutputOptions
Scott Bakerc328cf12019-05-28 16:03:12 -070091 Unbuffered bool `short:"u" long:"unbuffered" description:"Do not buffer console output and suppress default output processor"`
92 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
93 SyncTimeout time.Duration `long:"synctimeout" default:"600s" description:"Timeout for synchronization"`
94 Args struct {
Scott Baker175cb402019-05-17 16:13:06 -070095 ModelName ModelNameString
96 } `positional-args:"yes" required:"yes"`
97 IDArgs struct {
98 ID []int32
99 } `positional-args:"yes" required:"no"`
100}
101
Scott Baker2c0ebda2019-05-06 16:55:47 -0700102type ModelOpts struct {
Scott Baker5281d002019-05-16 10:45:26 -0700103 List ModelList `command:"list"`
104 Update ModelUpdate `command:"update"`
Scott Baker175cb402019-05-17 16:13:06 -0700105 Delete ModelDelete `command:"delete"`
Scott Baker8b8a2b52019-05-21 08:56:28 -0700106 Create ModelCreate `command:"create"`
107 Sync ModelSync `command:"sync"`
Scott Baker175cb402019-05-17 16:13:06 -0700108}
109
110type ModelStatusOutputRow struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700111 Id interface{} `json:"id"`
112 Message string `json:"message"`
Scott Baker175cb402019-05-17 16:13:06 -0700113}
114
115type ModelStatusOutput struct {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700116 Rows []ModelStatusOutputRow
117 Unbuffered bool
Scott Baker2c0ebda2019-05-06 16:55:47 -0700118}
119
120var modelOpts = ModelOpts{}
121
122func RegisterModelCommands(parser *flags.Parser) {
123 parser.AddCommand("model", "model commands", "Commands to query and manipulate XOS models", &modelOpts)
124}
125
Scott Baker8b8a2b52019-05-21 08:56:28 -0700126// Initialize ModelStatusOutput structure, creating a row for each model that will be output
127func InitModelStatusOutput(unbuffered bool, count int) ModelStatusOutput {
128 return ModelStatusOutput{Rows: make([]ModelStatusOutputRow, count), Unbuffered: unbuffered}
Scott Baker175cb402019-05-17 16:13:06 -0700129}
130
Scott Baker8b8a2b52019-05-21 08:56:28 -0700131// Update model status output row for the model
132// If unbuffered is set then we will output directly to the console. Regardless of the unbuffered
133// setting, we always update the row, as callers may check that row for status.
134// Args:
135// output - ModelStatusOutput struct to update
136// i - index of row to update
137// id - id of model, <nil> if no model exists
138// status - status text to set if there is no error
139// errror - if non-nil, then apply error text instead of status text
140// final - true if successful status should be reported, false if successful status is yet to come
141
142func UpdateModelStatusOutput(output *ModelStatusOutput, i int, id interface{}, status string, err error, final bool) {
Scott Baker175cb402019-05-17 16:13:06 -0700143 if err != nil {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700144 if output.Unbuffered {
145 fmt.Printf("%v: %s\n", id, HumanReadableError(err))
Scott Baker175cb402019-05-17 16:13:06 -0700146 }
Scott Baker8b8a2b52019-05-21 08:56:28 -0700147 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: HumanReadableError(err)}
Scott Baker175cb402019-05-17 16:13:06 -0700148 } else {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700149 if output.Unbuffered && final {
Scott Baker175cb402019-05-17 16:13:06 -0700150 fmt.Println(id)
Scott Baker175cb402019-05-17 16:13:06 -0700151 }
Scott Baker8b8a2b52019-05-21 08:56:28 -0700152 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: status}
Scott Baker175cb402019-05-17 16:13:06 -0700153 }
154}
155
Scott Baker6cf525a2019-05-09 12:25:08 -0700156func (options *ModelList) Execute(args []string) error {
157 conn, descriptor, err := InitReflectionClient()
158 if err != nil {
159 return err
160 }
161
162 defer conn.Close()
163
Scott Baker63ce82e2019-05-15 09:01:42 -0700164 err = CheckModelName(descriptor, string(options.Args.ModelName))
Scott Baker6cf525a2019-05-09 12:25:08 -0700165 if err != nil {
166 return err
167 }
168
Scott Baker5281d002019-05-16 10:45:26 -0700169 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700170 if err != nil {
171 return err
172 }
173
Scott Bakerc328cf12019-05-28 16:03:12 -0700174 models, err := ListOrFilterModels(context.Background(), conn, descriptor, string(options.Args.ModelName), queries)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700175 if err != nil {
176 return err
177 }
178
Scott Bakerea815fc2019-06-05 13:25:44 -0700179 var field_names []string
Scott Baker5201c0b2019-05-15 15:35:56 -0700180 data := make([]map[string]interface{}, len(models))
181 for i, val := range models {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700182 data[i] = make(map[string]interface{})
183 for _, field_desc := range val.GetKnownFields() {
184 field_name := field_desc.GetName()
Scott Baker2c0ebda2019-05-06 16:55:47 -0700185
186 isGuiHidden := strings.Contains(field_desc.GetFieldOptions().String(), "1005:1")
187 isFeedback := strings.Contains(field_desc.GetFieldOptions().String(), "1006:1")
188 isBookkeeping := strings.Contains(field_desc.GetFieldOptions().String(), "1007:1")
189
190 if isGuiHidden && (!options.ShowHidden) {
191 continue
192 }
193
194 if isFeedback && (!options.ShowFeedback) {
195 continue
196 }
197
Scott Baker6cf525a2019-05-09 12:25:08 -0700198 if isBookkeeping && (!options.ShowBookkeeping) {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700199 continue
200 }
201
202 if field_desc.IsRepeated() {
203 continue
204 }
205
Scott Baker6cf525a2019-05-09 12:25:08 -0700206 data[i][field_name] = val.GetFieldByName(field_name)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700207
Scott Bakerea815fc2019-06-05 13:25:44 -0700208 // Every row has the same set of known field names, so it suffices to use the names
209 // from the first row.
210 if i == 0 {
211 field_names = append(field_names, field_name)
212 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700213 }
214 }
215
Scott Bakerea815fc2019-06-05 13:25:44 -0700216 // Sort field names, making sure "id" appears first
217 sort.SliceStable(field_names, func(i, j int) bool {
218 if field_names[i] == "id" {
219 return true
220 } else if field_names[j] == "id" {
221 return false
222 } else {
223 return (field_names[i] < field_names[j])
224 }
225 })
226
Scott Baker2c0ebda2019-05-06 16:55:47 -0700227 var default_format strings.Builder
228 default_format.WriteString("table")
Scott Bakerea815fc2019-06-05 13:25:44 -0700229 for i, field_name := range field_names {
230 if i == 0 {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700231 fmt.Fprintf(&default_format, "{{ .%s }}", field_name)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700232 } else {
233 fmt.Fprintf(&default_format, "\t{{ .%s }}", field_name)
234 }
235 }
236
Scott Bakera00418a2019-06-03 16:15:28 -0700237 FormatAndGenerateListOutput(&options.ListOutputOptions, default_format.String(), "{{.id}}", data)
Scott Baker5281d002019-05-16 10:45:26 -0700238
239 return nil
240}
241
242func (options *ModelUpdate) Execute(args []string) error {
243 conn, descriptor, err := InitReflectionClient()
244 if err != nil {
245 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700246 }
247
Scott Baker5281d002019-05-16 10:45:26 -0700248 defer conn.Close()
249
250 err = CheckModelName(descriptor, string(options.Args.ModelName))
251 if err != nil {
252 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700253 }
254
Scott Baker5281d002019-05-16 10:45:26 -0700255 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
256 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
257 return fmt.Errorf("Use either an ID or a --filter to specify which models to update")
258 }
259
260 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
261 if err != nil {
262 return err
263 }
264
265 updates, err := CommaSeparatedQueryToMap(options.SetFields, true)
266 if err != nil {
267 return err
268 }
269
270 modelName := string(options.Args.ModelName)
271
272 var models []*dynamic.Message
273
274 if len(options.IDArgs.ID) > 0 {
275 models = make([]*dynamic.Message, len(options.IDArgs.ID))
276 for i, id := range options.IDArgs.ID {
Scott Bakerc328cf12019-05-28 16:03:12 -0700277 models[i], err = GetModel(context.Background(), conn, descriptor, modelName, id)
Scott Baker5281d002019-05-16 10:45:26 -0700278 if err != nil {
279 return err
280 }
281 }
282 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700283 models, err = ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker5281d002019-05-16 10:45:26 -0700284 if err != nil {
285 return err
286 }
287 }
288
289 if len(models) == 0 {
290 return fmt.Errorf("Filter matches no objects")
291 } else if len(models) > 1 {
292 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
293 return fmt.Errorf("Aborted by user")
294 }
295 }
296
297 fields := make(map[string]interface{})
298
299 if len(options.SetJSON) > 0 {
300 fields["_json"] = []byte(options.SetJSON)
301 }
302
303 for fieldName, value := range updates {
304 value = value[1:]
305 proto_value, err := TypeConvert(descriptor, modelName, fieldName, value)
306 if err != nil {
307 return err
308 }
309 fields[fieldName] = proto_value
310 }
311
Scott Baker8b8a2b52019-05-21 08:56:28 -0700312 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(models))
Scott Baker175cb402019-05-17 16:13:06 -0700313 for i, model := range models {
314 id := model.GetFieldByName("id").(int32)
315 fields["id"] = id
316 err := UpdateModel(conn, descriptor, modelName, fields)
317
Scott Baker8b8a2b52019-05-21 08:56:28 -0700318 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Updated", err, !options.Sync)
Scott Baker5281d002019-05-16 10:45:26 -0700319 }
320
Scott Baker8b8a2b52019-05-21 08:56:28 -0700321 if options.Sync {
Scott Bakerc328cf12019-05-28 16:03:12 -0700322 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
323 defer cancel()
Scott Baker8b8a2b52019-05-21 08:56:28 -0700324 for i, model := range models {
325 id := model.GetFieldByName("id").(int32)
326 if modelStatusOutput.Rows[i].Message == "Updated" {
327 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700328 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 -0700329 conditional_printf(!options.Quiet, "\n")
330 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
331 }
332 }
333 }
334
335 if !options.Unbuffered {
336 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_UPDATE_FORMAT, DEFAULT_UPDATE_FORMAT, modelStatusOutput.Rows)
Scott Baker175cb402019-05-17 16:13:06 -0700337 }
338
339 return nil
340}
341
342func (options *ModelDelete) Execute(args []string) error {
343 conn, descriptor, err := InitReflectionClient()
344 if err != nil {
345 return err
346 }
347
348 defer conn.Close()
349
350 err = CheckModelName(descriptor, string(options.Args.ModelName))
351 if err != nil {
352 return err
353 }
354
355 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
356 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
Scott Baker8b8a2b52019-05-21 08:56:28 -0700357 return fmt.Errorf("Use either an ID or a --filter to specify which models to delete")
Scott Baker175cb402019-05-17 16:13:06 -0700358 }
359
360 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
361 if err != nil {
362 return err
363 }
364
365 modelName := string(options.Args.ModelName)
366
367 var ids []int32
368
369 if len(options.IDArgs.ID) > 0 {
370 ids = options.IDArgs.ID
371 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700372 models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker175cb402019-05-17 16:13:06 -0700373 if err != nil {
374 return err
375 }
376 ids = make([]int32, len(models))
377 for i, model := range models {
378 ids[i] = model.GetFieldByName("id").(int32)
379 }
380 if len(ids) == 0 {
381 return fmt.Errorf("Filter matches no objects")
382 } else if len(ids) > 1 {
383 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
384 return fmt.Errorf("Aborted by user")
385 }
386 }
387 }
388
Scott Baker8b8a2b52019-05-21 08:56:28 -0700389 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(ids))
Scott Baker175cb402019-05-17 16:13:06 -0700390 for i, id := range ids {
391 err = DeleteModel(conn, descriptor, modelName, id)
Scott Baker8b8a2b52019-05-21 08:56:28 -0700392 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Deleted", err, true)
Scott Baker175cb402019-05-17 16:13:06 -0700393 }
394
Scott Baker8b8a2b52019-05-21 08:56:28 -0700395 if !options.Unbuffered {
396 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_DELETE_FORMAT, DEFAULT_DELETE_FORMAT, modelStatusOutput.Rows)
397 }
398
399 return nil
400}
401
402func (options *ModelCreate) Execute(args []string) error {
403 conn, descriptor, err := InitReflectionClient()
404 if err != nil {
405 return err
406 }
407
408 defer conn.Close()
409
410 err = CheckModelName(descriptor, string(options.Args.ModelName))
411 if err != nil {
412 return err
413 }
414
415 updates, err := CommaSeparatedQueryToMap(options.SetFields, true)
416 if err != nil {
417 return err
418 }
419
420 modelName := string(options.Args.ModelName)
421
422 fields := make(map[string]interface{})
423
424 if len(options.SetJSON) > 0 {
425 fields["_json"] = []byte(options.SetJSON)
426 }
427
428 for fieldName, value := range updates {
429 value = value[1:]
430 proto_value, err := TypeConvert(descriptor, modelName, fieldName, value)
431 if err != nil {
432 return err
433 }
434 fields[fieldName] = proto_value
435 }
436
437 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, 1)
438
439 err = CreateModel(conn, descriptor, modelName, fields)
440 UpdateModelStatusOutput(&modelStatusOutput, 0, fields["id"], "Created", err, !options.Sync)
441
442 if options.Sync {
Scott Bakerc328cf12019-05-28 16:03:12 -0700443 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
444 defer cancel()
Scott Baker8b8a2b52019-05-21 08:56:28 -0700445 if modelStatusOutput.Rows[0].Message == "Created" {
446 id := fields["id"].(int32)
447 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700448 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 -0700449 conditional_printf(!options.Quiet, "\n")
450 UpdateModelStatusOutput(&modelStatusOutput, 0, id, "Enacted", err, true)
451 }
452 }
453
454 if !options.Unbuffered {
455 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_CREATE_FORMAT, DEFAULT_CREATE_FORMAT, modelStatusOutput.Rows)
456 }
457
458 return nil
459}
460
461func (options *ModelSync) Execute(args []string) error {
462 conn, descriptor, err := InitReflectionClient()
463 if err != nil {
464 return err
465 }
466
467 defer conn.Close()
468
469 err = CheckModelName(descriptor, string(options.Args.ModelName))
470 if err != nil {
471 return err
472 }
473
474 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
475 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
476 return fmt.Errorf("Use either an ID or a --filter to specify which models to sync")
477 }
478
479 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
480 if err != nil {
481 return err
482 }
483
484 modelName := string(options.Args.ModelName)
485
486 var ids []int32
487
488 if len(options.IDArgs.ID) > 0 {
489 ids = options.IDArgs.ID
490 } else {
Scott Bakerc328cf12019-05-28 16:03:12 -0700491 models, err := ListOrFilterModels(context.Background(), conn, descriptor, modelName, queries)
Scott Baker8b8a2b52019-05-21 08:56:28 -0700492 if err != nil {
493 return err
494 }
495 ids = make([]int32, len(models))
496 for i, model := range models {
497 ids[i] = model.GetFieldByName("id").(int32)
498 }
499 if len(ids) == 0 {
500 return fmt.Errorf("Filter matches no objects")
501 } else if len(ids) > 1 {
502 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
503 return fmt.Errorf("Aborted by user")
504 }
505 }
506 }
507
Scott Bakerc328cf12019-05-28 16:03:12 -0700508 ctx, cancel := context.WithTimeout(context.Background(), options.SyncTimeout)
509 defer cancel()
510
Scott Baker8b8a2b52019-05-21 08:56:28 -0700511 modelStatusOutput := InitModelStatusOutput(options.Unbuffered, len(ids))
512 for i, id := range ids {
513 conditional_printf(!options.Quiet, "Wait for sync: %d ", id)
Scott Bakerc328cf12019-05-28 16:03:12 -0700514 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 -0700515 conditional_printf(!options.Quiet, "\n")
516 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Enacted", err, true)
517 }
518
519 if !options.Unbuffered {
520 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_SYNC_FORMAT, DEFAULT_SYNC_FORMAT, modelStatusOutput.Rows)
Scott Baker175cb402019-05-17 16:13:06 -0700521 }
Scott Baker5281d002019-05-16 10:45:26 -0700522
Scott Baker2c0ebda2019-05-06 16:55:47 -0700523 return nil
524}
Scott Baker63ce82e2019-05-15 09:01:42 -0700525
526func (modelName *ModelNameString) Complete(match string) []flags.Completion {
527 conn, descriptor, err := InitReflectionClient()
528 if err != nil {
529 return nil
530 }
531
532 defer conn.Close()
533
534 models, err := GetModelNames(descriptor)
535 if err != nil {
536 return nil
537 }
538
539 list := make([]flags.Completion, 0)
540 for k := range models {
541 if strings.HasPrefix(k, match) {
542 list = append(list, flags.Completion{Item: k})
543 }
544 }
545
546 return list
547}