Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add --format json for connect command #127

Merged
merged 1 commit into from
Sep 6, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
subpop marked this conversation as resolved.
Show resolved Hide resolved
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
Loading