github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/support-callhome.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "fmt" 22 23 "github.com/charmbracelet/bubbles/table" 24 "github.com/charmbracelet/lipgloss" 25 "github.com/minio/cli" 26 json "github.com/minio/colorjson" 27 "github.com/minio/madmin-go/v3" 28 "github.com/minio/mc/pkg/probe" 29 "github.com/minio/pkg/v2/console" 30 ) 31 32 var supportCallhomeFlags = append([]cli.Flag{ 33 cli.BoolFlag{ 34 Name: "logs", 35 Usage: "push logs to SUBNET in real-time", 36 }, 37 cli.BoolFlag{ 38 Name: "diag", 39 Usage: "push diagnostics info to SUBNET every 24hrs", 40 }, 41 }, supportGlobalFlags...) 42 43 var supportCallhomeCmd = cli.Command{ 44 Name: "callhome", 45 Usage: "configure callhome settings", 46 OnUsageError: onUsageError, 47 Action: mainCallhome, 48 Before: setGlobalsFromContext, 49 Flags: supportCallhomeFlags, 50 CustomHelpTemplate: `NAME: 51 {{.HelpName}} - {{.Usage}} 52 53 USAGE: 54 {{.HelpName}} enable|disable|status ALIAS 55 56 OPTIONS: 57 enable - Enable callhome 58 disable - Disable callhome 59 status - Display callhome settings 60 61 FLAGS: 62 {{range .VisibleFlags}}{{.}} 63 {{end}} 64 EXAMPLES: 65 1. Enable callhome for cluster with alias 'myminio' 66 {{.Prompt}} {{.HelpName}} enable myminio 67 68 2. Disable callhome for cluster with alias 'myminio' 69 {{.Prompt}} {{.HelpName}} disable myminio 70 71 3. Check callhome status for cluster with alias 'myminio' 72 {{.Prompt}} {{.HelpName}} status myminio 73 74 4. Enable diagnostics callhome for cluster with alias 'myminio' 75 {{.Prompt}} {{.HelpName}} enable myminio --diag 76 77 5. Disable logs callhome for cluster with alias 'myminio' 78 {{.Prompt}} {{.HelpName}} disable myminio --logs 79 80 6. Check logs callhome status for cluster with alias 'myminio' 81 {{.Prompt}} {{.HelpName}} status myminio --logs 82 `, 83 } 84 85 type supportCallhomeMessage struct { 86 Status string `json:"status"` 87 Diag string `json:"diag,omitempty"` 88 Logs string `json:"logs,omitempty"` 89 Feature string `json:"-"` 90 Action string `json:"-"` 91 } 92 93 // String colorized callhome command output message. 94 func (s supportCallhomeMessage) String() string { 95 if s.Action == "status" { 96 columns := []table.Column{ 97 {Title: "Features", Width: 20}, 98 {Title: "", Width: 15}, 99 } 100 101 rows := []table.Row{} 102 103 if len(s.Diag) > 0 { 104 rows = append(rows, table.Row{licInfoField("Diagnostics"), licInfoVal(s.Diag)}) 105 } 106 if len(s.Logs) > 0 { 107 rows = append(rows, table.Row{licInfoField("Logs"), licInfoVal(s.Logs)}) 108 } 109 110 t := table.New( 111 table.WithColumns(columns), 112 table.WithRows(rows), 113 table.WithFocused(true), 114 table.WithHeight(len(rows)), 115 ) 116 117 s := table.DefaultStyles() 118 s.Header = s.Header. 119 BorderStyle(lipgloss.NormalBorder()). 120 BorderForeground(lipgloss.Color("240")). 121 BorderBottom(true). 122 Bold(false) 123 s.Selected = s.Selected.Bold(false) 124 t.SetStyles(s) 125 126 return lipgloss.NewStyle(). 127 BorderStyle(lipgloss.NormalBorder()). 128 BorderForeground(lipgloss.Color("240")).Render(t.View()) 129 } 130 131 return console.Colorize(supportSuccessMsgTag, s.Feature+" is now "+s.Action) 132 } 133 134 // JSON jsonified callhome command output message. 135 func (s supportCallhomeMessage) JSON() string { 136 s.Status = "success" 137 jsonBytes, e := json.MarshalIndent(s, "", " ") 138 fatalIf(probe.NewError(e), "Unable to marshal into JSON.") 139 140 return string(jsonBytes) 141 } 142 143 func isDiagCallhomeEnabled(alias string) bool { 144 return isFeatureEnabled(alias, "callhome", madmin.Default) 145 } 146 147 func mainCallhome(ctx *cli.Context) error { 148 initLicInfoColors() 149 150 setSuccessMessageColor() 151 alias, arg := checkToggleCmdSyntax(ctx) 152 apiKey := validateClusterRegistered(alias, true) 153 154 diag, logs := parseCallhomeFlags(ctx) 155 156 if arg == "status" { 157 printCallhomeStatus(alias, diag, logs) 158 return nil 159 } 160 161 toggleCallhome(alias, apiKey, arg == "enable", diag, logs) 162 163 return nil 164 } 165 166 func parseCallhomeFlags(ctx *cli.Context) (diag, logs bool) { 167 diag = ctx.Bool("diag") 168 logs = ctx.Bool("logs") 169 170 if !diag && !logs { 171 // When both flags are not passed, apply the action to both 172 diag = true 173 logs = true 174 } 175 176 return diag, logs 177 } 178 179 func printCallhomeStatus(alias string, diag, logs bool) { 180 resultMsg := supportCallhomeMessage{Action: "status"} 181 if diag { 182 resultMsg.Diag = featureStatusStr(isDiagCallhomeEnabled(alias)) 183 } 184 185 if logs { 186 resultMsg.Logs = featureStatusStr(isLogsCallhomeEnabled(alias)) 187 } 188 printMsg(resultMsg) 189 } 190 191 func toggleCallhome(alias, apiKey string, enable, diag, logs bool) { 192 newStatus := featureStatusStr(enable) 193 resultMsg := supportCallhomeMessage{ 194 Action: newStatus, 195 Feature: getFeature(diag, logs), 196 } 197 198 if diag { 199 setCallhomeConfig(alias, enable) 200 resultMsg.Diag = newStatus 201 } 202 203 if logs { 204 configureSubnetWebhook(alias, apiKey, enable) 205 resultMsg.Logs = newStatus 206 } 207 208 printMsg(resultMsg) 209 } 210 211 func getFeature(diag, logs bool) string { 212 if diag && logs { 213 return "Diagnostics and logs callhome" 214 } 215 216 if diag { 217 return "Diagnostics" 218 } 219 220 return "Logs" 221 } 222 223 func setCallhomeConfig(alias string, enableCallhome bool) { 224 // Create a new MinIO Admin Client 225 client, err := newAdminClient(alias) 226 fatalIf(err, "Unable to initialize admin connection.") 227 228 if !minioConfigSupportsSubSys(client, "callhome") { 229 fatal(errDummy().Trace(), "Your version of MinIO doesn't support this configuration") 230 } 231 232 enableStr := "off" 233 if enableCallhome { 234 enableStr = "on" 235 } 236 configStr := "callhome enable=" + enableStr 237 _, e := client.SetConfigKV(globalContext, configStr) 238 fatalIf(probe.NewError(e), "Unable to set callhome config on minio") 239 } 240 241 func configureSubnetWebhook(alias, apiKey string, enable bool) { 242 // Create a new MinIO Admin Client 243 client, err := newAdminClient(alias) 244 fatalIf(err, "Unable to initialize admin connection.") 245 246 var input string 247 if enable { 248 input = fmt.Sprintf("logger_webhook:subnet endpoint=%s auth_token=%s enable=on", 249 subnetLogWebhookURL(), apiKey) 250 } else { 251 input = "logger_webhook:subnet enable=off" 252 } 253 254 // Call set config API 255 _, e := client.SetConfigKV(globalContext, input) 256 fatalIf(probe.NewError(e), "Unable to set '%s' to server", input) 257 } 258 259 func isLogsCallhomeEnabled(alias string) bool { 260 return isFeatureEnabled(alias, "logger_webhook", "subnet") 261 }