Skip to content

Commit 654f7dd

Browse files
authored
Additional hook points (#864)
* Define additional hook points Signed-off-by: Kimmo Lehto <[email protected]> * Make it pass Signed-off-by: Kimmo Lehto <[email protected]> --------- Signed-off-by: Kimmo Lehto <[email protected]>
1 parent e0347fc commit 654f7dd

22 files changed

+389
-203
lines changed

README.md

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,14 @@ Example:
384384

385385
```yaml
386386
hooks:
387+
connect:
388+
after:
389+
- echo "connected and detected" >> k0sctl-connect.log
390+
upgrade:
391+
before:
392+
- echo "about to upgrade" >> k0sctl-upgrade.log
393+
after:
394+
- echo "upgraded" >> k0sctl-upgrade.log
387395
apply:
388396
before:
389397
- date >> k0sctl-apply.log
@@ -393,16 +401,30 @@ hooks:
393401

394402
The currently available "hook points" are:
395403

404+
* `connect`:
405+
- `after`: Runs immediately after OS detection completes
396406
* `apply`: Runs during `k0sctl apply`
397407
- `before`: Runs after configuration and host validation, right before configuring k0s on the host
398408
- `after`: Runs before disconnecting from the host after a successful apply operation
409+
* `upgrade`: Runs during `k0sctl apply`
410+
- `before`: Runs for each host that is going to be upgraded, before the upgrade begins
411+
- `after`: Runs for each host that was upgraded, after the upgrade completes
412+
* `install`: Runs during `k0sctl apply`
413+
- `before`: Runs on each host just before installing its k0s components. This includes the first controller (Initialize the k0s cluster), additional controllers, and workers.
414+
- `after`: Runs on each host immediately after installing its k0s components (service started and ready checks done).
399415
* `backup`: Runs during `k0s backup`
400416
- `before`: Runs before k0sctl runs the `k0s backup` command
401417
- `after`: Runs before disconnecting from the host after successfully taking a backup
402-
* `reset`: Runs during `k0sctl reset`
418+
* `reset`: Runs during `k0sctl reset` or when `k0sctl apply` resets a host.
403419
- `before`: Runs after gathering information about the cluster, right before starting to remove the k0s installation.
404420
- `after`: Runs before disconnecting from the host after a successful reset operation
405421

422+
Notes:
423+
424+
- Hooks run on each host that defines them, using the same remote user as the connection. If elevated privileges are required, prefix commands with `sudo`.
425+
- In dry-run mode, hooks are not executed; k0sctl prints what would run on each host.
426+
- Hooks execute only on hosts targeted by the related phase. For example, `upgrade` hooks run only for hosts that need upgrade.
427+
406428
##### `spec.hosts[*].os` &lt;string&gt; (optional) (default: ``)
407429

408430
Override OS distribution auto-detection. By default `k0sctl` detects the OS by reading `/etc/os-release` or `/usr/lib/os-release` files. In case your system is based on e.g. Debian but the OS release info has something else configured you can override `k0sctl` to use Debian based functionality for the node with:
@@ -741,7 +763,7 @@ The following tokens can be used in the `k0sDownloadURL` and `files.[*].src` fie
741763
- `%%` - literal `%`
742764
- `%p` - host architecture (arm, arm64, amd64)
743765
- `%v` - k0s version (v1.21.0+k0s.0)
744-
- `%x` - k0s binary extension (currently always empty)
766+
- `%x` - k0s binary extension (.exe on Windows, empty elsewhere)
745767

746768
Any other tokens will be output as-is including the `%` character.
747769

@@ -752,8 +774,5 @@ Example:
752774
k0sDownloadURL: https://files.example.com/k0s%20files/k0s-%v-%p%x
753775
# Expands to https://files.example.com/k0s%20files/k0s-v1.21.0+k0s.0-amd64
754776
```
755-
756-
757-
758777
## License
759778
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fk0sproject%2Fk0sctl.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fk0sproject%2Fk0sctl?ref=badge_large)

