diff --git a/cmd/baremetal/baremetal.go b/cmd/baremetal/baremetal.go index e4695f270..e64c49319 100644 --- a/cmd/baremetal/baremetal.go +++ b/cmd/baremetal/baremetal.go @@ -15,12 +15,18 @@ package baremetal import ( + "fmt" + "io" + "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/remote" ) +// Action type is used to perform specific baremetal action +type Action int + const ( flagLabel = "labels" flagLabelShort = "l" @@ -32,25 +38,111 @@ const ( flagPhase = "phase" flagPhaseDescription = "airshipctl phase that contains the desired baremetal host document(s)" + + ejectAction Action = iota + powerOffAction + powerOnAction + powerStatusAction + rebootAction + remoteDirectAction ) +// CommonOptions is used to store common variables from cmd flags for baremetal command group +type CommonOptions struct { + labels string + name string + phase string +} + // NewBaremetalCommand creates a new command for interacting with baremetal using airshipctl. func NewBaremetalCommand(cfgFactory config.Factory) *cobra.Command { + options := &CommonOptions{} baremetalRootCmd := &cobra.Command{ Use: "baremetal", Short: "Perform actions on baremetal hosts", } - baremetalRootCmd.AddCommand(NewEjectMediaCommand(cfgFactory)) - baremetalRootCmd.AddCommand(NewPowerOffCommand(cfgFactory)) - baremetalRootCmd.AddCommand(NewPowerOnCommand(cfgFactory)) - baremetalRootCmd.AddCommand(NewPowerStatusCommand(cfgFactory)) - baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory)) - baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory)) + baremetalRootCmd.AddCommand(NewEjectMediaCommand(cfgFactory, options)) + baremetalRootCmd.AddCommand(NewPowerOffCommand(cfgFactory, options)) + baremetalRootCmd.AddCommand(NewPowerOnCommand(cfgFactory, options)) + baremetalRootCmd.AddCommand(NewPowerStatusCommand(cfgFactory, options)) + baremetalRootCmd.AddCommand(NewRebootCommand(cfgFactory, options)) + baremetalRootCmd.AddCommand(NewRemoteDirectCommand(cfgFactory, options)) return baremetalRootCmd } +func initFlags(options *CommonOptions, cmd *cobra.Command) { + flags := cmd.Flags() + flags.StringVarP(&options.labels, flagLabel, flagLabelShort, "", flagLabelDescription) + flags.StringVarP(&options.name, flagName, flagNameShort, "", flagNameDescription) + flags.StringVar(&options.phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) +} + +func performAction(cfgFactory config.Factory, options *CommonOptions, action Action, writer io.Writer) error { + cfg, err := cfgFactory() + if err != nil { + return err + } + + selectors := GetHostSelections(options.name, options.labels) + m, err := remote.NewManager(cfg, options.phase, selectors...) + if err != nil { + return err + } + + return selectAction(m, cfg, action, writer) +} + +func selectAction(m *remote.Manager, cfg *config.Config, action Action, writer io.Writer) error { + if action == remoteDirectAction { + if len(m.Hosts) != 1 { + return remote.NewRemoteDirectErrorf("more than one node defined as the ephemeral node") + } + + ephemeralHost := m.Hosts[0] + return ephemeralHost.DoRemoteDirect(cfg) + } + + for _, host := range m.Hosts { + switch action { + case ejectAction: + if err := host.EjectVirtualMedia(host.Context); err != nil { + return err + } + + fmt.Fprintf(writer, "All media ejected from host '%s'.\n", host.HostName) + case powerOffAction: + if err := host.SystemPowerOff(host.Context); err != nil { + return err + } + + fmt.Fprintf(writer, "Powered off host '%s'.\n", host.HostName) + case powerOnAction: + if err := host.SystemPowerOn(host.Context); err != nil { + return err + } + + fmt.Fprintf(writer, "Powered on host '%s'.\n", host.HostName) + case powerStatusAction: + powerStatus, err := host.SystemPowerStatus(host.Context) + if err != nil { + return err + } + + fmt.Fprintf(writer, "Host '%s' has power status: '%s'\n", + host.HostName, powerStatus) + case rebootAction: + if err := host.RebootSystem(host.Context); err != nil { + return err + } + + fmt.Fprintf(writer, "Rebooted host '%s'.\n", host.HostName) + } + } + return nil +} + // GetHostSelections builds a list of selectors that can be passed to a manager // using the name and label flags passed to airshipctl. func GetHostSelections(name string, labels string) []remote.HostSelector { @@ -63,5 +155,9 @@ func GetHostSelections(name string, labels string) []remote.HostSelector { selectors = append(selectors, remote.ByLabel(labels)) } + if len(selectors) == 0 { + selectors = append(selectors, remote.All()) + } + return selectors } diff --git a/cmd/baremetal/baremetal_test.go b/cmd/baremetal/baremetal_test.go index ec6d3221f..19e4835a6 100644 --- a/cmd/baremetal/baremetal_test.go +++ b/cmd/baremetal/baremetal_test.go @@ -33,32 +33,32 @@ func TestBaremetal(t *testing.T) { { Name: "baremetal-ejectmedia-with-help", CmdLine: "-h", - Cmd: baremetal.NewEjectMediaCommand(nil), + Cmd: baremetal.NewEjectMediaCommand(nil, &baremetal.CommonOptions{}), }, { Name: "baremetal-poweroff-with-help", CmdLine: "-h", - Cmd: baremetal.NewPowerOffCommand(nil), + Cmd: baremetal.NewPowerOffCommand(nil, &baremetal.CommonOptions{}), }, { Name: "baremetal-poweron-with-help", CmdLine: "-h", - Cmd: baremetal.NewPowerOnCommand(nil), + Cmd: baremetal.NewPowerOnCommand(nil, &baremetal.CommonOptions{}), }, { Name: "baremetal-powerstatus-with-help", CmdLine: "-h", - Cmd: baremetal.NewPowerStatusCommand(nil), + Cmd: baremetal.NewPowerStatusCommand(nil, &baremetal.CommonOptions{}), }, { Name: "baremetal-reboot-with-help", CmdLine: "-h", - Cmd: baremetal.NewRebootCommand(nil), + Cmd: baremetal.NewRebootCommand(nil, &baremetal.CommonOptions{}), }, { Name: "baremetal-remotedirect-with-help", CmdLine: "-h", - Cmd: baremetal.NewRemoteDirectCommand(nil), + Cmd: baremetal.NewRemoteDirectCommand(nil, &baremetal.CommonOptions{}), }, } @@ -79,5 +79,5 @@ func TestGetHostSelectionsBothSelectors(t *testing.T) { func TestGetHostSelectionsNone(t *testing.T) { selectors := baremetal.GetHostSelections("", "") - assert.Len(t, selectors, 0) + assert.Len(t, selectors, 1) } diff --git a/cmd/baremetal/ejectmedia.go b/cmd/baremetal/ejectmedia.go index 721147b08..5bdf6f167 100644 --- a/cmd/baremetal/ejectmedia.go +++ b/cmd/baremetal/ejectmedia.go @@ -15,52 +15,23 @@ package baremetal import ( - "fmt" - "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewEjectMediaCommand provides a command to eject media attached to a baremetal host. -func NewEjectMediaCommand(cfgFactory config.Factory) *cobra.Command { - var labels string - var name string - var phase string - +func NewEjectMediaCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "ejectmedia", Short: "Eject media attached to a baremetal host", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - selectors := GetHostSelections(name, labels) - m, err := remote.NewManager(cfg, phase, selectors...) - if err != nil { - return err - } - - for _, host := range m.Hosts { - if err := host.EjectVirtualMedia(host.Context); err != nil { - return err - } - - fmt.Fprintf(cmd.OutOrStdout(), "All media ejected from host '%s'.\n", host.HostName) - } - - return nil + return performAction(cfgFactory, options, ejectAction, cmd.OutOrStdout()) }, } - flags := cmd.Flags() - flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription) - flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription) - flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) + initFlags(options, cmd) return cmd } diff --git a/cmd/baremetal/poweroff.go b/cmd/baremetal/poweroff.go index f7ccd9e66..a7cd09a18 100644 --- a/cmd/baremetal/poweroff.go +++ b/cmd/baremetal/poweroff.go @@ -15,52 +15,23 @@ package baremetal import ( - "fmt" - "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewPowerOffCommand provides a command to shutdown a remote host. -func NewPowerOffCommand(cfgFactory config.Factory) *cobra.Command { - var labels string - var name string - var phase string - +func NewPowerOffCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "poweroff", Short: "Shutdown a baremetal host", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - selectors := GetHostSelections(name, labels) - m, err := remote.NewManager(cfg, phase, selectors...) - if err != nil { - return err - } - - for _, host := range m.Hosts { - if err := host.SystemPowerOff(host.Context); err != nil { - return err - } - - fmt.Fprintf(cmd.OutOrStdout(), "Powered off host '%s'.\n", host.HostName) - } - - return nil + return performAction(cfgFactory, options, powerOffAction, cmd.OutOrStdout()) }, } - flags := cmd.Flags() - flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription) - flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription) - flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) + initFlags(options, cmd) return cmd } diff --git a/cmd/baremetal/poweron.go b/cmd/baremetal/poweron.go index 3e2923393..a46026cda 100644 --- a/cmd/baremetal/poweron.go +++ b/cmd/baremetal/poweron.go @@ -15,52 +15,23 @@ Licensed under the Apache License, Version 2.0 (the "License"); package baremetal import ( - "fmt" - "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewPowerOnCommand provides a command with the capability to power on baremetal hosts. -func NewPowerOnCommand(cfgFactory config.Factory) *cobra.Command { - var labels string - var name string - var phase string - +func NewPowerOnCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "poweron", Short: "Power on a host", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - selectors := GetHostSelections(name, labels) - m, err := remote.NewManager(cfg, phase, selectors...) - if err != nil { - return err - } - - for _, host := range m.Hosts { - if err := host.SystemPowerOn(host.Context); err != nil { - return err - } - - fmt.Fprintf(cmd.OutOrStdout(), "Powered on host '%s'.\n", host.HostName) - } - - return nil + return performAction(cfgFactory, options, powerOnAction, cmd.OutOrStdout()) }, } - flags := cmd.Flags() - flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription) - flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription) - flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) + initFlags(options, cmd) return cmd } diff --git a/cmd/baremetal/powerstatus.go b/cmd/baremetal/powerstatus.go index 2456dbe77..9cf8e1842 100644 --- a/cmd/baremetal/powerstatus.go +++ b/cmd/baremetal/powerstatus.go @@ -15,54 +15,23 @@ package baremetal import ( - "fmt" - "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewPowerStatusCommand provides a command to retrieve the power status of a baremetal host. -func NewPowerStatusCommand(cfgFactory config.Factory) *cobra.Command { - var labels string - var name string - var phase string - +func NewPowerStatusCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "powerstatus", Short: "Retrieve the power status of a baremetal host", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - selectors := GetHostSelections(name, labels) - m, err := remote.NewManager(cfg, phase, selectors...) - if err != nil { - return err - } - - for _, host := range m.Hosts { - powerStatus, err := host.SystemPowerStatus(host.Context) - if err != nil { - return err - } - - fmt.Fprintf(cmd.OutOrStdout(), "Host '%s' has power status: '%s'\n", - host.HostName, powerStatus) - } - - return nil + return performAction(cfgFactory, options, powerStatusAction, cmd.OutOrStdout()) }, } - flags := cmd.Flags() - flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription) - flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription) - flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) + initFlags(options, cmd) return cmd } diff --git a/cmd/baremetal/reboot.go b/cmd/baremetal/reboot.go index c0956f365..28fc6498c 100644 --- a/cmd/baremetal/reboot.go +++ b/cmd/baremetal/reboot.go @@ -15,52 +15,23 @@ Licensed under the Apache License, Version 2.0 (the "License"); package baremetal import ( - "fmt" - "github.com/spf13/cobra" "opendev.org/airship/airshipctl/pkg/config" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewRebootCommand provides a command with the capability to reboot baremetal hosts. -func NewRebootCommand(cfgFactory config.Factory) *cobra.Command { - var labels string - var name string - var phase string - +func NewRebootCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "reboot", Short: "Reboot a host", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - selectors := GetHostSelections(name, labels) - m, err := remote.NewManager(cfg, phase, selectors...) - if err != nil { - return err - } - - for _, host := range m.Hosts { - if err := host.RebootSystem(host.Context); err != nil { - return err - } - - fmt.Fprintf(cmd.OutOrStdout(), "Rebooted host '%s'.\n", host.HostName) - } - - return nil + return performAction(cfgFactory, options, rebootAction, cmd.OutOrStdout()) }, } - flags := cmd.Flags() - flags.StringVarP(&labels, flagLabel, flagLabelShort, "", flagLabelDescription) - flags.StringVarP(&name, flagName, flagNameShort, "", flagNameDescription) - flags.StringVar(&phase, flagPhase, config.BootstrapPhase, flagPhaseDescription) + initFlags(options, cmd) return cmd } diff --git a/cmd/baremetal/remotedirect.go b/cmd/baremetal/remotedirect.go index 5f0a31678..d2fe2a767 100644 --- a/cmd/baremetal/remotedirect.go +++ b/cmd/baremetal/remotedirect.go @@ -19,33 +19,17 @@ import ( "opendev.org/airship/airshipctl/pkg/config" "opendev.org/airship/airshipctl/pkg/document" - "opendev.org/airship/airshipctl/pkg/remote" ) // NewRemoteDirectCommand provides a command with the capability to perform remote direct operations. -func NewRemoteDirectCommand(cfgFactory config.Factory) *cobra.Command { +func NewRemoteDirectCommand(cfgFactory config.Factory, options *CommonOptions) *cobra.Command { cmd := &cobra.Command{ Use: "remotedirect", Short: "Bootstrap the ephemeral host", RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := cfgFactory() - if err != nil { - return err - } - - manager, err := remote.NewManager(cfg, - config.BootstrapPhase, - remote.ByLabel(document.EphemeralHostSelector)) - if err != nil { - return err - } - - if len(manager.Hosts) != 1 { - return remote.NewRemoteDirectErrorf("more than one node defined as the ephemeral node") - } - - ephemeralHost := manager.Hosts[0] - return ephemeralHost.DoRemoteDirect(cfg) + options.phase = config.BootstrapPhase + options.labels = document.EphemeralHostSelector + return performAction(cfgFactory, options, remoteDirectAction, cmd.OutOrStdout()) }, } diff --git a/pkg/remote/management.go b/pkg/remote/management.go index 05f383252..7bf839959 100644 --- a/pkg/remote/management.go +++ b/pkg/remote/management.go @@ -115,6 +115,35 @@ func ByName(name string) HostSelector { } } +// All adds the host to a manager whose documents match the BareMetalHostKind. +func All() HostSelector { + return func(a *Manager, mgmtCfg config.ManagementConfiguration, docBundle document.Bundle) error { + selector := document.NewSelector().ByKind(document.BareMetalHostKind) + docs, err := docBundle.Select(selector) + if err != nil { + return err + } + + if len(docs) == 0 { + return document.ErrDocNotFound{Selector: selector} + } + + var matchingHosts []baremetalHost + for _, doc := range docs { + host, err := newBaremetalHost(mgmtCfg, doc, docBundle) + if err != nil { + return err + } + + matchingHosts = append(matchingHosts, host) + } + + a.Hosts = reconcileHosts(a.Hosts, matchingHosts...) + + return nil + } +} + // NewManager provides a manager that exposes the capability to perform remote direct functionality and other // out-of-band management on multiple hosts. func NewManager(cfg *config.Config, phaseName string, hosts ...HostSelector) (*Manager, error) {