Skip to content

Commit

Permalink
Enhancements in registration and diagnostics (#4200)
Browse files Browse the repository at this point in the history
- When the cluster is registered but either license or api-key
  are available in the config, and when `--airgap` is not passed 
  to the command, try to automatically fetch and store the other
  both.
- Add `--api-key` flag to `mc support diag`
- Use header-based auth with subnet
- Introduce a common function to initialize/check connectivity with subnet
- Make the health report upload function generic so that it can be used
  by other commands like profile in future
- Add a global variable for airgapped mode
- Add more examples to `mc license register`
  • Loading branch information
anjalshireesh authored Aug 23, 2022
1 parent f70b7e5 commit 07fffc3
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 308 deletions.
3 changes: 3 additions & 0 deletions cmd/globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var (
globalInsecure = false // Insecure flag set via command line
globalDevMode = false // dev flag set via command line
globalSubnetProxyURL *url.URL // Proxy to be used for communication with subnet
globalAirgapped = false // Airgapped flag set via command line

globalConnReadDeadline time.Duration
globalConnWriteDeadline time.Duration
Expand All @@ -88,6 +89,7 @@ func setGlobalsFromContext(ctx *cli.Context) error {
noColor := ctx.IsSet("no-color") || ctx.GlobalIsSet("no-color")
insecure := ctx.IsSet("insecure") || ctx.GlobalIsSet("insecure")
devMode := ctx.IsSet("dev") || ctx.GlobalIsSet("dev")
airgapped := ctx.IsSet("airgap") || ctx.GlobalIsSet("airgap")

globalQuiet = globalQuiet || quiet
globalDebug = globalDebug || debug
Expand All @@ -96,6 +98,7 @@ func setGlobalsFromContext(ctx *cli.Context) error {
globalNoColor = globalNoColor || noColor || globalJSONLine
globalInsecure = globalInsecure || insecure
globalDevMode = globalDevMode || devMode
globalAirgapped = globalAirgapped || airgapped

// Disable colorified messages if requested.
if globalNoColor || globalQuiet {
Expand Down
73 changes: 36 additions & 37 deletions cmd/license-info.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,57 +160,56 @@ func mainLicenseInfo(ctx *cli.Context) error {
initLicInfoColors()

aliasedURL := ctx.Args().Get(0)
alias, _ := url2Alias(aliasedURL)
alias, _ := initSubnetConnectivity(ctx, aliasedURL)

apiKey, lic, e := getSubnetCreds(alias)
fatalIf(probe.NewError(e), "Error in checking cluster registration status")

if len(apiKey) == 0 && len(lic) == 0 {
// Not registered. Default to AGPLv3
printMsg(licInfoMessage{
var lim licInfoMessage
if len(lic) > 0 {
lim = getLicInfoMsg(lic)
} else if len(apiKey) > 0 {
lim = licInfoMessage{
Status: "success",
Info: licInfo{
Plan: "AGPLv3",
Message: getAGPLMessage(),
Message: fmt.Sprintf("%s is registered with SUBNET. License info not available.", alias),
},
})
return nil
}

var ssm licInfoMessage
if len(lic) > 0 {
// If set, the subnet public key will not be downloaded from subnet
// and the offline key embedded in mc will be used.
airgap := ctx.Bool("airgap")

li, e := parseLicense(lic, airgap)
if e != nil {
ssm = licInfoMessage{
Status: "error",
Error: e.Error(),
}
} else {
ssm = licInfoMessage{
Status: "success",
Info: licInfo{
Organization: li.Organization,
Plan: li.Plan,
IssuedAt: &li.IssuedAt,
ExpiresAt: &li.ExpiresAt,
DeploymentID: li.DeploymentID,
},
}
}
} else {
// Only api key is available, no license info
ssm = licInfoMessage{
// Not registered. Default to AGPLv3
lim = licInfoMessage{
Status: "success",
Info: licInfo{
Message: fmt.Sprintf("%s is registered with SUBNET. License info not available.", alias),
Plan: "AGPLv3",
Message: getAGPLMessage(),
},
}
}

printMsg(ssm)
printMsg(lim)
return nil
}

func getLicInfoMsg(lic string) licInfoMessage {
li, e := parseLicense(lic)
if e != nil {
return licErrMsg(e)
}
return licInfoMessage{
Status: "success",
Info: licInfo{
Organization: li.Organization,
Plan: li.Plan,
IssuedAt: &li.IssuedAt,
ExpiresAt: &li.ExpiresAt,
DeploymentID: li.DeploymentID,
},
}
}

func licErrMsg(e error) licInfoMessage {
return licInfoMessage{
Status: "error",
Error: e.Error(),
}
}
85 changes: 27 additions & 58 deletions cmd/license-register.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
package cmd

import (
"errors"
"fmt"

"github.com/fatih/color"
"github.com/google/uuid"
"github.com/minio/cli"
json "github.com/minio/colorjson"
"github.com/minio/madmin-go"
"github.com/minio/mc/pkg/probe"
"github.com/minio/pkg/console"
)

const licRegisterMsgTag = "licenseRegisterMessage"

var licenseRegisterFlags = append([]cli.Flag{
cli.StringFlag{
Name: "api-key",
Expand Down Expand Up @@ -58,11 +58,19 @@ FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}
EXAMPLES:
1. Register MinIO cluster at alias 'play' on SUBNET, using alias as the cluster name.
{{.Prompt}} {{.HelpName}} play
1. Register MinIO cluster at alias 'play' on SUBNET, using api key for auth
{{.Prompt}} {{.HelpName}} play --api-key 08efc836-4289-dbd4-ad82-b5e8b6d25577
2. Register MinIO cluster at alias 'play' on SUBNET, using api key for auth,
and "play-cluster" as the preferred name for the cluster on SUBNET.
{{.Prompt}} {{.HelpName}} play --api-key 08efc836-4289-dbd4-ad82-b5e8b6d25577 --name play-cluster
3. Register MinIO cluster at alias 'play' on SUBNET in an airgapped environment
{{.Prompt}} {{.HelpName}} play --airgap
2. Register MinIO cluster at alias 'play' on SUBNET, using the name "play-cluster".
{{.Prompt}} {{.HelpName}} play --name play-cluster
4. Register MinIO cluster at alias 'play' on SUBNET, using alias as the cluster name.
This asks for SUBNET credentials if the cluster is not already registered.
{{.Prompt}} {{.HelpName}} play
`,
}

Expand All @@ -84,7 +92,7 @@ func (li licRegisterMessage) String() string {
msg = fmt.Sprintln("Open the following URL in the browser to register", li.Alias, "on SUBNET:")
msg += li.URL
}
return console.Colorize(licUpdateMsgTag, msg)
return console.Colorize(licRegisterMsgTag, msg)
}

// JSON jsonified license register message
Expand All @@ -95,8 +103,8 @@ func (li licRegisterMessage) JSON() string {
return string(jsonBytes)
}

// checklicenseRegisterSyntax - validate arguments passed by a user
func checklicenseRegisterSyntax(ctx *cli.Context) {
// checkLicenseRegisterSyntax - validate arguments passed by a user
func checkLicenseRegisterSyntax(ctx *cli.Context) {
if len(ctx.Args()) == 0 || len(ctx.Args()) > 1 {
cli.ShowCommandHelpAndExit(ctx, "register", 1) // last argument is exit code
}
Expand Down Expand Up @@ -142,43 +150,19 @@ type SubnetMFAReq struct {
Token string `json:"token"`
}

func validateAPIKey(apiKey string, offline bool) error {
if offline {
return errors.New("--api-key is not applicable in airgap mode")
}

_, e := uuid.Parse(apiKey)
if e != nil {
return e
}

return nil
}

func mainLicenseRegister(ctx *cli.Context) error {
console.SetColor("RegisterSuccessMessage", color.New(color.FgGreen, color.Bold))
checklicenseRegisterSyntax(ctx)
console.SetColor(licRegisterMsgTag, color.New(color.FgGreen, color.Bold))
checkLicenseRegisterSyntax(ctx)

// Get the alias parameter from cli
aliasedURL := ctx.Args().Get(0)
alias, accAPIKey := initSubnetConnectivity(ctx, aliasedURL)

offline := ctx.Bool("airgap") || ctx.Bool("offline")
if !offline {
fatalIf(checkURLReachable(subnetBaseURL()).Trace(aliasedURL), "Unable to reach %s register", subnetBaseURL())
}

accAPIKey := ctx.String("api-key")
if len(accAPIKey) > 0 {
e := validateAPIKey(accAPIKey, offline)
fatalIf(probe.NewError(e), "unable to parse input values")
}

alias, _ := url2Alias(aliasedURL)
clusterName := ctx.String("name")
if len(clusterName) == 0 {
clusterName = alias
} else {
if offline {
if globalAirgapped {
fatalIf(errInvalidArgument(), "'--name' is not allowed in airgapped mode")
}
}
Expand All @@ -190,10 +174,13 @@ func mainLicenseRegister(ctx *cli.Context) error {
fatalIf(probe.NewError(e), "Error in fetching subnet credentials")
if len(apiKey) > 0 || len(lic) > 0 {
alreadyRegistered = true
if len(accAPIKey) == 0 {
accAPIKey = apiKey
}
}

lrm := licRegisterMessage{Status: "success", Alias: alias}
if offline {
if globalAirgapped {
lrm.Type = "offline"

regToken, e := generateRegToken(regInfo)
Expand All @@ -202,7 +189,8 @@ func mainLicenseRegister(ctx *cli.Context) error {
lrm.URL = subnetOfflineRegisterURL(regToken)
} else {
lrm.Type = "online"
registerOnline(regInfo, alias, accAPIKey)
_, _, e = registerClusterOnSubnet(regInfo, alias, accAPIKey)
fatalIf(probe.NewError(e), "Could not register cluster with SUBNET:")

lrm.Action = "registered"
if alreadyRegistered {
Expand All @@ -214,25 +202,6 @@ func mainLicenseRegister(ctx *cli.Context) error {
return nil
}

func registerOnline(clusterRegInfo ClusterRegistrationInfo, alias string, accAPIKey string) {
var resp string
var e error

if len(accAPIKey) > 0 {
resp, e = registerClusterWithSubnetCreds(clusterRegInfo, accAPIKey, "")
if e == nil {
// save the api key in config
setSubnetAPIKey(alias, accAPIKey)
}
} else {
resp, e = registerClusterOnSubnet(alias, clusterRegInfo)
}

fatalIf(probe.NewError(e), "Could not register cluster with SUBNET:")

extractAndSaveLicense(alias, resp)
}

func getAdminInfo(aliasedURL string) madmin.InfoMessage {
// Create a new MinIO Admin Client
client := getClient(aliasedURL)
Expand Down
10 changes: 3 additions & 7 deletions cmd/license-update.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,11 @@ func mainLicenseUpdate(ctx *cli.Context) error {

licFile := ctx.Args().Get(1)

// If set, the subnet public key will not be downloaded from subnet
// and the offline key embedded in mc will be used.
airgap := ctx.Bool("airgap")

printMsg(performLicenseUpdate(licFile, alias, airgap))
printMsg(performLicenseUpdate(licFile, alias))
return nil
}

func performLicenseUpdate(licFile string, alias string, airgap bool) licUpdateMessage {
func performLicenseUpdate(licFile string, alias string) licUpdateMessage {
lum := licUpdateMessage{
Alias: alias,
Status: "success",
Expand All @@ -102,7 +98,7 @@ func performLicenseUpdate(licFile string, alias string, airgap bool) licUpdateMe
fatalIf(probe.NewError(e), fmt.Sprintf("Unable to read license file %s", licFile))

lic := string(licBytes)
li, e := parseLicense(lic, airgap)
li, e := parseLicense(lic)
fatalIf(probe.NewError(e), fmt.Sprintf("Error parsing license from %s", licFile))

if li.ExpiresAt.Before(time.Now()) {
Expand Down
Loading

0 comments on commit 07fffc3

Please sign in to comment.