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 }