action/apply.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func NewApply(opts ApplyOptions) *Apply {
5555
ApplyOptions: opts,
5656
Phases: phase.Phases{
5757
&phase.DefaultK0sVersion{},
58-
&phase.Connect{},
59-
&phase.DetectOS{},
58+
&phase.Connect{},
59+
&phase.DetectOS{},
6060
lockPhase,
6161
&phase.PrepareHosts{},
6262
&phase.GatherFacts{},

action/backup.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,22 @@ import (
1313
type Backup struct {
1414
// Manager is the phase manager
1515
Manager *phase.Manager
16-
Out io.Writer
16+
Out io.Writer
1717
}
1818

1919
func (b Backup) Run(ctx context.Context) error {
2020
start := time.Now()
2121

2222
lockPhase := &phase.Lock{}
2323

24-
b.Manager.AddPhase(
25-
&phase.Connect{},
26-
&phase.DetectOS{},
27-
lockPhase,
28-
&phase.PrepareHosts{},
24+
b.Manager.AddPhase(
25+
&phase.Connect{},
26+
&phase.DetectOS{},
27+
lockPhase,
28+
&phase.PrepareHosts{},
2929
&phase.GatherFacts{SkipMachineIDs: true},
3030
&phase.GatherK0sFacts{},
31-
&phase.RunHooks{Stage: "before", Action: "backup"},
3231
&phase.Backup{Out: b.Out},
33-
&phase.RunHooks{Stage: "after", Action: "backup"},
3432
&phase.Unlock{Cancel: lockPhase.Cancel},
3533
&phase.Disconnect{},
3634
)

action/kubeconfig.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ func (k *Kubeconfig) Run(ctx context.Context) error {
2222
// do not need to connect to all nodes
2323
k.Manager.Config.Spec.Hosts = cluster.Hosts{k.Manager.Config.Spec.K0sLeader()}
2424

25-
k.Manager.AddPhase(
26-
&phase.Connect{},
27-
&phase.DetectOS{},
28-
&phase.GetKubeconfig{APIAddress: k.KubeconfigAPIAddress, User: k.KubeconfigUser, Cluster: k.KubeconfigCluster},
29-
&phase.Disconnect{},
30-
)
25+
k.Manager.AddPhase(
26+
&phase.Connect{},
27+
&phase.DetectOS{},
28+
&phase.GetKubeconfig{APIAddress: k.KubeconfigAPIAddress, User: k.KubeconfigUser, Cluster: k.KubeconfigCluster},
29+
&phase.Disconnect{},
30+
)
3131

3232
return k.Manager.Run(ctx)
3333
}

action/reset.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,13 @@ func (r Reset) Run(ctx context.Context) error {
4242
}
4343

4444
lockPhase := &phase.Lock{}
45-
r.Manager.AddPhase(
46-
&phase.Connect{},
47-
&phase.DetectOS{},
48-
lockPhase,
45+
r.Manager.AddPhase(
46+
&phase.Connect{},
47+
&phase.DetectOS{},
48+
lockPhase,
4949
&phase.PrepareHosts{},
5050
&phase.GatherFacts{SkipMachineIDs: true},
5151
&phase.GatherK0sFacts{},
52-
&phase.RunHooks{Stage: "before", Action: "reset"},
5352
&phase.ResetWorkers{
5453
NoDrain: true,
5554
NoDelete: true,
@@ -61,7 +60,6 @@ func (r Reset) Run(ctx context.Context) error {
6160
},
6261
&phase.ResetLeader{},
6362
&phase.DaemonReload{},
64-
&phase.RunHooks{Stage: "after", Action: "reset"},
6563
&phase.Unlock{Cancel: lockPhase.Cancel},
6664
&phase.Disconnect{},
6765
)

phase/backup.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,22 @@ func (p *Backup) Title() string {
3131
return "Take backup"
3232
}
3333

34+
// Before runs "before backup" hooks
35+
func (p *Backup) Before() error {
36+
if err := p.runHooks(context.Background(), "backup", "before", p.leader); err != nil {
37+
return fmt.Errorf("running hooks failed: %w", err)
38+
}
39+
return nil
40+
}
41+
42+
// After runs "after backup" hooks
43+
func (p *Backup) After() error {
44+
if err := p.runHooks(context.Background(), "backup", "after", p.leader); err != nil {
45+
return fmt.Errorf("running hooks failed: %w", err)
46+
}
47+
return nil
48+
}
49+
3450
// Prepare the phase
3551
func (p *Backup) Prepare(config *v1beta1.Cluster) error {
3652
p.Config = config

phase/detect_os.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818

1919
// DetectOS performs remote OS detection
2020
type DetectOS struct {
21-
GenericPhase
21+
GenericPhase
2222
}
2323

2424
// Title for the phase
@@ -55,3 +55,8 @@ func (p *DetectOS) Run(ctx context.Context) error {
5555
return nil
5656
})
5757
}
58+
59+
// After runs the per-host "connect: after" hooks once OS detection has succeeded.
60+
func (p *DetectOS) After() error {
61+
return p.runHooks(context.Background(), "connect", "after", p.Config.Spec.Hosts...)
62+
}

phase/generic_phase.go

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package phase
22

33
import (
4-
"context"
5-
"fmt"
4+
"context"
5+
"fmt"
66

7-
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1"
8-
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster"
7+
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1"
8+
"github.com/k0sproject/k0sctl/pkg/apis/k0sctl.k0sproject.io/v1beta1/cluster"
99
)
1010

1111
// GenericPhase is a basic phase which gets a config via prepare, sets it into p.Config
@@ -28,12 +28,12 @@ func (p *GenericPhase) Prepare(c *v1beta1.Cluster) error {
2828

2929
// Wet is a shorthand for manager.Wet
3030
func (p *GenericPhase) Wet(host fmt.Stringer, msg string, funcs ...errorfunc) error {
31-
return p.manager.Wet(host, msg, funcs...)
31+
return p.manager.Wet(host, msg, funcs...)
3232
}
3333

34-
// IsWet returns true if manager is in dry-run mode
34+
// IsWet returns true when not in dry-run mode (i.e., wet mode)
3535
func (p *GenericPhase) IsWet() bool {
36-
return !p.manager.DryRun
36+
return !p.manager.DryRun
3737
}
3838

3939
// DryMsg is a shorthand for manager.DryMsg
@@ -59,8 +59,28 @@ func (p *GenericPhase) parallelDo(ctx context.Context, hosts cluster.Hosts, func
5959
}
6060

6161
func (p *GenericPhase) parallelDoUpload(ctx context.Context, hosts cluster.Hosts, funcs ...func(context.Context, *cluster.Host) error) error {
62-
if p.manager.Concurrency == 0 {
63-
return hosts.ParallelEach(ctx, funcs...)
64-
}
65-
return hosts.BatchedParallelEach(ctx, p.manager.ConcurrentUploads, funcs...)
62+
if p.manager.Concurrency == 0 {
63+
return hosts.ParallelEach(ctx, funcs...)
64+
}
65+
return hosts.BatchedParallelEach(ctx, p.manager.ConcurrentUploads, funcs...)
66+
}
67+
68+
// runHooks executes hooks for the provided hosts honoring the given context.
69+
func (p *GenericPhase) runHooks(ctx context.Context, action, stage string, hosts ...*cluster.Host) error {
70+
return p.parallelDo(ctx, hosts, func(_ context.Context, h *cluster.Host) error {
71+
if !p.IsWet() {
72+
// In dry-run, list each hook command that would be executed.
73+
cmds := h.Hooks.ForActionAndStage(action, stage)
74+
for _, cmd := range cmds {
75+
p.DryMsgf(h, "run %s %s hook: %q", stage, action, cmd)
76+
}
77+
return nil
78+
}
79+
80+
if err := h.RunHooks(ctx, action, stage); err != nil {
81+
return fmt.Errorf("running hooks failed: %w", err)
82+
}
83+
84+
return nil
85+
})
6686
}

phase/initialize_k0s.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import (
1515

1616
// InitializeK0s sets up the "initial" k0s controller
1717
type InitializeK0s struct {
18-
GenericPhase
19-
leader *cluster.Host
18+
GenericPhase
19+
leader *cluster.Host
2020
}
2121

2222
// Title for the phase
@@ -34,6 +34,22 @@ func (p *InitializeK0s) Prepare(config *v1beta1.Cluster) error {
3434
return nil
3535
}
3636

37+
// Before runs "before install" hooks for the leader controller
38+
func (p *InitializeK0s) Before() error {
39+
if p.leader == nil || p.leader.Reset {
40+
return nil
41+
}
42+
return p.runHooks(context.Background(), "install", "before", p.leader)
43+
}
44+
45+
// After runs "after install" hooks for the leader controller
46+
func (p *InitializeK0s) After() error {
47+
if p.leader == nil || p.leader.Reset {
48+
return nil
49+
}
50+
return p.runHooks(context.Background(), "install", "after", p.leader)
51+
}
52+
3753
// ShouldRun is true when there is a leader host
3854
func (p *InitializeK0s) ShouldRun() bool {
3955
return p.leader != nil && !p.leader.Reset

phase/install_controllers.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,16 @@ func (p *InstallControllers) ShouldRun() bool {
4848
return len(p.hosts) > 0
4949
}
5050

51+
// Before runs "before install" hooks for controller hosts
52+
func (p *InstallControllers) Before() error {
53+
if len(p.hosts) == 0 {
54+
return nil
55+
}
56+
return p.runHooks(context.Background(), "install", "before", p.hosts...)
57+
}
58+
5159
// CleanUp cleans up the environment override files on hosts
5260
func (p *InstallControllers) CleanUp() {
53-
_ = p.After()
5461
_ = p.hosts.Filter(func(h *cluster.Host) bool {
5562
return !h.Metadata.Ready
5663
}).ParallelEach(context.Background(), func(_ context.Context, h *cluster.Host) error {
@@ -69,7 +76,12 @@ func (p *InstallControllers) CleanUp() {
6976
})
7077
}
7178

79+
// After runs "after install" hooks for controller hosts and cleans up tokens
7280
func (p *InstallControllers) After() error {
81+
// Run "after install" hooks for controllers first
82+
if err := p.runHooks(context.Background(), "install", "after", p.hosts...); err != nil {
83+
return err
84+
}
7385
for i, h := range p.hosts {
7486
if h.Metadata.K0sTokenData.Token == "" {
7587
continue
@@ -80,7 +92,7 @@ func (p *InstallControllers) After() error {
8092
return p.leader.Exec(p.leader.Configurer.K0sCmdf("token invalidate --data-dir=%s %s", p.leader.K0sDataDir(), h.Metadata.K0sTokenData.ID), exec.Sudo(p.leader))
8193
})
8294
if err != nil {
83-
log.Warnf("%s: failed to invalidate worker join token: %v", p.leader, err)
95+
log.Warnf("%s: failed to invalidate controller join token: %v", p.leader, err)
8496
}
8597
_ = p.Wet(h, "overwrite k0s join token file", func() error {
8698
if err := h.Configurer.WriteFile(h, h.K0sJoinTokenPath(), "# overwritten by k0sctl after join\n", "0600"); err != nil {
@@ -222,6 +234,7 @@ func (p *InstallControllers) installK0s(ctx context.Context, h *cluster.Host) er
222234
return err
223235
}
224236
log.Infof("%s: installing k0s controller", h)
237+
225238
err = p.Wet(h, fmt.Sprintf("install k0s controller using `%s", strings.ReplaceAll(cmd, h.K0sInstallLocation(), "k0s")), func() error {
226239
var stdout, stderr bytes.Buffer
227240
runner, err := h.ExecStreams(cmd, nil, &stdout, &stderr, exec.Sudo(h))

0 commit comments

Comments
 (0)