go.ligato.io/vpp-agent/v3@v3.5.0/cmd/agentctl/cli/cli.go (about) 1 // Copyright (c) 2019 Cisco and/or its affiliates. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at: 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cli 16 17 import ( 18 "context" 19 "io" 20 "runtime" 21 22 "github.com/docker/cli/cli/streams" 23 "github.com/moby/term" 24 "go.ligato.io/cn-infra/v2/logging" 25 26 "go.ligato.io/vpp-agent/v3/cmd/agentctl/api" 27 "go.ligato.io/vpp-agent/v3/cmd/agentctl/client" 28 "go.ligato.io/vpp-agent/v3/pkg/debug" 29 ) 30 31 // Cli represents the agent command line client. 32 type Cli interface { 33 Client() client.APIClient 34 35 Out() *streams.Out 36 Err() io.Writer 37 In() *streams.In 38 SetIn(in *streams.In) 39 Apply(ops ...AgentCliOption) error 40 ServerInfo() ServerInfo 41 ClientInfo() ClientInfo 42 DefaultVersion() string 43 } 44 45 type AgentCli struct { 46 client client.APIClient 47 in *streams.In 48 out *streams.Out 49 err io.Writer 50 serverInfo ServerInfo 51 clientInfo ClientInfo 52 } 53 54 // NewAgentCli returns a AgentCli instance with all operators applied on it. 55 // It applies by default the standard streams. 56 func NewAgentCli(ops ...AgentCliOption) (*AgentCli, error) { 57 cli := new(AgentCli) 58 var defaultOps []AgentCliOption 59 ops = append(defaultOps, ops...) 60 if err := cli.Apply(ops...); err != nil { 61 return nil, err 62 } 63 if cli.out == nil || cli.in == nil || cli.err == nil { 64 stdin, stdout, stderr := term.StdStreams() 65 if cli.in == nil { 66 cli.in = streams.NewIn(stdin) 67 } 68 if cli.out == nil { 69 cli.out = streams.NewOut(stdout) 70 } 71 if cli.err == nil { 72 cli.err = stderr 73 } 74 } 75 return cli, nil 76 } 77 78 // Client returns the APIClient 79 func (cli *AgentCli) Client() client.APIClient { 80 return cli.client 81 } 82 83 // Apply all the operation on the cli 84 func (cli *AgentCli) Apply(ops ...AgentCliOption) error { 85 for _, op := range ops { 86 if err := op(cli); err != nil { 87 return err 88 } 89 } 90 return nil 91 } 92 93 func (cli *AgentCli) Out() *streams.Out { 94 return cli.out 95 } 96 97 func (cli *AgentCli) Err() io.Writer { 98 return cli.err 99 } 100 101 func (cli *AgentCli) In() *streams.In { 102 return cli.in 103 } 104 105 func (cli *AgentCli) SetIn(in *streams.In) { 106 cli.in = in 107 } 108 109 func (cli *AgentCli) ServerInfo() ServerInfo { 110 return cli.serverInfo 111 } 112 113 func (cli *AgentCli) ClientInfo() ClientInfo { 114 return cli.clientInfo 115 } 116 117 func (cli *AgentCli) DefaultVersion() string { 118 return cli.clientInfo.DefaultVersion 119 } 120 121 // ServerInfo stores details about the supported features and platform of the 122 // server 123 type ServerInfo struct { 124 OSType string 125 } 126 127 // ClientInfo stores details about the supported features of the client 128 type ClientInfo struct { 129 DefaultVersion string 130 } 131 132 // UserAgent returns the user agent string used for making API requests 133 func UserAgent() string { 134 return "Ligato-Client/" + api.DefaultVersion + " (" + runtime.GOOS + ")" 135 } 136 137 // InitializeOpt is the type of the functional options passed to AgentCli.Initialize 138 type InitializeOpt func(agentCli *AgentCli) error 139 140 // Initialize the agentCli runs initialization that must happen after command 141 // line flags are parsed. 142 func (cli *AgentCli) Initialize(opts *ClientOptions, ops ...InitializeOpt) error { 143 var err error 144 for _, o := range ops { 145 if err := o(cli); err != nil { 146 return err 147 } 148 } 149 if opts.Debug { 150 debug.Enable() 151 SetLogLevel("debug") 152 } else { 153 SetLogLevel(opts.LogLevel) 154 } 155 cfg, err := MakeConfig() 156 if err != nil { 157 return err 158 } 159 if opts.Debug { 160 logging.Debug(cfg.DebugOutput()) 161 } 162 if cli.client == nil { 163 clientOptions := buildClientOptions(cfg) 164 cli.client, err = client.NewClientWithOpts(clientOptions...) 165 if err != nil { 166 return err 167 } 168 } 169 cli.clientInfo = ClientInfo{ 170 DefaultVersion: cli.client.Version(), 171 } 172 cli.initializeFromClient() 173 return nil 174 } 175 176 func buildClientOptions(cfg *Config) []client.Opt { 177 clientOpts := []client.Opt{ 178 client.WithHost(cfg.Host), 179 client.WithTimeout(cfg.Timeout), 180 client.WithServiceLabel(cfg.ServiceLabel), 181 client.WithGrpcPort(cfg.GRPCPort), 182 client.WithHTTPPort(cfg.HTTPPort), 183 client.WithUserAgent(UserAgent()), 184 client.WithHTTPBasicAuth(cfg.HTTPBasicAuth), 185 client.WithVersion(cfg.LigatoAPIVersion), 186 client.WithEtcdEndpoints(cfg.EtcdEndpoints), 187 client.WithEtcdDialTimeout(cfg.EtcdDialTimeout), 188 } 189 if cfg.ShouldUseSecureGRPC() { 190 clientOpts = append(clientOpts, client.WithGrpcTLS( 191 cfg.GRPCSecure.CertFile, 192 cfg.GRPCSecure.KeyFile, 193 cfg.GRPCSecure.CAFile, 194 cfg.GRPCSecure.SkipVerify, 195 )) 196 } 197 if cfg.ShouldUseSecureHTTP() { 198 clientOpts = append(clientOpts, client.WithHTTPTLS( 199 cfg.HTTPSecure.CertFile, 200 cfg.HTTPSecure.KeyFile, 201 cfg.HTTPSecure.CAFile, 202 cfg.HTTPSecure.SkipVerify, 203 )) 204 } 205 if cfg.ShouldUseSecureKVDB() { 206 clientOpts = append(clientOpts, client.WithKvdbTLS( 207 cfg.KVDBSecure.CertFile, 208 cfg.KVDBSecure.KeyFile, 209 cfg.KVDBSecure.CAFile, 210 cfg.KVDBSecure.SkipVerify, 211 )) 212 } 213 return clientOpts 214 } 215 216 func (cli *AgentCli) initializeFromClient() { 217 logging.Debugf("initializeFromClient (DefaultVersion: %v)", cli.DefaultVersion()) 218 219 version, err := cli.client.AgentVersion(context.Background()) 220 if err != nil { 221 // Default to true if we fail to connect to daemon 222 cli.serverInfo = ServerInfo{} 223 224 if version != nil && version.APIVersion != "" { 225 cli.client.NegotiateAPIVersionPing(version) 226 } 227 return 228 } 229 cli.serverInfo = ServerInfo{ 230 OSType: version.OSType, 231 } 232 cli.client.NegotiateAPIVersionPing(version) 233 }