zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/server_info_cmd.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package client
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/spf13/cobra"
    14  	"gopkg.in/yaml.v2"
    15  
    16  	zerr "zotregistry.dev/zot/errors"
    17  	"zotregistry.dev/zot/pkg/api/constants"
    18  )
    19  
    20  const (
    21  	StatusOnline  = "online"
    22  	StatusOffline = "offline"
    23  	StatusUnknown = "unknown"
    24  )
    25  
    26  func NewServerStatusCommand() *cobra.Command {
    27  	serverInfoCmd := &cobra.Command{
    28  		Use:   "status",
    29  		Short: "Information about the server configuration and build information",
    30  		Long:  `Information about the server configuration and build information`,
    31  		Args:  cobra.NoArgs,
    32  		RunE: func(cmd *cobra.Command, args []string) error {
    33  			searchConfig, err := GetSearchConfigFromFlags(cmd, NewSearchService())
    34  			if err != nil {
    35  				return err
    36  			}
    37  
    38  			return GetServerStatus(searchConfig)
    39  		},
    40  	}
    41  
    42  	serverInfoCmd.PersistentFlags().String(URLFlag, "",
    43  		"Specify zot server URL if config-name is not mentioned")
    44  	serverInfoCmd.PersistentFlags().StringP(ConfigFlag, "c", "",
    45  		"Specify the registry configuration to use for connection")
    46  	serverInfoCmd.PersistentFlags().StringP(UserFlag, "u", "",
    47  		`User Credentials of zot server in "username:password" format`)
    48  	serverInfoCmd.Flags().StringP(OutputFormatFlag, "f", "text", "Specify the output format [text|json|yaml]")
    49  
    50  	return serverInfoCmd
    51  }
    52  
    53  func GetServerStatus(config SearchConfig) error {
    54  	ctx := context.Background()
    55  	username, password := getUsernameAndPassword(config.User)
    56  
    57  	checkAPISupportEndpoint, err := combineServerAndEndpointURL(config.ServURL, constants.RoutePrefix+"/")
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	_, err = makeGETRequest(ctx, checkAPISupportEndpoint, username, password, config.VerifyTLS, config.Debug,
    63  		nil, config.ResultWriter)
    64  	if err != nil {
    65  		serverInfo := ServerInfo{}
    66  
    67  		switch {
    68  		case errors.Is(err, zerr.ErrUnauthorizedAccess):
    69  			serverInfo.Status = StatusUnknown
    70  			serverInfo.ErrorMsg = fmt.Sprintf("unauthorised access, %s", getCredentialsSuggestion(username))
    71  		case errors.Is(err, zerr.ErrBadHTTPStatusCode), errors.Is(err, zerr.ErrURLNotFound):
    72  			serverInfo.Status = StatusOffline
    73  			serverInfo.ErrorMsg = fmt.Sprintf("%s: request at %s failed", zerr.ErrAPINotSupported.Error(),
    74  				checkAPISupportEndpoint)
    75  		default:
    76  			serverInfo.Status = StatusOffline
    77  			serverInfo.ErrorMsg = err.Error()
    78  		}
    79  
    80  		return PrintServerInfo(serverInfo, config)
    81  	}
    82  
    83  	mgmtEndpoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s",
    84  		constants.RoutePrefix, constants.ExtMgmt))
    85  	if err != nil {
    86  		return err
    87  	}
    88  
    89  	serverInfo := ServerInfo{}
    90  
    91  	_, err = makeGETRequest(ctx, mgmtEndpoint, username, password, config.VerifyTLS, config.Debug,
    92  		&serverInfo, config.ResultWriter)
    93  
    94  	switch {
    95  	case err == nil:
    96  		serverInfo.Status = StatusOnline
    97  	case errors.Is(err, zerr.ErrURLNotFound):
    98  		serverInfo.Status = StatusOnline
    99  		serverInfo.ErrorMsg = fmt.Sprintf("%s%s endpoint is not available", constants.RoutePrefix, constants.ExtMgmt)
   100  	case errors.Is(err, zerr.ErrUnauthorizedAccess):
   101  		serverInfo.Status = StatusOnline
   102  		serverInfo.ErrorMsg = fmt.Sprintf("unauthorised access, %s", getCredentialsSuggestion(username))
   103  	case errors.Is(err, zerr.ErrBadHTTPStatusCode):
   104  		serverInfo.Status = StatusOnline
   105  		serverInfo.ErrorMsg = fmt.Sprintf("%s: request at %s failed", zerr.ErrAPINotSupported.Error(),
   106  			checkAPISupportEndpoint)
   107  	default:
   108  		serverInfo.Status = StatusOffline
   109  		serverInfo.ErrorMsg = err.Error()
   110  	}
   111  
   112  	return PrintServerInfo(serverInfo, config)
   113  }
   114  
   115  func getCredentialsSuggestion(username string) string {
   116  	if username == "" {
   117  		return "endpoint requires valid user credentials (add the flag '--user [user]:[password]')"
   118  	}
   119  
   120  	return "given credentials are invalid"
   121  }
   122  
   123  func PrintServerInfo(serverInfo ServerInfo, config SearchConfig) error {
   124  	outputResult, err := serverInfo.ToStringFormat(config.OutputFormat)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	fmt.Fprintln(config.ResultWriter, outputResult)
   130  
   131  	return nil
   132  }
   133  
   134  type ServerInfo struct {
   135  	Status          string `json:"status,omitempty"          mapstructure:"status"`
   136  	ErrorMsg        string `json:"error,omitempty"           mapstructure:"error"`
   137  	DistSpecVersion string `json:"distSpecVersion,omitempty" mapstructure:"distSpecVersion"`
   138  	Commit          string `json:"commit,omitempty"          mapstructure:"commit"`
   139  	BinaryType      string `json:"binaryType,omitempty"      mapstructure:"binaryType"`
   140  	ReleaseTag      string `json:"releaseTag,omitempty"      mapstructure:"releaseTag"`
   141  }
   142  
   143  func (si *ServerInfo) ToStringFormat(format string) (string, error) {
   144  	switch format {
   145  	case "text", "":
   146  		return si.ToText()
   147  	case "json":
   148  		return si.ToJSON()
   149  	case "yaml", "yml":
   150  		return si.ToYAML()
   151  	default:
   152  		return "", zerr.ErrFormatNotSupported
   153  	}
   154  }
   155  
   156  func (si *ServerInfo) ToText() (string, error) {
   157  	flagsList := strings.Split(strings.Trim(si.BinaryType, "-"), "-")
   158  	flags := strings.Join(flagsList, ", ")
   159  
   160  	var output string
   161  
   162  	if si.ErrorMsg != "" {
   163  		serverStatus := fmt.Sprintf("Server Status: %s\n"+
   164  			"Error: %s", si.Status, si.ErrorMsg)
   165  
   166  		output = serverStatus
   167  	} else {
   168  		serverStatus := fmt.Sprintf("Server Status: %s", si.Status)
   169  		serverInfo := fmt.Sprintf("Server Version: %s\n"+
   170  			"Dist Spec Version: %s\n"+
   171  			"Built with: %s",
   172  			si.ReleaseTag, si.DistSpecVersion, flags,
   173  		)
   174  
   175  		output = serverStatus + "\n" + serverInfo
   176  	}
   177  
   178  	return output, nil
   179  }
   180  
   181  func (si *ServerInfo) ToJSON() (string, error) {
   182  	blob, err := json.MarshalIndent(*si, "", "    ")
   183  
   184  	return string(blob), err
   185  }
   186  
   187  func (si *ServerInfo) ToYAML() (string, error) {
   188  	body, err := yaml.Marshal(*si)
   189  
   190  	return string(body), err
   191  }