diff --git a/main.go b/main.go index cf77b48..8e66dec 100644 --- a/main.go +++ b/main.go @@ -298,10 +298,11 @@ func connectAction(ctx *cli.Context) error { message: fmt.Errorf("cannot connect to Red Hat Subscription Management: %w", err)} fmt.Printf( - uiSettings.iconError + " Cannot connect to Red Hat Subscription Management\n", + "%v Cannot connect to Red Hat Subscription Management\n", + uiSettings.iconError, ) } else { - fmt.Printf(uiSettings.iconOK + " " + returnedMsg + "\n") + fmt.Printf("%v %v\n", uiSettings.iconOK, returnedMsg) } durations["rhsm"] = time.Since(start) @@ -309,7 +310,8 @@ func connectAction(ctx *cli.Context) error { if errors, exist := errorMessages["rhsm"]; exist { if errors.level == log.LevelError { fmt.Printf( - uiSettings.iconError + " Skipping connection to Red Hat Insights\n", + "%v Skipping connection to Red Hat Insights\n", + uiSettings.iconError, ) } } else { @@ -320,9 +322,9 @@ func connectAction(ctx *cli.Context) error { level: log.LevelError, message: fmt.Errorf("cannot connect to Red Hat Insights: %w", err)} - fmt.Printf(uiSettings.iconError + " Cannot connect to Red Hat Insights\n") + fmt.Printf("%v Cannot connect to Red Hat Insights\n", uiSettings.iconError) } else { - fmt.Printf(uiSettings.iconOK + " Connected to Red Hat Insights\n") + fmt.Printf("%v Connected to Red Hat Insights\n", uiSettings.iconOK) } durations["insights"] = time.Since(start) } @@ -331,7 +333,8 @@ func connectAction(ctx *cli.Context) error { if errors, exist := errorMessages["rhsm"]; exist { if errors.level == log.LevelError { fmt.Printf( - uiSettings.iconError+" Skipping activation of %v service\n", + "%v Skipping activation of %v service\n", + uiSettings.iconError, ServiceName, ) } @@ -344,9 +347,9 @@ func connectAction(ctx *cli.Context) error { level: log.LevelError, message: fmt.Errorf("cannot activate %s service: %w", ServiceName, err)} - fmt.Printf(uiSettings.iconError+" Cannot activate the %v service\n", ServiceName) + fmt.Printf("%v Cannot activate the %v service\n", uiSettings.iconError, ServiceName) } else { - fmt.Printf(uiSettings.iconOK+" Activated the %v service\n", ServiceName) + fmt.Printf("%v Activated the %v service\n", uiSettings.iconOK, ServiceName) } durations[ServiceName] = time.Since(start) } @@ -379,7 +382,7 @@ func connectAction(ctx *cli.Context) error { message: fmt.Errorf("cannot get the user profile: %w", err)} } else { - fmt.Printf(uiSettings.iconInfo + " Enabled console.redhat.com services: ") + fmt.Printf("%v Enabled console.redhat.com services: ", uiSettings.iconInfo) showConfProfile(&profile) fmt.Printf("\n") } @@ -401,35 +404,129 @@ func connectAction(ctx *cli.Context) error { return nil } -// disconnectAction tries to stop rhscd service, disconnect from Red Hat Insights and finally -// it unregister system from Red Hat Subscription Management +// setupFormatOption ensures the user has supplied a correct `--format` flag +// and set values in uiSettings, when JSON format is used. +func setupFormatOption(ctx *cli.Context) error { + // This is run after the `app.Before()` has been run, + // the uiSettings is already set up for us to modify. + format := ctx.String("format") + switch format { + case "": + return nil + case "json": + uiSettings.isMachineReadable = true + uiSettings.isRich = false + return nil + default: + err := fmt.Errorf( + "unsupported format: %s (supported formats: %s)", + format, + `"json"`, + ) + return cli.Exit(err, 1) + } +} + +// DisconnectResult is structure holding information about result of +// disconnect command. The result could be printed in machine-readable format. +type DisconnectResult struct { + Hostname string `json:"hostname"` + HostnameError string `json:"hostname_error,omitempty"` + UID int `json:"uid"` + UIDError string `json:"uid_error,omitempty"` + RHSMDisconnected bool `json:"rhsm_disconnected"` + RHSMDisconnectedError string `json:"rhsm_disconnect_error,omitempty"` + InsightsDisconnected bool `json:"insights_disconnected"` + InsightsDisconnectedError string `json:"insights_disconnected_error,omitempty"` + YggdrasilStopped bool `json:"yggdrasil_stopped"` + YggdrasilStoppedError string `json:"yggdrasil_stopped_error,omitempty"` + format string +} + +// 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 { + case "json": + data, err := json.MarshalIndent(disconnectResult, "", " ") + if err != nil { + return err.Error() + } + result = string(data) + default: + result = "error: unsupported document format: " + disconnectResult.format + } + return result +} + +// beforeDisconnectAction ensures the used has supplied a correct `--format` flag +func beforeDisconnectAction(ctx *cli.Context) error { + return setupFormatOption(ctx) +} + +// interactivePrintf is method for printing human-readable output. It suppresses output, when +// machine-readable format is used. +func interactivePrintf(format string, a ...interface{}) { + if !uiSettings.isMachineReadable { + fmt.Printf(format, a...) + } +} + +// disconnectAction tries to stop (yggdrasil) rhcd service, disconnect from Red Hat Insights, +// and finally it unregisters system from Red Hat Subscription Management func disconnectAction(ctx *cli.Context) error { + var disconnectResult DisconnectResult + disconnectResult.format = ctx.String("format") + uid := os.Getuid() if uid != 0 { - return cli.Exit(fmt.Errorf("error: non-root user cannot disconnect system"), 1) + errMsg := "non-root user cannot disconnect system" + exitCode := 1 + if uiSettings.isMachineReadable { + disconnectResult.UID = uid + disconnectResult.UIDError = errMsg + return cli.Exit(disconnectResult, 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 { + disconnectResult.Hostname = hostname + } if err != nil { - return cli.Exit(err, 1) + exitCode := 1 + if uiSettings.isMachineReadable { + disconnectResult.HostnameError = err.Error() + return cli.Exit(disconnectResult, exitCode) + } else { + return cli.Exit(err, exitCode) + } } - fmt.Printf("Disconnecting %v from %v.\nThis might take a few seconds.\n\n", hostname, Provider) + + interactivePrintf("Disconnecting %v from %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. Deactivate yggdrasil (rhcd) service */ start = time.Now() progressMessage := fmt.Sprintf(" Deactivating the %v service", ServiceName) err = showProgress(progressMessage, deactivateService) if err != nil { + errMsg := fmt.Sprintf("Cannot deactivate %s service: %v", ServiceName, err) errorMessages[ServiceName] = LogMessage{ - level: log.LevelError, - message: fmt.Errorf("cannot deactivate %s service: %w", - ServiceName, err)} - fmt.Printf(uiSettings.iconError+" Cannot deactivate the %v service\n", ServiceName) + level: log.LevelError, + message: fmt.Errorf("%v", errMsg)} + disconnectResult.YggdrasilStopped = false + disconnectResult.YggdrasilStoppedError = errMsg + interactivePrintf("%v %v\n", uiSettings.iconError, errMsg) } else { - fmt.Printf(uiSettings.iconOK+" Deactivated the %v service\n", ServiceName) + disconnectResult.YggdrasilStopped = true + interactivePrintf("%v Deactivated the %v service\n", uiSettings.iconOK, ServiceName) } durations[ServiceName] = time.Since(start) @@ -437,13 +534,16 @@ func disconnectAction(ctx *cli.Context) error { start = time.Now() err = showProgress(" Disconnecting from Red Hat Insights...", unregisterInsights) if err != nil { + errMsg := fmt.Sprintf("Cannot disconnect from Red Hat Insights: %v", err) errorMessages["insights"] = LogMessage{ - level: log.LevelError, - message: fmt.Errorf("cannot disconnect from Red Hat Insights: %w", - err)} - fmt.Printf(uiSettings.iconError + " Cannot disconnect from Red Hat Insights\n") + level: log.LevelError, + message: fmt.Errorf("%v", errMsg)} + disconnectResult.InsightsDisconnected = false + disconnectResult.InsightsDisconnectedError = errMsg + interactivePrintf("%v %v\n", uiSettings.iconError, errMsg) } else { - fmt.Print(uiSettings.iconOK + " Disconnected from Red Hat Insights\n") + disconnectResult.InsightsDisconnected = true + interactivePrintf("%v Disconnected from Red Hat Insights\n", uiSettings.iconOK) } durations["insights"] = time.Since(start) @@ -452,28 +552,31 @@ func disconnectAction(ctx *cli.Context) error { " Disconnecting from Red Hat Subscription Management...", unregister, ) if err != nil { + errMsg := fmt.Sprintf("Cannot disconnect from Red Hat Subscription Management: %v", err) errorMessages["rhsm"] = LogMessage{ - level: log.LevelError, - message: fmt.Errorf("cannot disconnect from Red Hat Subscription Management: %w", - err)} - fmt.Printf( - uiSettings.iconError + " Cannot disconnect from Red Hat Subscription Management\n", - ) + level: log.LevelError, + message: fmt.Errorf("%v", errMsg)} + + disconnectResult.RHSMDisconnected = false + disconnectResult.RHSMDisconnectedError = errMsg + interactivePrintf("%v %v\n", uiSettings.iconError, errMsg) } else { - fmt.Printf(uiSettings.iconOK + " Disconnected from Red Hat Subscription Management\n") + disconnectResult.RHSMDisconnected = true + interactivePrintf("%v Disconnected from Red Hat Subscription Management\n", uiSettings.iconOK) } durations["rhsm"] = time.Since(start) - fmt.Printf("\nManage your connected systems: https://red.ht/connector\n") - - showTimeDuration(durations) + if !uiSettings.isMachineReadable { + fmt.Printf("\nManage your connected systems: https://red.ht/connector\n") + showTimeDuration(durations) - err = showErrorMessages("disconnect", errorMessages) - if err != nil { - return err + err = showErrorMessages("disconnect", errorMessages) + if err != nil { + return err + } } - return nil + return cli.Exit(disconnectResult, 0) } // canonicalFactAction tries to gather canonical facts about system, @@ -519,24 +622,7 @@ func printJSONStatus(systemStatus *SystemStatus) error { // beforeStatusAction ensures the user has supplied a correct `--format` flag. func beforeStatusAction(ctx *cli.Context) error { - // This is run after the `app.Before()` has been run, - // the uiSettings is already set up for us to modify. - format := ctx.String("format") - switch format { - case "": - return nil - case "json": - uiSettings.isMachineReadable = true - uiSettings.isRich = false - return nil - default: - err := fmt.Errorf( - "unsupported format: %s (supported formats: %s)", - format, - `"json"`, - ) - return cli.Exit(err, 1) - } + return setupFormatOption(ctx) } // statusAction tries to print status of system. It means that it gives @@ -774,10 +860,18 @@ func main() { Action: connectAction, }, { - Name: "disconnect", + Name: "disconnect", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "format", + Usage: "prints output of disconnection in machine-readable format (supported formats: \"json\")", + Aliases: []string{"f"}, + }, + }, Usage: "Disconnects the system from " + Provider, UsageText: fmt.Sprintf("%v disconnect", app.Name), Description: fmt.Sprintf("The disconnect command disconnects the system from Red Hat Subscription Management, Red Hat Insights and %v and deactivates the %v service. %v will no longer be able to interact with the system.", Provider, ServiceName, Provider), + Before: beforeDisconnectAction, Action: disconnectAction, }, { diff --git a/status.go b/status.go index 4be7d0b..baf44bd 100644 --- a/status.go +++ b/status.go @@ -22,13 +22,13 @@ func rhsmStatus(systemStatus *SystemStatus) error { if uiSettings.isMachineReadable { systemStatus.RHSMConnected = false } else { - fmt.Printf(uiSettings.iconInfo + " Not connected to Red Hat Subscription Management\n") + fmt.Printf("%v Not connected to Red Hat Subscription Management\n", uiSettings.iconInfo) } } else { if uiSettings.isMachineReadable { systemStatus.RHSMConnected = true } else { - fmt.Printf(uiSettings.iconOK + " Connected to Red Hat Subscription Management\n") + fmt.Printf("%v Connected to Red Hat Subscription Management\n", uiSettings.iconOK) } } return nil