Skip to content

Commit

Permalink
Refactor vlab reinstall/power
Browse files Browse the repository at this point in the history
Signed-off-by: Sergei Lukianov <[email protected]>
  • Loading branch information
Frostman committed Jan 15, 2025
1 parent 9ecec8c commit 5f15cd7
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 299 deletions.
177 changes: 96 additions & 81 deletions cmd/hhfab/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go.githedgehog.com/fabricator/pkg/fab"
"go.githedgehog.com/fabricator/pkg/fab/recipe"
"go.githedgehog.com/fabricator/pkg/hhfab"
"go.githedgehog.com/fabricator/pkg/hhfab/pdu"
"go.githedgehog.com/fabricator/pkg/version"
"golang.org/x/term"
"gopkg.in/natefinch/lumberjack.v2"
Expand Down Expand Up @@ -57,8 +58,6 @@ const (
FlagNameReady = "ready"
)

const AllSwitches string = "ALL"

func main() {
if err := Run(context.Background()); err != nil {
// TODO what if slog isn't initialized yet?
Expand Down Expand Up @@ -312,6 +311,29 @@ func Run(ctx context.Context) error {
buildModes = append(buildModes, string(m))
}

reinstallModes := []string{}
for _, m := range hhfab.ReinstallModes {
reinstallModes = append(reinstallModes, string(m))
}

powerActions := []string{}
for _, m := range pdu.Actions {
powerActions = append(powerActions, string(m))
}

pduFlags := []cli.Flag{
&cli.StringFlag{
Name: "pdu-username",
Usage: "PDU username to attempt a reboot (" + string(hhfab.ReinstallModeHardReset) + " mode only)",
EnvVars: []string{hhfab.VLABEnvPDUUsername},
},
&cli.StringFlag{
Name: "pdu-password",
Usage: "PDU password to attempt a reboot (" + string(hhfab.ReinstallModeHardReset) + " mode only)",
EnvVars: []string{hhfab.VLABEnvPDUPassword},
},
}

cli.VersionFlag.(*cli.BoolFlag).Aliases = []string{"V"}
app := &cli.App{
Name: "hhfab",
Expand Down Expand Up @@ -852,90 +874,91 @@ func Run(ctx context.Context) error {
},
},
{
Name: "switch",
Usage: "manage switch reinstall or power",
Flags: append(defaultFlags, accessNameFlag),
Before: before(false),
Name: "switch",
Usage: "manage switch reinstall or power",
Flags: append(defaultFlags, accessNameFlag),
Subcommands: []*cli.Command{
{
Name: "reinstall",
Usage: "rebot/reset and reinstall NOS on switches",
UsageText: "hhfab vlab switch reinstall --all[--name <switchName> [mode reboot|hard-reset]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "reinstall",
Usage: "reboot/reset and reinstall NOS on switches (if no switches specified, all switches will be reinstalled)",
Flags: append([]cli.Flag{
&cli.StringSliceFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "name of the switch to reinstall",
},
&cli.BoolFlag{
Name: "all",
Usage: "reinstall all switches",
Usage: "switch name to reinstall",
},
&cli.BoolFlag{
Name: "wait-ready",
Usage: "wait until switch(es) are Fabric-ready",
Name: "wait-ready",
Aliases: []string{"w"},
Usage: "wait until switch(es) are Fabric-ready",
},
&cli.StringFlag{
Name: "mode",
Usage: "restart mode: reboot or hard-reset",
Value: "reboot",
Name: "mode",
Aliases: []string{"m"},
Usage: "restart mode: " + strings.Join(reinstallModes, ", "),
Value: string(hhfab.ReinstallModeHardReset),
},
&cli.StringFlag{
Name: "username",
Usage: "required for reboot mode (if empty, user is prompted)",
Name: "switch-username",
Usage: "switch username to attempt a reboot (" + string(hhfab.ReinstallModeReboot) + " mode only, prompted for if empty)",
EnvVars: []string{"HHFAB_VLAB_REINSTALL_SWITCH_USERNAME"},
},
&cli.StringFlag{
Name: "password",
Usage: "required for reboot mode (if empty, user is prompted)",
Name: "switch-password",
Usage: "switch password to attempt a reboot (" + string(hhfab.ReinstallModeReboot) + " mode only, prompted for if empty)",
EnvVars: []string{"HHFAB_VLAB_REINSTALL_SWITCH_PASSWORD"},
},
verboseFlag,
yesFlag,
},
}, pduFlags...),
Before: before(false),
Action: func(c *cli.Context) error {
switchName := c.String("name")
if c.Bool("all") {
switchName = AllSwitches
}
if switchName == "" {
return fmt.Errorf("missing switch name") //nolint:goerr113
}

mode := c.String("mode")
validModes := map[string]bool{"reboot": true, "hard-reset": true}
if !validModes[mode] {
if !slices.Contains(reinstallModes, mode) {
return fmt.Errorf("invalid mode: %s", mode) //nolint:goerr113
}

if err := yesCheck(c); err != nil {
return err
}

username := c.String("username")
password := c.String("password")
if mode == "reboot" && (username == "" || password == "") {
fmt.Print("Enter username: ")
if _, err := fmt.Scanln(&username); err != nil {
return fmt.Errorf("failed to read username: %w", err)
username := c.String("switch-username")
password := c.String("switch-password")
if mode == string(hhfab.ReinstallModeReboot) {
if username == "" {
fmt.Print("Enter username: ")
if _, err := fmt.Scanln(&username); err != nil {
return fmt.Errorf("failed to read username: %w", err)
}
}
fmt.Print("Enter password: ")
bytePassword, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return fmt.Errorf("failed to read password: %w", err)

if password == "" {
fmt.Print("Enter password: ")
bytePassword, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return fmt.Errorf("failed to read password: %w", err)
}
password = string(bytePassword)
fmt.Println()
}
password = string(bytePassword)
fmt.Println()

if username == "" || password == "" {
return fmt.Errorf("credentials required for reboot mode") //nolint:goerr113
}
}

if mode == string(hhfab.ReinstallModeHardReset) && (c.String("pdu-username") == "" || c.String("pdu-password") == "") {
return fmt.Errorf("PDU credentials required for hard reset mode") //nolint:goerr113
}

opts := hhfab.SwitchReinstallOpts{
Name: switchName,
Mode: mode,
Username: username,
Password: password,
Verbose: verbose,
WaitReady: c.Bool("wait-ready"),
Switches: c.StringSlice("name"),
Mode: hhfab.SwitchReinstallMode(mode),
SwitchUsername: username,
SwitchPassword: password,
PDUUsername: c.String("pdu-username"),
PDUPassword: c.String("pdu-password"),
WaitReady: c.Bool("wait-ready"),
}

if err := hhfab.DoSwitchReinstall(ctx, workDir, cacheDir, opts); err != nil {
Expand All @@ -944,49 +967,41 @@ func Run(ctx context.Context) error {

return nil
},
HelpName: "hhfab vlab switch reinstall",
},
{
Name: "power",
Usage: "manage switch power state (ON, OFF, or CYCLE)",
Flags: []cli.Flag{
&cli.StringFlag{
Usage: "manage switch power state using the PDU (if no switches specified, all switches will be affected)",
Flags: append([]cli.Flag{
&cli.StringSliceFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "name of the switch to power ON|OFF|CYCLE",
Usage: "switch name to manage power",
},
&cli.BoolFlag{
Name: "all",
Usage: "apply action to all switches",
&cli.StringFlag{
Name: "action",
Aliases: []string{"a"},
Usage: "power action: one of " + strings.Join(powerActions, ", "),
Value: string(pdu.ActionCycle),
},
verboseFlag,
yesFlag,
},
UsageText: "hhfab vlab switch power [--all|--name <switchName>] <action>",
}, pduFlags...),
Before: before(false),
Action: func(c *cli.Context) error {
switchName := c.String("name")
if c.Bool("all") {
switchName = AllSwitches
}
if switchName == "" {
return fmt.Errorf("missing switch name") //nolint:goerr113
}

if c.NArg() != 1 {
return fmt.Errorf("unexpected amount of agruments (use ON, OFF, or CYCLE)") //nolint:goerr113
}

powerAction := strings.ToUpper(c.Args().First())
if powerAction != "ON" && powerAction != "OFF" && powerAction != "CYCLE" {
return fmt.Errorf("invalid power action: %s (use ON, OFF, or CYCLE)", powerAction) //nolint:goerr113
action := strings.ToLower(c.String("action"))
if !slices.Contains(powerActions, action) {
return fmt.Errorf("invalid action: %s", action) //nolint:goerr113
}

if err := yesCheck(c); err != nil {
return err
}

opts := hhfab.SwitchPowerOpts{
Name: switchName,
Action: powerAction,
Switches: c.StringSlice("name"),
Action: pdu.Action(action),
PDUUsername: c.String("pdu-username"),
PDUPassword: c.String("pdu-password"),
}

if err := hhfab.DoSwitchPower(ctx, workDir, cacheDir, opts); err != nil {
Expand Down
26 changes: 0 additions & 26 deletions pkg/hhfab/cmdconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (

const (
RegistryConfigFile = ".registry.yaml"
PDUConfigFile = ".pdu.yaml"
FabConfigFile = "fab.yaml"
IncludeDir = "include"
ResultDir = "result"
Expand Down Expand Up @@ -51,15 +50,6 @@ type RegistryConfig struct {
Prefix string `json:"prefix,omitempty"`
}

type PDUAPICreds struct {
User string `yaml:"user"`
Password string `yaml:"password"`
}

type PDUConfig struct {
PDUs map[string]PDUAPICreds `yaml:"pdus"`
}

func checkWorkCacheDir(workDir, cacheDir string) error {
stat, err := os.Stat(workDir)
if err != nil {
Expand Down Expand Up @@ -371,22 +361,6 @@ func loadRegConf(workDir string) (*RegistryConfig, error) {
return regConf, nil
}

func loadPDUConf(workDir string) (*PDUConfig, error) {
pduConf := &PDUConfig{}
pduConfData, err := os.ReadFile(filepath.Join(workDir, PDUConfigFile))
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("reading registry config: %w", err)
}

if err == nil {
if err := yaml.UnmarshalStrict(pduConfData, pduConf); err != nil {
return nil, fmt.Errorf("unmarshalling PDU config: %w", err)
}
}

return pduConf, nil
}

func getLocalDockerCredsFor(ctx context.Context, repo string) (string, string, error) {
storeOpts := credentials.StoreOptions{}
credStore, err := credentials.NewStoreFromDocker(storeOpts)
Expand Down
54 changes: 28 additions & 26 deletions pkg/hhfab/cmdvlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"go.githedgehog.com/fabricator/pkg/fab/recipe"
"go.githedgehog.com/fabricator/pkg/hhfab/pdu"
"go.githedgehog.com/fabricator/pkg/util/apiutil"
)

Expand Down Expand Up @@ -166,19 +167,10 @@ func DoVLABTestConnectivity(ctx context.Context, workDir, cacheDir string, opts
}

type SwitchPowerOpts struct {
Name string // The name of the switch (or AllSwitches)
Action string // Power action (e.g., ON, OFF, CYCLE)
PDUConf *PDUConfig // PDU configuration
}

type SwitchReinstallOpts struct {
Name string // The name of the switch (or AllSwitches)
Mode string // "reboot" or "hard-reset"
Username string // Username for switch access (reboot mode)
Password string // Password for switch access (reboot mode)
Verbose bool // Enable verbose logging
WaitReady bool // Wait for the switch to be ready
PDUConf *PDUConfig // PDU configuration for hard-reset mode
Switches []string // All switches if empty
Action pdu.Action // Power action (e.g., on, off, cycle)
PDUUsername string
PDUPassword string
}

func DoSwitchPower(ctx context.Context, workDir, cacheDir string, opts SwitchPowerOpts) error {
Expand All @@ -187,13 +179,29 @@ func DoSwitchPower(ctx context.Context, workDir, cacheDir string, opts SwitchPow
return err
}

// Load PDU configuration from YAML
opts.PDUConf, err = loadPDUConf(workDir)
if err != nil {
return fmt.Errorf("failed to load PDU config: %w", err)
}
return c.VLABSwitchPower(ctx, opts)
}

type SwitchReinstallOpts struct {
Switches []string // All switches if empty
Mode SwitchReinstallMode // "reboot" or "hard-reset"
SwitchUsername string // Username for switch access (reboot mode only )
SwitchPassword string // Password for switch access (reboot mode only)
PDUUsername string // (hard-reset mode only)
PDUPassword string // (hard-reset mode only)
WaitReady bool // Wait for the switch to be ready
}

type SwitchReinstallMode string

const (
ReinstallModeReboot SwitchReinstallMode = "reboot"
ReinstallModeHardReset SwitchReinstallMode = "hard-reset"
)

return c.VLABPower(ctx, opts)
var ReinstallModes = []SwitchReinstallMode{
ReinstallModeReboot,
ReinstallModeHardReset,
}

func DoSwitchReinstall(ctx context.Context, workDir, cacheDir string, opts SwitchReinstallOpts) error {
Expand All @@ -202,11 +210,5 @@ func DoSwitchReinstall(ctx context.Context, workDir, cacheDir string, opts Switc
return err
}

// Load PDU configuration from YAML
opts.PDUConf, err = loadPDUConf(workDir)
if err != nil {
return fmt.Errorf("failed to load PDU config: %w", err)
}

return c.SwitchReinstall(ctx, opts)
return c.VLABSwitchReinstall(ctx, opts)
}
Loading

0 comments on commit 5f15cd7

Please sign in to comment.