Skip to content

Commit

Permalink
feat: Add CLI option --format to disconnect command
Browse files Browse the repository at this point in the history
* It is possible to print output of disconnect command in
  machine-readable format. The --format requires argument
  and it could be only "json" ATM. It follows practice of
  status command.
* Refactored code a little to share some code with status
  command.
  • Loading branch information
jirihnidek authored and subpop committed Sep 4, 2024
1 parent acf3c24 commit 4966a0e
Showing 1 changed file with 183 additions and 50 deletions.
233 changes: 183 additions & 50 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,49 +401,188 @@ 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"`
exitCode int
format string
}

// Error implement error interface for structure DisconnectResult
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
}

// printJSONDisconnectResult tries to print the result of disconnect as JSON to stdout.
// When marshaling of systemStatus fails, then error is returned
func printJSONDisconnectResult(disconnectResult *DisconnectResult) error {
data, err := json.MarshalIndent(disconnectResult, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}

// 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
var machineReadablePrintFunc func(disconnectResult *DisconnectResult) error

disconnectResult.format = ctx.String("format")
format := ctx.String("format")
switch format {
case "json":
machineReadablePrintFunc = printJSONDisconnectResult
default:
break
}

// When printing of status is requested, then print machine-readable file format
// at the end of this function
if uiSettings.isMachineReadable {
defer func(disconnectResult *DisconnectResult) {
// When exit code is zero, then print machine-readable output
// When exit code has non-zero value, then disconnectResult is returned as a error
if disconnectResult.exitCode == 0 && machineReadablePrintFunc != nil {
err := machineReadablePrintFunc(disconnectResult)
// When it was not possible to print result of disconnect to machine-readable format, then
// change returned error to CLI exit error to be able to set exit code to
// a non-zero value
if err != nil {
panic(fmt.Errorf("unable to print status as %s document: %s", format, err.Error()))
}
}
}(&disconnectResult)
}

disconnectResult.exitCode = 0

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
disconnectResult.exitCode = exitCode
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()
disconnectResult.exitCode = exitCode
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(errMsg)}
disconnectResult.YggdrasilStopped = false
disconnectResult.YggdrasilStoppedError = errMsg
interactivePrintf(uiSettings.iconError + " " + errMsg + "\n")
} else {
fmt.Printf(uiSettings.iconOK+" Deactivated the %v service\n", ServiceName)
disconnectResult.YggdrasilStopped = true
interactivePrintf(uiSettings.iconOK+" Deactivated the %v service\n", ServiceName)
}
durations[ServiceName] = time.Since(start)

/* 2. Disconnect from Red Hat Insights */
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(errMsg)}
disconnectResult.InsightsDisconnected = false
disconnectResult.InsightsDisconnectedError = errMsg
interactivePrintf(uiSettings.iconError + " " + errMsg + "\n")
} else {
fmt.Print(uiSettings.iconOK + " Disconnected from Red Hat Insights\n")
disconnectResult.InsightsDisconnected = true
interactivePrintf(uiSettings.iconOK + " Disconnected from Red Hat Insights\n")
}
durations["insights"] = time.Since(start)

Expand All @@ -452,25 +591,28 @@ 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(errMsg)}

disconnectResult.RHSMDisconnected = false
disconnectResult.RHSMDisconnectedError = errMsg
interactivePrintf(uiSettings.iconError + " " + errMsg + "\n")
} else {
fmt.Printf(uiSettings.iconOK + " Disconnected from Red Hat Subscription Management\n")
disconnectResult.RHSMDisconnected = true
interactivePrintf(uiSettings.iconOK + " Disconnected from Red Hat Subscription Management\n")
}
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
Expand Down Expand Up @@ -519,24 +661,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
Expand Down Expand Up @@ -774,10 +899,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,
},
{
Expand Down

0 comments on commit 4966a0e

Please sign in to comment.