github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/license-info.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  	"net/http"
    23  	"time"
    24  
    25  	"github.com/charmbracelet/bubbles/table"
    26  	"github.com/charmbracelet/lipgloss"
    27  	"github.com/fatih/color"
    28  	"github.com/minio/cli"
    29  	json "github.com/minio/colorjson"
    30  	"github.com/minio/mc/pkg/probe"
    31  	"github.com/minio/pkg/v2/console"
    32  )
    33  
    34  var licenseInfoCmd = cli.Command{
    35  	Name:         "info",
    36  	Usage:        "display license information",
    37  	OnUsageError: onUsageError,
    38  	Action:       mainLicenseInfo,
    39  	Before:       setGlobalsFromContext,
    40  	Flags:        subnetCommonFlags,
    41  	CustomHelpTemplate: `NAME:
    42    {{.HelpName}} - {{.Usage}}
    43  
    44  USAGE:
    45    {{.HelpName}} ALIAS
    46  
    47  FLAGS:
    48    {{range .VisibleFlags}}{{.}}
    49    {{end}}
    50  
    51  EXAMPLES:
    52    1. Display license configuration for cluster with alias 'play'
    53       {{.Prompt}} {{.HelpName}} play
    54  `,
    55  }
    56  
    57  const (
    58  	licInfoMsgTag   = "licenseInfoMessage"
    59  	licInfoErrTag   = "licenseInfoError"
    60  	licInfoFieldTag = "licenseInfoField"
    61  	licInfoValTag   = "licenseValueField"
    62  )
    63  
    64  type licInfoMessage struct {
    65  	Status string  `json:"status"`
    66  	Info   licInfo `json:"info,omitempty"`
    67  	Error  string  `json:"error,omitempty"`
    68  }
    69  
    70  type licInfo struct {
    71  	LicenseID    string     `json:"license_id,omitempty"`    // Unique ID of the license
    72  	Organization string     `json:"org,omitempty"`           // Subnet organization name
    73  	Plan         string     `json:"plan,omitempty"`          // Subnet plan
    74  	IssuedAt     *time.Time `json:"issued_at,omitempty"`     // Time of license issue
    75  	ExpiresAt    *time.Time `json:"expires_at,omitempty"`    // Time of license expiry
    76  	DeploymentID string     `json:"deployment_id,omitempty"` // Cluster deployment ID
    77  	Message      string     `json:"message,omitempty"`       // Message to be displayed
    78  	APIKey       string     `json:"api_key,omitempty"`       // API Key of the org account
    79  }
    80  
    81  func licInfoField(s string) string {
    82  	return console.Colorize(licInfoFieldTag, s)
    83  }
    84  
    85  func licInfoVal(s string) string {
    86  	return console.Colorize(licInfoValTag, s)
    87  }
    88  
    89  func licInfoMsg(s string) string {
    90  	return console.Colorize(licInfoMsgTag, s)
    91  }
    92  
    93  func licInfoErr(s string) string {
    94  	return console.Colorize(licInfoErrTag, s)
    95  }
    96  
    97  // String colorized license info
    98  func (li licInfoMessage) String() string {
    99  	if len(li.Error) > 0 {
   100  		return licInfoErr(li.Error)
   101  	}
   102  
   103  	if len(li.Info.Message) > 0 {
   104  		return licInfoMsg(li.Info.Message)
   105  	}
   106  
   107  	return getLicInfoStr(li.Info)
   108  }
   109  
   110  // JSON jsonified license info
   111  func (li licInfoMessage) JSON() string {
   112  	jsonBytes, e := json.MarshalIndent(li, "", " ")
   113  	fatalIf(probe.NewError(e), "Unable to marshal into JSON.")
   114  
   115  	return string(jsonBytes)
   116  }
   117  
   118  func getLicInfoStr(li licInfo) string {
   119  	columns := []table.Column{
   120  		{Title: "License", Width: 20},
   121  		{Title: "", Width: 45},
   122  	}
   123  
   124  	rows := []table.Row{
   125  		{licInfoField("Organization"), licInfoVal(li.Organization)},
   126  		{licInfoField("Plan"), licInfoVal(li.Plan)},
   127  		{licInfoField("Issued"), licInfoVal(li.IssuedAt.Format(http.TimeFormat))},
   128  		{licInfoField("Expires"), licInfoVal(li.ExpiresAt.Format(http.TimeFormat))},
   129  	}
   130  
   131  	if len(li.LicenseID) > 0 {
   132  		rows = append(rows, table.Row{licInfoField("License ID"), licInfoVal(li.LicenseID)})
   133  	}
   134  	if len(li.DeploymentID) > 0 {
   135  		rows = append(rows, table.Row{licInfoField("Deployment ID"), licInfoVal(li.DeploymentID)})
   136  	}
   137  	if len(li.APIKey) > 0 {
   138  		rows = append(rows, table.Row{licInfoField("API Key"), licInfoVal(li.APIKey)})
   139  	}
   140  
   141  	t := table.New(
   142  		table.WithColumns(columns),
   143  		table.WithRows(rows),
   144  		table.WithFocused(true),
   145  		table.WithHeight(len(rows)),
   146  	)
   147  
   148  	s := table.DefaultStyles()
   149  	s.Header = s.Header.
   150  		BorderStyle(lipgloss.NormalBorder()).
   151  		BorderForeground(lipgloss.Color("240")).
   152  		BorderBottom(true).
   153  		Bold(false)
   154  	s.Selected = s.Selected.Bold(false)
   155  	t.SetStyles(s)
   156  
   157  	return lipgloss.NewStyle().
   158  		BorderStyle(lipgloss.NormalBorder()).
   159  		BorderForeground(lipgloss.Color("240")).Render(t.View())
   160  }
   161  
   162  func getAGPLMessage() string {
   163  	return `License: GNU AGPL v3 <https://www.gnu.org/licenses/agpl-3.0.txt>
   164  If you are distributing or hosting MinIO along with your proprietary application as combined works, you may require a commercial license included in the Standard and Enterprise subscription plans. (https://min.io/signup?ref=mc)`
   165  }
   166  
   167  func initLicInfoColors() {
   168  	console.SetColor(licInfoMsgTag, color.New(color.FgGreen, color.Bold))
   169  	console.SetColor(licInfoErrTag, color.New(color.FgRed, color.Bold))
   170  	console.SetColor(licInfoFieldTag, color.New(color.FgCyan))
   171  	console.SetColor(licInfoValTag, color.New(color.FgWhite))
   172  }
   173  
   174  func mainLicenseInfo(ctx *cli.Context) error {
   175  	if len(ctx.Args()) != 1 {
   176  		showCommandHelpAndExit(ctx, 1) // last argument is exit code
   177  	}
   178  
   179  	initLicInfoColors()
   180  
   181  	aliasedURL := ctx.Args().Get(0)
   182  	alias, _ := initSubnetConnectivity(ctx, aliasedURL, true)
   183  
   184  	apiKey, lic, e := getSubnetCreds(alias)
   185  	fatalIf(probe.NewError(e), "Error in checking cluster registration status")
   186  
   187  	var lim licInfoMessage
   188  	if len(lic) > 0 {
   189  		lim = getLicInfoMsg(lic)
   190  	} else if len(apiKey) > 0 {
   191  		lim = licInfoMessage{
   192  			Status: "success",
   193  			Info: licInfo{
   194  				Message: fmt.Sprintf("%s is registered with SUBNET. License info not available.", alias),
   195  			},
   196  		}
   197  	} else {
   198  		// Not registered. Default to AGPLv3
   199  		lim = licInfoMessage{
   200  			Status: "success",
   201  			Info: licInfo{
   202  				Plan:    "AGPLv3",
   203  				Message: getAGPLMessage(),
   204  			},
   205  		}
   206  	}
   207  
   208  	printMsg(lim)
   209  	return nil
   210  }
   211  
   212  func getLicInfoMsg(lic string) licInfoMessage {
   213  	li, e := parseLicense(lic)
   214  	if e != nil {
   215  		return licErrMsg(e)
   216  	}
   217  	return licInfoMessage{
   218  		Status: "success",
   219  		Info: licInfo{
   220  			LicenseID:    li.LicenseID,
   221  			Organization: li.Organization,
   222  			Plan:         li.Plan,
   223  			IssuedAt:     &li.IssuedAt,
   224  			ExpiresAt:    &li.ExpiresAt,
   225  			DeploymentID: li.DeploymentID,
   226  			APIKey:       li.APIKey,
   227  		},
   228  	}
   229  }
   230  
   231  func licErrMsg(e error) licInfoMessage {
   232  	return licInfoMessage{
   233  		Status: "error",
   234  		Error:  e.Error(),
   235  	}
   236  }