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

Configurable hooks to run commands on target hosts during operations #154

Merged
merged 4 commits into from
Jun 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions cmd/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -87,6 +88,7 @@ var applyCommand = &cli.Command{
&phase.UpgradeWorkers{
NoDrain: ctx.Bool("no-drain"),
},
&phase.RunHooks{Stage: "after", Action: "apply"},
&phase.Disconnect{},
)

Expand Down
2 changes: 2 additions & 0 deletions cmd/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
)

Expand Down
2 changes: 2 additions & 0 deletions cmd/reset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
)

Expand Down
12 changes: 12 additions & 0 deletions config/cluster/hook.go
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions config/cluster/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
Expand Down
86 changes: 86 additions & 0 deletions phase/runhooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package phase

import (
"fmt"
"strings"
"sync"

"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

steps map[*cluster.Host][]string
}

// 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.steps = make(map[*cluster.Host][]string)
for _, h := range config.Spec.Hosts {
if len(h.Hooks) > 0 {
p.steps[h] = h.Hooks.ForActionAndStage(p.Action, p.Stage)
}
}

return nil
}

// ShouldRun is true when there are hosts that need to be connected
func (p *RunHooks) ShouldRun() bool {
return len(p.steps) > 0
}

// Run does all the prep work on the hosts in parallel
func (p *RunHooks) Run() error {
var wg sync.WaitGroup
var errors []string
type erritem struct {
host string
err error
}
ec := make(chan erritem, 1)

wg.Add(len(p.steps))

for h, steps := range p.steps {
go func(h *cluster.Host, steps []string) {
for _, s := range steps {
err := h.Exec(s)
if err != nil {
ec <- erritem{h.String(), err}
return // do not exec remaining steps if one fails
}
}

ec <- erritem{h.String(), nil}
}(h, steps)
}

go func() {
kke marked this conversation as resolved.
Show resolved Hide resolved
for e := range ec {
if e.err != nil {
errors = append(errors, fmt.Sprintf("%s: %s", e.host, e.err.Error()))
}
wg.Done()
}
}()

wg.Wait()

if len(errors) > 0 {
return fmt.Errorf("failed on %d hosts:\n - %s", len(errors), strings.Join(errors, "\n - "))
}

return nil
}
6 changes: 6 additions & 0 deletions smoke-test/k0sctl.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ spec:
address: "127.0.0.1"
port: 9022
keyPath: ./id_rsa_k0s
hooks:
apply:
before:
- "date > /before-apply.hook"
after:
- "date > /after-apply.hook"
kke marked this conversation as resolved.
Show resolved Hide resolved
- role: worker
uploadBinary: true
os: "$OS_OVERRIDE"
Expand Down