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  }