blob: bae5dbc09dbf74b59bc7a5e00f05d0f1906bc40d [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 Baker2c0ebda2019-05-06 16:55:47 -070020 "fmt"
Scott Baker2c0ebda2019-05-06 16:55:47 -070021 flags "github.com/jessevdk/go-flags"
22 "github.com/jhump/protoreflect/dynamic"
Scott Baker2c0ebda2019-05-06 16:55:47 -070023 "strings"
24)
25
Scott Baker175cb402019-05-17 16:13:06 -070026const (
27 DEFAULT_DELETE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
28 DEFAULT_UPDATE_FORMAT = "table{{ .Id }}\t{{ .Message }}"
29)
30
Scott Baker63ce82e2019-05-15 09:01:42 -070031type ModelNameString string
32
Scott Baker2c0ebda2019-05-06 16:55:47 -070033type ModelList struct {
34 OutputOptions
Scott Baker5201c0b2019-05-15 15:35:56 -070035 ShowHidden bool `long:"showhidden" description:"Show hidden fields in default output"`
36 ShowFeedback bool `long:"showfeedback" description:"Show feedback fields in default output"`
37 ShowBookkeeping bool `long:"showbookkeeping" description:"Show bookkeeping fields in default output"`
Scott Baker175cb402019-05-17 16:13:06 -070038 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
Scott Baker6cf525a2019-05-09 12:25:08 -070039 Args struct {
Scott Baker63ce82e2019-05-15 09:01:42 -070040 ModelName ModelNameString
Scott Baker2c0ebda2019-05-06 16:55:47 -070041 } `positional-args:"yes" required:"yes"`
42}
43
Scott Baker5281d002019-05-16 10:45:26 -070044type ModelUpdate struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070045 OutputOptions
Scott Baker175cb402019-05-17 16:13:06 -070046 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
Scott Baker5281d002019-05-16 10:45:26 -070047 SetFields string `long:"set-field" description:"Comma-separated list of field=value to set"`
48 SetJSON string `long:"set-json" description:"JSON dictionary to use for settings fields"`
49 Args struct {
50 ModelName ModelNameString
51 } `positional-args:"yes" required:"yes"`
52 IDArgs struct {
53 ID []int32
54 } `positional-args:"yes" required:"no"`
Scott Baker6cf525a2019-05-09 12:25:08 -070055}
56
Scott Baker175cb402019-05-17 16:13:06 -070057type ModelDelete struct {
58 OutputOptions
59 Filter string `short:"f" long:"filter" description:"Comma-separated list of filters"`
60 Args struct {
61 ModelName ModelNameString
62 } `positional-args:"yes" required:"yes"`
63 IDArgs struct {
64 ID []int32
65 } `positional-args:"yes" required:"no"`
66}
67
Scott Baker2c0ebda2019-05-06 16:55:47 -070068type ModelOpts struct {
Scott Baker5281d002019-05-16 10:45:26 -070069 List ModelList `command:"list"`
70 Update ModelUpdate `command:"update"`
Scott Baker175cb402019-05-17 16:13:06 -070071 Delete ModelDelete `command:"delete"`
72}
73
74type ModelStatusOutputRow struct {
75 Id int32 `json:"id"`
76 Message string `json:"message"`
77}
78
79type ModelStatusOutput struct {
80 Rows []ModelStatusOutputRow
81 Quiet bool
Scott Baker2c0ebda2019-05-06 16:55:47 -070082}
83
84var modelOpts = ModelOpts{}
85
86func RegisterModelCommands(parser *flags.Parser) {
87 parser.AddCommand("model", "model commands", "Commands to query and manipulate XOS models", &modelOpts)
88}
89
Scott Baker175cb402019-05-17 16:13:06 -070090func InitModelStatusOutput(quiet bool, count int) ModelStatusOutput {
91 if quiet {
92 return ModelStatusOutput{Quiet: quiet}
93 } else {
94 return ModelStatusOutput{Rows: make([]ModelStatusOutputRow, count), Quiet: quiet}
95 }
96}
97
98func UpdateModelStatusOutput(output *ModelStatusOutput, i int, id int32, status string, err error) {
99 if err != nil {
100 if output.Quiet {
101 fmt.Printf("%d: %s\n", id, HumanReadableError(err))
102 } else {
103 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: HumanReadableError(err)}
104 }
105 } else {
106 if output.Quiet {
107 fmt.Println(id)
108 } else {
109 output.Rows[i] = ModelStatusOutputRow{Id: id, Message: status}
110 }
111 }
112}
113
Scott Baker6cf525a2019-05-09 12:25:08 -0700114func (options *ModelList) Execute(args []string) error {
115 conn, descriptor, err := InitReflectionClient()
116 if err != nil {
117 return err
118 }
119
120 defer conn.Close()
121
Scott Baker63ce82e2019-05-15 09:01:42 -0700122 err = CheckModelName(descriptor, string(options.Args.ModelName))
Scott Baker6cf525a2019-05-09 12:25:08 -0700123 if err != nil {
124 return err
125 }
126
Scott Baker5281d002019-05-16 10:45:26 -0700127 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700128 if err != nil {
129 return err
130 }
131
Scott Baker5281d002019-05-16 10:45:26 -0700132 models, err := ListOrFilterModels(conn, descriptor, string(options.Args.ModelName), queries)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700133 if err != nil {
134 return err
135 }
136
137 field_names := make(map[string]bool)
Scott Baker5201c0b2019-05-15 15:35:56 -0700138 data := make([]map[string]interface{}, len(models))
139 for i, val := range models {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700140 data[i] = make(map[string]interface{})
141 for _, field_desc := range val.GetKnownFields() {
142 field_name := field_desc.GetName()
Scott Baker2c0ebda2019-05-06 16:55:47 -0700143
144 isGuiHidden := strings.Contains(field_desc.GetFieldOptions().String(), "1005:1")
145 isFeedback := strings.Contains(field_desc.GetFieldOptions().String(), "1006:1")
146 isBookkeeping := strings.Contains(field_desc.GetFieldOptions().String(), "1007:1")
147
148 if isGuiHidden && (!options.ShowHidden) {
149 continue
150 }
151
152 if isFeedback && (!options.ShowFeedback) {
153 continue
154 }
155
Scott Baker6cf525a2019-05-09 12:25:08 -0700156 if isBookkeeping && (!options.ShowBookkeeping) {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700157 continue
158 }
159
160 if field_desc.IsRepeated() {
161 continue
162 }
163
Scott Baker6cf525a2019-05-09 12:25:08 -0700164 data[i][field_name] = val.GetFieldByName(field_name)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700165
166 field_names[field_name] = true
167 }
168 }
169
170 var default_format strings.Builder
171 default_format.WriteString("table")
172 first := true
173 for field_name, _ := range field_names {
174 if first {
175 fmt.Fprintf(&default_format, "{{ .%s }}", field_name)
176 first = false
177 } else {
178 fmt.Fprintf(&default_format, "\t{{ .%s }}", field_name)
179 }
180 }
181
Scott Baker5281d002019-05-16 10:45:26 -0700182 FormatAndGenerateOutput(&options.OutputOptions, default_format.String(), "{{.id}}", data)
183
184 return nil
185}
186
187func (options *ModelUpdate) Execute(args []string) error {
188 conn, descriptor, err := InitReflectionClient()
189 if err != nil {
190 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700191 }
192
Scott Baker5281d002019-05-16 10:45:26 -0700193 defer conn.Close()
194
195 err = CheckModelName(descriptor, string(options.Args.ModelName))
196 if err != nil {
197 return err
Scott Baker2c0ebda2019-05-06 16:55:47 -0700198 }
199
Scott Baker5281d002019-05-16 10:45:26 -0700200 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
201 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
202 return fmt.Errorf("Use either an ID or a --filter to specify which models to update")
203 }
204
205 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
206 if err != nil {
207 return err
208 }
209
210 updates, err := CommaSeparatedQueryToMap(options.SetFields, true)
211 if err != nil {
212 return err
213 }
214
215 modelName := string(options.Args.ModelName)
216
217 var models []*dynamic.Message
218
219 if len(options.IDArgs.ID) > 0 {
220 models = make([]*dynamic.Message, len(options.IDArgs.ID))
221 for i, id := range options.IDArgs.ID {
222 models[i], err = GetModel(conn, descriptor, modelName, id)
223 if err != nil {
224 return err
225 }
226 }
227 } else {
228 models, err = ListOrFilterModels(conn, descriptor, modelName, queries)
229 if err != nil {
230 return err
231 }
232 }
233
234 if len(models) == 0 {
235 return fmt.Errorf("Filter matches no objects")
236 } else if len(models) > 1 {
237 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
238 return fmt.Errorf("Aborted by user")
239 }
240 }
241
242 fields := make(map[string]interface{})
243
244 if len(options.SetJSON) > 0 {
245 fields["_json"] = []byte(options.SetJSON)
246 }
247
248 for fieldName, value := range updates {
249 value = value[1:]
250 proto_value, err := TypeConvert(descriptor, modelName, fieldName, value)
251 if err != nil {
252 return err
253 }
254 fields[fieldName] = proto_value
255 }
256
Scott Baker175cb402019-05-17 16:13:06 -0700257 modelStatusOutput := InitModelStatusOutput(options.Quiet, len(models))
258 for i, model := range models {
259 id := model.GetFieldByName("id").(int32)
260 fields["id"] = id
261 err := UpdateModel(conn, descriptor, modelName, fields)
262
263 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Updated", err)
Scott Baker5281d002019-05-16 10:45:26 -0700264 }
265
Scott Baker175cb402019-05-17 16:13:06 -0700266 if !options.Quiet {
267 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_UPDATE_FORMAT, "", modelStatusOutput.Rows)
268 }
269
270 return nil
271}
272
273func (options *ModelDelete) Execute(args []string) error {
274 conn, descriptor, err := InitReflectionClient()
275 if err != nil {
276 return err
277 }
278
279 defer conn.Close()
280
281 err = CheckModelName(descriptor, string(options.Args.ModelName))
282 if err != nil {
283 return err
284 }
285
286 if (len(options.IDArgs.ID) == 0 && len(options.Filter) == 0) ||
287 (len(options.IDArgs.ID) != 0 && len(options.Filter) != 0) {
288 return fmt.Errorf("Use either an ID or a --filter to specify which models to update")
289 }
290
291 queries, err := CommaSeparatedQueryToMap(options.Filter, true)
292 if err != nil {
293 return err
294 }
295
296 modelName := string(options.Args.ModelName)
297
298 var ids []int32
299
300 if len(options.IDArgs.ID) > 0 {
301 ids = options.IDArgs.ID
302 } else {
303 models, err := ListOrFilterModels(conn, descriptor, modelName, queries)
304 if err != nil {
305 return err
306 }
307 ids = make([]int32, len(models))
308 for i, model := range models {
309 ids[i] = model.GetFieldByName("id").(int32)
310 }
311 if len(ids) == 0 {
312 return fmt.Errorf("Filter matches no objects")
313 } else if len(ids) > 1 {
314 if !Confirmf("Filter matches %d objects. Continue [y/n] ? ", len(models)) {
315 return fmt.Errorf("Aborted by user")
316 }
317 }
318 }
319
320 modelStatusOutput := InitModelStatusOutput(options.Quiet, len(ids))
321 for i, id := range ids {
322 err = DeleteModel(conn, descriptor, modelName, id)
323 UpdateModelStatusOutput(&modelStatusOutput, i, id, "Deleted", err)
324 }
325
326 if !options.Quiet {
327 FormatAndGenerateOutput(&options.OutputOptions, DEFAULT_DELETE_FORMAT, "", modelStatusOutput.Rows)
328 }
Scott Baker5281d002019-05-16 10:45:26 -0700329
Scott Baker2c0ebda2019-05-06 16:55:47 -0700330 return nil
331}
Scott Baker63ce82e2019-05-15 09:01:42 -0700332
333func (modelName *ModelNameString) Complete(match string) []flags.Completion {
334 conn, descriptor, err := InitReflectionClient()
335 if err != nil {
336 return nil
337 }
338
339 defer conn.Close()
340
341 models, err := GetModelNames(descriptor)
342 if err != nil {
343 return nil
344 }
345
346 list := make([]flags.Completion, 0)
347 for k := range models {
348 if strings.HasPrefix(k, match) {
349 list = append(list, flags.Completion{Item: k})
350 }
351 }
352
353 return list
354}