Skip to content

Commit

Permalink
feat: Add --format json to connect command
Browse files Browse the repository at this point in the history
* It is possible to run connect command with CLI option
  --format json. The output of connect command is in machine
  readable format in such case.
  • Loading branch information
jirihnidek authored and subpop committed Sep 6, 2024
1 parent 19d1ee4 commit 30d5394
Showing 1 changed file with 161 additions and 49 deletions.
210 changes: 161 additions & 49 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,20 @@ func showTimeDuration(durations map[string]time.Duration) {
// showErrorMessages shows table with all error messages gathered during action
func showErrorMessages(action string, errorMessages map[string]LogMessage) error {
if hasPriorityErrors(errorMessages, log.CurrentLevel()) {
fmt.Println()
fmt.Printf("The following errors were encountered during %s:\n\n", action)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "TYPE\tSTEP\tERROR\t")
for step, logMsg := range errorMessages {
if logMsg.level <= log.CurrentLevel() {
_, _ = fmt.Fprintf(w, "%v\t%v\t%v\n", logMsg.level, step, logMsg.message)
if !uiSettings.isMachineReadable {
fmt.Println()
fmt.Printf("The following errors were encountered during %s:\n\n", action)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "TYPE\tSTEP\tERROR\t")
for step, logMsg := range errorMessages {
if logMsg.level <= log.CurrentLevel() {
_, _ = fmt.Fprintf(w, "%v\t%v\t%v\n", logMsg.level, step, logMsg.message)
}
}
_ = w.Flush()
if hasPriorityErrors(errorMessages, log.LevelError) {
return cli.Exit("", 1)
}
}
_ = w.Flush()
if hasPriorityErrors(errorMessages, log.LevelError) {
return cli.Exit("", 1)
}
}
return nil
Expand Down Expand Up @@ -269,40 +271,100 @@ func registerRHSM(ctx *cli.Context) (string, error) {
return successMsg, nil
}

// beforeConnectAction ensures that user has supplied a correct CLI options
// and there is no conflict between provided options
func beforeConnectAction(ctx *cli.Context) error {
// First check if machine-readable format is used
err := setupFormatOption(ctx)
if err != nil {
return err
}

username := ctx.String("username")
password := ctx.String("password")
organization := ctx.String("organization")
activationKeys := ctx.StringSlice("activation-key")

if len(activationKeys) > 0 {
if username != "" {
return fmt.Errorf("--username and --activation-key can not be used together")
}
if organization == "" {
return fmt.Errorf("--organization is required, when --activation-key is used")
}
}

// When machine-readable format is used, then additional requirements have to be met
if uiSettings.isMachineReadable {
if username != "" {
if password == "" {
return fmt.Errorf("--password is required, when --username and machine-readable format are used")
}
}
}
return nil
}

// connectAction tries to register system against Red Hat Subscription Management,
// gather the profile information that the system will configure
// connect system to Red Hat Insights and it also tries to start rhcd service
// connect system to Red Hat Insights, and it also tries to start rhcd service
func connectAction(ctx *cli.Context) error {
var connectResult ConnectResult
connectResult.format = ctx.String("format")

uid := os.Getuid()
if uid != 0 {
return cli.Exit(fmt.Errorf("error: non-root user cannot connect system"), 1)
errMsg := "non-root user cannot connect system"
exitCode := 1
if uiSettings.isMachineReadable {
connectResult.UID = uid
connectResult.UIDError = errMsg
return cli.Exit(connectResult, exitCode)
} else {
return cli.Exit(fmt.Errorf("error: %s", errMsg), exitCode)
}
}

var start time.Time
durations := make(map[string]time.Duration)
errorMessages := make(map[string]LogMessage)
hostname, err := os.Hostname()
if uiSettings.isMachineReadable {
connectResult.Hostname = hostname
}
if err != nil {
return cli.Exit(err, 1)
exitCode := 1
if uiSettings.isMachineReadable {
connectResult.HostnameError = err.Error()
return cli.Exit(connectResult, exitCode)
} else {
return cli.Exit(err, exitCode)
}
}

fmt.Printf("Connecting %v to %v.\nThis might take a few seconds.\n\n", hostname, Provider)
interactivePrintf("Connecting %v to %v.\nThis might take a few seconds.\n\n", hostname, Provider)

var start time.Time
durations := make(map[string]time.Duration)
errorMessages := make(map[string]LogMessage)
/* 1. Register to RHSM, because we need to get consumer certificate. This blocks following action */
start = time.Now()
var returnedMsg string
returnedMsg, err = registerRHSM(ctx)
if err != nil {
connectResult.RHSMConnected = false
errorMessages["rhsm"] = LogMessage{
level: log.LevelError,
message: fmt.Errorf("cannot connect to Red Hat Subscription Management: %w",
err)}
fmt.Printf(
"%v Cannot connect to Red Hat Subscription Management\n",
uiSettings.iconError,
)
if uiSettings.isMachineReadable {
connectResult.RHSMConnectError = errorMessages["rhsm"].message.Error()
} else {
fmt.Printf(
"%v Cannot connect to Red Hat Subscription Management\n",
uiSettings.iconError,
)
}
} else {
fmt.Printf("%v %v\n", uiSettings.iconOK, returnedMsg)
connectResult.RHSMConnected = true
interactivePrintf("%v %v\n", uiSettings.iconOK, returnedMsg)
}
durations["rhsm"] = time.Since(start)

Expand All @@ -318,38 +380,48 @@ func connectAction(ctx *cli.Context) error {
start = time.Now()
err = showProgress(" Connecting to Red Hat Insights...", registerInsights)
if err != nil {
connectResult.InsightsConnected = false
errorMessages["insights"] = LogMessage{
level: log.LevelError,
message: fmt.Errorf("cannot connect to Red Hat Insights: %w",
err)}
fmt.Printf("%v Cannot connect to Red Hat Insights\n", uiSettings.iconError)
level: log.LevelError,
message: fmt.Errorf("cannot connect to Red Hat Insights: %w", err)}
if uiSettings.isMachineReadable {
connectResult.InsightsError = errorMessages["insights"].message.Error()
} else {
fmt.Printf("%v Cannot connect to Red Hat Insights\n", uiSettings.iconError)
}
} else {
fmt.Printf("%v Connected to Red Hat Insights\n", uiSettings.iconOK)
connectResult.InsightsConnected = true
interactivePrintf("%v Connected to Red Hat Insights\n", uiSettings.iconOK)
}
durations["insights"] = time.Since(start)
}

/* 3. Start yggdrasil (rhcd) service */
if errors, exist := errorMessages["rhsm"]; exist {
if errors.level == log.LevelError {
fmt.Printf(
"%v Skipping activation of %v service\n",
uiSettings.iconError,
ServiceName,
)
}
if rhsmErrMsg, exist := errorMessages["rhsm"]; exist && rhsmErrMsg.level == log.LevelError {
connectResult.YggdrasilStarted = false
interactivePrintf(
"%v Skipping activation of %v service\n",
uiSettings.iconError,
ServiceName,
)
} else {
start = time.Now()
progressMessage := fmt.Sprintf(" Activating the %v service", ServiceName)
err = showProgress(progressMessage, activateService)
if err != nil {
connectResult.YggdrasilStarted = false
errorMessages[ServiceName] = LogMessage{
level: log.LevelError,
message: fmt.Errorf("cannot activate %s service: %w",
ServiceName, err)}
fmt.Printf("%v Cannot activate the %v service\n", uiSettings.iconError, ServiceName)
if uiSettings.isMachineReadable {
connectResult.YggdrasilStartedError = errorMessages[ServiceName].message.Error()
} else {
fmt.Printf("%v Cannot activate the %v service\n", uiSettings.iconError, ServiceName)
}
} else {
fmt.Printf("%v Activated the %v service\n", uiSettings.iconOK, ServiceName)
connectResult.YggdrasilStarted = true
interactivePrintf("%v Activated the %v service\n", uiSettings.iconOK, ServiceName)
}
durations[ServiceName] = time.Since(start)
}
Expand Down Expand Up @@ -382,26 +454,29 @@ func connectAction(ctx *cli.Context) error {
message: fmt.Errorf("cannot get the user profile: %w",
err)}
} else {
fmt.Printf("%v Enabled console.redhat.com services: ", uiSettings.iconInfo)
showConfProfile(&profile)
fmt.Printf("\n")
if !uiSettings.isMachineReadable {
fmt.Printf("%v Enabled console.redhat.com services: ", uiSettings.iconInfo)
showConfProfile(&profile)
fmt.Printf("\n")
}
}
fmt.Printf("\nSuccessfully connected to Red Hat!\n")

interactivePrintf("\nSuccessfully connected to Red Hat!\n")
}

/* 5. Show footer message */
fmt.Printf("\nManage your connected systems: https://red.ht/connector\n")
if !uiSettings.isMachineReadable {
/* 5. Show footer message */
fmt.Printf("\nManage your connected systems: https://red.ht/connector\n")

/* 6. Optionally display duration time of each sub-action */
showTimeDuration(durations)
/* 6. Optionally display duration time of each sub-action */
showTimeDuration(durations)
}

err = showErrorMessages("connect", errorMessages)
if err != nil {
return err
}

return nil
return cli.Exit(connectResult, 0)
}

