blob: bdd7dfb59f05582a15a9a4f8abb048e95327779b [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 (
20 "encoding/json"
21 "fmt"
22 "github.com/opencord/cordctl/format"
Scott Bakera00418a2019-06-03 16:15:28 -070023 "github.com/opencord/cordctl/order"
Scott Baker2c0ebda2019-05-06 16:55:47 -070024 "google.golang.org/grpc"
25 "gopkg.in/yaml.v2"
Scott Baker14c8f182019-05-22 18:05:29 -070026 "io"
Scott Baker2c0ebda2019-05-06 16:55:47 -070027 "io/ioutil"
28 "log"
29 "os"
30 "strings"
31 "time"
32)
33
34type OutputType uint8
35
36const (
37 OUTPUT_TABLE OutputType = iota
38 OUTPUT_JSON
39 OUTPUT_YAML
40)
41
Scott Baker14c8f182019-05-22 18:05:29 -070042// Make it easy to override output stream for testing
43var OutputStream io.Writer = os.Stdout
44
Scott Baker2c0ebda2019-05-06 16:55:47 -070045var CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
46
47type GrpcConfigSpec struct {
48 Timeout time.Duration `yaml:"timeout"`
49}
50
51type TlsConfigSpec struct {
52 UseTls bool `yaml:"useTls"`
53 CACert string `yaml:"caCert"`
54 Cert string `yaml:"cert"`
55 Key string `yaml:"key"`
56 Verify string `yaml:"verify"`
57}
58
59type GlobalConfigSpec struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070060 Server string `yaml:"server"`
61 Username string `yaml:"username"`
62 Password string `yaml:"password"`
Scott Baker14c8f182019-05-22 18:05:29 -070063 Protoset string `yaml:"protoset"`
Scott Baker6cf525a2019-05-09 12:25:08 -070064 Tls TlsConfigSpec `yaml:"tls"`
65 Grpc GrpcConfigSpec
Scott Baker2c0ebda2019-05-06 16:55:47 -070066}
67
68var GlobalConfig = GlobalConfigSpec{
Scott Baker6cf525a2019-05-09 12:25:08 -070069 Server: "localhost",
Scott Baker2c0ebda2019-05-06 16:55:47 -070070 Tls: TlsConfigSpec{
71 UseTls: false,
72 },
73 Grpc: GrpcConfigSpec{
74 Timeout: time.Second * 10,
75 },
76}
77
78var GlobalOptions struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070079 Config string `short:"c" long:"config" env:"CORDCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
80 Server string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of XOS"`
81 Username string `short:"u" long:"username" value-name:"USERNAME" default:"" description:"Username to authenticate with XOS"`
82 Password string `short:"p" long:"password" value-name:"PASSWORD" default:"" description:"Password to authenticate with XOS"`
Scott Baker14c8f182019-05-22 18:05:29 -070083 Protoset string `long:"protoset" value-name:"FILENAME" description:"Load protobuf definitions from protoset instead of reflection api"`
Scott Baker6cf525a2019-05-09 12:25:08 -070084 Debug bool `short:"d" long:"debug" description:"Enable debug mode"`
85 UseTLS bool `long:"tls" description:"Use TLS"`
86 CACert string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
87 Cert string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
88 Key string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
89 Verify bool `long:"tlsverify" description:"Use TLS and verify the remote"`
Scott Baker5281d002019-05-16 10:45:26 -070090 Yes bool `short:"y" long:"yes" description:"answer yes to any confirmation prompts"`
Scott Baker2c0ebda2019-05-06 16:55:47 -070091}
92
93type OutputOptions struct {
94 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
95 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
96 OutputAs string `short:"o" long:"outputas" default:"table" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
97}
98
Scott Bakera00418a2019-06-03 16:15:28 -070099type ListOutputOptions struct {
100 OutputOptions
101 OrderBy string `short:"r" long:"orderby" default:"" description:"Specify the sort order of the results"`
102}
103
Scott Baker2c0ebda2019-05-06 16:55:47 -0700104func toOutputType(in string) OutputType {
105 switch in {
106 case "table":
107 fallthrough
108 default:
109 return OUTPUT_TABLE
110 case "json":
111 return OUTPUT_JSON
112 case "yaml":
113 return OUTPUT_YAML
114 }
115}
116
117type CommandResult struct {
118 Format format.Format
Scott Bakera00418a2019-06-03 16:15:28 -0700119 OrderBy string
Scott Baker2c0ebda2019-05-06 16:55:47 -0700120 OutputAs OutputType
121 Data interface{}
122}
123
124type config struct {
Scott Baker6cf525a2019-05-09 12:25:08 -0700125 Server string `yaml:"server"`
Scott Baker2c0ebda2019-05-06 16:55:47 -0700126}
127
Scott Baker63ce82e2019-05-15 09:01:42 -0700128func ProcessGlobalOptions() {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700129 if len(GlobalOptions.Config) == 0 {
Scott Baker63ce82e2019-05-15 09:01:42 -0700130 home, err := os.UserHomeDir()
131 if err != nil {
132 log.Printf("Unable to discover the users home directory: %s\n", err)
133 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700134 GlobalOptions.Config = fmt.Sprintf("%s/.cord/config", home)
135 }
136
137 info, err := os.Stat(GlobalOptions.Config)
138 if err == nil && !info.IsDir() {
139 configFile, err := ioutil.ReadFile(GlobalOptions.Config)
140 if err != nil {
141 log.Printf("configFile.Get err #%v ", err)
142 }
143 err = yaml.Unmarshal(configFile, &GlobalConfig)
144 if err != nil {
145 log.Fatalf("Unmarshal: %v", err)
146 }
147 }
148
Scott Baker14c8f182019-05-22 18:05:29 -0700149 // Override from environment
150 // in particualr, for passing env vars via `go test`
151 env_server, present := os.LookupEnv("CORDCTL_SERVER")
152 if present {
153 GlobalConfig.Server = env_server
154 }
155 env_username, present := os.LookupEnv("CORDCTL_USERNAME")
156 if present {
157 GlobalConfig.Username = env_username
158 }
159 env_password, present := os.LookupEnv("CORDCTL_PASSWORD")
160 if present {
161 GlobalConfig.Password = env_password
162 }
163 env_protoset, present := os.LookupEnv("CORDCTL_PROTOSET")
164 if present {
165 GlobalConfig.Protoset = env_protoset
166 }
167
Scott Baker2c0ebda2019-05-06 16:55:47 -0700168 // Override from command line
169 if GlobalOptions.Server != "" {
170 GlobalConfig.Server = GlobalOptions.Server
171 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700172 if GlobalOptions.Username != "" {
173 GlobalConfig.Username = GlobalOptions.Username
174 }
175 if GlobalOptions.Password != "" {
176 GlobalConfig.Password = GlobalOptions.Password
177 }
Scott Baker14c8f182019-05-22 18:05:29 -0700178 if GlobalOptions.Protoset != "" {
179 GlobalConfig.Protoset = GlobalOptions.Protoset
180 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700181
Scott Baker5281d002019-05-16 10:45:26 -0700182 // Generate error messages for required settings
Scott Baker63ce82e2019-05-15 09:01:42 -0700183 if GlobalConfig.Server == "" {
184 log.Fatal("Server is not set. Please update config file or use the -s option")
185 }
186 if GlobalConfig.Username == "" {
187 log.Fatal("Username is not set. Please update config file or use the -u option")
188 }
189 if GlobalConfig.Password == "" {
190 log.Fatal("Password is not set. Please update config file or use the -p option")
191 }
192}
193
194func NewConnection() (*grpc.ClientConn, error) {
195 ProcessGlobalOptions()
Scott Baker2c0ebda2019-05-06 16:55:47 -0700196 return grpc.Dial(GlobalConfig.Server, grpc.WithInsecure())
197}
198
199func GenerateOutput(result *CommandResult) {
200 if result != nil && result.Data != nil {
Scott Bakera00418a2019-06-03 16:15:28 -0700201 data := result.Data
202 if result.OrderBy != "" {
203 s, err := order.Parse(result.OrderBy)
204 if err != nil {
205 panic(err)
206 }
207 data, err = s.Process(data)
208 if err != nil {
209 panic(err)
210 }
211 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700212 if result.OutputAs == OUTPUT_TABLE {
213 tableFormat := format.Format(result.Format)
Scott Bakera00418a2019-06-03 16:15:28 -0700214 tableFormat.Execute(OutputStream, true, data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700215 } else if result.OutputAs == OUTPUT_JSON {
Scott Bakera00418a2019-06-03 16:15:28 -0700216 asJson, err := json.Marshal(&data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700217 if err != nil {
218 panic(err)
219 }
Scott Baker14c8f182019-05-22 18:05:29 -0700220 fmt.Fprintf(OutputStream, "%s", asJson)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700221 } else if result.OutputAs == OUTPUT_YAML {
Scott Bakera00418a2019-06-03 16:15:28 -0700222 asYaml, err := yaml.Marshal(&data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700223 if err != nil {
224 panic(err)
225 }
Scott Baker14c8f182019-05-22 18:05:29 -0700226 fmt.Fprintf(OutputStream, "%s", asYaml)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700227 }
228 }
229}
Scott Baker5281d002019-05-16 10:45:26 -0700230
231// Applies common output options to format and generate output
232func FormatAndGenerateOutput(options *OutputOptions, default_format string, quiet_format string, data interface{}) {
233 outputFormat := CharReplacer.Replace(options.Format)
234 if outputFormat == "" {
235 outputFormat = default_format
236 }
Scott Baker175cb402019-05-17 16:13:06 -0700237 if options.Quiet {
Scott Baker5281d002019-05-16 10:45:26 -0700238 outputFormat = quiet_format
239 }
240
241 result := CommandResult{
242 Format: format.Format(outputFormat),
243 OutputAs: toOutputType(options.OutputAs),
244 Data: data,
245 }
246
247 GenerateOutput(&result)
248}
Scott Bakera00418a2019-06-03 16:15:28 -0700249
250// Applies common output options to format and generate output
251func FormatAndGenerateListOutput(options *ListOutputOptions, default_format string, quiet_format string, data interface{}) {
252 outputFormat := CharReplacer.Replace(options.Format)
253 if outputFormat == "" {
254 outputFormat = default_format
255 }
256 if options.Quiet {
257 outputFormat = quiet_format
258 }
259
260 result := CommandResult{
261 Format: format.Format(outputFormat),
262 OutputAs: toOutputType(options.OutputAs),
263 Data: data,
264 OrderBy: options.OrderBy,
265 }
266
267 GenerateOutput(&result)
268}