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 }