diff --git a/cmd/rhc/connect_cmd.go b/cmd/rhc/connect_cmd.go index b3dcb1cc..609fb89e 100644 --- a/cmd/rhc/connect_cmd.go +++ b/cmd/rhc/connect_cmd.go @@ -8,13 +8,12 @@ import ( "strings" "time" - "github.com/redhatinsights/rhc/internal/features" - "github.com/redhatinsights/rhc/internal/rhsm" - "github.com/urfave/cli/v2" - "github.com/redhatinsights/rhc/internal/datacollection" + "github.com/redhatinsights/rhc/internal/features" "github.com/redhatinsights/rhc/internal/remotemanagement" + "github.com/redhatinsights/rhc/internal/rhsm" "github.com/redhatinsights/rhc/internal/ui" + "github.com/urfave/cli/v2" ) type FeatureResult struct { @@ -77,7 +76,8 @@ func (connectResult *ConnectResult) errorMessages() map[string]string { // will be stored in RHSMConnectError. func (connectResult *ConnectResult) TryRegisterRHSM(ctx *cli.Context) { slog.Info("Registering the system with Red Hat Subscription Management") - returnedMsg, err := rhsm.RegisterRHSM(ctx, features.ContentFeature.Enabled) + + err := registerRHSM(ctx) if err != nil { connectResult.RHSMConnected = false connectResult.RHSMConnectError = fmt.Sprintf("cannot connect to Red Hat Subscription Management: %s", err) @@ -96,8 +96,8 @@ func (connectResult *ConnectResult) TryRegisterRHSM(ctx *cli.Context) { ) } else { connectResult.RHSMConnected = true - slog.Debug(returnedMsg) - ui.Printf("%s[%v] %s\n", ui.Indent.Small, ui.Icons.Ok, returnedMsg) + slog.Debug("Connected to Red Hat Subscription Management") + ui.Printf("%s[%v] Connected to Red Hat Subscription Management\n", ui.Indent.Small, ui.Icons.Ok) if features.ContentFeature.Enabled { connectResult.Features.Content.Successful = true slog.Info("redhat.repo has been generated") @@ -218,6 +218,7 @@ func beforeConnectAction(ctx *cli.Context) error { // When machine is already connected, then return error slog.Info("Checking system connection status") + uuid, err := rhsm.GetConsumerUUID() if err != nil { return cli.Exit( diff --git a/cmd/rhc/disconnect_cmd.go b/cmd/rhc/disconnect_cmd.go index 162eae77..eb10fc08 100644 --- a/cmd/rhc/disconnect_cmd.go +++ b/cmd/rhc/disconnect_cmd.go @@ -133,10 +133,11 @@ func (disconnectResult *DisconnectResult) TryUnregisterInsightsClient() error { func (disconnectResult *DisconnectResult) TryUnregisterRHSM() error { slog.Info("Unregistering system from Red Hat Subscription Management") - isRegistered, err := rhsm.IsRHSMRegistered() + isRegistered, err := rhsm.IsSystemRegistered() if err != nil { return err } + if !isRegistered { infoMsg := "Already disconnected from Red Hat Subscription Management" disconnectResult.RHSMDisconnected = true diff --git a/cmd/rhc/rhsm.go b/cmd/rhc/rhsm.go new file mode 100644 index 00000000..7d2308f7 --- /dev/null +++ b/cmd/rhc/rhsm.go @@ -0,0 +1,122 @@ +package main + +import ( + "bufio" + "fmt" + "log/slog" + "os" + "strings" + "text/tabwriter" + "time" + + "github.com/briandowns/spinner" + "github.com/redhatinsights/rhc/internal/features" + "github.com/redhatinsights/rhc/internal/rhsm" + "github.com/redhatinsights/rhc/internal/ui" + "github.com/urfave/cli/v2" + "golang.org/x/term" +) + +// readOrganization displays the available organizations in a tabular format +// and prompts the user to select one. It returns the organization name entered +// by the user. +func readOrganization(availableOrgs []string) string { + scanner := bufio.NewScanner(os.Stdin) + fmt.Println("Available Organizations:") + writer := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0) + for i, org := range availableOrgs { + _, _ = fmt.Fprintf(writer, "%v\t", org) + if (i+1)%4 == 0 { + _, _ = fmt.Fprint(writer, "\n") + } + } + _ = writer.Flush() + fmt.Print("\nOrganization: ") + _ = scanner.Scan() + organization := strings.TrimSpace(scanner.Text()) + fmt.Printf("\n") + return organization +} + +// registerRHSM controls the actual registration flow for connecting the system +// to Red Hat Subscription Management. It handles both username/password and +// activation key registration methods. If credentials are not provided via flags, +// it prompts the user interactively. When the user belongs to multiple organizations +// and no organization is specified, it prompts the user to select one from the +// available list. +func registerRHSM(ctx *cli.Context) error { + username := ctx.String("username") + password := ctx.String("password") + organization := ctx.String("organization") + activationKeys := ctx.StringSlice("activation-key") + contentTemplates := ctx.StringSlice("content-template") + enableContent := features.ContentFeature.Enabled + + if len(activationKeys) == 0 { + if username == "" { + password = "" + scanner := bufio.NewScanner(os.Stdin) + fmt.Print("Username: ") + _ = scanner.Scan() + username = strings.TrimSpace(scanner.Text()) + } + if password == "" { + fmt.Print("Password: ") + data, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + return fmt.Errorf("unable to read password: %w", err) + } + password = string(data) + fmt.Printf("\n\n") + } + } + + var s *spinner.Spinner + if ui.IsOutputRich() { + s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + s.Prefix = ui.Indent.Small + "[" + s.Suffix = "] Connecting to Red Hat Subscription Management..." + s.Start() + defer s.Stop() + } + + registerServer, err := rhsm.NewRegisterServer() + if err != nil { + return err + } + + defer func() { + if err = registerServer.Close(); err != nil { + slog.Debug(err.Error()) + } + }() + + if len(activationKeys) > 0 { + return registerServer.RegisterWithActivationKeys( + organization, + activationKeys, + contentTemplates, + enableContent, + ) + } + + err = registerServer.RegisterWithUsernamePassword(username, password, organization, contentTemplates, enableContent) + if rhsm.IsOrgNotSpecified(err) { + /* When organization was not specified using CLI option --organization, and it is + required, because user is member of more than one organization, then ask for + the organization. */ + var orgs []string + orgs, err = registerServer.GetOrganizations(username, password) + if err != nil { + return err + } + + // Ask for organization and display hint with list of organizations + organization = readOrganization(orgs) + + // Try to register once again with given organization + return registerServer.RegisterWithUsernamePassword(username, password, organization, contentTemplates, enableContent) + } + + return err +} diff --git a/cmd/rhc/status_cmd.go b/cmd/rhc/status_cmd.go index 2ea5ef9c..2f36fc85 100644 --- a/cmd/rhc/status_cmd.go +++ b/cmd/rhc/status_cmd.go @@ -9,7 +9,6 @@ import ( "reflect" "time" - "github.com/godbus/dbus/v5" "github.com/redhatinsights/rhc/internal/rhsm" "github.com/urfave/cli/v2" @@ -17,7 +16,6 @@ import ( systemd "github.com/coreos/go-systemd/v22/dbus" "github.com/redhatinsights/rhc/internal/datacollection" - "github.com/redhatinsights/rhc/internal/localization" "github.com/redhatinsights/rhc/internal/ui" ) @@ -54,41 +52,28 @@ func rhsmStatus(systemStatus *SystemStatus) error { func isContentEnabled(systemStatus *SystemStatus) error { slog.Info("Checking content status") - conn, err := dbus.SystemBus() + contentEnabled, err := rhsm.IsContentEnabled() if err != nil { - return fmt.Errorf("cannot connect to system D-Bus: %w", err) - } - - locale := localization.GetLocale() - - config := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/Config") - - var contentEnabled string - if err := config.Call( - "com.redhat.RHSM1.Config.Get", - 0, - "rhsm.manage_repos", - locale).Store(&contentEnabled); err != nil { systemStatus.returnCode += 1 systemStatus.ContentError = err.Error() - return rhsm.UnpackDBusError(err) + return err } - uuid, err := rhsm.GetConsumerUUID() + isRegistered, err := rhsm.IsSystemRegistered() if err != nil { systemStatus.returnCode += 1 systemStatus.ContentError = err.Error() - return fmt.Errorf("unable to get consumer UUID: %s", err) + return err } - if contentEnabled == "1" && uuid != "" { + if contentEnabled && isRegistered { systemStatus.ContentEnabled = true infoMsg := "Red Hat repository file generated" slog.Info(infoMsg) ui.Printf("%s[%v] Content ... %v\n", ui.Indent.Medium, ui.Icons.Ok, infoMsg) } else { systemStatus.ContentEnabled = false - if uuid != "" { + if isRegistered { infoMsg := "Generating of Red Hat repository file disabled in rhsm.conf" slog.Info(infoMsg) ui.Printf("%s[ ] Content ... %v\n", ui.Indent.Medium, infoMsg) diff --git a/internal/rhsm/rhsm.go b/internal/rhsm/rhsm.go index 337eae82..676df5bb 100644 --- a/internal/rhsm/rhsm.go +++ b/internal/rhsm/rhsm.go @@ -1,27 +1,21 @@ package rhsm import ( - "bufio" "encoding/json" + "errors" "fmt" "log/slog" - "os" "strings" - "text/tabwriter" - "time" - - "github.com/briandowns/spinner" - "github.com/urfave/cli/v2" - "golang.org/x/term" "github.com/godbus/dbus/v5" "github.com/redhatinsights/rhc/internal/localization" - "github.com/redhatinsights/rhc/internal/ui" ) const EnvTypeContentTemplate = "content-template" +// GetConsumerUUID returns the consumer uuid set on the system. +// If consumer uuid is not set, then an empty string is returned func GetConsumerUUID() (string, error) { conn, err := dbus.SystemBus() if err != nil { @@ -31,250 +25,221 @@ func GetConsumerUUID() (string, error) { locale := localization.GetLocale() var uuid string - if err := conn.Object( + err = conn.Object( "com.redhat.RHSM1", "/com/redhat/RHSM1/Consumer").Call( "com.redhat.RHSM1.Consumer.GetUuid", dbus.Flags(0), - locale).Store(&uuid); err != nil { + locale).Store(&uuid) + if err != nil { return "", UnpackDBusError(err) } + return uuid, nil } -// Organization is structure containing information about RHSM organization (sometimes called owner) -// JSON document returned from candlepin server can have the following format. We care only about key, -// but it can be extended and more information can be added to the structure in the future. -// -// { -// "created": "2022-11-02T16:00:23+0000", -// "updated": "2022-11-02T16:00:48+0000", -// "id": "4028face84391264018439127db10004", -// "displayName": "Donald Duck", -// "key": "donaldduck", -// "contentPrefix": null, -// "defaultServiceLevel": null, -// "logLevel": null, -// "contentAccessMode": "org_environment", -// "contentAccessModeList": "entitlement,org_environment", -// "autobindHypervisorDisabled": false, -// "autobindDisabled": false, -// "lastRefreshed": "2022-11-02T16:00:48+0000", -// "parentOwner": null, -// "upstreamConsumer": null -// } -type Organization struct { - Key string `json:"key"` +// IsSystemRegistered returns true when consumer uuid is set on the system, +// indicating the system is connected to Red Hat Subscription Management +func IsSystemRegistered() (bool, error) { + uuid, err := GetConsumerUUID() + if err != nil { + return false, err + } + if uuid != "" { + return true, nil + } + return false, nil } -// unpackOrgs tries to unpack list organization from JSON document returned by D-Bus method GetOrgs. -// When it is possible to unmarshal the JSON document, then return list of organization keys (IDs). -// When it is not possible to get list of organizations, then return empty slice and error. -func unpackOrgs(s string) ([]string, error) { - var orgs []string +// Unregister unregisters the system from Red Hat Subscription Management +// using the RHSM D-Bus API +func Unregister() error { + isRegistered, err := IsSystemRegistered() + if err != nil { + return err + } + if !isRegistered { + return fmt.Errorf("warning: the system is already unregistered") + } - var organizations []Organization + locale := localization.GetLocale() - err := json.Unmarshal([]byte(s), &organizations) + conn, err := dbus.SystemBus() if err != nil { - return orgs, err + return err } - for _, org := range organizations { - orgs = append(orgs, org.Key) + slog.Debug("Calling method com.redhat.RHSM1.Unregister.Unregister") + err = conn.Object( + "com.redhat.RHSM1", + "/com/redhat/RHSM1/Unregister").Call( + "com.redhat.RHSM1.Unregister.Unregister", + dbus.Flags(0), + map[string]string{}, + locale).Err + if err != nil { + return UnpackDBusError(err) } - return orgs, nil + return nil } -// registerUsernamePassword tries to register system against candlepin server (Red Hat Management Service) -// username and password are mandatory. When organization is not obtained, then this method -// returns list of available organization and user can select one organization from the list. -func registerUsernamePassword(username, password, organization string, environments []string, enableContent bool) ([]string, error) { - var orgs []string - +// IsContentEnabled returns true when manage_repos is set to 1 in the RHSM config +func IsContentEnabled() (bool, error) { conn, err := dbus.SystemBus() if err != nil { - return orgs, err + return false, err } - uuid, err := GetConsumerUUID() + locale := localization.GetLocale() + + config := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/Config") + + slog.Debug("Calling method com.redhat.RHSM1.Config.Get") + var contentEnabled string + err = config.Call( + "com.redhat.RHSM1.Config.Get", + 0, + "rhsm.manage_repos", + locale).Store(&contentEnabled) if err != nil { - return orgs, err - } - if uuid != "" { - return orgs, fmt.Errorf("warning: the system is already registered") + return false, UnpackDBusError(err) } - registerServer := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/RegisterServer") + return contentEnabled == "1", nil +} - locale := localization.GetLocale() +type RegisterServer struct { + privConn *dbus.Conn +} - slog.Debug("Opening private D-Bus UNIX socket") - var privateDbusSocketURI string - if err := registerServer.Call( - "com.redhat.RHSM1.RegisterServer.Start", - dbus.Flags(0), - locale).Store(&privateDbusSocketURI); err != nil { - return orgs, err +// NewRegisterServer opens a private D-Bus UNIX socket for RHSM registration +func NewRegisterServer() (*RegisterServer, error) { + privateDbusSocketURI, err := startRegisterServer() + if err != nil { + return nil, err } - defer func() { - slog.Debug("Closing private UNIX socket", "socket", privateDbusSocketURI) - registerServer.Call( - "com.redhat.RHSM1.RegisterServer.Stop", - dbus.FlagNoReplyExpected, - locale) - }() - - slog.Debug("Connecting to private D-Bus UNIX socket", "socket", privateDbusSocketURI) + privConn, err := dbus.Dial(privateDbusSocketURI) if err != nil { - return orgs, err + return nil, err } - defer func() { - slog.Debug("Closing connection to private D-Bus UNIX socket", "socket", privateDbusSocketURI) - err = privConn.Close() - if err != nil { - slog.Warn( - "Unable to close connection to private D-Bus UNIX socket", - "socket", privateDbusSocketURI, - "err", err, - ) - } - }() - if err := privConn.Auth(nil); err != nil { - return orgs, err + err = privConn.Auth(nil) + if err != nil { + return nil, err } - options := make(map[string]string) - if len(environments) != 0 { - options["environment_names"] = strings.Join(environments, ",") - options["environment_type"] = EnvTypeContentTemplate + return &RegisterServer{privConn: privConn}, nil +} +// startRegisterServer starts a private D-Bus UNIX socket to be used for RHSM registration +// and returns its URI +func startRegisterServer() (string, error) { + conn, err := dbus.SystemBus() + if err != nil { + return "", err } - options["enable_content"] = fmt.Sprintf("%v", enableContent) + registerServer := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/RegisterServer") + locale := localization.GetLocale() - slog.Debug("Calling method com.redhat.RHSM1.Register.Register") - if err := privConn.Object( - "com.redhat.RHSM1", - "/com/redhat/RHSM1/Register").Call( - "com.redhat.RHSM1.Register.Register", + slog.Debug("Calling method com.redhat.RHSM1.RegisterServer.Start") + var privateDbusSocketURI string + err = registerServer.Call( + "com.redhat.RHSM1.RegisterServer.Start", dbus.Flags(0), - organization, - username, - password, - options, - map[string]string{}, - locale).Err; err != nil { - - // Try to unpack D-Bus method - err := UnpackDBusError(err) + locale, + ).Store(&privateDbusSocketURI) + if err != nil { + return "", UnpackDBusError(err) + } - // Is unpacked error DBusError - dbusError, ok := err.(DBusError) - if !ok { - return orgs, err - } + return privateDbusSocketURI, nil +} - // When organization was not specified, and it is required to specify it, then - // try to get list of available organizations - if organization == "" && dbusError.Exception == "OrgNotSpecifiedException" { - slog.Debug("Organization was not specified, retrieving list of available organizations") - slog.Debug("Calling method com.redhat.RHSM1.Register.GetOrgs") - var s string - orgsCall := privConn.Object( - "com.redhat.RHSM1", - "/com/redhat/RHSM1/Register", - ).Call( - "com.redhat.RHSM1.Register.GetOrgs", - dbus.Flags(0), - username, - password, - map[string]string{}, - locale, - ) - - err = orgsCall.Store(&s) - if err != nil { - return orgs, err - } +// Close closes the private D-Bus connection +func (r *RegisterServer) Close() error { + slog.Debug("Closing connection to private D-Bus UNIX socket") + err1 := r.privConn.Close() + if err1 != nil { + slog.Debug("Error closing connection to private D-Bus UNIX socket", "err", err1) + } - orgs, err = unpackOrgs(s) - return orgs, err - } - return orgs, UnpackDBusError(err) + slog.Debug("Closing private D-Bus UNIX socket") + err2 := stopRegisterServer() + if err2 != nil { + slog.Debug("Error closing private D-Bus UNIX socket", "err", err2) } - return orgs, nil + return errors.Join(err1, err2) } -func registerActivationKey(orgID string, activationKeys []string, environments []string, enableContent bool) error { +// stopRegisterServer closes the private D-Bus UNIX socket used for RHSM registration +func stopRegisterServer() error { conn, err := dbus.SystemBus() if err != nil { return err } - uuid, err := GetConsumerUUID() + registerServer := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/RegisterServer") + + slog.Debug("Calling method com.redhat.RHSM1.RegisterServer.Stop") + err = registerServer.Call( + "com.redhat.RHSM1.RegisterServer.Stop", + dbus.FlagNoReplyExpected, + localization.GetLocale()).Err if err != nil { - return err - } - if uuid != "" { - return fmt.Errorf("warning: the system is already registered") + return UnpackDBusError(err) } + return nil +} - registerServer := conn.Object("com.redhat.RHSM1", "/com/redhat/RHSM1/RegisterServer") - - locale := localization.GetLocale() +// RegisterWithUsernamePassword tries to register system against candlepin server (Red Hat Management Service) +// username and password are mandatory. When organization is not obtained, then this method +// returns a DBus error with the exception type "OrgNotSpecifiedException" +func (r *RegisterServer) RegisterWithUsernamePassword(username, password, organization string, environments []string, enableContent bool) error { + options := make(map[string]string) + if len(environments) != 0 { + options["environment_names"] = strings.Join(environments, ",") + options["environment_type"] = EnvTypeContentTemplate + } + options["enable_content"] = fmt.Sprintf("%v", enableContent) - slog.Debug("Opening private D-Bus UNIX socket") - var privateDbusSocketURI string - if err := registerServer.Call( - "com.redhat.RHSM1.RegisterServer.Start", + slog.Debug("Calling method com.redhat.RHSM1.Register.Register") + err := r.privConn.Object( + "com.redhat.RHSM1", + "/com/redhat/RHSM1/Register").Call( + "com.redhat.RHSM1.Register.Register", dbus.Flags(0), - locale).Store(&privateDbusSocketURI); err != nil { - return err - } - defer func() { - slog.Debug("Closing private D-Bus UNIX socket", "socket", privateDbusSocketURI) - registerServer.Call( - "com.redhat.RHSM1.RegisterServer.Stop", - dbus.FlagNoReplyExpected, - locale) - }() - - slog.Debug("Connecting to private D-Bus UNIX socket", "socket", privateDbusSocketURI) - privConn, err := dbus.Dial(privateDbusSocketURI) + organization, + username, + password, + options, + map[string]string{}, + localization.GetLocale(), + ).Err + if err != nil { - return err + // Try to unpack D-Bus error + return UnpackDBusError(err) } - defer func() { - slog.Debug("Closing connection to private D-Bus UNIX socket", "socket", privateDbusSocketURI) - err = privConn.Close() - if err != nil { - slog.Warn( - "Unable to close connection to private D-Bus UNIX socket", - "socket", privateDbusSocketURI, - "err", err, - ) - } - }() - if err := privConn.Auth(nil); err != nil { - return err - } + return nil +} +// RegisterWithActivationKeys tries to register system against candlepin server (Red Hat Management Service) +// with the provided activation keys. +func (r *RegisterServer) RegisterWithActivationKeys(orgID string, activationKeys []string, environments []string, enableContent bool) error { options := make(map[string]string) if len(environments) != 0 { options["environment_names"] = strings.Join(environments, ",") options["environment_type"] = EnvTypeContentTemplate } - options["enable_content"] = fmt.Sprintf("%v", enableContent) slog.Debug("Calling method com.redhat.RHSM1.Register.RegisterWithActivationKeys") - if err := privConn.Object( + err := r.privConn.Object( "com.redhat.RHSM1", "/com/redhat/RHSM1/Register").Call( "com.redhat.RHSM1.Register.RegisterWithActivationKeys", @@ -283,43 +248,84 @@ func registerActivationKey(orgID string, activationKeys []string, environments [ activationKeys, options, map[string]string{}, - locale).Err; err != nil { + localization.GetLocale(), + ).Err + + if err != nil { return UnpackDBusError(err) } return nil } -func Unregister() error { - conn, err := dbus.SystemBus() - if err != nil { - return err - } - - uuid, err := GetConsumerUUID() - if err != nil { - return err - } - if uuid == "" { - return fmt.Errorf("warning: the system is already unregistered") - } - +// GetOrganizations tries to retrieve a list of organizations from candlepin server (Red Hat Management Service) +func (r *RegisterServer) GetOrganizations(username string, password string) ([]string, error) { locale := localization.GetLocale() - slog.Debug("Calling method com.redhat.RHSM1.Unregister.Unregister") - err = conn.Object( + slog.Debug("Calling method com.redhat.RHSM1.Register.GetOrgs") + var s string + err := r.privConn.Object( "com.redhat.RHSM1", - "/com/redhat/RHSM1/Unregister").Call( - "com.redhat.RHSM1.Unregister.Unregister", + "/com/redhat/RHSM1/Register", + ).Call( + "com.redhat.RHSM1.Register.GetOrgs", dbus.Flags(0), + username, + password, map[string]string{}, - locale).Err + locale, + ).Store(&s) if err != nil { - return UnpackDBusError(err) + return nil, UnpackDBusError(err) } - return nil + return unpackOrgs(s) +} + +// organization is structure containing information about RHSM organization (sometimes called owner) +// JSON document returned from candlepin server can have the following format. We care only about key, +// but it can be extended and more information can be added to the structure in the future. +// +// { +// "created": "2022-11-02T16:00:23+0000", +// "updated": "2022-11-02T16:00:48+0000", +// "id": "4028face84391264018439127db10004", +// "displayName": "Donald Duck", +// "key": "donaldduck", +// "contentPrefix": null, +// "defaultServiceLevel": null, +// "logLevel": null, +// "contentAccessMode": "org_environment", +// "contentAccessModeList": "entitlement,org_environment", +// "autobindHypervisorDisabled": false, +// "autobindDisabled": false, +// "lastRefreshed": "2022-11-02T16:00:48+0000", +// "parentOwner": null, +// "upstreamConsumer": null +// } +type organization struct { + Key string `json:"key"` +} + +// unpackOrgs tries to unpack list organization from JSON document returned by D-Bus method GetOrgs. +// When it is possible to unmarshal the JSON document, then return list of organization keys (IDs). +// When it is not possible to get list of organizations, then return empty slice and error. +func unpackOrgs(s string) ([]string, error) { + var orgs []string + + var organizations []organization + + err := json.Unmarshal([]byte(s), &organizations) + if err != nil { + return orgs, err + } + + for _, org := range organizations { + orgs = append(orgs, org.Key) + } + + return orgs, nil } // DBusError is used for parsing JSON document returned by D-Bus methods. @@ -335,6 +341,18 @@ func (dbusError DBusError) Error() string { return fmt.Sprintf("%v: %v", dbusError.Severity, dbusError.Message) } +// IsOrgNotSpecified checks if an error is a DBusError with exception type OrgNotSpecifiedException. +// OrgNotSpecifiedException is returned when the user must specify an organization +// but did not provide one. This typically occurs when the user has access to +// multiple organizations. +func IsOrgNotSpecified(err error) bool { + if err == nil { + return false + } + dbusErr, ok := err.(DBusError) + return ok && dbusErr.Exception == "OrgNotSpecifiedException" +} + // UnpackDBusError tries to unpack a JSON document (part of the error) into the structure DBusError. When it is // not possible to parse an error into structure, then a corresponding or original error is returned. // When it is possible to parse error into structure, then DBusError is returned @@ -352,125 +370,3 @@ func UnpackDBusError(err error) error { } return err } - -// RegisterRHSM tries to register system against Red Hat Subscription Management server (candlepin server) -func RegisterRHSM(ctx *cli.Context, enableContent bool) (string, error) { - uuid, err := GetConsumerUUID() - if err != nil { - return "Unable to get consumer UUID", cli.Exit(err, 1) - } - var successMsg string - - if uuid == "" { - username := ctx.String("username") - password := ctx.String("password") - organization := ctx.String("organization") - activationKeys := ctx.StringSlice("activation-key") - contentTemplates := ctx.StringSlice("content-template") - - if len(activationKeys) == 0 { - if username == "" { - password = "" - scanner := bufio.NewScanner(os.Stdin) - fmt.Print("Username: ") - _ = scanner.Scan() - username = strings.TrimSpace(scanner.Text()) - } - if password == "" { - fmt.Print("Password: ") - data, err := term.ReadPassword(int(os.Stdin.Fd())) - if err != nil { - return "Unable to read password", cli.Exit(err, 1) - } - password = string(data) - fmt.Printf("\n\n") - } - } - - var s *spinner.Spinner - if ui.IsOutputRich() { - s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) - s.Prefix = ui.Indent.Small + "[" - s.Suffix = "] Connecting to Red Hat Subscription Management..." - s.Start() - defer s.Stop() - } - - var err error - if len(activationKeys) > 0 { - slog.Debug("Registering system with activation keys") - err = registerActivationKey( - organization, - ctx.StringSlice("activation-key"), - contentTemplates, - enableContent) - } else { - slog.Debug("Registering system with username and password") - var orgs []string - if organization != "" { - _, err = registerUsernamePassword(username, password, organization, contentTemplates, enableContent) - } else { - orgs, err = registerUsernamePassword(username, password, "", contentTemplates, enableContent) - /* When organization was not specified using CLI option --organization, and it is - required, because user is member of more than one organization, then ask for - the organization. */ - if len(orgs) > 0 { - if ui.IsOutputMachineReadable() { - return "Unable to register system to RHSM", cli.Exit("no organization specified", 1) - } - // Stop spinner to be able to display message and ask for organization - if ui.IsOutputRich() { - s.Stop() - } - - // Ask for organization and display hint with list of organizations - scanner := bufio.NewScanner(os.Stdin) - fmt.Println("Available Organizations:") - writer := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0) - for i, org := range orgs { - _, _ = fmt.Fprintf(writer, "%v\t", org) - if (i+1)%4 == 0 { - _, _ = fmt.Fprint(writer, "\n") - } - } - _ = writer.Flush() - fmt.Print("\nOrganization: ") - _ = scanner.Scan() - organization = strings.TrimSpace(scanner.Text()) - fmt.Printf("\n") - - // Start spinner again - if ui.IsOutputRich() { - s.Start() - } - - // Try to register once again with given organization - slog.Debug("Re-attempting registration with username, password and organization") - _, err = registerUsernamePassword(username, password, organization, contentTemplates, enableContent) - } - } - } - if err != nil { - return "Unable to register system to RHSM", cli.Exit(err, 1) - } - successMsg = "Connected to Red Hat Subscription Management" - } else { - successMsg = "This system is already connected to Red Hat Subscription Management" - } - return successMsg, nil -} - -// IsRHSMRegistered returns true, when system is registered -func IsRHSMRegistered() (bool, error) { - slog.Debug("Checking if system is registered to Red Hat Subscription Management") - uuid, err := GetConsumerUUID() - if err != nil { - return false, err - } - if uuid != "" { - slog.Debug("Consumer UUID is set, system is registered") - return true, nil - } - slog.Debug("Consumer UUID is not set, system is not registered") - return false, nil -}