blob: 1cae97b5152f19683508cdb49a3329f0b4619de1 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
2 * Copyright 2019-present Ciena Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package commands
17
18import (
19 "encoding/json"
20 "fmt"
Scott Baker2b0ad652019-08-21 14:57:07 -070021 "github.com/opencord/voltctl/pkg/filter"
22 "github.com/opencord/voltctl/pkg/format"
23 "github.com/opencord/voltctl/pkg/order"
Zack Williamse940c7a2019-08-21 14:25:39 -070024 "google.golang.org/grpc"
25 "gopkg.in/yaml.v2"
26 "io/ioutil"
27 "log"
28 "os"
29 "path/filepath"
30 "strings"
31 "time"
32)
33
34type OutputType uint8
35
36const (
37 OUTPUT_TABLE OutputType = iota
38 OUTPUT_JSON
39 OUTPUT_YAML
40)
41
42type GrpcConfigSpec struct {
43 Timeout time.Duration `yaml:"timeout"`
44}
45
46type TlsConfigSpec struct {
47 UseTls bool `yaml:"useTls"`
48 CACert string `yaml:"caCert"`
49 Cert string `yaml:"cert"`
50 Key string `yaml:"key"`
51 Verify string `yaml:"verify"`
52}
53
54type GlobalConfigSpec struct {
55 ApiVersion string `yaml:"apiVersion"`
56 Server string `yaml:"server"`
Scott Bakered4efab2020-01-13 19:12:25 -080057 Kafka string `yaml:"kafka"`
Zack Williamse940c7a2019-08-21 14:25:39 -070058 Tls TlsConfigSpec `yaml:"tls"`
59 Grpc GrpcConfigSpec `yaml:"grpc"`
60 K8sConfig string `yaml:"-"`
61}
62
63var (
64 ParamNames = map[string]map[string]string{
65 "v1": {
66 "ID": "voltha.ID",
67 },
68 "v2": {
69 "ID": "common.ID",
70 },
kesavand12cd8eb2020-01-20 22:25:22 -050071 "v3": {
72 "ID": "common.ID",
73 "port": "voltha.Port",
74 },
Zack Williamse940c7a2019-08-21 14:25:39 -070075 }
76
77 CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
78
79 GlobalConfig = GlobalConfigSpec{
kesavand12cd8eb2020-01-20 22:25:22 -050080 ApiVersion: "v3",
David Bainbridge0f758d42019-10-26 05:17:48 +000081 Server: "localhost:55555",
Scott Bakered4efab2020-01-13 19:12:25 -080082 Kafka: "",
Zack Williamse940c7a2019-08-21 14:25:39 -070083 Tls: TlsConfigSpec{
84 UseTls: false,
85 },
86 Grpc: GrpcConfigSpec{
David K. Bainbridge402f8482020-02-26 17:14:46 -080087 Timeout: time.Minute * 5,
Zack Williamse940c7a2019-08-21 14:25:39 -070088 },
89 }
90
David Bainbridgea6722342019-10-24 23:55:53 +000091 GlobalCommandOptions = make(map[string]map[string]string)
92
Zack Williamse940c7a2019-08-21 14:25:39 -070093 GlobalOptions struct {
David Bainbridgec4029aa2019-09-26 18:56:39 +000094 Config string `short:"c" long:"config" env:"VOLTCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
95 Server string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of VOLTHA"`
Scott Bakered4efab2020-01-13 19:12:25 -080096 Kafka string `short:"k" long:"kafka" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of Kafka"`
David Bainbridgec4029aa2019-09-26 18:56:39 +000097 // Do not set the default for the API version here, else it will override the value read in the config
David K. Bainbridge2b627612020-02-18 14:50:13 -080098 // nolint: staticcheck
kesavand12cd8eb2020-01-20 22:25:22 -050099 ApiVersion string `short:"a" long:"apiversion" description:"API version" value-name:"VERSION" choice:"v1" choice:"v2" choice:"v3"`
David Bainbridgea6722342019-10-24 23:55:53 +0000100 Debug bool `short:"d" long:"debug" description:"Enable debug mode"`
David K. Bainbridge402f8482020-02-26 17:14:46 -0800101 Timeout string `short:"t" long:"timeout" description:"API call timeout duration" value-name:"DURATION" default:""`
David Bainbridgea6722342019-10-24 23:55:53 +0000102 UseTLS bool `long:"tls" description:"Use TLS"`
103 CACert string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
104 Cert string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
105 Key string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
106 Verify bool `long:"tlsverify" description:"Use TLS and verify the remote"`
107 K8sConfig string `short:"8" long:"k8sconfig" env:"KUBECONFIG" value-name:"FILE" default:"" description:"Location of Kubernetes config file"`
108 CommandOptions string `short:"o" long:"command-options" env:"VOLTCTL_COMMAND_OPTIONS" value-name:"FILE" default:"" description:"Location of command options default configuration file"`
Zack Williamse940c7a2019-08-21 14:25:39 -0700109 }
David Bainbridgea6722342019-10-24 23:55:53 +0000110
111 Debug = log.New(os.Stdout, "DEBUG: ", 0)
112 Info = log.New(os.Stdout, "INFO: ", 0)
113 Warn = log.New(os.Stderr, "WARN: ", 0)
114 Error = log.New(os.Stderr, "ERROR: ", 0)
Zack Williamse940c7a2019-08-21 14:25:39 -0700115)
116
117type OutputOptions struct {
David K. Bainbridge2b627612020-02-18 14:50:13 -0800118 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
119 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
120 // nolint: staticcheck
Zack Williamse940c7a2019-08-21 14:25:39 -0700121 OutputAs string `short:"o" long:"outputas" default:"table" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
122 NameLimit int `short:"l" long:"namelimit" default:"-1" value-name:"LIMIT" description:"Limit the depth (length) in the table column name"`
123}
124
125type ListOutputOptions struct {
126 OutputOptions
127 Filter string `short:"f" long:"filter" default:"" value-name:"FILTER" description:"Only display results that match filter"`
128 OrderBy string `short:"r" long:"orderby" default:"" value-name:"ORDER" description:"Specify the sort order of the results"`
129}
130
131type OutputOptionsJson struct {
David K. Bainbridge2b627612020-02-18 14:50:13 -0800132 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
133 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
134 // nolint: staticcheck
Zack Williamse940c7a2019-08-21 14:25:39 -0700135 OutputAs string `short:"o" long:"outputas" default:"json" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
136 NameLimit int `short:"l" long:"namelimit" default:"-1" value-name:"LIMIT" description:"Limit the depth (length) in the table column name"`
137}
138
139type ListOutputOptionsJson struct {
140 OutputOptionsJson
141 Filter string `short:"f" long:"filter" default:"" value-name:"FILTER" description:"Only display results that match filter"`
142 OrderBy string `short:"r" long:"orderby" default:"" value-name:"ORDER" description:"Specify the sort order of the results"`
143}
144
145func toOutputType(in string) OutputType {
146 switch in {
147 case "table":
148 fallthrough
149 default:
150 return OUTPUT_TABLE
151 case "json":
152 return OUTPUT_JSON
153 case "yaml":
154 return OUTPUT_YAML
155 }
156}
157
158type CommandResult struct {
159 Format format.Format
160 Filter string
161 OrderBy string
162 OutputAs OutputType
163 NameLimit int
164 Data interface{}
165}
166
David Bainbridgea6722342019-10-24 23:55:53 +0000167func GetCommandOptionWithDefault(name, option, defaultValue string) string {
168 if cmd, ok := GlobalCommandOptions[name]; ok {
169 if val, ok := cmd[option]; ok {
170 return CharReplacer.Replace(val)
171 }
172 }
173 return defaultValue
174}
175
Zack Williamse940c7a2019-08-21 14:25:39 -0700176func ProcessGlobalOptions() {
177 if len(GlobalOptions.Config) == 0 {
178 home, err := os.UserHomeDir()
179 if err != nil {
David Bainbridgea6722342019-10-24 23:55:53 +0000180 Warn.Printf("Unable to discover the user's home directory: %s", err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700181 home = "~"
182 }
183 GlobalOptions.Config = filepath.Join(home, ".volt", "config")
184 }
185
David Bainbridgea6722342019-10-24 23:55:53 +0000186 if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
Zack Williamse940c7a2019-08-21 14:25:39 -0700187 configFile, err := ioutil.ReadFile(GlobalOptions.Config)
188 if err != nil {
David Bainbridgea6722342019-10-24 23:55:53 +0000189 Error.Fatalf("Unable to read the configuration file '%s': %s",
190 GlobalOptions.Config, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700191 }
David Bainbridgea6722342019-10-24 23:55:53 +0000192 if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
193 Error.Fatalf("Unable to parse the configuration file '%s': %s",
194 GlobalOptions.Config, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700195 }
196 }
197
198 // Override from command line
199 if GlobalOptions.Server != "" {
200 GlobalConfig.Server = GlobalOptions.Server
201 }
Scott Bakered4efab2020-01-13 19:12:25 -0800202 if GlobalOptions.Kafka != "" {
203 GlobalConfig.Kafka = GlobalOptions.Kafka
204 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700205 if GlobalOptions.ApiVersion != "" {
206 GlobalConfig.ApiVersion = GlobalOptions.ApiVersion
207 }
208
David K. Bainbridge402f8482020-02-26 17:14:46 -0800209 if GlobalOptions.Timeout != "" {
210 timeout, err := time.ParseDuration(GlobalOptions.Timeout)
211 if err != nil {
212 Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
213 GlobalOptions.Timeout, err.Error())
214 }
215 GlobalConfig.Grpc.Timeout = timeout
216 }
217
Zack Williamse940c7a2019-08-21 14:25:39 -0700218 // If a k8s cert/key were not specified, then attempt to read it from
219 // any $HOME/.kube/config if it exists
220 if len(GlobalOptions.K8sConfig) == 0 {
221 home, err := os.UserHomeDir()
222 if err != nil {
David Bainbridgea6722342019-10-24 23:55:53 +0000223 Warn.Printf("Unable to discover the user's home directory: %s", err)
Zack Williamse940c7a2019-08-21 14:25:39 -0700224 home = "~"
225 }
226 GlobalOptions.K8sConfig = filepath.Join(home, ".kube", "config")
227 }
David Bainbridgea6722342019-10-24 23:55:53 +0000228
229 if len(GlobalOptions.CommandOptions) == 0 {
230 home, err := os.UserHomeDir()
231 if err != nil {
232 Warn.Printf("Unable to discover the user's home directory: %s", err)
233 home = "~"
234 }
235 GlobalOptions.CommandOptions = filepath.Join(home, ".volt", "command_options")
236 }
237
238 if info, err := os.Stat(GlobalOptions.CommandOptions); err == nil && !info.IsDir() {
239 optionsFile, err := ioutil.ReadFile(GlobalOptions.CommandOptions)
240 if err != nil {
241 Error.Fatalf("Unable to read command options configuration file '%s' : %s",
242 GlobalOptions.CommandOptions, err.Error())
243 }
244 if err = yaml.Unmarshal(optionsFile, &GlobalCommandOptions); err != nil {
245 Error.Fatalf("Unable to parse the command line options configuration file '%s': %s",
246 GlobalOptions.CommandOptions, err.Error())
247 }
248 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700249}
250
251func NewConnection() (*grpc.ClientConn, error) {
252 ProcessGlobalOptions()
253 return grpc.Dial(GlobalConfig.Server, grpc.WithInsecure())
254}
255
256func GenerateOutput(result *CommandResult) {
257 if result != nil && result.Data != nil {
258 data := result.Data
259 if result.Filter != "" {
260 f, err := filter.Parse(result.Filter)
261 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000262 Error.Fatalf("Unable to parse specified output filter '%s': %s", result.Filter, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700263 }
264 data, err = f.Process(data)
265 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000266 Error.Fatalf("Unexpected error while filtering command results: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700267 }
268 }
269 if result.OrderBy != "" {
270 s, err := order.Parse(result.OrderBy)
271 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000272 Error.Fatalf("Unable to parse specified sort specification '%s': %s", result.OrderBy, err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700273 }
274 data, err = s.Process(data)
275 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000276 Error.Fatalf("Unexpected error while sorting command result: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700277 }
278 }
279 if result.OutputAs == OUTPUT_TABLE {
280 tableFormat := format.Format(result.Format)
David Bainbridge12f036f2019-10-15 22:09:04 +0000281 if err := tableFormat.Execute(os.Stdout, true, result.NameLimit, data); err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000282 Error.Fatalf("Unexpected error while attempting to format results as table : %s", err.Error())
David Bainbridge12f036f2019-10-15 22:09:04 +0000283 }
Zack Williamse940c7a2019-08-21 14:25:39 -0700284 } else if result.OutputAs == OUTPUT_JSON {
285 asJson, err := json.Marshal(&data)
286 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000287 Error.Fatalf("Unexpected error while processing command results to JSON: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700288 }
289 fmt.Printf("%s", asJson)
290 } else if result.OutputAs == OUTPUT_YAML {
291 asYaml, err := yaml.Marshal(&data)
292 if err != nil {
David Bainbridge0f758d42019-10-26 05:17:48 +0000293 Error.Fatalf("Unexpected error while processing command results to YAML: %s", err.Error())
Zack Williamse940c7a2019-08-21 14:25:39 -0700294 }
295 fmt.Printf("%s", asYaml)
296 }
297 }
298}