// setupFormatOption ensures the user has supplied a correct `--format` flag
Expand Down Expand Up @@ -444,7 +519,6 @@ type DisconnectResult struct {
}

// Error implement error interface for structure DisconnectResult
// It is used for printing DisconnectResult by cli.Exit()
func (disconnectResult DisconnectResult) Error() string {
var result string
switch disconnectResult.format {
Expand All @@ -460,6 +534,38 @@ func (disconnectResult DisconnectResult) Error() string {
return result
}

// ConnectResult is structure holding information about results
// of connect command. The result could be printed in machine-readable format.
type ConnectResult struct {
Hostname string `json:"hostname"`
HostnameError string `json:"hostname_error,omitempty"`
UID int `json:"uid"`
UIDError string `json:"uid_error,omitempty"`
RHSMConnected bool `json:"rhsm_connected"`
RHSMConnectError string `json:"rhsm_connect_error,omitempty"`
InsightsConnected bool `json:"insights_connected"`
InsightsError string `json:"insights_connect_error,omitempty"`
YggdrasilStarted bool `json:"yggdrasil_started"`
YggdrasilStartedError string `json:"yggdrasil_started_error,omitempty"`
format string
}

// Error implement error interface for structure ConnectResult
func (connectResult ConnectResult) Error() string {
var result string
switch connectResult.format {
case "json":
data, err := json.MarshalIndent(connectResult, "", " ")
if err != nil {
return err.Error()
}
result = string(data)
default:
result = "error: unsupported document format: " + connectResult.format
}
return result
}

// beforeDisconnectAction ensures the used has supplied a correct `--format` flag
func beforeDisconnectAction(ctx *cli.Context) error {
return setupFormatOption(ctx)
Expand Down Expand Up @@ -853,10 +959,16 @@ func main() {
Hidden: true,
Usage: "register against `URL`",
},
&cli.StringFlag{
Name: "format",
Usage: "prints output of connection in machine-readable format (supported formats: \"json\")",
Aliases: []string{"f"},
},
},
Usage: "Connects the system to " + Provider,
UsageText: fmt.Sprintf("%v connect [command options]", app.Name),
Description: fmt.Sprintf("The connect command connects the system to Red Hat Subscription Management, Red Hat Insights and %v and activates the %v service that enables %v to interact with the system. For details visit: https://red.ht/connector", Provider, ServiceName, Provider),
Before: beforeConnectAction,
Action: connectAction,
},
{
Expand Down

0 comments on commit 30d5394

Please sign in to comment.