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  }