diff --git a/cmd/apply.go b/cmd/apply.go index 537df418..79bb1121 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -76,6 +76,7 @@ var applyCommand = &cli.Command{ &phase.ValidateHosts{}, &phase.GatherK0sFacts{}, &phase.ValidateFacts{SkipDowngradeCheck: ctx.Bool("disable-downgrade-check")}, + &phase.RunHooks{Stage: "before", Action: "apply"}, &phase.ConfigureK0s{}, &phase.Restore{ RestoreFrom: ctx.String("restore-from"), @@ -87,6 +88,7 @@ var applyCommand = &cli.Command{ &phase.UpgradeWorkers{ NoDrain: ctx.Bool("no-drain"), }, + &phase.RunHooks{Stage: "after", Action: "apply"}, &phase.Disconnect{}, ) diff --git a/cmd/backup.go b/cmd/backup.go index a3228cf2..f4dc09cf 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -47,7 +47,9 @@ var backupCommand = &cli.Command{ &phase.DetectOS{}, &phase.GatherFacts{}, &phase.GatherK0sFacts{}, + &phase.RunHooks{Stage: "before", Action: "backup"}, &phase.Backup{}, + &phase.RunHooks{Stage: "after", Action: "backup"}, &phase.Disconnect{}, ) diff --git a/cmd/reset.go b/cmd/reset.go index 62d5b97a..3461d31f 100644 --- a/cmd/reset.go +++ b/cmd/reset.go @@ -70,7 +70,9 @@ var resetCommand = &cli.Command{ &phase.DetectOS{}, &phase.PrepareHosts{}, &phase.GatherK0sFacts{}, + &phase.RunHooks{Stage: "before", Action: "reset"}, &phase.Reset{}, + &phase.RunHooks{Stage: "after", Action: "reset"}, &phase.Disconnect{}, ) diff --git a/config/cluster/hook.go b/config/cluster/hook.go new file mode 100644 index 00000000..6fcfc066 --- /dev/null +++ b/config/cluster/hook.go @@ -0,0 +1,12 @@ +package cluster + +// Hooks define a list of hooks such as hooks["apply"]["before"] = ["ls -al", "rm foo.txt"] +type Hooks map[string]map[string][]string + +// ForActionAndStage return hooks for given action and stage +func (h Hooks) ForActionAndStage(action, stage string) []string { + if len(h[action]) > 0 { + return h[action][stage] + } + return nil +} diff --git a/config/cluster/host.go b/config/cluster/host.go index 821ad4cf..705e9361 100644 --- a/config/cluster/host.go +++ b/config/cluster/host.go @@ -28,6 +28,7 @@ type Host struct { InstallFlags Flags `yaml:"installFlags,omitempty"` Files []UploadFile `yaml:"files,omitempty"` OSIDOverride string `yaml:"os,omitempty"` + Hooks Hooks `yaml:"hooks,omitempty"` Metadata HostMetadata `yaml:"-"` Configurer configurer `yaml:"-"` diff --git a/phase/runhooks.go b/phase/runhooks.go new file mode 100644 index 00000000..5c5ef2c4 --- /dev/null +++ b/phase/runhooks.go @@ -0,0 +1,53 @@ +package phase + +import ( + "fmt" + "strings" + + "github.com/k0sproject/k0sctl/config" + "github.com/k0sproject/k0sctl/config/cluster" +) + +var _ phase = &RunHooks{} + +// RunHooks phase runs a set of hooks configured for the host +type RunHooks struct { + Action string + Stage string + hosts cluster.Hosts +} + +// Title for the phase +func (p *RunHooks) Title() string { + return fmt.Sprintf("Run %s %s Hooks", strings.Title(p.Stage), strings.Title(p.Action)) +} + +// Prepare digs out the hosts with steps from the config +func (p *RunHooks) Prepare(config *config.Cluster) error { + p.hosts = config.Spec.Hosts.Filter(func(h *cluster.Host) bool { + return len(h.Hooks.ForActionAndStage(p.Action, p.Stage)) > 0 + }) + + return nil +} + +// ShouldRun is true when there are hosts that need to be connected +func (p *RunHooks) ShouldRun() bool { + return len(p.hosts) > 0 +} + +// Run does all the prep work on the hosts in parallel +func (p *RunHooks) Run() error { + return p.hosts.ParallelEach(p.runHooksForHost) +} + +func (p *RunHooks) runHooksForHost(h *cluster.Host) error { + steps := h.Hooks.ForActionAndStage(p.Action, p.Stage) + for _, s := range steps { + err := h.Exec(s) + if err != nil { + return err + } + } + return nil +} diff --git a/smoke-test/k0sctl.yaml.tpl b/smoke-test/k0sctl.yaml.tpl index 961f6e41..3850ca55 100644 --- a/smoke-test/k0sctl.yaml.tpl +++ b/smoke-test/k0sctl.yaml.tpl @@ -9,6 +9,12 @@ spec: address: "127.0.0.1" port: 9022 keyPath: ./id_rsa_k0s + hooks: + apply: + before: + - "echo hello > apply.hook" + after: + - "grep -q hello apply.hook" - role: worker uploadBinary: true os: "$OS_OVERRIDE" diff --git a/smoke-test/smoke-basic.sh b/smoke-test/smoke-basic.sh index ceada968..c8567480 100755 --- a/smoke-test/smoke-basic.sh +++ b/smoke-test/smoke-basic.sh @@ -13,4 +13,7 @@ envsubst < "${K0SCTL_TEMPLATE}" > k0sctl.yaml deleteCluster createCluster ../k0sctl apply --config k0sctl.yaml --debug +# Check that the hooks got actually ran properly +footloose ssh root@manager0 -- grep -q hello apply.hook + ../k0sctl kubeconfig --config k0sctl.yaml | grep -v -- "-data"