blob: ef28d548344003e2030c63c6c312de78cea64472 [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"
A R Karthickefb1eef2019-07-16 12:43:24 -070032 "net"
Scott Baker2c0ebda2019-05-06 16:55:47 -070033)
34
35type OutputType uint8
36
37const (
38 OUTPUT_TABLE OutputType = iota
39 OUTPUT_JSON
40 OUTPUT_YAML
Scott Baker867aa302019-06-19 13:18:45 -070041
42 CORE_VERSION_CONSTRAINT = ">= 3, < 4" // Support XOS major version 3
Scott Baker2c0ebda2019-05-06 16:55:47 -070043)
44
Scott Baker14c8f182019-05-22 18:05:29 -070045// Make it easy to override output stream for testing
46var OutputStream io.Writer = os.Stdout
47
Scott Baker2c0ebda2019-05-06 16:55:47 -070048var CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
49
50type GrpcConfigSpec struct {
51 Timeout time.Duration `yaml:"timeout"`
52}
53
54type TlsConfigSpec struct {
55 UseTls bool `yaml:"useTls"`
56 CACert string `yaml:"caCert"`
57 Cert string `yaml:"cert"`
58 Key string `yaml:"key"`
59 Verify string `yaml:"verify"`
60}
61
62type GlobalConfigSpec struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070063 Server string `yaml:"server"`
64 Username string `yaml:"username"`
65 Password string `yaml:"password"`
Scott Baker14c8f182019-05-22 18:05:29 -070066 Protoset string `yaml:"protoset"`
Scott Baker6cf525a2019-05-09 12:25:08 -070067 Tls TlsConfigSpec `yaml:"tls"`
68 Grpc GrpcConfigSpec
Scott Baker2c0ebda2019-05-06 16:55:47 -070069}
70
71var GlobalConfig = GlobalConfigSpec{
Scott Baker6cf525a2019-05-09 12:25:08 -070072 Server: "localhost",
Scott Baker2c0ebda2019-05-06 16:55:47 -070073 Tls: TlsConfigSpec{
74 UseTls: false,
75 },
76 Grpc: GrpcConfigSpec{
77 Timeout: time.Second * 10,
78 },
79}
80
81var GlobalOptions struct {
Scott Baker6cf525a2019-05-09 12:25:08 -070082 Config string `short:"c" long:"config" env:"CORDCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
83 Server string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of XOS"`
84 Username string `short:"u" long:"username" value-name:"USERNAME" default:"" description:"Username to authenticate with XOS"`
85 Password string `short:"p" long:"password" value-name:"PASSWORD" default:"" description:"Password to authenticate with XOS"`
Scott Baker14c8f182019-05-22 18:05:29 -070086 Protoset string `long:"protoset" value-name:"FILENAME" description:"Load protobuf definitions from protoset instead of reflection api"`
Scott Baker6cf525a2019-05-09 12:25:08 -070087 Debug bool `short:"d" long:"debug" description:"Enable debug mode"`
88 UseTLS bool `long:"tls" description:"Use TLS"`
89 CACert string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
90 Cert string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
91 Key string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
92 Verify bool `long:"tlsverify" description:"Use TLS and verify the remote"`
Scott Baker5281d002019-05-16 10:45:26 -070093 Yes bool `short:"y" long:"yes" description:"answer yes to any confirmation prompts"`
Scott Baker2c0ebda2019-05-06 16:55:47 -070094}
95
96type OutputOptions struct {
97 Format string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
98 Quiet bool `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
99 OutputAs string `short:"o" long:"outputas" default:"table" choice:"table" choice:"json" choice:"yaml" description:"Type of output to generate"`
100}
101
Scott Bakera00418a2019-06-03 16:15:28 -0700102type ListOutputOptions struct {
103 OutputOptions
104 OrderBy string `short:"r" long:"orderby" default:"" description:"Specify the sort order of the results"`
105}
106
Scott Baker2c0ebda2019-05-06 16:55:47 -0700107func toOutputType(in string) OutputType {
108 switch in {
109 case "table":
110 fallthrough
111 default:
112 return OUTPUT_TABLE
113 case "json":
114 return OUTPUT_JSON
115 case "yaml":
116 return OUTPUT_YAML
117 }
118}
119
120type CommandResult struct {
121 Format format.Format
Scott Bakera00418a2019-06-03 16:15:28 -0700122 OrderBy string
Scott Baker2c0ebda2019-05-06 16:55:47 -0700123 OutputAs OutputType
124 Data interface{}
125}
126
127type config struct {
Scott Baker6cf525a2019-05-09 12:25:08 -0700128 Server string `yaml:"server"`
Scott Baker2c0ebda2019-05-06 16:55:47 -0700129}
130
Scott Baker63ce82e2019-05-15 09:01:42 -0700131func ProcessGlobalOptions() {
Scott Baker2c0ebda2019-05-06 16:55:47 -0700132 if len(GlobalOptions.Config) == 0 {
Scott Baker63ce82e2019-05-15 09:01:42 -0700133 home, err := os.UserHomeDir()
134 if err != nil {
135 log.Printf("Unable to discover the users home directory: %s\n", err)
136 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700137 GlobalOptions.Config = fmt.Sprintf("%s/.cord/config", home)
138 }
139
140 info, err := os.Stat(GlobalOptions.Config)
141 if err == nil && !info.IsDir() {
142 configFile, err := ioutil.ReadFile(GlobalOptions.Config)
143 if err != nil {
144 log.Printf("configFile.Get err #%v ", err)
145 }
146 err = yaml.Unmarshal(configFile, &GlobalConfig)
147 if err != nil {
148 log.Fatalf("Unmarshal: %v", err)
149 }
150 }
151
Scott Baker14c8f182019-05-22 18:05:29 -0700152 // Override from environment
153 // in particualr, for passing env vars via `go test`
154 env_server, present := os.LookupEnv("CORDCTL_SERVER")
155 if present {
156 GlobalConfig.Server = env_server
157 }
158 env_username, present := os.LookupEnv("CORDCTL_USERNAME")
159 if present {
160 GlobalConfig.Username = env_username
161 }
162 env_password, present := os.LookupEnv("CORDCTL_PASSWORD")
163 if present {
164 GlobalConfig.Password = env_password
165 }
166 env_protoset, present := os.LookupEnv("CORDCTL_PROTOSET")
167 if present {
168 GlobalConfig.Protoset = env_protoset
169 }
170
Scott Baker2c0ebda2019-05-06 16:55:47 -0700171 // Override from command line
172 if GlobalOptions.Server != "" {
173 GlobalConfig.Server = GlobalOptions.Server
174 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700175 if GlobalOptions.Username != "" {
176 GlobalConfig.Username = GlobalOptions.Username
177 }
178 if GlobalOptions.Password != "" {
179 GlobalConfig.Password = GlobalOptions.Password
180 }
Scott Baker14c8f182019-05-22 18:05:29 -0700181 if GlobalOptions.Protoset != "" {
182 GlobalConfig.Protoset = GlobalOptions.Protoset
183 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700184
Scott Baker5281d002019-05-16 10:45:26 -0700185 // Generate error messages for required settings
Scott Baker63ce82e2019-05-15 09:01:42 -0700186 if GlobalConfig.Server == "" {
187 log.Fatal("Server is not set. Please update config file or use the -s option")
188 }
189 if GlobalConfig.Username == "" {
190 log.Fatal("Username is not set. Please update config file or use the -u option")
191 }
192 if GlobalConfig.Password == "" {
193 log.Fatal("Password is not set. Please update config file or use the -p option")
194 }
A R Karthickefb1eef2019-07-16 12:43:24 -0700195 //Try to resolve hostname if provided for the server
196 if host, port, err := net.SplitHostPort(GlobalConfig.Server); err == nil {
197 if addrs, err := net.LookupHost(host); err == nil {
198 GlobalConfig.Server = net.JoinHostPort(addrs[0], port)
199 }
200 }
Scott Baker63ce82e2019-05-15 09:01:42 -0700201}
202
203func NewConnection() (*grpc.ClientConn, error) {
204 ProcessGlobalOptions()
Scott Baker2c0ebda2019-05-06 16:55:47 -0700205 return grpc.Dial(GlobalConfig.Server, grpc.WithInsecure())
206}
207
208func GenerateOutput(result *CommandResult) {
209 if result != nil && result.Data != nil {
Scott Bakera00418a2019-06-03 16:15:28 -0700210 data := result.Data
211 if result.OrderBy != "" {
212 s, err := order.Parse(result.OrderBy)
213 if err != nil {
214 panic(err)
215 }
216 data, err = s.Process(data)
217 if err != nil {
218 panic(err)
219 }
220 }
Scott Baker2c0ebda2019-05-06 16:55:47 -0700221 if result.OutputAs == OUTPUT_TABLE {
222 tableFormat := format.Format(result.Format)
Scott Bakera00418a2019-06-03 16:15:28 -0700223 tableFormat.Execute(OutputStream, true, data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700224 } else if result.OutputAs == OUTPUT_JSON {
Scott Bakera00418a2019-06-03 16:15:28 -0700225 asJson, err := json.Marshal(&data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700226 if err != nil {
227 panic(err)
228 }
Scott Baker14c8f182019-05-22 18:05:29 -0700229 fmt.Fprintf(OutputStream, "%s", asJson)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700230 } else if result.OutputAs == OUTPUT_YAML {
Scott Bakera00418a2019-06-03 16:15:28 -0700231 asYaml, err := yaml.Marshal(&data)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700232 if err != nil {
233 panic(err)
234 }
Scott Baker14c8f182019-05-22 18:05:29 -0700235 fmt.Fprintf(OutputStream, "%s", asYaml)
Scott Baker2c0ebda2019-05-06 16:55:47 -0700236 }
237 }
238}
Scott Baker5281d002019-05-16 10:45:26 -0700239
240// Applies common output options to format and generate output
241func FormatAndGenerateOutput(options *OutputOptions, default_format string, quiet_format string, data interface{}) {
242 outputFormat := CharReplacer.Replace(options.Format)
243 if outputFormat == "" {
244 outputFormat = default_format
245 }
Scott Baker175cb402019-05-17 16:13:06 -0700246 if options.Quiet {
Scott Baker5281d002019-05-16 10:45:26 -0700247 outputFormat = quiet_format
248 }
249
250 result := CommandResult{
251 Format: format.Format(outputFormat),
252 OutputAs: toOutputType(options.OutputAs),
253 Data: data,
254 }
255
256 GenerateOutput(&result)
257}
Scott Bakera00418a2019-06-03 16:15:28 -0700258
259// Applies common output options to format and generate output
260func FormatAndGenerateListOutput(options *ListOutputOptions, default_format string, quiet_format string, data interface{}) {
261 outputFormat := CharReplacer.Replace(options.Format)
262 if outputFormat == "" {
263 outputFormat = default_format
264 }
265 if options.Quiet {
266 outputFormat = quiet_format
267 }
268
269 result := CommandResult{
270 Format: format.Format(outputFormat),
271 OutputAs: toOutputType(options.OutputAs),
272 Data: data,
273 OrderBy: options.OrderBy,
274 }
275
276 GenerateOutput(&result)
277}