Compare commits

...

209 Commits

Author SHA1 Message Date
Itxaka
a6f34820fb
Revert "Pxe uki (#791)" (#795) 2025-05-28 12:18:52 +02:00
renovate[bot]
75edc8b146
fix(deps): update module github.com/google/go-github/v69 to v72 (#783)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-23 09:52:23 +00:00
Itxaka
7842ad8059
Pxe uki (#791) 2025-05-23 11:48:26 +02:00
renovate[bot]
530e2df7cf
fix(deps): update module github.com/labstack/echo/v4 to v4.13.4 (#793)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 22:49:57 +00:00
renovate[bot]
7fb6ee0ff7
fix(deps): update module github.com/google/go-containerregistry to v0.20.5 (#792)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 22:49:30 +00:00
renovate[bot]
e3d2a860d0
fix(deps): update module github.com/google/go-containerregistry to v0.20.4 (#790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-21 02:40:09 +00:00
renovate[bot]
8aaf0b9cac
chore(deps): update dependency cypress to v14.4.0 (#789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-20 22:12:59 +00:00
Dimitris Karakasilis
53c1b6c9ea
Fix wrong error message when upgrading recovery (#788)
* Fix wrong error message when upgrading recovery

fix command help text and simplify variables in function

Fixes https://github.com/kairos-io/kairos/issues/3393

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Fix test

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-05-19 18:37:25 +03:00
renovate[bot]
2b1e5e66fb
fix(deps): update module k8s.io/mount-utils to v0.33.1 (#787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-16 02:03:57 +00:00
renovate[bot]
5ec00e6ec1
fix(deps): update module github.com/mudler/yip to v1.16.1 (#786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-15 22:17:37 +00:00
renovate[bot]
a708ffd175
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.9.3 (#785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-13 23:09:32 +00:00
renovate[bot]
6d1b5c0c9d
chore(deps): update dependabot/fetch-metadata action to v2.4.0 (#784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-10 03:24:18 +00:00
renovate[bot]
86f54cf5c3
fix(deps): update module github.com/google/go-github/v69 to v72 (#782)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-09 02:52:00 +00:00
renovate[bot]
e8bca1dcf0
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.9.2 (#781)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 22:49:54 +00:00
renovate[bot]
24d365e19b
chore(deps): update securego/gosec action to v2.22.4 (#780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 22:49:09 +00:00
renovate[bot]
62c2b834c6
chore(deps): update actions/setup-go action to v5.5.0 (#779)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-08 03:33:01 +00:00
renovate[bot]
69e0ef7631
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.9.1 (#778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 22:53:32 +00:00
renovate[bot]
599d53de34
chore(deps): update dependency cypress to v14.3.3 (#777)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 22:52:40 +00:00
Itxaka
79cd5620c5
Drop kcrypt, use sdk (#769) 2025-05-06 09:02:44 +00:00
renovate[bot]
30596d666b
fix(deps): update module golang.org/x/oauth2 to v0.30.0 (#776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-06 03:57:36 +00:00
renovate[bot]
4368a15d79
fix(deps): update module golang.org/x/net to v0.40.0 (#775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 23:38:11 +00:00
renovate[bot]
3129aa16ea
fix(deps): update dependency bootstrap to v5.3.6 (#774)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 23:37:32 +00:00
renovate[bot]
251934559b
chore(deps): update google/osv-scanner-action action to v2.0.2 (#773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-05 02:06:08 +00:00
renovate[bot]
75a45c762f
fix(deps): update module github.com/urfave/cli/v3 to v3.3.2 (#772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-28 23:31:20 +00:00
renovate[bot]
d1c439c2dc
fix(deps): update module github.com/urfave/cli/v3 to v3.3.1 (#771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 21:59:07 +00:00
renovate[bot]
103789cb69
fix(deps): update module github.com/urfave/cli/v3 to v3.3.0 (#770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-27 21:54:26 +00:00
Itxaka
d0f0710c78
Use grub binaries and libs from rootfs (#760) 2025-04-25 10:43:21 +02:00
renovate[bot]
5d5a52930f
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.9.0 (#768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 00:04:17 +00:00
renovate[bot]
fb3e245554
fix(deps): update module k8s.io/mount-utils to v0.33.0 (#767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 22:10:24 +00:00
renovate[bot]
1a1f738903
chore(deps): update dependency cypress to v14.3.2 (#766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-22 23:05:22 +00:00
renovate[bot]
685dd0090c
fix(deps): update module github.com/urfave/cli/v2 to v3 (#754)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-20 03:09:23 +00:00
renovate[bot]
cccda67781
fix(deps): update module github.com/urfave/cli/v3 to v3.2.0 (#764)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-20 03:08:44 +00:00
renovate[bot]
5d444a7ab9
chore(deps): update dependency cypress to v14.3.1 (#763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-18 01:52:39 +00:00
renovate[bot]
19b0348659
fix(deps): update module github.com/kairos-io/kcrypt to v0.15.0 (#762)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-16 21:34:40 +00:00
Itxaka
db0f21164e
On build, check fips binary for confirmation (#761) 2025-04-16 12:05:36 +03:00
renovate[bot]
af8c24846c
fix(deps): update module github.com/diskfs/go-diskfs to v1.6.0 (#759)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 23:03:21 +00:00
Itxaka
e5b98de8b3
Add support for common and recovery folders in sysext (#757) 2025-04-14 15:42:10 +02:00
Itxaka
e61dc8f00a Use tag instead of version for release archive
Signed-off-by: Itxaka <itxaka@kairos.io>
2025-04-11 18:35:03 +02:00
Itxaka
1182776075 Install arm64 gcc compiler
Signed-off-by: Itxaka <itxaka@kairos.io>
2025-04-11 17:21:49 +02:00
Itxaka
ad825b1308
Make goreleaser release fips binaries (#756) 2025-04-11 15:59:01 +02:00
Itxaka
80d6f064c3
First iteration of the sysext command (#738)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-04-09 10:18:11 +00:00
renovate[bot]
7a39098c13
chore(deps): update dependency cypress to v14.3.0 (#752)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 22:04:46 +00:00
renovate[bot]
32d3026be3
fix(deps): update module github.com/urfave/cli/v2 to v3 (#736)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 03:44:58 +00:00
renovate[bot]
97a7806148
fix(deps): update module github.com/google/go-github/v69 to v71 (#750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 03:44:39 +00:00
renovate[bot]
1c50dd2584
fix(deps): update module github.com/google/go-github/v70 to v71 (#751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-08 03:40:24 +00:00
renovate[bot]
d18bedd2de
fix(deps): update module golang.org/x/net to v0.39.0 (#748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 22:22:49 +00:00
renovate[bot]
b7ecc30b8f
fix(deps): update module github.com/google/go-github/v69 to v71 (#749)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 22:22:32 +00:00
renovate[bot]
c407692c10
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.23.4 (#746)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-06 22:30:06 +00:00
Itxaka
c94cd8c685
Disable atimes in rsync (#743)
Some very old distros like ubuntu dont have that option
2025-04-05 20:41:15 +02:00
renovate[bot]
86d710dd02
fix(deps): update module golang.org/x/sys to v0.32.0 (#745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 13:59:22 +00:00
renovate[bot]
9cd047ed88
fix(deps): update module golang.org/x/oauth2 to v0.29.0 (#744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 13:55:02 +00:00
renovate[bot]
cf0afb0cce
fix(deps): update dependency bootstrap to v5.3.5 (#742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-05 02:35:13 +00:00
renovate[bot]
62b6a63f57
chore(deps): update securego/gosec action to v2.22.3 (#741)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-04 22:58:15 +00:00
Itxaka
b2ced7173f Allow skipping the users check via sentinel
Signed-off-by: Itxaka <itxaka@kairos.io>
(cherry picked from commit b45d95c256)
2025-04-04 15:19:20 +02:00
renovate[bot]
2f50886ba2
fix(deps): update dependency bootstrap to v5.3.4 (#740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 21:58:28 +00:00
renovate[bot]
c62f26884e
chore(deps): update google/osv-scanner-action action to v2.0.1 (#739)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-03 21:58:13 +00:00
Itxaka
2b9a3359db
Fix platform in ExtractImage (#737)
We were passing the platform but doing nothing with it. Thats bad.
This will now check if the platform is valid by trying to parse it and
if it fails or platform is empty, it will default to the current
platform as it did before

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-04-03 17:55:56 +02:00
renovate[bot]
d6a9cd869c
fix(deps): update module github.com/onsi/gomega to v1.37.0 (#734)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 21:58:11 +00:00
renovate[bot]
cb1bda7e3c
fix(deps): update module github.com/google/go-github/v69 to v70 (#726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 12:56:11 +00:00
renovate[bot]
bce1cdce45
fix(deps): update module github.com/urfave/cli/v2 to v3 (#732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-02 12:51:38 +00:00
Itxaka
d83f78047f
Bump sdk (#733)
* Bump sdk

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-04-02 14:47:05 +02:00
renovate[bot]
f0bdaaacce
chore(deps): update dependency go to v1.24.2 (#731)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 22:54:40 +00:00
renovate[bot]
ea9ca53912
fix(deps): update module github.com/urfave/cli/v2 to v3 (#729)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 16:52:36 +00:00
renovate[bot]
8393b4401f
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.8.0 (#730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-01 09:55:54 +00:00
renovate[bot]
d4d1aac9ec
fix(deps): update module github.com/urfave/cli/v2 to v3 (#728)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 22:23:40 +00:00
renovate[bot]
cb3d349fc8
fix(deps): update module golang.org/x/net to v0.38.0 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-27 23:47:27 +00:00
renovate[bot]
dc78072602
chore(deps): update dependency cypress to v14.2.1 (#725)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 23:33:52 +00:00
renovate[bot]
96501020b3
fix(deps): update module github.com/google/go-github/v69 to v70 (#711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-26 11:02:08 +00:00
Itxaka
4975b9b914
Bump yip and diskfs (#717)
* Bump yip and diskfs

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-26 11:57:29 +01:00
Itxaka
db703db5e5
Try to fix hooks (#718)
* fix hooks


---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-24 16:05:39 +01:00
renovate[bot]
06aa2ce4e4
chore(deps): update google/osv-scanner-action action to v2 (#724)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 02:14:31 +00:00
renovate[bot]
030204d466
fix(deps): update module github.com/rs/zerolog to v1.34.0 (#723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 05:26:35 +00:00
renovate[bot]
2f3f2ce8ac
fix(deps): update module github.com/onsi/gomega to v1.36.3 (#721)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-22 02:35:08 +00:00
renovate[bot]
44a397a758
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.23.3 (#720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 23:02:05 +00:00
renovate[bot]
796252bdc0
fix(deps): update module github.com/kairos-io/kcrypt to v0.14.1 (#719)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 23:01:56 +00:00
renovate[bot]
bea5878fd2
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.23.2 (#716)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 02:56:40 +00:00
renovate[bot]
ca05938938
fix(deps): update dependency codemirror to v5.65.19 (#715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-21 02:55:35 +00:00
renovate[bot]
eb9a8d1736
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.23.1 (#714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-20 01:41:32 +00:00
renovate[bot]
6845373bfd
chore(deps): update actions/setup-go action to v5.4.0 (#712)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-19 05:46:58 +00:00
renovate[bot]
50fde398bc
fix(deps): update module github.com/google/go-github/v69 to v70 (#709)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 02:47:02 +00:00
dependabot[bot]
9bf5c602ab
Bump github.com/containerd/containerd (#710)
Bumps the go_modules group with 1 update in the / directory: [github.com/containerd/containerd](https://github.com/containerd/containerd).


Updates `github.com/containerd/containerd` from 1.7.25 to 1.7.27
- [Release notes](https://github.com/containerd/containerd/releases)
- [Changelog](https://github.com/containerd/containerd/blob/main/RELEASES.md)
- [Commits](https://github.com/containerd/containerd/compare/v1.7.25...v1.7.27)

---
updated-dependencies:
- dependency-name: github.com/containerd/containerd
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-18 02:42:50 +00:00
renovate[bot]
5c9e4dd7b9
fix(deps): update module github.com/google/go-github/v69 to v70 (#708)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-18 02:36:59 +00:00
renovate[bot]
694ecdc63d
fix(deps): update module k8s.io/mount-utils to v0.32.3 (#705)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 10:30:45 +00:00
renovate[bot]
fd0471143f
chore(deps): update dependency cypress to v14.2.0 (#706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 10:26:55 +00:00
renovate[bot]
a77b041b23
fix(deps): update dependency alpinejs to v3.14.9 (#704)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-13 10:25:47 +00:00
Itxaka
b5869b4017
Fix hooks under encrypted partitions (#702)
* Fix hooks under encrypted partitions

We had a mess of mounting and unmounting things around when we try to
copy things to persistent.

Part of the changes (using the by-label to mount the persistent) are due
to the change in kcrypt. As we set the same label to the encrypted fs
and unencrypted fs, our utils.Mount could get mistaken and return the
first hit, which usually its the encrypted one, and we cannot mount that
one.

This patch brings it up to date.

 - Makes bundles and logs hooks work when we have encrypted persistent.
   It didnt work before.
 - Makes both workflows the same.
 - Locks everything once its over, to not leave encrypted parts around
 - Mounts OEM so kcrypt can read the config if we are using a remote
   server for encryption
 - Mounts by label so there is not a change of getting the wrong device
 - Uses the mount syscall directly. The util can mistake and return the
   actual encrypted part if they both have the same label and finds it
   first

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-13 11:22:26 +01:00
renovate[bot]
e8e05379a5
fix(deps): update module golang.org/x/oauth2 to v0.28.0 (#701)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-07 02:31:41 +00:00
renovate[bot]
8695ba7526
fix(deps): update module golang.org/x/net to v0.37.0 (#700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 22:18:33 +00:00
renovate[bot]
cc82369b30
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.23.0 (#699)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 22:18:26 +00:00
Itxaka
11cdddc452
Disable xattrs on rsync (#694)
* Disable xattrs on rsync

For systems with selinux enabled, this makes impossible to generate the
proper files as its being blocked

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-03-06 10:33:40 +01:00
renovate[bot]
bb4225884e
fix(deps): update module github.com/urfave/cli/v2 to v2.27.6 (#698)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 02:49:46 +00:00
renovate[bot]
e2f2b53b99
chore(deps): update securego/gosec action to v2.22.2 (#697)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-06 02:49:05 +00:00
renovate[bot]
127df512c0
fix(deps): update module golang.org/x/net to v0.36.0 (#696)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 23:03:20 +00:00
renovate[bot]
09c138ee8b
chore(deps): update dependency go to v1.24.1 (#695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-04 23:02:39 +00:00
renovate[bot]
12d6227008
chore(deps): update dependency cypress to v14.1.0 (#693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-26 03:11:17 +00:00
renovate[bot]
9e9042fee3
fix(deps): update module golang.org/x/oauth2 to v0.27.0 (#692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-25 02:55:42 +00:00
renovate[bot]
21d9d48947
fix(deps): update module github.com/mudler/yip to v1.15.0 (#691)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 22:05:40 +00:00
renovate[bot]
081014bd1a
chore(config): migrate config renovate.json (#690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-18 14:29:07 +00:00
renovate[bot]
013f24dfd5
fix(deps): update module github.com/google/go-github/v69 to v69.2.0 (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 22:57:58 +00:00
renovate[bot]
b3f6db40ab
fix(deps): update module github.com/sanity-io/litter to v1.5.8 (#688)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-15 17:55:25 +00:00
renovate[bot]
6eea367c2a
fix(deps): update module github.com/sanity-io/litter to v1.5.7 (#687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 23:10:06 +00:00
Dimitris Karakasilis
445da19c7e
Bump github.com/google/go-github properly (#686)
because renovate doesn't know how to

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-02-14 16:14:04 +02:00
renovate[bot]
615cd97436
fix(deps): update module github.com/google/go-github/v66 to v69 (#685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-14 13:32:59 +00:00
Dimitris Karakasilis
0323e7d59d
Revert litter bump and run go mod tidy
because this changes:
https://github.com/sanity-io/litter/compare/v1.5.6...v1.5.7#diff-7e208a132876ae1bd6e4709fb93c4a5710e91335c1ed5926a423cc57d63c9cbdR137

create these errors:

```
   2025-02-14T13:12:04Z INF Boot in uki mode result=false
  panic: reflect.Value.Interface: cannot return value obtained from unexported field or method

  goroutine 1 [running]:
  reflect.valueInterface({0x129bf40?, 0xc0004228c0?, 0xbab950?}, 0xc0?)
  	/usr/local/go/src/reflect/value.go:1492 +0xc6
```

https://github.com/kairos-io/kairos/actions/runs/13329130652/job/37231009324?pr=3194

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-02-14 15:27:34 +02:00
renovate[bot]
d11dc122ae
fix(deps): update module k8s.io/mount-utils to v0.32.2 (#684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 23:27:25 +00:00
renovate[bot]
d9381c3ae3
chore(deps): update securego/gosec action to v2.22.1 (#683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 23:26:24 +00:00
renovate[bot]
3db8924ce8
fix(deps): update module github.com/google/go-github/v66 to v69 (#678)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-13 18:54:16 +00:00
renovate[bot]
ae7b2d08a6
fix(deps): update module github.com/sanity-io/litter to v1.5.7 (#682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-12 23:31:14 +00:00
renovate[bot]
a3dca67856
chore(deps): update dependency cypress to v14.0.3 (#681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 22:35:34 +00:00
renovate[bot]
edcf8723ab
fix(deps): update module golang.org/x/net to v0.35.0 (#680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-11 00:54:24 +00:00
Ettore Di Giacinto
a91fb13e39
feat: allow to override standard cloud init paths (#679)
* feat: allow to override standard cloud init paths

Signed-off-by: mudler <mudler@localai.io>

* Address feedback from review

Signed-off-by: mudler <mudler@localai.io>

* Address feedback from review

Signed-off-by: mudler <mudler@localai.io>

---------

Signed-off-by: mudler <mudler@localai.io>
2025-02-07 09:58:28 +01:00
renovate[bot]
9b7b5db760
fix(deps): update module github.com/google/go-github/v66 to v69 (#677)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-07 01:52:46 +00:00
renovate[bot]
892ad54e6f
fix(deps): update module github.com/google/go-github/v68 to v69 (#676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 05:34:21 +00:00
renovate[bot]
163fa3d08b
fix(deps): update module github.com/google/go-github/v66 to v69 (#675)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 05:30:05 +00:00
renovate[bot]
ea105176e4
fix(deps): update module golang.org/x/sys to v0.30.0 (#674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 00:55:07 +00:00
renovate[bot]
eb36d8b6c1
chore(deps): update dependency cypress to v14.0.2 (#673)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-06 00:54:12 +00:00
renovate[bot]
2955e3275b
chore(deps): update dependabot/fetch-metadata action to v2.3.0 (#669)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 00:28:05 +00:00
renovate[bot]
477f0e6323
fix(deps): update module golang.org/x/oauth2 to v0.26.0 (#672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 00:24:59 +00:00
renovate[bot]
a942a90dc4
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.7.3 (#671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-05 00:24:37 +00:00
renovate[bot]
e2ff555707
chore(deps): update dependency cypress to v14.0.1 (#670)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-28 22:18:03 +00:00
renovate[bot]
3412d7a2a2
fix(deps): update module github.com/mudler/yip to v1.14.1 (#667)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 05:52:15 +00:00
renovate[bot]
900779f98e
chore(deps): update actions/setup-go action to v5.3.0 (#668)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-21 05:48:35 +00:00
renovate[bot]
626fbe4ec0
chore(deps): update dependency cypress to v14 (#666)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 01:49:49 +00:00
renovate[bot]
e2c6ef5a9b
fix(deps): update module github.com/mudler/yip to v1.14.0 (#665)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 01:49:06 +00:00
renovate[bot]
1b6c87d1c1
fix(deps): update module github.com/kairos-io/kairos-sdk to v0.7.2 (#662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 08:16:27 +00:00
renovate[bot]
2e667634f5
fix(deps): update module k8s.io/mount-utils to v0.32.1 (#664)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 04:55:17 +00:00
renovate[bot]
0266183421
fix(deps): update module github.com/google/go-containerregistry to v0.20.3 (#663)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-16 01:42:03 +00:00
renovate[bot]
18911b7231
fix(deps): update module github.com/mudler/yip to v1.13.1 (#659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-14 10:04:50 +00:00
Itxaka
488841ebf2
Use extensions dir path as source (#660)
Otherwise it will copy the dir itself and we just want to copy the
contents of the dir

Signed-off-by: Itxaka <itxaka@kairos.io>
2025-01-14 10:59:45 +01:00
Dimitris Karakasilis
347a30b261
Fix https://github.com/kairos-io/kairos-agent/security/code-scanning/57 (#658)
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-01-10 16:14:29 +02:00
renovate[bot]
8a8ef8ff45
fix(deps): update module github.com/sanity-io/litter to v1.5.6 (#655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 23:16:09 +00:00
renovate[bot]
63067dcbcb
chore(deps): update securego/gosec action to v2.22.0 (#656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 23:15:54 +00:00
renovate[bot]
d57bea8263
fix(deps): update module github.com/google/go-github/v66 to v68 (#645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 13:57:20 +00:00
dependabot[bot]
235440e393
Bump github.com/go-git/go-git/v5 (#654)
Bumps the go_modules group with 1 update in the / directory: [github.com/go-git/go-git/v5](https://github.com/go-git/go-git).


Updates `github.com/go-git/go-git/v5` from 5.12.0 to 5.13.0
- [Release notes](https://github.com/go-git/go-git/releases)
- [Commits](https://github.com/go-git/go-git/compare/v5.12.0...v5.13.0)

---
updated-dependencies:
- dependency-name: github.com/go-git/go-git/v5
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-09 13:52:43 +00:00
renovate[bot]
c572314bcc
fix(deps): update module github.com/google/go-github/v67 to v68 (#646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 02:41:36 +00:00
renovate[bot]
2a7be43e74
fix(deps): update module golang.org/x/net to v0.34.0 (#653)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 02:37:12 +00:00
renovate[bot]
e6ed19177f
fix(deps): update module golang.org/x/oauth2 to v0.25.0 (#650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-04 15:16:56 +00:00
renovate[bot]
195fb484d2
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.22.2 (#648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-04 00:23:10 +00:00
renovate[bot]
f111e10906
fix(deps): update module github.com/onsi/gomega to v1.36.2 (#647)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-24 22:45:02 +00:00
renovate[bot]
d8d75ebd37
fix(deps): update dependency alpinejs to v3.14.8 (#643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 22:59:24 +00:00
renovate[bot]
9e6d7f8b5c
fix(deps): update module github.com/google/go-github/v66 to v68 (#644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 22:55:59 +00:00
renovate[bot]
d34d7e834b
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.22.1 (#642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-23 10:45:20 +00:00
Andrew Steurer
78c109040d
adding error message (#626)
* adding error message

Signed-off-by: Andrew Steurer <andrew.steurer@fermyon.com>

* Apply suggestions from code review

---------

Signed-off-by: Andrew Steurer <andrew.steurer@fermyon.com>
Co-authored-by: Itxaka <itxaka@kairos.io>
2024-12-23 10:41:31 +00:00
renovate[bot]
71e8e5b801
chore(deps): update google/osv-scanner-action action to v1.9.2 (#640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 04:19:36 +00:00
renovate[bot]
a8c24b7534
fix(deps): update module github.com/labstack/echo/v4 to v4.13.3 (#641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-20 00:46:51 +00:00
renovate[bot]
1d7321fe00
fix(deps): update module golang.org/x/net to v0.33.0 (#639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-18 22:10:55 +00:00
renovate[bot]
1197f49c00
chore(deps): update dependency cypress to v13.17.0 (#636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-17 23:13:36 +00:00
Itxaka
a5a55c636d
Expand DeployImage to be more flexible (#635)
Usually we create the system dirs in images by default, but that means
that we cannot reuse the DeployImage for deploying random non-system
images.

This fixes it by adding an extra param to create the dir structure in
the created image

Signed-off-by: Itxaka <itxaka@kairos.io>
2024-12-17 15:05:38 +01:00
renovate[bot]
92ca5a6d53
fix(deps): update dependency @fortawesome/fontawesome-free to v6.7.2 (#634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-17 01:15:07 +00:00
renovate[bot]
248c622260
fix(deps): update module github.com/labstack/echo/v4 to v4.13.2 (#632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 04:51:45 +00:00
renovate[bot]
9d9da86e69
chore(deps): update google/osv-scanner-action action to v1.9.1 (#633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-13 02:50:50 +00:00
renovate[bot]
22c65b3ac8
fix(deps): update module github.com/google/go-github/v66 to v67 (#613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 04:13:12 +00:00
dependabot[bot]
a15e7746b1
Bump golang.org/x/crypto in the go_modules group across 1 directory (#630)
Bumps the go_modules group with 1 update in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto).


Updates `golang.org/x/crypto` from 0.30.0 to 0.31.0
- [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 04:09:05 +00:00
renovate[bot]
d80f7d60a4
fix(deps): update module k8s.io/mount-utils to v0.32.0 (#629)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 04:05:05 +00:00
renovate[bot]
163b28729f
fix(deps): update module github.com/labstack/echo/v4 to v4.13.1 (#628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-12 00:15:57 +00:00
renovate[bot]
c6166c9b7a
chore(deps): update actions/setup-go action to v5.2.0 (#627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 04:36:57 +00:00
renovate[bot]
6e1d1bbed3
fix(deps): update module k8s.io/mount-utils to v0.31.4 (#625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-11 02:32:10 +00:00
renovate[bot]
694cdff23e
fix(deps): update module github.com/onsi/gomega to v1.36.1 (#623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-10 04:42:42 +00:00
renovate[bot]
bb11c5759e
fix(deps): update module github.com/mudler/yip to v1.13.0 (#620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 23:28:25 +00:00
renovate[bot]
704cba1d69
fix(deps): update module github.com/kairos-io/go-nodepair to v0.3.0 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 23:24:23 +00:00
renovate[bot]
70a5d04e99
fix(deps): update dependency alpinejs to v3.14.7 (#621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-06 23:23:21 +00:00
renovate[bot]
697b9edec4
fix(deps): update module golang.org/x/sys to v0.28.0 (#619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 04:46:41 +00:00
renovate[bot]
45aedb89f1
fix(deps): update module golang.org/x/net to v0.32.0 (#618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 04:46:14 +00:00
renovate[bot]
49c6f0bdde
chore(deps): update dependency cypress to v13.16.1 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 00:44:18 +00:00
renovate[bot]
953b40cafd
fix(deps): update module github.com/labstack/echo/v4 to v4.13.0 (#617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-05 00:43:57 +00:00
renovate[bot]
5a1c6de530
fix(deps): update dependency alpinejs to v3.14.6 (#615)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-03 02:18:14 +00:00
renovate[bot]
a2071ac740
fix(deps): update dependency alpinejs to v3.14.5 (#614)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 22:25:28 +00:00
renovate[bot]
6b57b8dd93
fix(deps): update module github.com/google/go-github/v66 to v67 (#612)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-28 00:57:49 +00:00
renovate[bot]
d45a645de5
fix(deps): update module github.com/pterm/pterm to v0.12.80 (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 23:04:46 +00:00
renovate[bot]
724a671607
fix(deps): update dependency alpinejs to v3.14.4 (#610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-27 23:03:59 +00:00
Itxaka
07480abde2
Add sort-key during install based on the entry name (#609)
* Add sort-key during install based on the entry name

Signed-off-by: Itxaka <itxaka@kairos.io>

* Fix logger output

Signed-off-by: Itxaka <itxaka@kairos.io>

---------

Signed-off-by: Itxaka <itxaka@kairos.io>
2024-11-27 16:18:50 +02:00
Itxaka
7be897c1d5
Add boot assesment for install and bootentry (#604) 2024-11-27 11:16:56 +01:00
renovate[bot]
8516a19ff9
fix(deps): update module github.com/onsi/gomega to v1.36.0 (#608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-25 22:47:44 +00:00
renovate[bot]
a6dd348055
fix(deps): update module k8s.io/mount-utils to v0.31.3 (#607)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-22 01:50:08 +00:00
renovate[bot]
8a726b1076
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.22.0 (#606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 04:44:17 +00:00
renovate[bot]
c726263c27
fix(deps): update dependency @fortawesome/fontawesome-free to v6.7.1 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-21 01:13:54 +00:00
Itxaka
895e571bb3
Expose pcrs for uki encryption (#603) 2024-11-20 10:41:52 +01:00
renovate[bot]
800b7a0246
chore(deps): update dependency cypress to v13.16.0 (#602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 23:13:08 +00:00
renovate[bot]
15a85eb614
fix(deps): update module github.com/masterminds/semver/v3 to v3.3.1 (#601)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-19 23:12:07 +00:00
Itxaka
00409e2357
fix reset not updating EFI files (#600) 2024-11-19 16:38:05 +01:00
renovate[bot]
04ef1ff989
fix(deps): update github.com/sirupsen/logrus digest to d1e6332 (#598)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 22:26:55 +00:00
renovate[bot]
4f284cc117
fix(deps): update dependency @fortawesome/fontawesome-free to v6.7.0 (#599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-18 22:26:36 +00:00
renovate[bot]
94cb4c9a7c
chore(deps): update codecov/codecov-action action to v5 (#597)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-15 01:54:35 +00:00
renovate[bot]
1e81cdef38
fix(deps): update module golang.org/x/net to v0.31.0 (#596)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 22:52:08 +00:00
Dimitris Karakasilis
785d9d2300
Bump kairos-sdk to v0.6.1 to fix the schema (#595)
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2024-11-08 11:38:13 +02:00
renovate[bot]
b421676aec
fix(deps): update module golang.org/x/sys to v0.27.0 (#594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 00:09:11 +00:00
renovate[bot]
9fb65f43d3
fix(deps): update module golang.org/x/oauth2 to v0.24.0 (#593)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-08 00:04:50 +00:00
Itxaka
9ea89f7610
Bump nodepair (#592)
Signed-off-by: Itxaka <itxaka@kairos.io>
2024-11-07 09:24:24 +01:00
Itxaka
dcad8beac2
Drop those stupid github and vfs deps bumps (#591) 2024-11-06 12:50:14 +01:00
Itxaka
6634e18aa3
Respect user defined/default sizes on upgrade (#587) 2024-11-06 11:34:01 +01:00
renovate[bot]
87fca9570f
chore(deps): update dependency cypress to v13.15.2 (#588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-06 04:14:58 +00:00
renovate[bot]
00d0447757
fix(deps): update module github.com/onsi/gomega to v1.35.1 (#586)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 08:31:31 +00:00
renovate[bot]
527ed05b1c
fix(deps): update module github.com/mudler/yip to v1.12.0 (#581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 08:31:13 +00:00
renovate[bot]
0f4b83d8c8
fix(deps): update module github.com/twpayne/go-vfs/v4 to v5 (#546)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 08:30:54 +00:00
renovate[bot]
3425759377
fix(deps): update module github.com/google/go-github/v63 to v66 (#573)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-11-05 08:30:33 +00:00
renovate[bot]
7cb2219042 fix(deps): update module github.com/diskfs/go-diskfs to v1.4.2 2024-11-05 08:30:20 +00:00
renovate[bot]
c60b35f187 fix(deps): update module github.com/urfave/cli/v2 to v2.27.5 2024-11-05 08:29:53 +00:00
Itxaka
3f2458d42c
Update renovate.json 2024-11-05 09:23:21 +01:00
Dimitris Karakasilis
a288b14e48
Merge pull request #585 from kairos-io/renovate/github.com-onsi-ginkgo-v2-2.x
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.21.0
2024-10-30 08:52:21 +02:00
Dimitris Karakasilis
4573dffbb6 go mod tidy
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2024-10-30 08:17:34 +02:00
renovate[bot]
9f193d8d2e
fix(deps): update module github.com/onsi/ginkgo/v2 to v2.21.0 2024-10-29 23:03:45 +00:00
Itxaka
8184c366ad
Fix partitioner on disks with secotor size other than 512 (#582) 2024-10-28 20:23:18 +01:00
renovate[bot]
c7df8f3f0e
chore(deps): update actions/setup-go action to v5.1.0 (#584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-10-25 00:48:15 +00:00
renovate[bot]
1e7cfe438c chore(deps): update dependency cypress to v13.15.1 2024-10-24 22:30:44 +00:00
Itxaka
f24511b2dd
Enable debug log asap (#578) 2024-10-16 17:27:00 +02:00
Itxaka
e2c3a0e638
Read actual system configs during k8s upgrade (#579) 2024-10-16 12:36:19 +02:00
renovate[bot]
d5ded907a1 fix(deps): update dependency alpinejs to v3.14.3 2024-10-16 02:45:32 +00:00
Itxaka
97d25b8993
Check for user+admin validity before actions (#575) 2024-10-15 10:47:09 +02:00
Itxaka
a3aadbbaa9
Allow installing with no users (#574) 2024-10-10 14:18:59 +02:00
68 changed files with 3911 additions and 2926 deletions

View File

@ -14,7 +14,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.2.0
uses: dependabot/fetch-metadata@v2.4.0
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
skip-commit-verification: true

View File

@ -18,4 +18,4 @@ permissions:
jobs:
scan-pr:
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@v1.9.0"
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@v2.0.2"

View File

@ -14,6 +14,8 @@ jobs:
git fetch --prune --unshallow
- name: Generate version
run: echo "VERSION=$(git describe --always --tags --dirty)" >> $GITHUB_ENV
- name: Install gcc for arm64
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Set up Go
uses: actions/setup-go@v5
with:

View File

@ -19,7 +19,7 @@ jobs:
- name: Checkout Source
uses: actions/checkout@v4
- name: Run Gosec Security Scanner
uses: securego/gosec@v2.21.4
uses: securego/gosec@v2.22.4
with:
# we let the report trigger content trigger a failure using the GitHub Security features.
args: '-no-fail -fmt sarif -out results.sarif ./...'

View File

@ -11,9 +11,6 @@ concurrency:
cancel-in-progress: true
jobs:
unit-tests:
strategy:
matrix:
go-version: [ "1.23" ]
runs-on: ubuntu-latest
steps:
- name: Checkout code
@ -21,15 +18,15 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go environment
uses: actions/setup-go@v5.0.2
uses: actions/setup-go@v5.5.0
with:
go-version: '${{ matrix.go-version }}'
go-version-file: go.mod
- name: Run tests
run: |
go run github.com/onsi/ginkgo/v2/ginkgo run -p --github-output --covermode=atomic --coverprofile=coverage.out --race -r ./...
- name: Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
file: ./coverage.out
files: ./coverage.out

View File

@ -12,14 +12,13 @@ jobs:
webui:
strategy:
matrix:
go-version: [ "1.23-bookworm" ]
go-version: [ "1.24-bookworm" ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install earthly
uses: Luet-lab/luet-install-action@v1
uses: earthly/actions-setup@v1
with:
repository: quay.io/kairos/packages
packages: utils/earthly
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: WebUI tests
run: earthly +webui-tests --GO_VERSION=${{ matrix.go-version }}

View File

@ -1,8 +1,8 @@
# Make sure to check the documentation at http://goreleaser.com
project_name: kairos-agent
version: 2
builds:
- ldflags:
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}"
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
env:
- CGO_ENABLED=0
goos:
@ -11,17 +11,53 @@ builds:
- amd64
- arm64
binary: '{{ .ProjectName }}'
id: default
- ldflags:
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
env:
- CGO_ENABLED=1
- GOEXPERIMENT=boringcrypto
goos:
- linux
goarch:
- amd64
binary: '{{ .ProjectName }}'
id: fips-amd64
hooks:
post:
- bash -c 'set -e; go version {{.Path}} | grep boringcrypto || (echo "boringcrypto not found" && exit 1)'
- ldflags:
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
env:
- CGO_ENABLED=1
- GOEXPERIMENT=boringcrypto
- CC=aarch64-linux-gnu-gcc
goos:
- linux
goarch:
- arm64
binary: '{{ .ProjectName }}'
id: fips-arm64
hooks:
post:
- bash -c 'set -e; go version {{.Path}} | grep boringcrypto || (echo "boringcrypto not found" && exit 1)'
source:
enabled: true
name_template: '{{ .ProjectName }}-{{ .Tag }}-source'
archives:
# Default template uses underscores instead of -
- name_template: >-
{{ .ProjectName }}-{{ .Tag }}-{{- title .Os }}-{{- if eq .Arch "amd64" }}x86_64{{- else if eq .Arch "386" }}i386{{- else }}{{ .Arch }}{{ end }}{{- if .Arm }}v{{ .Arm }}{{ end }}
- id: default-archive
ids:
- default
name_template: '{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: fips-archive
ids:
- fips-arm64
- fips-amd64
name_template: '{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-fips'
checksum:
name_template: '{{ .ProjectName }}-{{ .Tag }}-checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
version_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:

196
go.mod
View File

@ -1,46 +1,47 @@
module github.com/kairos-io/kairos-agent/v2
go 1.23.1
go 1.24.2
require (
github.com/Masterminds/semver/v3 v3.3.0
github.com/Masterminds/semver/v3 v3.3.1
github.com/Masterminds/sprig/v3 v3.3.0
github.com/cavaliergopher/grab/v3 v3.0.1
github.com/diskfs/go-diskfs v1.4.1
github.com/diskfs/go-diskfs v1.6.0
github.com/erikgeiser/promptkit v0.9.0
github.com/google/go-containerregistry v0.20.2
github.com/google/go-containerregistry v0.20.5
github.com/hashicorp/go-multierror v1.1.1
github.com/jaypipes/ghw v0.13.0 // indirect
github.com/jaypipes/ghw v0.16.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/kairos-io/kairos-sdk v0.6.0
github.com/kairos-io/kcrypt v0.12.2
github.com/labstack/echo/v4 v4.12.0
github.com/kairos-io/go-nodepair v0.3.0
github.com/kairos-io/kairos-sdk v0.9.3
github.com/labstack/echo/v4 v4.13.4
github.com/mitchellh/mapstructure v1.5.0
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82
github.com/mudler/yip v1.11.0
github.com/mudler/yip v1.16.1
github.com/nxadm/tail v1.4.11
github.com/onsi/ginkgo/v2 v2.20.2
github.com/onsi/gomega v1.34.2
github.com/pterm/pterm v0.12.79
github.com/rs/zerolog v1.33.0
github.com/sanity-io/litter v1.5.5
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/pterm/pterm v0.12.80
github.com/rs/zerolog v1.34.0
github.com/sanity-io/litter v1.5.8
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448
github.com/spf13/viper v1.19.0
github.com/urfave/cli/v2 v2.27.4
golang.org/x/net v0.29.0
golang.org/x/oauth2 v0.23.0
golang.org/x/sys v0.25.0
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.33.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/mount-utils v0.31.1
k8s.io/mount-utils v0.33.1
)
require (
github.com/distribution/reference v0.6.0
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f
github.com/gofrs/uuid v4.4.0+incompatible
github.com/google/go-github/v63 v63.0.0
github.com/twpayne/go-vfs/v4 v4.3.0
github.com/google/go-github/v69 v69.2.0
github.com/google/go-github/v72 v72.0.0
github.com/twpayne/go-vfs/v5 v5.0.4
github.com/urfave/cli/v2 v2.27.6
)
require (
@ -50,12 +51,11 @@ require (
dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.7 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/Microsoft/hcsshim v0.12.9 // indirect
github.com/ProtonMail/go-crypto v1.1.5 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/alecthomas/assert/v2 v2.6.0 // indirect
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba // indirect
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98 // indirect
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/avast/retry-go v3.0.0+incompatible // indirect
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
@ -65,66 +65,65 @@ require (
github.com/charmbracelet/bubbletea v0.24.2 // indirect
github.com/charmbracelet/lipgloss v0.7.1 // indirect
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/cgroups v1.1.0 // indirect
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect
github.com/containerd/containerd v1.7.22 // indirect
github.com/containerd/continuity v0.4.2 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/containerd/console v1.0.4 // indirect
github.com/containerd/containerd v1.7.27 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/djherbis/times v1.6.0 // indirect
github.com/docker/cli v27.1.1+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v27.3.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/cli v28.1.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v28.1.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/edsrzf/mmap-go v1.1.0 // indirect
github.com/eliukblau/pixterm v1.3.1 // indirect
github.com/edsrzf/mmap-go v1.2.0 // indirect
github.com/eliukblau/pixterm v1.3.2 // indirect
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.12.0 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-git/go-git/v5 v5.14.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/itchyny/gojq v0.12.16 // indirect
github.com/itchyny/gojq v0.12.17 // indirect
github.com/itchyny/timefmt-go v0.1.6 // indirect
github.com/jaypipes/pcidb v1.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
github.com/jezek/xgb v1.1.0 // indirect
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 // indirect
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c // indirect
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 // indirect
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
@ -132,45 +131,41 @@ require (
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/makiuchi-d/gozxing v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mauromorales/xpasswd v0.4.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mauromorales/xpasswd v0.4.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/mountinfo v0.7.1 // indirect
github.com/moby/sys/sequential v0.5.0 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/mudler/entities v0.8.1 // indirect
github.com/mudler/entities v0.8.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.14 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/otiai10/copy v1.14.0 // indirect
github.com/packethost/packngo v0.29.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/qeesung/image2ascii v1.0.1 // indirect
github.com/rancher-sandbox/linuxkit v1.0.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/saferwall/pe v1.5.4 // indirect
github.com/saferwall/pe v1.5.6 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/samber/lo v1.37.0 // indirect
github.com/samber/lo v1.49.1 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
@ -178,58 +173,59 @@ require (
github.com/shirou/gopsutil/v4 v4.24.7 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spectrocloud-labs/herd v0.4.2 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/swaggest/jsonschema-go v0.3.62 // indirect
github.com/swaggest/refl v1.3.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tredoe/osutil v1.5.0 // indirect
github.com/twpayne/go-vfs/v4 v4.3.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vbatts/tar-split v0.11.3 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 // indirect
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
github.com/zcalusic/sysinfo v1.1.2 // indirect
github.com/zcalusic/sysinfo v1.1.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
google.golang.org/grpc v1.62.1 // indirect
google.golang.org/protobuf v1.34.1 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/image v0.20.0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/tools v0.33.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
howett.net/plist v1.0.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
pault.ag/go/modprobe v0.1.2 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
pault.ag/go/modprobe v0.2.0 // indirect
pault.ag/go/topsort v0.1.1 // indirect
)

527
go.sum
View File

@ -9,10 +9,9 @@ atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8p
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
@ -24,17 +23,17 @@ github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
@ -43,8 +42,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba h1:LJ/tQNki21ep58+YZElkXQVpswENcK66NMNv4JGZf7w=
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba/go.mod h1:yZpXZj/k3rAZDY43DteaEzbnnxiz9OYijJqRcqWMKSw=
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98 h1:SML/05friOcB5ohyBaVC1TZPaMsnLFU7OTcTBhTdygk=
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98/go.mod h1:71hQWy01rC95qOpZ315jMB69d4pI/PU6HnZhpnemx90=
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f h1:4tLJrnm3h3biCFsXHQ9w6DVGwuZXW4KMfiKV/atSYXg=
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f/go.mod h1:kEOnWwULAKOORfFvE4dEkdRZJS7+NMJKxRb/vWvmARk=
github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc h1:xMQuzBhj6hXQZufedPQM2OiGX2UcQHSptXtG3+28S8Q=
github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc/go.mod h1:NC+g66bgkUjV1unIJXhHO35RHxVViWUzNeeKAkkO7DU=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@ -66,13 +65,12 @@ github.com/bool64/dev v0.2.31 h1:OS57EqYaYe2M/2bw9uhDCIFiZZwywKFS/4qMLN6JUmQ=
github.com/bool64/dev v0.2.31/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cavaliergopher/grab v2.0.0+incompatible h1:XLeGNAc7MIRTMb8RlRbN76uO8vx1/AeNMWWN7FYpDw8=
github.com/cavaliergopher/grab v2.0.0+incompatible/go.mod h1:6ICNRTQPwkMP0m2sKIDv/9XkhFJJwiEOQyZ+8E4H7Yg=
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
@ -83,32 +81,37 @@ github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNW
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 h1:Ig+OPkE3XQrrl+SKsOqAjlkrBN/zrr+Qpw7rCuDjRCE=
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0=
github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g=
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -120,31 +123,37 @@ github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGwe
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/diskfs/go-diskfs v1.4.1 h1:iODgkzHLmvXS+1VDztpW53T+dQm8GQzi20y9yUd5UCA=
github.com/diskfs/go-diskfs v1.4.1/go.mod h1:+tOkQs8CMMog6Nvljg8DGIxEXrgL48iyT3OM3IlSz74=
github.com/diskfs/go-diskfs v1.6.0 h1:YmK5+vLSfkwC6kKKRTRPGaDGNF+Xh8FXeiNHwryDfu4=
github.com/diskfs/go-diskfs v1.6.0/go.mod h1:bRFumZeGFCO8C2KNswrQeuj2m1WCVr4Ms5IjWMczMDk=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/eliukblau/pixterm v1.3.1 h1:XeouQViH+lmzCa7sMUoK2cd7qlgHYGLIjwRKaOdJbKA=
github.com/eliukblau/pixterm v1.3.1/go.mod h1:on5ueknFt+ZFVvIVVzQ7/JXwPjv5fJd8Q1Ybh7XixfU=
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/eliukblau/pixterm v1.3.2 h1:kAF9qvbaDV3emb9LPHw1Bvd9D5o4y28U0e8Q9vfl24I=
github.com/eliukblau/pixterm v1.3.2/go.mod h1:CgaInx2l92Xo3GTldly4UQeNghSFXmIQNk3zL77Xo/A=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@ -157,8 +166,8 @@ github.com/erikgeiser/promptkit v0.9.0 h1:3qL1mS/ntCrXdb8sTP/ka82CJ9kEQaGuYXNrYJ
github.com/erikgeiser/promptkit v0.9.0/go.mod h1:pU9dtogSe3Jlc2AY77EP7R4WFP/vgD4v+iImC83KsCo=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca h1:ErxkaWK5AIt8gQf3KpAuQQBdZI4ps72HzEe123kh+So=
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca/go.mod h1:ffg/fkDeOYicEQLoO2yFFGt00KUTYVXI+rfnc8il6vQ=
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f h1:SGo7y1xmmGWiQzp7QU3ueehmdMVkjj9Yyo1IDEuHbYw=
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f/go.mod h1:q85c4IRlhhwdRJgGIUWrisDjU8dgcMj8dnXZCXo3hus=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -166,18 +175,18 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -191,18 +200,16 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -224,16 +231,22 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE=
github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-containerregistry v0.20.4 h1:w/Fdj3ef046SdV/GJU69cCnreaLpqbTo1X9XPyHbkd4=
github.com/google/go-containerregistry v0.20.4/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
github.com/google/go-containerregistry v0.20.5 h1:4RnlYcDs5hoA++CeFjlbZ/U9Yp1EuWr+UhhTyYQjOP0=
github.com/google/go-containerregistry v0.20.5/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -259,46 +272,53 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4=
github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
github.com/jaypipes/ghw v0.16.0 h1:3HurCTS38VNpeQLo5fIdZsySuo/qAfpPSJ5t05QBFPM=
github.com/jaypipes/ghw v0.16.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
github.com/kairos-io/kairos-sdk v0.4.6 h1:6dbKozJTku99P2vytz9M0xAnpkKKiAggSMPfT2vpw68=
github.com/kairos-io/kairos-sdk v0.4.6/go.mod h1:QXYmZ2BMrJ0Iyp7I3+rvCYpZRMvwOtK/6IGCLhNL4tY=
github.com/kairos-io/kairos-sdk v0.6.0 h1:A096lZVHE4rkvA5kG0Oss0085T0noUcf7AeppWGySR8=
github.com/kairos-io/kairos-sdk v0.6.0/go.mod h1:7Y6Y/McniCyAJcmQfoTfKd09cwmwS40URaIVbJn8V2k=
github.com/kairos-io/kcrypt v0.12.2 h1:+lr8FGS0AW6D5dWSmaR3+AobL1TBTnOFgCSYctKY+5I=
github.com/kairos-io/kcrypt v0.12.2/go.mod h1:7SPiHzNMYl4MlxeB30s1YlHDYByTusu7u1mU5Nvicm0=
github.com/kairos-io/go-nodepair v0.3.0 h1:JIMBAtbNhIAsx89aP61mQDGMuGFoIQH/woK2tMDYD6k=
github.com/kairos-io/go-nodepair v0.3.0/go.mod h1:7i905W/KmR9DAcMSVJr/Wdb84E5Yyu9YLgj7chwX1xs=
github.com/kairos-io/kairos-sdk v0.9.0 h1:Bcpf3nUwGvzreIdXBIZZRnS2LDPs496C0Reo+dpbkMs=
github.com/kairos-io/kairos-sdk v0.9.0/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
github.com/kairos-io/kairos-sdk v0.9.1 h1:5MagNf3ghNsQaH6sVXXLVSjClrDQ9UZrxjHYRghk26Q=
github.com/kairos-io/kairos-sdk v0.9.1/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
github.com/kairos-io/kairos-sdk v0.9.2 h1:A/9rbRpjZsBWniXSPzvT7I2dbbukgveUjrvk9iXH4AE=
github.com/kairos-io/kairos-sdk v0.9.2/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
github.com/kairos-io/kairos-sdk v0.9.3 h1:je3Q0mfm1p4y3jO3k0P/SUp4NEax8IwLveDlnZBB8Yc=
github.com/kairos-io/kairos-sdk v0.9.3/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c h1:eKb4PqwAMhlqwXw0W3atpKaYaPGlXE/Fwh+xpCEYaPk=
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c/go.mod h1:VOfm8h1NySetVlpHDSnbpCMsvCgYaU+YDn4XezUy2+4=
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 h1:Ds6xHRvL0yjG4kZD05leRKt70mM18Fjt0+B5gIqqe1g=
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767/go.mod h1:VOfm8h1NySetVlpHDSnbpCMsvCgYaU+YDn4XezUy2+4=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -306,13 +326,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
@ -325,6 +346,8 @@ github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -333,10 +356,10 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mauromorales/xpasswd v0.4.0 h1:Jf6mfA8lwQsYzwgfQADPDGV7l/liAvRrnG+nQTPy0j8=
github.com/mauromorales/xpasswd v0.4.0/go.mod h1:Z3+aY19mhNfcGi3st0+RAVSz2vC+pyoju2S/FPN8kEg=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mauromorales/xpasswd v0.4.1 h1:b2UalYW/p4CN6D01+ZMXvMLoPAFnTwUuWxPvQBckGSw=
github.com/mauromorales/xpasswd v0.4.1/go.mod h1:Imb54ard3hSA6jTEBLdues/Q4e1IfDzu9fPw6Eip28w=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -347,28 +370,26 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mudler/entities v0.8.1 h1:/iZ3VrhZy8bSVr39IqoSwL4jphna2rgSYnJCUZakZ3s=
github.com/mudler/entities v0.8.1/go.mod h1:exnXZF6qVnu4b9dEiH3sLEyxYBTknfkcJ3UCxyc/dwE=
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb h1:F6TP0DW7C0U9sgm9g4uAs0Vp2JSkhn2umlyrNlxUKXw=
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb/go.mod h1:6QsBHK5vY/wDnSpueKek3/qfoCZmdJ9pz7FnzQbP/gE=
github.com/mudler/entities v0.8.2 h1:a7MHgXFC05IqTPJA4dVbm2TpMsmb3zmnvYsIZmaQTcQ=
github.com/mudler/entities v0.8.2/go.mod h1:exnXZF6qVnu4b9dEiH3sLEyxYBTknfkcJ3UCxyc/dwE=
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5 h1:FaZD86+A9mVt7lh9glAryzQblMsbJYU2VnrdZ8yHlTs=
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI=
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 h1:FVT07EI8njvsD4tC2Hw8Xhactp5AWhsQWD4oTeQuSAU=
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82/go.mod h1:Urp7LG5jylKoDq0663qeBh0pINGcRl35nXdKx82PSoU=
github.com/mudler/yip v1.10.0 h1:MwEIySEfSRRwTUz2BmQQpRn6+M7jqVGf/OldsepBvz0=
github.com/mudler/yip v1.10.0/go.mod h1:gwH7iGcr1Jimox2xKtN2AprEO00GzY7smvuycqCL7+Y=
github.com/mudler/yip v1.11.0 h1:h+npjzSKM9VbShHxa+ywWZzpGIolKvN/e2FOT+rxKkI=
github.com/mudler/yip v1.11.0/go.mod h1:gwH7iGcr1Jimox2xKtN2AprEO00GzY7smvuycqCL7+Y=
github.com/mudler/yip v1.16.0 h1:TZr9zLghe5CJXRdvBK6f5uHe6RJtotweDU+m/GNT+gY=
github.com/mudler/yip v1.16.0/go.mod h1:Wk3CIZCqdK58+1CzamA87atJD2y/dhDKXrguUyYipCc=
github.com/mudler/yip v1.16.1 h1:SUq136jJ6QnX0FgP87IoIvkLT8OdEp3DYQUlKzI/gOQ=
github.com/mudler/yip v1.16.1/go.mod h1:Wk3CIZCqdK58+1CzamA87atJD2y/dhDKXrguUyYipCc=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@ -387,35 +408,30 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/packethost/packngo v0.29.0 h1:gRIhciVZQ/zLNrIdIdbOUyB/Tw5IgoaXyhP4bvE+D2s=
github.com/packethost/packngo v0.29.0/go.mod h1:/UHguFdPs6Lf6FOkkSEPnRY5tgS0fsVM+Zv/bvBrmt0=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
@ -426,6 +442,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
@ -434,33 +452,32 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4=
github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=
github.com/qeesung/image2ascii v1.0.1 h1:Fe5zTnX/v/qNC3OC4P/cfASOXS501Xyw2UUcgrLgtp4=
github.com/qeesung/image2ascii v1.0.1/go.mod h1:kZKhyX0h2g/YXa/zdJR3JnLnJ8avHjZ3LrvEKSYyAyU=
github.com/rancher-sandbox/linuxkit v1.0.2 h1:mUFPL2Mgl1XZ5H82ABR57t5H2G2Qd+lu3gMYvUGmeZo=
github.com/rancher-sandbox/linuxkit v1.0.2/go.mod h1:n6Fkjc5qoMeWrnLSA5oqUF8ZzFKMrM960CtBwfvH1ZM=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saferwall/pe v1.5.4 h1:tLmMggEMUfeqrpJ25zS/okUQmyFdD5xWKL2+z9njCqg=
github.com/saferwall/pe v1.5.4/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc=
github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
@ -479,11 +496,10 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448 h1:4T/wluVIsyQ0Kqamo3he0Q0FhZG7CBd5LJgb4KOmftM=
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@ -492,10 +508,10 @@ github.com/spectrocloud-labs/herd v0.4.2 h1:90cYZmW0yxNw4PEbqNGSerrtqKSx1nvRbSwA
github.com/spectrocloud-labs/herd v0.4.2/go.mod h1:WBlMIs1QZ7XtVrt9rAAFZpkh/fZYA4l2gGOCUS1LDHE=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -506,15 +522,15 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
@ -533,28 +549,28 @@ github.com/tredoe/osutil v1.5.0 h1:UGVxbbHRoZi8xXVmbNZ2vgG6XoJ15ndE4LniiQ3rJKg=
github.com/tredoe/osutil v1.5.0/go.mod h1:TEzphzUUunysbdDRfdOgqkg10POQbnfIPV50ynqOfIg=
github.com/twpayne/go-vfs/v4 v4.3.0 h1:rTqFzzOQ/6ESKTSiwVubHlCBedJDOhQyVSnw8rQNZhU=
github.com/twpayne/go-vfs/v4 v4.3.0/go.mod h1:tq2UVhnUepesc0lSnPJH/jQ8HruGhzwZe2r5kDFpEIw=
github.com/twpayne/go-vfs/v5 v5.0.4 h1:/ne3h+rW7f5YOyOFguz+3ztfUwzOLR0Vts3y0mMAitg=
github.com/twpayne/go-vfs/v5 v5.0.4/go.mod h1:zTPFJUbgsEMFNSWnWQlLq9wh4AN83edZzx3VXbxrS1w=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 h1:v6jG/tdl4O07LNVp74Nt7/OyL+1JsIW1M2f/nSvQheY=
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3/go.mod h1:CSBTxrhePCm0cmXNKDGeu+6bOQzpaEklfCqEpn89JWk=
github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g=
github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
@ -569,50 +585,57 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zcalusic/sysinfo v1.1.2 h1:38KUgZQmCxlN9vUTt4miis4rU5ISJXGXOJ2rY7bMC8g=
github.com/zcalusic/sysinfo v1.1.2/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=
github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -620,8 +643,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -637,14 +660,16 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -652,8 +677,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -663,16 +690,13 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -685,40 +709,40 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -729,31 +753,33 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47 h1:5iw9XJTD4thFidQmFVvx0wi4g5yOHk76rNRUxz1ZG5g=
google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -763,8 +789,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@ -786,20 +812,21 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/mount-utils v0.31.1 h1:f8UrH9kRynljmdNGM6BaCvFUON5ZPKDgE+ltmYqI4wA=
k8s.io/mount-utils v0.31.1/go.mod h1:HV/VYBUGqYUj4vt82YltzpWvgv8FPg0G9ItyInT3NPU=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
pault.ag/go/modprobe v0.1.2 h1:bblunaPhqpTxGDJ5TVFW/4gheohBPleF2dIV6j6sWkI=
pault.ag/go/modprobe v0.1.2/go.mod h1:afr2STC/2Maz/qi4+Bma1s0dszZgO/PcM8AKar9DWhM=
pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a/go.mod h1:INqx0ClF7kmPAMk2zVTX8DRnhZ/yaA/Mg52g8KFKE7k=
k8s.io/mount-utils v0.33.0 h1:hH6EcCcax4lFNIERaGMj6d7oGMW1qW3eTCwHUuLtLog=
k8s.io/mount-utils v0.33.0/go.mod h1:1JR4rKymg8B8bCPo618hpSAdrpO6XLh0Acqok/xVwPE=
k8s.io/mount-utils v0.33.1 h1:hodPhfyoK+gG0SgnYwx1iPrlnpaESZiJ9GFzF5V/imE=
k8s.io/mount-utils v0.33.1/go.mod h1:1JR4rKymg8B8bCPo618hpSAdrpO6XLh0Acqok/xVwPE=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
pault.ag/go/modprobe v0.2.0 h1:VF04w/Lez7qFHZX9QUZo2jVyTZINvYZG9dfvgZVXwXU=
pault.ag/go/modprobe v0.2.0/go.mod h1:GDTMW6GviL6BFKkaYuzIPUCaPizqO1XD7Uf9pnNO6sg=
pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=

View File

@ -3,12 +3,15 @@ package hook
import (
"os"
"os/exec"
"path/filepath"
"syscall"
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-sdk/bundles"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
)
// BundlePostInstall install bundles just after installation
@ -28,33 +31,45 @@ func (b BundlePostInstall) Run(c config.Config, _ v1.Spec) error {
// Note that the binding of /usr/local/.state/var-lib-extensions.bind to /var/lib/extensions on active/passive its done by inmmucore based on the
// 00_rootfs.yaml config which sets the bind and ephemeral paths.
c.Logger.Logger.Debug().Msg("Running BundlePostInstall hook")
_ = machine.Umount(constants.PersistentDir)
machine.Mount("COS_PERSISTENT", "/usr/local") //nolint:errcheck
_ = machine.Mount(constants.OEMLabel, constants.OEMPath)
defer func() {
machine.Umount("/usr/local") //nolint:errcheck
_ = machine.Umount(constants.OEMPath)
}()
err := os.MkdirAll("/usr/local/.state/var-lib-extensions.bind", os.ModeDir|os.ModePerm)
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
syscall.Sync()
err := c.Syscall.Mount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel), constants.UsrLocalPath, "ext4", 0, "")
if err != nil {
c.Logger.Logger.Err(err).Msg("could not mount persistent")
return err
}
defer func() {
c.Logger.Debugf("Unmounting persistent partition")
err := machine.Umount(constants.UsrLocalPath)
if err != nil {
c.Logger.Errorf("could not unmount persistent partition: %s", err)
}
}()
err = os.MkdirAll("/usr/local/.state/var-lib-extensions.bind", os.ModeDir|os.ModePerm)
if c.FailOnBundleErrors && err != nil {
return err
}
cmd := exec.Command("rsync", "-aqAX", "/var/lib/extensions", "/usr/local/.state/var-lib-extensions.bind")
cmd := exec.Command("rsync", "-aqAX", "/var/lib/extensions/", "/usr/local/.state/var-lib-extensions.bind")
_, err = cmd.CombinedOutput()
if c.FailOnBundleErrors && err != nil {
return err
}
err = syscall.Mount("/usr/local/.state/var-lib-extensions.bind", "/var/lib/extensions", "", syscall.MS_BIND, "")
err = c.Syscall.Mount("/usr/local/.state/var-lib-extensions.bind", "/var/lib/extensions", "", syscall.MS_BIND, "")
if c.FailOnBundleErrors && err != nil {
return err
}
defer func() {
_ = syscall.Unmount("/var/lib/extensions", 0)
}()
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
defer func() {
machine.Umount("/oem") //nolint:errcheck
_ = machine.Umount("/var/lib/extensions")
}()
opts := c.Install.Bundles.Options()

View File

@ -2,25 +2,127 @@ package hook
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-sdk/kcrypt"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
"os"
"regexp"
"strconv"
"strings"
"syscall"
"time"
)
type KcryptUKI struct{}
// Finish is a hook that runs after the install process.
// It is used to encrypt partitions and run the BundlePostInstall, CustomMounts and CopyLogs hooks
type Finish struct{}
func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
func (k Finish) Run(c config.Config, spec v1.Spec) error {
var err error
if len(c.Install.Encrypt) != 0 || internalutils.IsUki() {
c.Logger.Logger.Info().Msg("Running encrypt hook")
if internalutils.IsUki() {
err = EncryptUKI(c, spec)
} else {
err = Encrypt(c, spec)
}
// They return with partitions unlocked so make sure to lock them before we end
defer lockPartitions(c)
if err != nil {
c.Logger.Logger.Error().Err(err).Msg("could not encrypt partitions")
return err
}
c.Logger.Logger.Info().Msg("Finished encrypt hook")
}
// Now that we have everything encrypted and ready to mount if needed
err = GrubPostInstallOptions{}.Run(c, spec)
if err != nil {
return err
}
err = BundlePostInstall{}.Run(c, spec)
if err != nil {
c.Logger.Logger.Warn().Err(err).Msg("could not copy run bundles post install")
if c.FailOnBundleErrors {
return err
}
}
err = CustomMounts{}.Run(c, spec)
if err != nil {
c.Logger.Logger.Warn().Err(err).Msg("could not create custom mounts")
}
err = CopyLogs{}.Run(c, spec)
if err != nil {
c.Logger.Logger.Warn().Err(err).Msg("could not copy logs")
}
return nil
}
// Encrypt is a hook that encrypts partitions using kcrypt for non uki.
// It will unmount OEM and PERSISTENT and return with them unmounted
func Encrypt(c config.Config, _ v1.Spec) error {
// Start with unmounted partitions
_ = machine.Umount(constants.OEMDir) //nolint:errcheck
_ = machine.Umount(constants.PersistentDir) //nolint:errcheck
// Config passed during install ends up here, so we need to read it, try to mount it
_ = machine.Mount("COS_OEM", "/oem")
defer func() {
err := syscall.Unmount(constants.OEMPath, 0)
if err != nil {
c.Logger.Warnf("could not unmount Oem partition: %s", err)
}
}()
for _, p := range c.Install.Encrypt {
_, err := kcrypt.Encrypt(p, c.Logger)
if err != nil {
c.Logger.Errorf("could not encrypt partition: %s", err)
return err
}
}
_ = kcrypt.UnlockAll(false, c.Logger)
for _, p := range c.Install.Encrypt {
for i := 0; i < 10; i++ {
c.Logger.Infof("Waiting for unlocked partition %s to appear", p)
_, _ = utils.SH("sync")
part, _ := utils.SH(fmt.Sprintf("blkid -L %s", p))
if part == "" {
c.Logger.Infof("Partition %s not found, waiting %d seconds before retrying", p, i)
time.Sleep(time.Duration(i) * time.Second)
// Retry the unlock as well, because maybe the partition was not refreshed on time for unlock to unlock it
// So no matter how many tries we do, it will still be locked and will never appear
err := kcrypt.UnlockAll(false, c.Logger)
if err != nil {
c.Logger.Debugf("UnlockAll returned: %s", err)
}
if i == 9 {
c.Logger.Errorf("Partition %s not unlocked/found after 10 retries", p)
return fmt.Errorf("partition %s not unlocked/found after 10 retries", p)
}
continue
}
c.Logger.Infof("Partition found, continuing")
break
}
}
return nil
}
// EncryptUKI encrypts the partitions using kcrypt in uki mode
// It will unmount OEM and PERSISTENT and return with them unmounted
func EncryptUKI(c config.Config, spec v1.Spec) error {
// pre-check for systemd version, we need something higher or equal to 252
run, err := utils.SH("systemctl --version | head -1 | awk '{ print $2}'")
systemdVersion := strings.TrimSpace(string(run))
@ -48,7 +150,7 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
// If systemd version is less than 252 return
if systemdVersionInt < 252 {
c.Logger.Infof("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
return nil
return fmt.Errorf("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
}
// Check for a TPM 2.0 device as its needed to encrypt
@ -57,11 +159,9 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
_, err = os.Stat("/dev/tpmrm0")
if err != nil {
c.Logger.Warnf("Skipping partition encryption, could not find TPM 2.0 device at /dev/tpmrm0")
return nil
return fmt.Errorf("Skipping partition encryption, could not find TPM 2.0 device at /dev/tpmrm0")
}
c.Logger.Logger.Debug().Msg("Running KcryptUKI hook")
// We always encrypt OEM and PERSISTENT under UKI
// If mounted, unmount it
_ = machine.Umount(constants.OEMDir) //nolint:errcheck
@ -92,16 +192,11 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
c.Logger.Infof("Encrypting %s", p)
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
err := kcrypt.LuksifyMeasurements(p, []string{"11"}, []string{}, c.Logger.Logger)
err = kcrypt.EncryptWithPcrs(p, c.BindPublicPCRs, c.BindPCRs, c.Logger)
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
if err != nil {
c.Logger.Errorf("could not encrypt partition: %s", err)
if c.FailOnBundleErrors {
return err
}
// Give time to show the error
time.Sleep(10 * time.Second)
return nil // do not error out
return err
}
c.Logger.Infof("Done encrypting %s", p)
}
@ -109,31 +204,20 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
_, _ = utils.SH("sync")
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
err = kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
err = kcrypt.UnlockAll(true, c.Logger)
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
if err != nil {
lockPartitions(c)
c.Logger.Errorf("could not unlock partitions: %s", err)
return err
}
// Close the unlocked partitions after dealing with them, otherwise we leave them open and they can be mounted by anyone
defer func() {
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
// There is a known error with cryptsetup that it can't close the device because of a semaphore
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
// output of the error
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
c.Logger.Errorf("could not close /dev/disk/by-label/%s: %s", p, out)
}
}
}()
// Here it can take the oem partition a bit of time to appear after unlocking so we need to retry a couple of time with some waiting
// retry + backoff
// Check all encrypted partitions are unlocked
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}) {
for _, p := range []string{constants.OEMLabel, constants.PersistentLabel} {
for i := 0; i < 10; i++ {
c.Logger.Infof("Waiting for unlocked partition %s to appear", p)
_, _ = utils.SH("sync")
@ -143,7 +227,7 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
time.Sleep(time.Duration(i) * time.Second)
// Retry the unlock as well, because maybe the partition was not refreshed on time for unlock to unlock it
// So no matter how many tries we do, it will still be locked and will never appear
err := kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
err := kcrypt.UnlockAll(true, c.Logger)
if err != nil {
c.Logger.Debugf("UnlockAll returned: %s", err)
}
@ -158,29 +242,21 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
}
}
// Mount the unlocked oem partition
err = machine.Mount(constants.OEMLabel, constants.OEMDir)
if err != nil {
return err
}
// Copy back the contents of the oem partition that we saved before encrypting
err = internalutils.SyncData(c.Logger, c.Runner, c.Fs, tmpDir, constants.OEMDir, []string{}...)
if err != nil {
return err
}
// Unmount the oem partition and leave everything unmounted
err = machine.Umount(constants.OEMDir)
if err != nil {
return err
}
c.Logger.Logger.Debug().Msg("Finish KcryptUKI hook")
// We now have the partitions unlocked and ready, lets call the other hooks here instead of closing and reopening them each time
err = BundlePostInstall{}.Run(c, spec)
if err != nil {
return err
}
_ = CustomMounts{}.Run(c, spec)
err = CopyLogs{}.Run(c, spec)
if err != nil {
return err
}
return nil
}

View File

@ -1,11 +1,13 @@
package hook
import (
"github.com/kairos-io/kairos-agent/v2/pkg/config"
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"strings"
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-sdk/system"
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/state"
"path/filepath"
)
type GrubOptions struct{}
@ -16,9 +18,9 @@ func (b GrubOptions) Run(c config.Config, _ v1.Spec) error {
}
c.Logger.Logger.Debug().Msg("Running GrubOptions hook")
c.Logger.Debugf("Setting grub options: %s", c.Install.GrubOptions)
err := system.Apply(system.SetGRUBOptions(c.Install.GrubOptions))
if err != nil && !strings.Contains(err.Error(), "0 errors occurred") {
c.Logger.Logger.Error().Err(err).Msg("Failed to set grub options")
err := grubOptions(c, c.Install.GrubOptions)
if err != nil {
return err
}
c.Logger.Logger.Debug().Msg("Finish GrubOptions hook")
return nil
@ -31,10 +33,32 @@ func (b GrubPostInstallOptions) Run(c config.Config, _ v1.Spec) error {
return nil
}
c.Logger.Logger.Debug().Msg("Running GrubOptions hook")
err := system.Apply(system.SetGRUBOptions(c.GrubOptions))
c.Logger.Debugf("Setting grub options: %s", c.GrubOptions)
err := grubOptions(c, c.GrubOptions)
if err != nil {
return err
}
c.Logger.Logger.Debug().Msg("Finish GrubOptions hook")
return nil
}
// grubOptions sets the grub options in the grubenv file
// It mounts the OEM partition if not already mounted
// If its mounted but RO, it remounts it as RW
func grubOptions(c config.Config, opts map[string]string) error {
runtime, err := state.NewRuntime()
if err != nil {
return err
}
if !runtime.OEM.Mounted {
err = machine.Mount(cnst.OEMLabel, cnst.OEMPath)
defer func() {
_ = machine.Umount(cnst.OEMPath)
}()
}
err = utils.SetPersistentVariables(filepath.Join(runtime.OEM.MountPoint, "grubenv"), opts, &c)
if err != nil {
c.Logger.Logger.Error().Err(err).Msg("Failed to set grub options")
}
c.Logger.Logger.Debug().Msg("Running GrubOptions hook")
return nil
return err
}

View File

@ -1,48 +1,53 @@
package hook
import (
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
"fmt"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-sdk/utils"
"strings"
)
type Interface interface {
Run(c config.Config, spec v1.Spec) error
}
var AfterInstall = []Interface{
&GrubOptions{}, // Set custom GRUB options
&BundlePostInstall{},
&CustomMounts{},
&CopyLogs{},
// FinishInstall is a list of hooks that run when the install process is finished completely.
// Its mean for options that are not related to the install process itself
var FinishInstall = []Interface{
&Lifecycle{}, // Handles poweroff/reboot by config options
}
var AfterReset = []Interface{
&CopyLogs{},
&Lifecycle{},
// FinishReset is a list of hooks that run when the reset process is finished completely.
var FinishReset = []Interface{
&CopyLogs{}, // Try to copy the reset logs to the persistent partition
&Lifecycle{}, // Handles poweroff/reboot by config options
}
var AfterUpgrade = []Interface{
&Lifecycle{},
// FinishUpgrade is a list of hooks that run when the upgrade process is finished completely.
var FinishUpgrade = []Interface{
&Lifecycle{}, // Handles poweroff/reboot by config options
}
// FirstBoot is a list of hooks that run on the first boot of the node.
var FirstBoot = []Interface{
&BundleFirstBoot{},
&GrubPostInstallOptions{},
}
// AfterUkiInstall sets which Hooks to run after uki runs the install action
var AfterUkiInstall = []Interface{
&SysExtPostInstall{},
&Lifecycle{},
// FinishUKIInstall is a list of hooks that run when the install process is finished completely.
// Its mean for options that are not related to the install process itself
var FinishUKIInstall = []Interface{
&SysExtPostInstall{}, // Installs sysexts into the EFI partition
&Lifecycle{}, // Handles poweroff/reboot by config options
}
var UKIEncryptionHooks = []Interface{
&KcryptUKI{},
}
var EncryptionHooks = []Interface{
&Kcrypt{},
// PostInstall is a list of hooks that run after the install process has run.
// Runs things that need to be done before we run other post install stages like
// encrypting partitions, copying the install logs or installing bundles
// Most of this options are optional so they are not run by default unless specified in the config
var PostInstall = []Interface{
&Finish{},
}
func Run(c config.Config, spec v1.Spec, hooks ...Interface) error {
@ -53,3 +58,18 @@ func Run(c config.Config, spec v1.Spec, hooks ...Interface) error {
}
return nil
}
// lockPartitions will try to close all the partitions that are unencrypted.
func lockPartitions(c config.Config) {
for _, p := range c.Install.Encrypt {
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
// There is a known error with cryptsetup that it can't close the device because of a semaphore
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
// output of the error
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
c.Logger.Debugf("could not close /dev/disk/by-label/%s: %s", p, out)
}
}
}

View File

@ -18,8 +18,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
func TestConfig(t *testing.T) {

View File

@ -1,45 +0,0 @@
package hook
import (
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-sdk/machine"
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
"path/filepath"
)
type Kcrypt struct{}
func (k Kcrypt) Run(c config.Config, _ v1.Spec) error {
if len(c.Install.Encrypt) == 0 {
return nil
}
c.Logger.Logger.Info().Msg("Running encrypt hook")
// We need to unmount the persistent partition to encrypt it
// we dont know the state here so we better try
err := machine.Umount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel)) //nolint:errcheck
if err != nil {
c.Logger.Errorf("could not unmount persistent partition: %s", err)
return err
}
// Config passed during install ends up here, so we need to read it
_ = machine.Mount("COS_OEM", "/oem")
defer func() {
_ = machine.Umount("/oem") //nolint:errcheck
}()
for _, p := range c.Install.Encrypt {
_, err := kcrypt.Luksify(p, c.Logger.Logger)
if err != nil {
c.Logger.Errorf("could not encrypt partition: %s", err)
if c.FailOnBundleErrors {
return err
}
}
}
c.Logger.Logger.Info().Msg("Finished encrypt hook")
return nil
}

View File

@ -1,7 +1,9 @@
package hook
import (
"fmt"
"path/filepath"
"syscall"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
@ -9,49 +11,44 @@ import (
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
"path/filepath"
"strings"
"syscall"
)
// CopyLogs copies all current logs to the persistent partition
// useful during install to keep the livecd logs
// best effort, no error handling
type CopyLogs struct{}
// Run for CopyLogs copies all current logs to the persistent partition.
// useful during install to keep the livecd logs. Its also run during reset
// best effort, no error handling
func (k CopyLogs) Run(c config.Config, _ v1.Spec) error {
// TODO: If we have encryption under RESET we need to make sure to:
// - Unlock the partitions
// - Mount OEM so we can read the config for encryption (remote server)
// - Mount the persistent partition
c.Logger.Logger.Debug().Msg("Running CopyLogs hook")
c.Logger.Debugf("Copying logs to persistent partition")
_ = machine.Umount(constants.PersistentDir)
_ = machine.Umount(constants.OEMDir)
_ = machine.Umount(constants.OEMPath)
// Path if we have encrypted persistent
if len(c.Install.Encrypt) != 0 {
err := kcrypt.UnlockAll(false)
if err != nil {
return err
}
// Close the unencrypted persistent partition at the end!
defer func() {
for _, p := range []string{constants.PersistentLabel} {
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
// There is a known error with cryptsetup that it can't close the device because of a semaphore
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
// output of the error
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
c.Logger.Errorf("could not close /dev/disk/by-label/%s: %s", p, out)
}
}
}()
}
// Config passed during install ends up here, kcrypt challenger needs to read it if we are using a server for encryption
_ = machine.Mount(constants.OEMLabel, constants.OEMPath)
defer func() {
_ = machine.Umount(constants.OEMPath)
}()
err := machine.Mount(constants.PersistentLabel, constants.PersistentDir)
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
_ = utils.MkdirAll(c.Fs, constants.PersistentDir, 0755)
err := c.Syscall.Mount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel), constants.PersistentDir, "ext4", 0, "")
if err != nil {
c.Logger.Errorf("could not mount persistent partition: %s", err)
c.Logger.Logger.Warn().Err(err).Msg("could not mount persistent")
return nil
}
defer func() {
err := machine.Umount(constants.PersistentDir)
if err != nil {
c.Logger.Errorf("could not unmount persistent partition: %s", err)
}
}()
// Create the directory on persistent
varLog := filepath.Join(constants.PersistentDir, ".state", "var-log.bind")
@ -66,11 +63,6 @@ func (k CopyLogs) Run(c config.Config, _ v1.Spec) error {
c.Logger.Errorf("could not copy logs to persistent partition: %s", err)
return nil
}
err = machine.Umount(constants.PersistentDir)
if err != nil {
c.Logger.Errorf("could not unmount persistent partition: %s", err)
return nil
}
syscall.Sync()
c.Logger.Debugf("Logs copied to persistent partition")
c.Logger.Logger.Debug().Msg("Finish CopyLogs hook")

View File

@ -24,23 +24,21 @@ func saveCloudConfig(name config.Stage, yc yip.YipConfig) error {
return os.WriteFile(filepath.Join("/oem", fmt.Sprintf("10_%s.yaml", name)), yipYAML, 0400)
}
// Read the keys sections ephemeral_mounts and bind mounts from install key in the cloud config.
// Run Read the keys sections ephemeral_mounts and bind mounts from install key in the cloud config.
// If not empty write an environment file to /run/cos/custom-layout.env.
// That env file is in turn read by /overlay/files/system/oem/11_persistency.yaml in fs.after stage.
func (cm CustomMounts) Run(c config.Config, _ v1.Spec) error {
//fmt.Println("Custom mounts hook")
//fmt.Println(strings.Join(c.Install.BindMounts, " "))
//fmt.Println(strings.Join(c.Install.EphemeralMounts, " "))
if len(c.Install.BindMounts) == 0 && len(c.Install.EphemeralMounts) == 0 {
return nil
}
c.Logger.Logger.Debug().Msg("Running CustomMounts hook")
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
err := machine.Mount("COS_OEM", "/oem")
if err != nil {
return err
}
defer func() {
machine.Umount("/oem") //nolint:errcheck
_ = machine.Umount("/oem")
}()
var mountsList = map[string]string{}
@ -48,15 +46,22 @@ func (cm CustomMounts) Run(c config.Config, _ v1.Spec) error {
mountsList["CUSTOM_BIND_MOUNTS"] = strings.Join(c.Install.BindMounts, " ")
mountsList["CUSTOM_EPHEMERAL_MOUNTS"] = strings.Join(c.Install.EphemeralMounts, " ")
config := yip.YipConfig{Stages: map[string][]schema.Stage{
"rootfs": []yip.Stage{{
Name: "user_custom_mounts",
EnvironmentFile: "/run/cos/custom-layout.env",
Environment: mountsList,
}},
}}
cfg := yip.YipConfig{
Stages: map[string][]schema.Stage{
"rootfs": {
{
Name: "user_custom_mounts",
EnvironmentFile: "/run/cos/custom-layout.env",
Environment: mountsList,
},
},
},
}
saveCloudConfig("user_custom_mounts", config) //nolint:errcheck
err = saveCloudConfig("user_custom_mounts", cfg)
if err != nil {
return err
}
c.Logger.Logger.Debug().Msg("Finish CustomMounts hook")
return nil
}

View File

@ -19,6 +19,7 @@ import (
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/sanity-io/litter"
qr "github.com/kairos-io/go-nodepair/qrcode"
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
@ -27,7 +28,6 @@ import (
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
)
@ -215,6 +215,11 @@ func RunInstall(c *config.Config) error {
utils.SetEnv(c.Env)
utils.SetEnv(c.Install.Env)
err := c.CheckForUsers()
if err != nil {
return err
}
// UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install
// From the installed system
if internalutils.IsUkiWithFs(c.Fs) {

View File

@ -11,7 +11,7 @@ import (
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5/vfst"
"gopkg.in/yaml.v3"
. "github.com/onsi/ginkgo/v2"

View File

@ -152,31 +152,45 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
return err
}
userName, err := prompt("User to setup", "kairos", canBeEmpty, true, false)
createUser, err := prompt("Do you want to create any users? If not, system will not be accesible via terminal or ssh", "y", yesNo, true, false)
if err != nil {
return err
}
userPassword, err := prompt("Password", "", canBeEmpty, true, true)
if err != nil {
return err
}
var userName, userPassword, sshKeys, makeAdmin string
if userPassword == "" {
userPassword = "!"
}
if isYes(createUser) {
userName, err = prompt("User to setup", "kairos", canBeEmpty, true, false)
if err != nil {
return err
}
users, err := prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}
userPassword, err = prompt("Password", "", canBeEmpty, true, true)
if err != nil {
return err
}
// Cleanup the users if we selected the default values as they are not valid users
if users == "github:someuser,github:someuser2" {
users = ""
}
if users != "" {
sshUsers = strings.Split(users, ",")
if userPassword == "" {
userPassword = "!"
}
sshKeys, err = prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}
makeAdmin, err = prompt("Make the user an admin (with sudo permissions)?", "y", yesNo, true, false)
if err != nil {
return err
}
// Cleanup the users if we selected the default values as they are not valid users
if sshKeys == "github:someuser,github:someuser2" {
sshKeys = ""
}
if sshKeys != "" {
sshUsers = strings.Split(sshKeys, ",")
}
}
// Prompt the user by prompts defined by the provider
@ -216,41 +230,49 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
return InteractiveInstall(debug, spawnShell, sourceImgURL)
}
usersToSet := map[string]schema.User{}
stage := config.NetworkStage.String()
if userName != "" {
user := schema.User{
Name: userName,
PasswordHash: userPassword,
Groups: []string{"admin"},
SSHAuthorizedKeys: sshUsers,
}
// If we got no ssh keys, we don't need network, do the user as soon as possible
if len(sshUsers) == 0 {
stage = config.InitramfsStage.String()
}
usersToSet = map[string]schema.User{
userName: user,
}
}
cloudConfig := schema.YipConfig{Name: "Config generated by the installer",
Stages: map[string][]schema.Stage{stage: {
{
Users: usersToSet,
},
}}}
// This is temporal to generate a valid cc file, no need to properly initialize everything
cc := &config.Config{
Install: &config.Install{
Device: device,
},
}
var cloudConfig schema.YipConfig
// Only add the user stage if we have any users
if userName != "" {
var isAdmin []string
if isYes(makeAdmin) {
isAdmin = append(isAdmin, "admin")
}
user := schema.User{
Name: userName,
PasswordHash: userPassword,
Groups: isAdmin,
SSHAuthorizedKeys: sshUsers,
}
stage := config.NetworkStage.String()
// If we got no ssh keys, we don't need network, do the user as soon as possible
if len(sshUsers) == 0 {
stage = config.InitramfsStage.String()
}
cloudConfig = schema.YipConfig{Name: "Config generated by the installer",
Stages: map[string][]schema.Stage{stage: {
{
Users: map[string]schema.User{
userName: user,
},
},
}}}
} else {
// If no users, we need to set this option to skip the user validation and confirm that we want a system with no users.
cc.Install.NoUsers = true
}
// Merge all yamls into one
dat, err := config.MergeYAML(cloudConfig, cc, result)
if err != nil {

View File

@ -4,12 +4,12 @@ import (
"fmt"
"time"
qr "github.com/kairos-io/go-nodepair/qrcode"
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
)
@ -42,7 +42,7 @@ func Recovery() error {
}
if busErr != "" {
return fmt.Errorf(busErr)
return fmt.Errorf("%s", busErr)
}
if !agentConfig.Fast {

View File

@ -38,6 +38,10 @@ func reset(reboot, unattended, resetOem bool, dir ...string) error {
if err != nil {
return err
}
err = cfg.CheckForUsers()
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadResetSpecFromConfig(cfg)
if err != nil {
@ -57,7 +61,7 @@ func reset(reboot, unattended, resetOem bool, dir ...string) error {
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
return hook.Run(*cfg, resetSpec, hook.AfterReset...)
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
}
func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
@ -65,6 +69,10 @@ func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
if err != nil {
return err
}
err = cfg.CheckForUsers()
if err != nil {
return err
}
// Load the installation Config from the cloud-config data
resetSpec, err := config.ReadUkiResetSpecFromConfig(cfg)
if err != nil {
@ -84,7 +92,7 @@ func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
return hook.Run(*cfg, resetSpec, hook.AfterReset...)
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
}
// sharedReset is the common reset code for both uki and non-uki

View File

@ -3,6 +3,7 @@ package agent
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
@ -11,9 +12,9 @@ import (
"github.com/kairos-io/kairos-agent/v2/internal/bus"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
k8sutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/k8s"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/utils"
@ -64,23 +65,44 @@ func ListNewerReleases(includePrereleases bool) ([]string, error) {
return tagList.FullImages()
}
// TODO: Check where force and preReleases is being used? They dont seem to be used anywhere?
func Upgrade(
source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error {
bus.Manager.Initialize()
fixedDirs := make([]string, len(dirs))
// Check and fix dirs if we are under k8s, so we read the actual running system configs instead of only
// the container configs
// we can run it blindly as it will return an empty string if not under k8s
hostdir := k8sutils.GetHostDirForK8s()
for _, dir := range dirs {
fixedDirs = append(fixedDirs, filepath.Join(hostdir, dir))
}
if internalutils.UkiBootMode() == internalutils.UkiHDD {
return upgradeUki(source, dirs, upgradeEntry, strictValidations)
return upgradeUki(source, fixedDirs, upgradeEntry, strictValidations)
} else {
return upgrade(source, force, strictValidations, dirs, upgradeEntry, preReleases)
return upgrade(source, fixedDirs, upgradeEntry, strictValidations)
}
}
func upgrade(source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error {
upgradeSpec, c, err := generateUpgradeSpec(source, force, strictValidations, dirs, upgradeEntry, preReleases)
func upgrade(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error {
c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations)
if err != nil {
return err
}
utils.SetEnv(c.Env)
err = c.CheckForUsers()
if err != nil {
return err
}
// Load the upgrade Config from the system
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
if err != nil {
return err
}
err = upgradeSpec.Sanitize()
if err != nil {
return err
@ -93,7 +115,56 @@ func upgrade(source string, force, strictValidations bool, dirs []string, upgrad
return err
}
return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
return hook.Run(*c, upgradeSpec, hook.FinishUpgrade...)
}
func upgradeUki(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error {
c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations)
if err != nil {
return err
}
utils.SetEnv(c.Env)
err = c.CheckForUsers()
if err != nil {
return err
}
// Load the upgrade Config from the system
upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c)
if err != nil {
return err
}
err = upgradeSpec.Sanitize()
if err != nil {
return err
}
upgradeAction := uki.NewUpgradeAction(c, upgradeSpec)
err = upgradeAction.Run()
if err != nil {
return err
}
return hook.Run(*c, upgradeSpec, hook.FinishUpgrade...)
}
func getConfig(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) (*config.Config, error) {
cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry)
if err != nil {
return nil, err
}
c, err := config.Scan(collector.Directories(dirs...),
collector.Readers(strings.NewReader(cliConf)),
collector.StrictValidation(strictValidations))
if err != nil {
return nil, err
}
return c, err
}
func allReleases() (versioneer.TagList, error) {
@ -155,30 +226,6 @@ func generateUpgradeConfForCLIArgs(source, upgradeEntry string) (string, error)
return string(d), err
}
func generateUpgradeSpec(sourceImageURL string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) (*v1.UpgradeSpec, *config.Config, error) {
cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry)
if err != nil {
return nil, nil, err
}
c, err := config.Scan(collector.Directories(dirs...),
collector.Readers(strings.NewReader(cliConf)),
collector.StrictValidation(strictValidations))
if err != nil {
return nil, nil, err
}
utils.SetEnv(c.Env)
// Load the upgrade Config from the system
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
if err != nil {
return nil, nil, err
}
return upgradeSpec, c, nil
}
func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
var result []string
bus.Manager.Response(events.EventAvailableReleases, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
@ -199,42 +246,6 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
return result, nil
}
func upgradeUki(source string, dirs []string, upgradeEntry string, strictValidations bool) error {
cliConf, err := generateUpgradeConfForCLIArgs(source, upgradeEntry)
if err != nil {
return err
}
c, err := config.Scan(collector.Directories(dirs...),
collector.Readers(strings.NewReader(cliConf)),
collector.StrictValidation(strictValidations))
if err != nil {
return err
}
utils.SetEnv(c.Env)
// Load the upgrade Config from the system
upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c)
if err != nil {
return err
}
err = upgradeSpec.Sanitize()
if err != nil {
return err
}
upgradeAction := uki.NewUpgradeAction(c, upgradeSpec)
err = upgradeAction.Run()
if err != nil {
return err
}
return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
}
// ExtraConfigUpgrade is the struct that holds the upgrade options that come from flags and events
type ExtraConfigUpgrade struct {
Upgrade struct {

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,6 @@
"yamljs": "^0.3.0"
},
"devDependencies": {
"cypress": "^13.0.0"
"cypress": "^14.0.0"
}
}

328
main.go
View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
@ -74,12 +75,13 @@ var cmds = []*cli.Command{
&cli.BoolFlag{Name: "recovery", Usage: "Upgrade recovery"},
},
Description: `
Manually upgrade a kairos node Active image. Does not upgrade passive or recovery images.
Manually upgrade a kairos node Active image. Does not upgrade the passive image. It upgrades the recovery image when the --recovery flag is passed.
With no arguments, it defaults to latest available release. To specify a version, pass it as argument using the --source flag.
Passing just the Kairos version as the first argument is no longer supported. If you speficy a positional argument, it will be treated
To specify a version, pass it as argument using the --source flag. Passing just the Kairos version as the first argument is no longer supported. If you speficy a positional argument, it will be treated
as a value for the --source flag.
You can also specify the upgrade image by setting "upgrade.system.uri" for the active image or "upgrade.recovery-system.uri" for the recovery image, in the cloud config.
To retrieve all the available versions, use "kairos upgrade list-releases"
$ kairos upgrade list-releases
@ -166,24 +168,21 @@ See https://kairos.io/docs/upgrade/manual/ for documentation.
return checkRoot()
},
Action: func(c *cli.Context) error {
var v string
var source string
if c.Args().Len() == 1 {
v = c.Args().First()
fmt.Println("Warning: Passing a version as a positional argument is deprecated. Use --source flag instead.")
fmt.Println("The value will be used as a value for the --source flag")
source = c.Args().First()
}
if v := c.String("source"); v != "" {
source = v
}
image := c.String("image")
if v := c.String("source"); v != "" {
source = c.String("source")
}
if image != "" {
if v := c.String("image"); v != "" {
fmt.Println("--image flag is deprecated, please use --source")
// override source with image for now until we drop it
source = fmt.Sprintf("oci:%s", image)
source = fmt.Sprintf("oci:%s", v)
}
if c.Bool("recovery") && c.String("boot-entry") != "" {
@ -667,6 +666,10 @@ The validate command expects a configuration file as its only argument. Local fi
Name: "cloud-init-paths",
Usage: "Extra paths to add to the run stage",
},
&cli.StringSliceFlag{
Name: "override-cloud-init-paths",
Usage: "Override paths to use when running the stage, removing defaults. Supercedes --cloud-init-paths",
},
&cli.BoolFlag{
Name: "analyze",
Usage: "Only print the modules that would run in the order they would run",
@ -690,6 +693,11 @@ The validate command expects a configuration file as its only argument. Local fi
if len(c.StringSlice("cloud-init-paths")) > 0 {
config.CloudInitPaths = append(config.CloudInitPaths, c.StringSlice("cloud-init-paths")...)
}
if len(c.StringSlice("override-cloud-init-paths")) > 0 {
config.CloudInitPaths = c.StringSlice("override-cloud-init-paths")
}
if c.Bool("debug") {
config.Logger.SetLevel("debug")
}
@ -796,6 +804,261 @@ The validate command expects a configuration file as its only argument. Local fi
return action.ListBootEntries(cfg)
},
},
{
Name: "sysext",
Usage: "sysext subcommands",
Description: "sysext subcommands",
Before: func(c *cli.Context) error {
_, err := exec.LookPath("systemd-sysext")
if err != nil {
return fmt.Errorf("systemd-sysext not found in PATH")
}
return nil
},
Subcommands: []*cli.Command{
{
Name: "list",
Usage: "List all the installed system extensions",
Description: "List all the installed system extensions",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "active",
Usage: "List the system extensions for the active boot entry",
},
&cli.BoolFlag{
Name: "passive",
Usage: "List the system extensions for the passive boot entry",
},
&cli.BoolFlag{
Name: "recovery",
Usage: "List the system extensions for the recovery boot entry",
},
&cli.BoolFlag{
Name: "common",
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
},
},
Before: func(c *cli.Context) error {
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
}
if err := checkRoot(); err != nil {
return err
}
return nil
},
Action: func(c *cli.Context) error {
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
if err != nil {
return err
}
var bootState string
if c.Bool("active") {
bootState = "active"
}
if c.Bool("passive") {
bootState = "passive"
}
out, err := action.ListSystemExtensions(cfg, bootState)
if err != nil {
return err
}
if len(out) == 0 {
cfg.Logger.Logger.Info().Msg("No system extensions found")
return nil
}
for _, ext := range out {
cfg.Logger.Info(litter.Sdump(ext))
}
return nil
},
},
{
Name: "enable",
Usage: "Enable a installed system extension for a give entry",
UsageText: "enable [--active|--passive] EXTENSION",
Description: "Enable a system extension for a given boot entry (active or passive)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "active",
Usage: "Enable the system extension for the active boot entry",
},
&cli.BoolFlag{
Name: "passive",
Usage: "Enable the system extension for the passive boot entry",
},
&cli.BoolFlag{
Name: "recovery",
Usage: "List the system extensions for the recovery boot entry",
},
&cli.BoolFlag{
Name: "common",
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
},
&cli.BoolFlag{
Name: "now",
Usage: "Enable the system extension now and reload systemd-sysext",
},
},
Before: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return fmt.Errorf("extension name required")
}
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
}
if noneOfEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
return fmt.Errorf("either --active, --passive, --recovery or --common must be set")
}
if err := checkRoot(); err != nil {
return err
}
return nil
},
Action: func(c *cli.Context) error {
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
if err != nil {
return err
}
var bootState string
if c.Bool("active") {
bootState = "active"
}
if c.Bool("passive") {
bootState = "passive"
}
ext := c.Args().First()
if err := action.EnableSystemExtension(cfg, ext, bootState, c.Bool("now")); err != nil {
cfg.Logger.Logger.Error().Err(err).Msg("failed enabling system extension")
return err
}
return nil
},
},
{
Name: "disable",
Usage: "Disable a installed system extension for a give entry",
UsageText: "disable [--active|--passive] EXTENSION",
Description: "Disable a system extension for a given boot entry (active or passive)",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "active",
Usage: "Disable the system extension for the active boot entry",
},
&cli.BoolFlag{
Name: "passive",
Usage: "Disable the system extension for the passive boot entry",
},
&cli.BoolFlag{
Name: "recovery",
Usage: "List the system extensions for the recovery boot entry",
},
&cli.BoolFlag{
Name: "common",
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
},
&cli.BoolFlag{
Name: "now",
Usage: "Disable the system extension now and reload systemd-sysext",
},
},
Before: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return fmt.Errorf("extension name required")
}
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
}
if noneOfEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
return fmt.Errorf("either --active, --passive, --recovery or --common must be set")
}
if err := checkRoot(); err != nil {
return err
}
return nil
},
Action: func(c *cli.Context) error {
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
if err != nil {
return err
}
var bootState string
if c.Bool("active") {
bootState = "active"
}
if c.Bool("passive") {
bootState = "passive"
}
ext := c.Args().First()
if err := action.DisableSystemExtension(cfg, ext, bootState, c.Bool("now")); err != nil {
cfg.Logger.Logger.Error().Err(err).Msg("failed disabling system extension")
return err
}
return nil
},
},
{
Name: "install",
Usage: "Install a system extension",
UsageText: "install URI",
Description: "Install a system extension from a given URI",
Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return fmt.Errorf("extension URI required")
}
uri := c.Args().First()
if err := validateSourceSysext(uri); err != nil {
return err
}
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
if err != nil {
return err
}
if err := action.InstallSystemExtension(cfg, uri); err != nil {
cfg.Logger.Logger.Error().Err(err).Msg("failed installing system extension")
return err
}
cfg.Logger.Logger.Info().Msgf("System extension %s installed", uri)
return nil
},
},
{
Name: "remove",
Usage: "Remove a system extension",
UsageText: "remove EXTENSION",
Description: "Remove a installed system extension",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "now",
Usage: "Remove the system extension now and reload systemd-sysext",
},
},
Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return fmt.Errorf("extension required")
}
extension := c.Args().First()
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
if err != nil {
return err
}
if err := action.RemoveSystemExtension(cfg, extension, c.Bool("now")); err != nil {
cfg.Logger.Logger.Error().Err(err).Msg("failed removing system extension")
return err
}
cfg.Logger.Logger.Info().Msgf("System extension %s removed", extension)
return nil
},
},
},
},
}
func main() {
@ -876,7 +1139,7 @@ func validateSource(source string) error {
return nil
}
r, err := regexp.Compile(`^oci:|dir:|file:`)
r, err := regexp.Compile(`^oci:|^dir:|^file:`)
if err != nil {
return err
}
@ -887,6 +1150,22 @@ func validateSource(source string) error {
return nil
}
func validateSourceSysext(source string) error {
if source == "" {
return nil
}
r, err := regexp.Compile(`^oci:|^file:|^http:|^https:`)
if err != nil {
return err
}
if !r.MatchString(source) {
return fmt.Errorf("source %s does not match any of oci:, file: or http(s): ", source)
}
return nil
}
// Check
func bootFromLiveMedia() bool {
// Check if the system is booted from a LIVE media by checking if the file /run/cos/livecd is present
@ -920,3 +1199,26 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
return tags, nil
}
func moreThanOneEnabled(bools ...bool) bool {
count := 0
for _, b := range bools {
if b {
count++
}
if count > 1 {
return true
}
}
return false
}
func noneOfEnabled(bools ...bool) bool {
count := 0
for _, b := range bools {
if b {
count++
}
}
return count == 0
}

View File

@ -60,7 +60,7 @@ func selectBootEntryGrub(cfg *config.Config, entry string) error {
vars := map[string]string{
"next_entry": entry,
}
err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg.Fs)
err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg)
if err != nil {
cfg.Logger.Errorf("could not set default boot entry: %s\n", err)
return err
@ -129,10 +129,16 @@ func selectBootEntrySystemd(cfg *config.Config, entry string) error {
}
}
}
bootName, err := bootNameToSystemdConf(entry)
bootFileName, err := bootNameToSystemdConf(entry)
if err != nil {
return err
}
assessment, err := utils.ReadAssessmentFromEntry(cfg.Fs, bootFileName, cfg.Logger)
if err != nil {
cfg.Logger.Logger.Err(err).Str("entry", entry).Str("boot file name", bootFileName).Msg("could not read assessment from entry")
return err
}
bootName := fmt.Sprintf("%s%s.conf", bootFileName, assessment)
// Set the default entry to the selected entry
systemdConf["default"] = bootName
err = utils.SystemdBootConfWriter(cfg.Fs, filepath.Join(efiPartition.MountPoint, "loader/loader.conf"), systemdConf)
@ -173,6 +179,10 @@ func systemdConfToBootName(conf string) (string, error) {
fileName := strings.TrimSuffix(conf, ".conf")
// Remove the boot assesment from the name we show
re := regexp.MustCompile(`\+\d+(-\d+)?$`)
fileName = re.ReplaceAllString(fileName, "")
if strings.HasPrefix(fileName, "active") {
bootName := "cos"
confName := strings.TrimPrefix(fileName, "active")
@ -220,6 +230,8 @@ func systemdConfToBootName(conf string) (string, error) {
return strings.ReplaceAll(fileName, "_", " "), nil
}
// bootNameToSystemdConf converts a boot name to a systemd-boot conf file name
// skips the .conf extension
func bootNameToSystemdConf(name string) (string, error) {
differenciator := ""
@ -227,38 +239,38 @@ func bootNameToSystemdConf(name string) (string, error) {
if name != "cos" {
differenciator = "_" + strings.TrimPrefix(name, "cos ")
}
return "active" + differenciator + ".conf", nil
return "active" + differenciator, nil
}
if strings.HasPrefix(name, "active") {
if name != "active" {
differenciator = "_" + strings.TrimPrefix(name, "active ")
}
return "active" + differenciator + ".conf", nil
return "active" + differenciator, nil
}
if strings.HasPrefix(name, "fallback") {
if name != "fallback" {
differenciator = "_" + strings.TrimPrefix(name, "fallback ")
}
return "passive" + differenciator + ".conf", nil
return "passive" + differenciator, nil
}
if strings.HasPrefix(name, "recovery") {
if name != "recovery" {
differenciator = "_" + strings.TrimPrefix(name, "recovery ")
}
return "recovery" + differenciator + ".conf", nil
return "recovery" + differenciator, nil
}
if strings.HasPrefix(name, "statereset") {
if name != "statereset" {
differenciator = "_" + strings.TrimPrefix(name, "statereset ")
}
return "statereset" + differenciator + ".conf", nil
return "statereset" + differenciator, nil
}
return strings.ReplaceAll(name, " ", "_") + ".conf", nil
return strings.ReplaceAll(name, " ", "_"), nil
}
// listBootEntriesSystemd lists the boot entries available in the systemd-boot config files

View File

@ -14,8 +14,8 @@ import (
sdkTypes "github.com/kairos-io/kairos-sdk/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Bootentries tests", Label("bootentry"), func() {
@ -138,14 +138,42 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("does not exist"))
})
It("selects the boot entry in a default installation", func() {
It("works without boot assessment", func() {
err := fs.WriteFile("/efi/loader/entries/active.conf", []byte("title kairos\nefi /EFI/kairos/active.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
err = fs.WriteFile("/efi/loader/loader.conf", []byte("default active.conf"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/recovery.conf", []byte("title kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
err = SelectBootEntry(config, "active")
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/statereset.conf", []byte("title kairos state reset (auto)\nefi /EFI/kairos/statereset.efi\n"), os.ModePerm)
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active"))
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("active.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
"/efi",
"",
syscall.MS_REMOUNT,
"")).To(BeTrue())
// Should have called a remount to make it RO
Expect(syscallMock.WasMountCalledWith(
"",
"/efi",
"",
syscall.MS_REMOUNT|syscall.MS_RDONLY,
"")).To(BeTrue())
})
It("selects the boot entry in a default installation", func() {
err := fs.WriteFile("/efi/loader/entries/active+2-1.conf", []byte("title kairos\nefi /EFI/kairos/active.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/passive+3.conf", []byte("title kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/recovery+1-2.conf", []byte("title kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/statereset+2-1.conf", []byte("title kairos state reset (auto)\nefi /EFI/kairos/statereset.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/loader.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
@ -155,7 +183,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("passive.conf"))
Expect(reader["default"]).To(Equal("passive+3.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -176,7 +204,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to recovery"))
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("recovery.conf"))
Expect(reader["default"]).To(Equal("recovery+1-2.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -197,7 +225,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to statereset"))
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("statereset.conf"))
Expect(reader["default"]).To(Equal("statereset+2-1.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -218,7 +246,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to cos"))
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("active.conf"))
Expect(reader["default"]).To(Equal("active+2-1.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -240,7 +268,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active"))
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("active.conf"))
Expect(reader["default"]).To(Equal("active+2-1.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -260,7 +288,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
It("selects the boot entry in a extend-cmdline installation with boot branding", func() {
err := fs.WriteFile("/efi/loader/entries/active_install-mode_awesomeos.conf", []byte("title awesomeos\nefi /EFI/kairos/active_install-mode_awesomeos.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/passive_install-mode_awesomeos.conf", []byte("title awesomeos (fallback)\nefi /EFI/kairos/passive_install-mode_awesomeos.efi\n"), os.ModePerm)
err = fs.WriteFile("/efi/loader/entries/passive_install-mode_awesomeos+3.conf", []byte("title awesomeos (fallback)\nefi /EFI/kairos/passive_install-mode_awesomeos.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/recovery_install-mode_awesomeos.conf", []byte("title awesomeos recovery\nefi /EFI/kairos/recovery_install-mode_awesomeos.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
@ -274,7 +302,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("passive_install-mode_awesomeos.conf"))
Expect(reader["default"]).To(Equal("passive_install-mode_awesomeos+3.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -381,11 +409,11 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/active_foobar.conf", []byte("title Kairos\nefi /EFI/kairos/active_foobar.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
err = fs.WriteFile("/efi/loader/entries/passive+3.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/passive_foobar.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive_foobar.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/recovery.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
err = fs.WriteFile("/efi/loader/entries/recovery+3.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/recovery_foobar.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery_foobar.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
@ -393,7 +421,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/statereset_foobar.conf", []byte("title Kairos state reset (auto)\nefi /EFI/kairos/state_reset_foobar.efi\n"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/loader.conf", []byte(""), os.ModePerm)
err = fs.WriteFile("/efi/loader/loader.conf", []byte("default active.conf"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = SelectBootEntry(config, "fallback")
@ -401,7 +429,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("passive.conf"))
Expect(reader["default"]).To(Equal("passive+3.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -443,7 +471,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to recovery"))
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
Expect(err).ToNot(HaveOccurred())
Expect(reader["default"]).To(Equal("recovery.conf"))
Expect(reader["default"]).To(Equal("recovery+3.conf"))
// Should have called a remount to make it RW
Expect(syscallMock.WasMountCalledWith(
"",
@ -666,7 +694,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
err = SelectBootEntry(config, "kairos")
Expect(err).ToNot(HaveOccurred())
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to kairos"))
variables, err := utils.ReadPersistentVariables("/oem/grubenv", fs)
variables, err := utils.ReadPersistentVariables("/oem/grubenv", config)
Expect(err).ToNot(HaveOccurred())
Expect(variables["next_entry"]).To(Equal("kairos"))
})

View File

@ -9,8 +9,8 @@ import (
sdkTypes "github.com/kairos-io/kairos-sdk/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
"os"
"path/filepath"
)

View File

@ -270,7 +270,7 @@ func (i InstallAction) Run() (err error) {
return err
}
err = hook.Run(*i.cfg, i.spec, hook.EncryptionHooks...)
err = hook.Run(*i.cfg, i.spec, hook.PostInstall...)
if err != nil {
return err
}
@ -307,5 +307,5 @@ func (i InstallAction) Run() (err error) {
_ = utils.RunStage(i.cfg, "kairos-install.after")
_ = events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck
return hook.Run(*i.cfg, i.spec, hook.AfterInstall...)
return hook.Run(*i.cfg, i.spec, hook.FinishInstall...)
}

View File

@ -19,17 +19,15 @@ package action_test
import (
"bytes"
"fmt"
"github.com/diskfs/go-diskfs"
"os"
"path/filepath"
"regexp"
fileBackend "github.com/diskfs/go-diskfs/backend/file"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
"github.com/kairos-io/kairos-sdk/collector"
@ -38,8 +36,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Install action tests", func() {
@ -105,7 +103,8 @@ var _ = Describe("Install action tests", func() {
device = filepath.Join(tmpdir, "test.img")
Expect(os.RemoveAll(device)).Should(Succeed())
// at least 2Gb in size as state is set to 1G
_, err = diskfs.Create(device, 2*1024*1024*1024, diskfs.Raw, 512)
_, err = fileBackend.CreateFromPath(device, 2*1024*1024*1024)
Expect(err).ToNot(HaveOccurred())
config.Install.Device = device
@ -150,6 +149,16 @@ var _ = Describe("Install action tests", func() {
Expect(err).To(BeNil())
_, err = fs.Create(grubCfg)
Expect(err).To(BeNil())
// Create fake grub dir in rootfs and fake grub binaries
err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "sbin"), constants.DirPerm)
Expect(err).To(BeNil())
f, err := fs.Create(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install"))
Expect(err).To(BeNil())
Expect(f.Chmod(0755)).ToNot(HaveOccurred())
err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc"), constants.DirPerm)
Expect(err).To(BeNil())
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
Expect(err).To(BeNil())
mainDisk := sdkTypes.Disk{
Name: "device",
@ -349,9 +358,10 @@ var _ = Describe("Install action tests", func() {
Expect(cl.WasGetCalledWith("http://my.config.org")).To(BeTrue())
})
It("Fails on grub2-install errors", Label("grub"), func() {
It("Fails to find grub2-install", Label("grub"), func() {
spec.Target = device
cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"})
err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install"))
Expect(err).To(BeNil())
Expect(installer.Run()).NotTo(BeNil())
Expect(runner.MatchMilestones([][]string{{"grub2-install"}}))
})
@ -362,5 +372,12 @@ var _ = Describe("Install action tests", func() {
Expect(installer.Run()).NotTo(BeNil())
Expect(runner.MatchMilestones([][]string{{"tune2fs", "-L", constants.PassiveLabel}}))
})
It("Fails if there is no grub2 artifacts", Label("grub"), func() {
spec.Target = device
err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
Expect(err).To(BeNil())
Expect(installer.Run()).NotTo(BeNil())
Expect(runner.MatchMilestones([][]string{{"grub2-install"}}))
})
})
})

View File

@ -199,6 +199,16 @@ func (r ResetAction) Run() (err error) {
// Create extra dirs in rootfs as afterwards this will be impossible due to RO system
createExtraDirsInRootfs(r.cfg, r.spec.ExtraDirsRootfs, r.spec.Active.MountPoint)
// Mount EFI partition before installing grub as under EFI this copies stuff in there
if r.spec.Efi {
err = e.MountPartition(r.spec.Partitions.EFI)
if err != nil {
return err
}
cleanup.Push(func() error { return e.UnmountPartition(r.spec.Partitions.EFI) })
}
//TODO: does bios needs to be mounted here?
// install grub
grub := utils.NewGrub(r.cfg)
err = grub.Install(

View File

@ -20,23 +20,20 @@ import (
"bytes"
"errors"
"fmt"
"path/filepath"
"regexp"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"path/filepath"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Reset action tests", func() {
@ -133,7 +130,8 @@ var _ = Describe("Reset action tests", func() {
ghwTest.AddDisk(mainDisk)
ghwTest.CreateDevices()
fs.Create(constants.EfiDevice)
Expect(fsutils.MkdirAll(fs, constants.EfiDevice, constants.DirPerm)).ToNot(HaveOccurred())
bootedFrom = constants.SystemLabel
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
if cmd == cmdFail {
@ -159,76 +157,82 @@ var _ = Describe("Reset action tests", func() {
_, err = fs.Create(grubCfg)
Expect(err).To(BeNil())
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
regexCmd := regexp.MustCompile(cmdFail)
if cmdFail != "" && regexCmd.MatchString(cmd) {
return []byte{}, errors.New("command failed")
}
return []byte{}, nil
// create the fake grub dir with modules, it needs an arch in the path
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, constants.Archx86), constants.DirPerm)).ToNot(HaveOccurred())
for _, mod := range constants.GetGrubModules() {
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, constants.Archx86, mod))
Expect(err).ToNot(HaveOccurred())
}
// create fake shim
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/"), constants.DirPerm)).ToNot(HaveOccurred())
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/", "shim.efi"))
Expect(err).ToNot(HaveOccurred())
// create fake grub
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/"), constants.DirPerm)).ToNot(HaveOccurred())
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/", "grub.efi"))
Expect(err).ToNot(HaveOccurred())
reset = action.NewResetAction(config, spec)
})
AfterEach(func() {
ghwTest.Clean()
})
Describe("With EFI", func() {
It("Successfully resets on non-squashfs recovery", func() {
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets on non-squashfs recovery including persistent data", func() {
spec.FormatPersistent = true
spec.FormatOEM = true
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets from a squashfs recovery image", Label("channel"), func() {
err := fsutils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Source = v1.NewDirSrc(constants.IsoBaseTree)
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets despite having errors on hooks", func() {
cloudInit.Error = true
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets from a docker image", Label("docker"), func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
Expect(reset.Run()).To(BeNil())
It("Successfully resets on non-squashfs recovery", func() {
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets on non-squashfs recovery including persistent data", func() {
spec.FormatPersistent = true
spec.FormatOEM = true
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets from a squashfs recovery image", Label("channel"), func() {
err := fsutils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm)
Expect(err).ShouldNot(HaveOccurred())
spec.Active.Source = v1.NewDirSrc(constants.IsoBaseTree)
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets despite having errors on hooks", func() {
cloudInit.Error = true
Expect(reset.Run()).To(BeNil())
})
It("Successfully resets from a docker image", Label("docker"), func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
Expect(reset.Run()).To(BeNil())
})
It("Fails installing grub", func() {
cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"})
Expect(reset.Run()).NotTo(BeNil())
Expect(runner.IncludesCmds([][]string{{cmdFail}}))
})
It("Fails formatting state partition", func() {
cmdFail = "mkfs.ext4"
Expect(reset.Run()).NotTo(BeNil())
Expect(runner.IncludesCmds([][]string{{"mkfs.ext4"}}))
})
It("Fails setting the active label on non-squashfs recovery", func() {
cmdFail = "tune2fs"
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails setting the passive label on squashfs recovery", func() {
cmdFail = "tune2fs"
Expect(reset.Run()).NotTo(BeNil())
Expect(runner.IncludesCmds([][]string{{"tune2fs"}}))
})
It("Fails mounting partitions", func() {
mounter.ErrorOnMount = true
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails unmounting partitions", func() {
mounter.ErrorOnUnmount = true
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails unpacking docker image ", func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
extractor.SideEffect = func(imageRef, destination, platformRef string) error {
return fmt.Errorf("error")
}
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails formatting state partition", func() {
cmdFail = "mkfs.ext4"
Expect(reset.Run()).NotTo(BeNil())
Expect(runner.IncludesCmds([][]string{{"mkfs.ext4"}}))
})
It("Fails setting the active label on non-squashfs recovery", func() {
cmdFail = "tune2fs"
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails setting the passive label on squashfs recovery", func() {
cmdFail = "tune2fs"
Expect(reset.Run()).NotTo(BeNil())
Expect(runner.IncludesCmds([][]string{{"tune2fs"}}))
})
It("Fails mounting partitions", func() {
mounter.ErrorOnMount = true
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails unmounting partitions", func() {
mounter.ErrorOnUnmount = true
Expect(reset.Run()).NotTo(BeNil())
})
It("Fails unpacking docker image ", func() {
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
extractor.SideEffect = func(imageRef, destination, platformRef string) error {
return fmt.Errorf("error")
}
Expect(reset.Run()).NotTo(BeNil())
})
})
})
})

433
pkg/action/sysext.go Normal file
View File

@ -0,0 +1,433 @@
package action
import (
"fmt"
"github.com/distribution/reference"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-sdk/types"
"github.com/twpayne/go-vfs/v5"
"net/url"
"os"
"path/filepath"
"regexp"
)
// Implementation details for not trusted boot
// sysext are stored under
// /var/lib/kairos/extensions/
// we link them to /var/lib/kairos/extensions/{active,passive} depending on where we want it to be enabled
// Immucore on boot after mounting the persistent dir, will check those dirs\
// it will then create the proper links to them under /run/extensions
// This means they are enabled on boot and they are ephemeral, nothing is left behind in the actual sysext dirs
// This prevents us from having to clean up in different dirs, we can just do cleaning in our dirs (remove links)
// and on reboot they will not be enabled on boot
// So all the actions (list, upgrade, download, remove) will be done on the persistent dir
// And on boot we dinamycally link and enable them based on the boot type (active,passive) via immucore
// TODO: Check which extensions are running? is that possible?
// TODO: On disable we should check if the extension is running and refresh systemd-sysext? YES
// TODO: On remove we should check if the extension is running and refresh systemd-sysext? YES
const (
sysextDir = "/var/lib/kairos/extensions/"
sysextDirActive = "/var/lib/kairos/extensions/active"
sysextDirPassive = "/var/lib/kairos/extensions/passive"
sysextDirRecovery = "/var/lib/kairos/extensions/recovery"
sysextDirCommon = "/var/lib/kairos/extensions/common"
)
// SysExtension represents a system extension
type SysExtension struct {
Name string
Location string
}
func (s *SysExtension) String() string {
return s.Name
}
// ListSystemExtensions lists the system extensions in the given directory
// If none is passed then it shows the generic ones
func ListSystemExtensions(cfg *config.Config, bootState string) ([]SysExtension, error) {
switch bootState {
case "active":
cfg.Logger.Debug("Listing active system extensions")
return getDirExtensions(cfg, sysextDirActive)
case "passive":
cfg.Logger.Debug("Listing passive system extensions")
return getDirExtensions(cfg, sysextDirPassive)
case "recovery":
cfg.Logger.Debug("Listing recovery system extensions")
return getDirExtensions(cfg, sysextDirRecovery)
case "common":
cfg.Logger.Debug("Listing common system extensions")
return getDirExtensions(cfg, sysextDirCommon)
default:
cfg.Logger.Debug("Listing all system extensions (Enabled or not)")
return getDirExtensions(cfg, sysextDir)
}
}
// getDirExtensions lists the system extensions in the given directory
func getDirExtensions(cfg *config.Config, dir string) ([]SysExtension, error) {
var out []SysExtension
// get all the extensions in the sysextDir
// Try to create the dir if it does not exist
if _, err := cfg.Fs.Stat(dir); os.IsNotExist(err) {
if err := vfs.MkdirAll(cfg.Fs, dir, 0755); err != nil {
return nil, fmt.Errorf("failed to create target dir %s: %w", dir, err)
}
}
entries, err := cfg.Fs.ReadDir(dir)
// We don't care if the dir does not exist, we just return an empty list
if err != nil && !os.IsNotExist(err) {
return nil, err
}
for _, entry := range entries {
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".raw" {
out = append(out, SysExtension{Name: entry.Name(), Location: filepath.Join(dir, entry.Name())})
}
}
return out, nil
}
// GetSystemExtension returns the system extension for a given name
func GetSystemExtension(cfg *config.Config, name, bootState string) (SysExtension, error) {
// Get a list of all installed system extensions
installed, err := ListSystemExtensions(cfg, bootState)
if err != nil {
return SysExtension{}, err
}
// Check if the extension is installed
// regex against the name
re, err := regexp.Compile(name)
if err != nil {
return SysExtension{}, err
}
for _, ext := range installed {
if re.MatchString(ext.Name) {
return ext, nil
}
}
// If not, return an error
return SysExtension{}, fmt.Errorf("system extension %s not found", name)
}
// EnableSystemExtension enables a system extension that is already in the system for a given bootstate
// It creates a symlink to the extension in the target dir according to the bootstate given
// It will create the target dir if it does not exist
// It will check if the extension is already enabled but not fail if it is
// It will check if the extension is installed
// If now is true, it will enable the extension immediately by linking it to /run/extensions and refreshing systemd-sysext
func EnableSystemExtension(cfg *config.Config, ext, bootState string, now bool) error {
// first check if the extension is installed
extension, err := GetSystemExtension(cfg, ext, "")
if err != nil {
return err
}
var targetDir string
switch bootState {
case "active":
targetDir = sysextDirActive
case "passive":
targetDir = sysextDirPassive
case "recovery":
targetDir = sysextDirRecovery
case "common":
targetDir = sysextDirCommon
default:
return fmt.Errorf("boot state %s not supported", bootState)
}
// Check if the target dir exists and create it if it doesn't
if _, err := cfg.Fs.Stat(targetDir); os.IsNotExist(err) {
if err := vfs.MkdirAll(cfg.Fs, targetDir, 0755); err != nil {
return fmt.Errorf("failed to create target dir %s: %w", targetDir, err)
}
}
// Check if the extension is already enabled
enabled, err := GetSystemExtension(cfg, ext, bootState)
// This doesnt fail if we have it already enabled
if err == nil {
if enabled.Name == extension.Name {
cfg.Logger.Infof("System extension %s is already enabled in %s", extension.Name, bootState)
return nil
}
}
// Create a symlink to the extension in the target dir
if err := cfg.Fs.Symlink(extension.Location, filepath.Join(targetDir, extension.Name)); err != nil {
return fmt.Errorf("failed to create symlink for %s: %w", extension.Name, err)
}
cfg.Logger.Infof("System extension %s enabled in %s", extension.Name, bootState)
if now {
// Check if the boot state is the same as the one we are enabling
// This is to avoid enabling the extension in the wrong boot state
_, stateMatches := cfg.Fs.Stat(fmt.Sprintf("/run/cos/%s_mode", bootState))
// TODO: Check in UKI?
cfg.Logger.Logger.Debug().Str("boot_state", bootState).Str("filecheck", fmt.Sprintf("/run/cos/%s_state", bootState)).Msg("Checking boot state")
if stateMatches == nil || bootState == "common" {
err = cfg.Fs.Symlink(filepath.Join(targetDir, extension.Name), filepath.Join("/run/extensions", extension.Name))
if err != nil {
return fmt.Errorf("failed to create symlink for %s: %w", extension.Name, err)
}
cfg.Logger.Infof("System extension %s enabled in /run/extensions", extension.Name)
// It makes the sysext check the extension for a valid signature
// Refresh systemd-sysext by restarting the service. As the config is set via the service overrides to nice things
output, err := cfg.Runner.Run("systemctl", "restart", "systemd-sysext")
if err != nil {
cfg.Logger.Logger.Err(err).Str("output", string(output)).Msg("Failed to refresh systemd-sysext")
return err
}
cfg.Logger.Infof("System extension %s merged by systemd-sysext", extension.Name)
} else {
cfg.Logger.Infof("System extension %s enabled in %s but not merged by systemd-sysext as we are currently not booted in %s", extension.Name, bootState, bootState)
}
}
return nil
}
// DisableSystemExtension disables a system extension that is already in the system for a given bootstate
// It removes the symlink from the target dir according to the bootstate given
func DisableSystemExtension(cfg *config.Config, ext string, bootState string, now bool) error {
var targetDir string
switch bootState {
case "active":
targetDir = sysextDirActive
case "passive":
targetDir = sysextDirPassive
case "recovery":
targetDir = sysextDirRecovery
case "common":
targetDir = sysextDirCommon
default:
return fmt.Errorf("boot state %s not supported", bootState)
}
// Check if the target dir exists
if _, err := cfg.Fs.Stat(targetDir); os.IsNotExist(err) {
return fmt.Errorf("target dir %s does not exist", targetDir)
}
// Check if the extension is enabled, do not fail if it is not
extension, err := GetSystemExtension(cfg, ext, bootState)
if err != nil {
cfg.Logger.Infof("system extension %s is not enabled in %s", ext, bootState)
return nil
}
// Remove the symlink
if err := cfg.Fs.Remove(extension.Location); err != nil {
return fmt.Errorf("failed to remove symlink for %s: %w", ext, err)
}
if now {
// Check if the boot state is the same as the one we are disabling
// This is to avoid disabling the extension in the wrong boot state
_, stateMatches := cfg.Fs.Stat(fmt.Sprintf("/run/cos/%s_mode", bootState))
cfg.Logger.Logger.Debug().Str("boot_state", bootState).Str("filecheck", fmt.Sprintf("/run/cos/%s_mode", bootState)).Msg("Checking boot state")
if stateMatches == nil || bootState == "common" {
// Remove the symlink from /run/extensions if is in there
cfg.Logger.Logger.Debug().Str("stat", filepath.Join("/run/extensions", extension.Name)).Msg("Checking if symlink exists")
_, stat := cfg.Fs.Readlink(filepath.Join("/run/extensions", extension.Name))
if stat == nil {
err = cfg.Fs.Remove(filepath.Join("/run/extensions", extension.Name))
if err != nil {
return fmt.Errorf("failed to remove symlink for %s: %w", extension.Name, err)
}
cfg.Logger.Infof("System extension %s disabled from /run/extensions", extension.Name)
// Now that its removed we refresh systemd-sysext
output, err := cfg.Runner.Run("systemctl", "restart", "systemd-sysext")
if err != nil {
cfg.Logger.Logger.Err(err).Str("output", string(output)).Msg("Failed to refresh systemd-sysext")
return err
}
cfg.Logger.Infof("System extension %s refreshed by systemd-sysext", extension.Name)
} else {
cfg.Logger.Logger.Info().Msg("Extension not in /run/extensions, not refreshing")
}
} else {
cfg.Logger.Infof("System extension %s disabled in %s but not refreshed by systemd-sysext as we are currently not booted in %s", extension.Name, bootState, bootState)
}
}
cfg.Logger.Infof("System extension %s disabled in %s", ext, bootState)
return nil
}
// InstallSystemExtension installs a system extension from a given URI
// It will download the extension and extract it to the target dir
// It will check if the extension is already installed before doing anything
func InstallSystemExtension(cfg *config.Config, uri string) error {
// Parse the URI
download, err := parseURI(cfg, uri)
if err != nil {
return fmt.Errorf("failed to parse URI %s: %w", uri, err)
}
// Check if directory exists or create it
if _, err := cfg.Fs.Stat(sysextDir); os.IsNotExist(err) {
if err := vfs.MkdirAll(cfg.Fs, sysextDir, 0755); err != nil {
return fmt.Errorf("failed to create target dir %s: %w", sysextDir, err)
}
}
// Download the extension
if err := download.Download(sysextDir); err != nil {
return err
}
return nil
}
// RemoveSystemExtension removes a system extension from the system
// It will remove any symlinks to the extension
// Then it will remove the extension
// It will check if the extension is installed before doing anything
func RemoveSystemExtension(cfg *config.Config, extension string, now bool) error {
// Check if the extension is installed
installed, err := GetSystemExtension(cfg, extension, "")
if err != nil {
return err
}
if installed.Name == "" && installed.Location == "" {
cfg.Logger.Infof("System extension %s is not installed", extension)
return nil
}
// Check if the extension is enabled in active or passive
for _, state := range []string{"active", "passive", "recovery", "common"} {
enabled, err := GetSystemExtension(cfg, extension, state)
if err == nil {
// Remove the symlink
if err := cfg.Fs.Remove(enabled.Location); err != nil {
return fmt.Errorf("failed to remove symlink for %s: %w", enabled.Name, err)
}
cfg.Logger.Infof("System extension %s disabled from %s", enabled.Name, state)
}
}
// Remove the extension
if err := cfg.Fs.RemoveAll(installed.Location); err != nil {
return fmt.Errorf("failed to remove extension %s: %w", installed.Name, err)
}
if now {
// Here as we are removing the extension we need to check if its in /run/extensions
// We dont care about the bootState because we are removing it from all
_, stat := cfg.Fs.Readlink(filepath.Join("/run/extensions", installed.Name))
if stat == nil {
err = cfg.Fs.Remove(filepath.Join("/run/extensions", installed.Name))
if err != nil {
return fmt.Errorf("failed to remove symlink for %s: %w", installed.Name, err)
}
cfg.Logger.Infof("System extension %s removed from /run/extensions", installed.Name)
// Now that its removed we refresh systemd-sysext
output, err := cfg.Runner.Run("systemctl", "restart", "systemd-sysext")
if err != nil {
cfg.Logger.Logger.Err(err).Str("output", string(output)).Msg("Failed to refresh systemd-sysext")
return err
}
cfg.Logger.Infof("System extension %s refreshed by systemd-sysext", installed.Name)
} else {
cfg.Logger.Logger.Info().Msg("Extension not in /run/extensions, not refreshing")
}
}
cfg.Logger.Infof("System extension %s removed", installed.Name)
return nil
}
// ParseURI parses a URI and returns a SourceDownload
// implementation based on the scheme of the URI
func parseURI(cfg *config.Config, uri string) (SourceDownload, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}
scheme := u.Scheme
value := u.Opaque
if value == "" {
value = filepath.Join(u.Host, u.Path)
}
switch scheme {
case "oci", "docker", "container":
n, err := reference.ParseNormalizedNamed(value)
if err != nil {
return nil, fmt.Errorf("invalid image reference %s", value)
} else if reference.IsNameOnly(n) {
value += ":latest"
}
return &dockerSource{value, cfg}, nil
case "file":
return &fileSource{value, cfg}, nil
case "http", "https":
// Pass the full uri including the protocol
return &httpSource{uri, cfg}, nil
default:
return nil, fmt.Errorf("invalid URI reference %s", uri)
}
}
// SourceDownload is an interface for downloading system extensions
// from different sources. It allows for different implementations
// for different sources of system extensions, such as files, directories,
// or docker images. The interface defines a single method, Download,
// which takes a destination path as an argument and returns an error
type SourceDownload interface {
Download(string) error
}
// fileSource is a struct that implements the SourceDownload interface
// for downloading system extensions from a file. It has two fields,
// uri, which is the URI of the file to be downloaded and cfg which points to the Config
// The Download method takes a destination path as an argument and returns an error if the
// download fails.
type fileSource struct {
uri string
cfg *config.Config
}
// Download just copies the file to the destination
// As this is a file source, we just copy the file to the destination, not much to it
func (f *fileSource) Download(dst string) error {
src, err := f.cfg.Fs.ReadFile(f.uri)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", f.uri, err)
}
stat, _ := f.cfg.Fs.Stat(f.uri)
dstFile := filepath.Join(dst, filepath.Base(f.uri))
f.cfg.Logger.Logger.Debug().Str("uri", f.uri).Str("target", dstFile).Msg("Copying system extension")
// Keep original permissions
if err = f.cfg.Fs.WriteFile(dstFile, src, stat.Mode()); err != nil {
return fmt.Errorf("failed to copy file %s to %s: %w", f.uri, dstFile, err)
}
return nil
}
type httpSource struct {
uri string
cfg *config.Config
}
func (h httpSource) Download(s string) error {
// Download the file from the URI
// and save it to the destination path
h.cfg.Logger.Logger.Debug().Str("uri", h.uri).Str("target", filepath.Join(s, filepath.Base(h.uri))).Msg("Downloading system extension")
return h.cfg.Client.GetURL(types.NewNullLogger(), h.uri, filepath.Join(s, filepath.Base(h.uri)))
}
type dockerSource struct {
uri string
cfg *config.Config
}
func (d dockerSource) Download(s string) error {
// Download the file from the URI
// and save it to the destination path
err := d.cfg.ImageExtractor.ExtractImage(d.uri, s, "")
if err != nil {
return err
}
return nil
}

588
pkg/action/sysext_test.go Normal file
View File

@ -0,0 +1,588 @@
package action_test
import (
"bytes"
"fmt"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Sysext Actions test", func() {
var config *agentConfig.Config
var runner *v1mock.FakeRunner
var fs vfs.FS
var logger sdkTypes.KairosLogger
var mounter *v1mock.ErrorMounter
var syscall *v1mock.FakeSyscall
var httpClient *v1mock.FakeHTTPClient
var cloudInit *v1mock.FakeCloudInitRunner
var cleanup func()
var memLog *bytes.Buffer
var extractor *v1mock.FakeImageExtractor
var err error
BeforeEach(func() {
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
mounter = v1mock.NewErrorMounter()
httpClient = &v1mock.FakeHTTPClient{}
memLog = &bytes.Buffer{}
logger = sdkTypes.NewBufferLogger(memLog)
logger.SetLevel("debug")
extractor = v1mock.NewFakeImageExtractor(logger)
cloudInit = &v1mock.FakeCloudInitRunner{}
fs, cleanup, err = vfst.NewTestFS(map[string]interface{}{})
Expect(err).ToNot(HaveOccurred())
err := vfs.MkdirAll(fs, "/var/lib/kairos/extensions", 0755)
Expect(err).ToNot(HaveOccurred())
err = vfs.MkdirAll(fs, "/run/extensions", 0755)
Expect(err).ToNot(HaveOccurred())
// Config object with all of our fakes on it
config = agentConfig.NewConfig(
agentConfig.WithFs(fs),
agentConfig.WithRunner(runner),
agentConfig.WithLogger(logger),
agentConfig.WithMounter(mounter),
agentConfig.WithSyscall(syscall),
agentConfig.WithClient(httpClient),
agentConfig.WithCloudInitRunner(cloudInit),
agentConfig.WithImageExtractor(extractor),
agentConfig.WithPlatform("linux/amd64"),
)
})
AfterEach(func() {
cleanup()
})
Describe("Listing extensions", func() {
It("should NOT fail if the bootstate is not valid", func() {
extensions, err := action.ListSystemExtensions(config, "invalid")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
Describe("With no dir", func() {
BeforeEach(func() {
err = config.Fs.RemoveAll("/var/lib/kairos/extensions")
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
cleanup()
})
It("should return no extensions for installed extensions", func() {
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for active enabled extensions", func() {
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for passive enabled extensions", func() {
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for recovery enabled extensions", func() {
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "recovery")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for common enabled extensions", func() {
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "common")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
})
Describe("With empty dir", func() {
It("should return no extensions", func() {
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for active enabled extensions", func() {
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for passive enabled extensions", func() {
extensions, err := action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for recovery enabled extensions", func() {
extensions, err := action.ListSystemExtensions(config, "recovery")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return no extensions for common enabled extensions", func() {
extensions, err := action.ListSystemExtensions(config, "common")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
})
Describe("With dir with files", func() {
It("should not return files that are not valid extensions", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/invalid", []byte("invalid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should return files that are valid extensions", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/valid.raw",
},
}))
})
It("should ONLY return files that are valid extensions", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/invalid", []byte("invalid"), 0644)
Expect(err).ToNot(HaveOccurred())
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(len(extensions)).To(Equal(1))
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/valid.raw",
},
}))
})
})
})
Describe("Enabling extensions", func() {
It("should fail to enable a extension if bootState is not valid", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
err = action.EnableSystemExtension(config, "valid", "invalid", false)
Expect(err).To(HaveOccurred())
})
It("should enable an installed extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Passive should be empty
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
extensions, err = action.ListSystemExtensions(config, "recovery")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
extensions, err = action.ListSystemExtensions(config, "common")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for passive
err = action.EnableSystemExtension(config, "valid.raw", "passive", false)
Expect(err).ToNot(HaveOccurred())
// Passive should have the extension
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/passive/valid.raw",
},
}))
// Check active again to see if it is still there
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Enable it for recovery
err = action.EnableSystemExtension(config, "valid.raw", "recovery", false)
Expect(err).ToNot(HaveOccurred())
// Passive should have the extension
extensions, err = action.ListSystemExtensions(config, "recovery")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/recovery/valid.raw",
},
}))
})
It("should enable an installed extension and reload the system with it", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
// Fake the boot state
Expect(config.Fs.Mkdir("/run/cos", 0755)).ToNot(HaveOccurred())
Expect(config.Fs.WriteFile("/run/cos/active_mode", []byte("true"), 0644)).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
// This basically returns an error if the command is not executed
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).To(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", true)
Expect(err).ToNot(HaveOccurred())
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Passive should be empty
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Symlink should be created in /run/extensions
_, err = config.Fs.Stat("/run/extensions/valid.raw")
Expect(err).ToNot(HaveOccurred())
readlink, err := config.Fs.Readlink("/run/extensions/valid.raw")
Expect(err).ToNot(HaveOccurred())
// Get the raw path as the readlink will return the real path, not the one in our fake fs
realPath, err := config.Fs.RawPath("/var/lib/kairos/extensions/active/valid.raw")
Expect(err).ToNot(HaveOccurred())
Expect(readlink).To(Equal(realPath))
})
It("should enable an installed extension and reload the system with it if its a common one", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "common")
Expect(err).ToNot(HaveOccurred())
// This basically returns an error if the command is not executed
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).To(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for common
err = action.EnableSystemExtension(config, "valid.raw", "common", true)
Expect(err).ToNot(HaveOccurred())
// Should have refreshed the systemd-sysext
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).ToNot(HaveOccurred())
// Should be enabled
extensions, err = action.ListSystemExtensions(config, "common")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/common/valid.raw",
},
}))
// Active and Passive should be empty
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Symlink should be created in /run/extensions
_, err = config.Fs.Stat("/run/extensions/valid.raw")
Expect(err).ToNot(HaveOccurred())
})
It("should enable an installed extension and NOT reload the system with it if we are on the wrong boot state", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
// This basically returns an error if the command is not executed
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).To(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", true)
Expect(err).ToNot(HaveOccurred())
Expect(runner.IncludesCmds([][]string{
{"systemctl", "restart", "systemd-sysext"},
})).To(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Passive should be empty
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Symlink should be created in /run/extensions
_, err = config.Fs.Stat("/run/extensions/valid.raw")
Expect(err).To(HaveOccurred())
})
It("should fail to enable a missing extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "invalid.raw", "active", false)
Expect(err).To(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Passive should be empty
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should not fail if the extension is already enabled", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
})
})
Describe("Disabling extensions", func() {
It("should fail if bootState is not valid", func() {
err := action.DisableSystemExtension(config, "whatever", "invalid", false)
Expect(err).To(HaveOccurred())
})
It("should disable an enabled extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Disable it
err = action.DisableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should not fail to disable a not enabled extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
// Disable a non enabled extension
err = action.DisableSystemExtension(config, "invalid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
})
})
Describe("Installing extensions", func() {
Describe("With a file source", func() {
It("should install a extension", func() {
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
err = config.Fs.WriteFile("/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
err = action.InstallSystemExtension(config, "file:///valid.raw")
Expect(err).ToNot(HaveOccurred(), memLog.String())
// Check if the extension is installed
extensions, err = action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/valid.raw",
},
}))
})
It("should fail to install a missing extension", func() {
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
err = action.InstallSystemExtension(config, "file:///invalid.raw")
Expect(err).To(HaveOccurred(), memLog.String())
// Check if the extension is installed
extensions, err = action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
})
Describe("with a docker source", func() {
It("should install a extension", func() {
err = action.InstallSystemExtension(config, "docker://quay.io/valid:v1.0.0")
Expect(err).ToNot(HaveOccurred(), memLog.String())
expectedCall := v1mock.ExtractCall{ImageRef: "quay.io/valid:v1.0.0", Destination: "/var/lib/kairos/extensions/", PlatformRef: ""}
Expect(extractor.WasCalledWithExtractCall(expectedCall)).To(BeTrue())
})
It("should fail to install a missing extension", func() {
extractor.SideEffect = func(imageRef, destination, platformRef string) error {
return fmt.Errorf("error")
}
err = action.InstallSystemExtension(config, "docker://quay.io/invalid:v1.0.0")
Expect(err).To(HaveOccurred(), memLog.String())
expectedCall := v1mock.ExtractCall{ImageRef: "quay.io/invalid:v1.0.0", Destination: "/var/lib/kairos/extensions/", PlatformRef: ""}
Expect(extractor.WasCalledWithExtractCall(expectedCall)).To(BeTrue())
})
})
Describe("with a http source", func() {
It("should install a extension", func() {
err = action.InstallSystemExtension(config, "http://localhost:8080/valid.raw")
Expect(err).ToNot(HaveOccurred(), memLog.String())
Expect(httpClient.WasGetCalledWith("http://localhost:8080/valid.raw")).To(BeTrue())
})
It("should fail to install a missing extension", func() {
httpClient.Error = true
err = action.InstallSystemExtension(config, "http://localhost:8080/invalid.raw")
Expect(err).To(HaveOccurred())
Expect(httpClient.WasGetCalledWith("http://localhost:8080/invalid.raw")).To(BeTrue())
})
})
})
Describe("Removing extensions", func() {
It("should remove an installed extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/valid.raw",
},
}))
err = action.RemoveSystemExtension(config, "valid.raw", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should disable and remove an enabled extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Enable it for active
err = action.EnableSystemExtension(config, "valid.raw", "active", false)
Expect(err).ToNot(HaveOccurred())
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/active/valid.raw",
},
}))
err = action.RemoveSystemExtension(config, "valid.raw", false)
Expect(err).ToNot(HaveOccurred())
// Check if it is removed from active
extensions, err = action.ListSystemExtensions(config, "active")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Check if it is removed from passive
extensions, err = action.ListSystemExtensions(config, "passive")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
// Check if it is removed from installed
extensions, err = action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(BeEmpty())
})
It("should fail to remove a missing extension", func() {
err = config.Fs.WriteFile("/var/lib/kairos/extensions/valid.raw", []byte("valid"), 0644)
Expect(err).ToNot(HaveOccurred())
extensions, err := action.ListSystemExtensions(config, "")
Expect(err).ToNot(HaveOccurred())
Expect(extensions).To(Equal([]action.SysExtension{
{
Name: "valid.raw",
Location: "/var/lib/kairos/extensions/valid.raw",
},
}))
err = action.RemoveSystemExtension(config, "invalid.raw", false)
Expect(err).To(HaveOccurred())
})
})
})

View File

@ -18,27 +18,22 @@ package action_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/kairos-io/kairos-agent/v2/internal/agent"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
"github.com/kairos-io/kairos-sdk/collector"
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"os"
"path/filepath"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Upgrade Actions test", func() {
@ -55,10 +50,8 @@ var _ = Describe("Upgrade Actions test", func() {
var ghwTest ghwMock.GhwMock
var extractor *v1mock.FakeImageExtractor
var dummySourceFile string
var dummySourceSizeMb int64
BeforeEach(func() {
dummySourceSizeMb = 20
runner = v1mock.NewFakeRunner()
syscall = &v1mock.FakeSyscall{}
mounter = v1mock.NewErrorMounter()
@ -153,34 +146,6 @@ var _ = Describe("Upgrade Actions test", func() {
AfterEach(func() {
ghwTest.Clean()
})
It("calculates the recovery source size correctly", func() {
dummySourceFile = createDummyFile(fs, dummySourceSizeMb)
upgradeConfig := agent.ExtraConfigUpgrade{}
upgradeConfig.Upgrade.Entry = constants.BootEntryRecovery
upgradeConfig.Upgrade.RecoverySystem.URI = fmt.Sprintf("file:%s", dummySourceFile)
d, err := json.Marshal(upgradeConfig)
Expect(err).ToNot(HaveOccurred())
cliConfig := string(d)
config, err := agentConfig.Scan(collector.Readers(strings.NewReader(cliConfig)))
Expect(err).ToNot(HaveOccurred())
agentConfig.WithFs(fs)(config)
agentConfig.WithRunner(runner)(config)
agentConfig.WithLogger(logger)(config)
agentConfig.WithMounter(mounter)(config)
agentConfig.WithSyscall(syscall)(config)
agentConfig.WithClient(client)(config)
agentConfig.WithCloudInitRunner(cloudInit)(config)
agentConfig.WithImageExtractor(extractor)(config)
agentConfig.WithPlatform("linux/amd64")(config)
config.ImageExtractor = extractor
spec, err = agentConfig.NewUpgradeSpec(config)
Expect(err).ShouldNot(HaveOccurred())
Expect(spec.Entry).To(Equal(constants.BootEntryRecovery))
Expect(spec.Recovery.Size).To(Equal(uint(100 + dummySourceSizeMb))) // We adding 100Mb on top
})
Describe(fmt.Sprintf("Booting from %s", constants.ActiveLabel), Label("active_label"), func() {
var err error
BeforeEach(func() {
@ -659,25 +624,3 @@ var _ = Describe("Upgrade Actions test", func() {
})
})
})
func createDummyFile(fs vfs.FS, sizeMb int64) string {
fileSize := int64(sizeMb * 1024 * 1024)
tmpFile, err := os.CreateTemp("", "dummyfile_*.tmp")
Expect(err).ToNot(HaveOccurred())
tmpName := tmpFile.Name()
tmpFile.Close()
os.RemoveAll(tmpName)
dir := filepath.Dir(tmpName)
err = fs.Mkdir(dir, os.ModePerm)
Expect(err).ToNot(HaveOccurred())
f, err := fs.Create(tmpName)
Expect(err).ShouldNot(HaveOccurred())
err = f.Truncate(fileSize)
Expect(err).ShouldNot(HaveOccurred())
f.Close()
return tmpName
}

View File

@ -22,7 +22,7 @@ import (
"github.com/mudler/yip/pkg/executor"
"github.com/mudler/yip/pkg/plugins"
"github.com/mudler/yip/pkg/schema"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v5"
)
type YipCloudInitRunner struct {

View File

@ -34,7 +34,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5/vfst"
)
// Parted print sample output

View File

@ -23,7 +23,7 @@ import (
yip "github.com/mudler/yip/pkg/schema"
"github.com/sanity-io/litter"
"github.com/spf13/viper"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v5"
"gopkg.in/yaml.v3"
"k8s.io/mount-utils"
)
@ -55,10 +55,15 @@ type Install struct {
ExtraPartitions sdkTypes.PartitionList `yaml:"extra-partitions,omitempty" mapstructure:"extra-partitions"`
ExtraDirsRootfs []string `yaml:"extra-dirs-rootfs,omitempty" mapstructure:"extra-dirs-rootfs"`
Force bool `yaml:"force,omitempty" mapstructure:"force"`
NoUsers bool `yaml:"nousers,omitempty" mapstructure:"nousers"`
}
func NewConfig(opts ...GenericOptions) *Config {
log := sdkTypes.NewKairosLogger("agent", "info", false)
// Get the viper config in case something in command line or env var has set it and set the level asap
if viper.GetBool("debug") {
log.SetLevel("debug")
}
hostPlatform, err := v1.NewPlatformFromArch(runtime.GOARCH)
if err != nil {
@ -150,6 +155,8 @@ type Config struct {
SquashFsCompressionConfig []string `yaml:"squash-compression,omitempty" mapstructure:"squash-compression"`
SquashFsNoCompression bool `yaml:"squash-no-compression,omitempty" mapstructure:"squash-no-compression"`
UkiMaxEntries int `yaml:"uki-max-entries,omitempty" mapstructure:"uki-max-entries"`
BindPCRs []string `yaml:"bind-pcrs,omitempty" mapstructure:"bind-pcrs"`
BindPublicPCRs []string `yaml:"bind-public-pcrs,omitempty" mapstructure:"bind-public-pcrs"`
}
// WriteInstallState writes the state.yaml file to the given state and recovery paths
@ -188,6 +195,61 @@ func (c Config) LoadInstallState() (*v1.InstallState, error) {
return installState, nil
}
func contains(s []string, str string) bool {
for _, v := range s {
if v == str {
return true
}
}
return false
}
// CheckForUsers will check the config for any users and validate that at least we have 1 admin.
// Since Kairos 3.3.x we don't ship a default user with the system, so before a system with no specific users
// was relying in our default cloud-configs which created a kairos user ALWAYS (with SUDO!)
// But now we don't ship it anymore. So a user upgrading from 3.2.x to 3.3.x that created no users, will end up with a blocked
// system.
// So we need to see if they are setting a user in their config and if not refuse to continue
func (c Config) CheckForUsers() (err error) {
// If nousers is enabled we do not check for the validity of the users and such
// At this point, the config should be fully parsed and the yip stages ready
// Check if the sentinel is present
_, sentinel := c.Fs.Stat("/etc/kairos/.nousers")
if sentinel == nil {
c.Logger.Logger.Debug().Msg("Sentinel file found, skipping user check")
return nil
}
if !c.Install.NoUsers {
anyAdmin := false
cc, _ := c.Config.String()
yamlConfig, err := yip.Load(cc, vfs.OSFS, nil, nil)
if err != nil {
return err
}
for _, stage := range yamlConfig.Stages {
for _, x := range stage {
if len(x.Users) > 0 {
for _, user := range x.Users {
if contains(user.Groups, "admin") || user.PrimaryGroup == "admin" {
anyAdmin = true
break
}
}
}
}
}
if !anyAdmin {
return fmt.Errorf("No users found in any stage that are part of the 'admin' group.\n" +
"In Kairos 3.3.x we no longer ship a default hardcoded user with the system configs and require users to provide their own user." +
"Please provide at least 1 user that is part of the 'admin' group(for sudo) with your cloud configs." +
"If you still want to continue without creating any users in the system, set 'install.nousers: true' to be in the config in order to allow a system with no users.")
}
}
return err
}
// Sanitize checks the consistency of the struct, returns error
// if unsolvable inconsistencies are found
func (c *Config) Sanitize() error {
@ -339,10 +401,11 @@ func FilterKeys(d []byte) ([]byte, error) {
}
// ScanNoLogs is a wrapper around Scan that sets the logger to null
// Also sets the NoLogs option to true by default
func ScanNoLogs(opts ...collector.Option) (c *Config, err error) {
log := sdkTypes.NewNullLogger()
result := NewConfig(WithLogger(log))
return scan(result, opts...)
return scan(result, append(opts, collector.NoLogs)...)
}
// Scan is a wrapper around collector.Scan that sets the logger to the default Kairos logger

View File

@ -22,12 +22,13 @@ import (
"reflect"
"strings"
pkgConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
v1mocks "github.com/kairos-io/kairos-agent/v2/tests/mocks"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
"gopkg.in/yaml.v3"
. "github.com/kairos-io/kairos-agent/v2/pkg/config"
@ -93,7 +94,7 @@ func structFieldsContainedInOtherStruct(left, right interface{}) {
leftFieldName := leftTypes.Field(i).Name
if leftTypes.Field(i).IsExported() {
It(fmt.Sprintf("Checks that the new schema contians the field %s", leftFieldName), func() {
if leftFieldName == "Source" {
if leftFieldName == "Source" || leftFieldName == "NoUsers" || leftFieldName == "BindPublicPCRs" || leftFieldName == "BindPCRs" {
Skip("Schema not updated yet")
}
Expect(
@ -227,7 +228,7 @@ var _ = Describe("Schema", func() {
cleanup()
})
It("Scan can override options", func() {
c, err := Scan(collector.Readers(strings.NewReader(`uki-max-entries: 34`)), collector.NoLogs)
c, err := ScanNoLogs(collector.Readers(strings.NewReader(`uki-max-entries: 34`)))
Expect(err).ShouldNot(HaveOccurred())
Expect(c.UkiMaxEntries).To(Equal(34))
})
@ -265,4 +266,40 @@ var _ = Describe("Schema", func() {
Expect(err).Should(HaveOccurred())
})
})
Describe("Validate users in config", func() {
It("Validates a existing user in the system", func() {
cc := `#cloud-config
stages:
initramfs:
- name: "Set user and password"
users:
kairos:
passwd: "kairos"
groups:
- "admin"
`
config, err := pkgConfig.ScanNoLogs(collector.Readers(strings.NewReader(cc)))
Expect(err).ToNot(HaveOccurred())
Expect(config.CheckForUsers()).ToNot(HaveOccurred())
})
It("Fails if there is no user", func() {
config, err := pkgConfig.ScanNoLogs(collector.NoLogs)
Expect(err).ToNot(HaveOccurred())
Expect(config.CheckForUsers()).To(HaveOccurred())
})
It("Fails if there is user but its not admin", func() {
cc := `#cloud-config
stages:
initramfs:
- name: "Set user and password"
users:
kairos:
passwd: "kairos"
`
config, err := pkgConfig.ScanNoLogs(collector.Readers(strings.NewReader(cc)))
Expect(err).ToNot(HaveOccurred())
Expect(config.CheckForUsers()).To(HaveOccurred())
})
})
})

View File

@ -27,6 +27,7 @@ import (
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
k8sutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/k8s"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
"github.com/kairos-io/kairos-sdk/collector"
"github.com/kairos-io/kairos-sdk/ghw"
@ -396,12 +397,32 @@ func NewUpgradeSpec(cfg *Config) (*v1.UpgradeSpec, error) {
if err != nil {
return nil, fmt.Errorf("failed calculating size: %w", err)
}
if spec.Active.Source.IsDocker() {
cfg.Logger.Infof("Checking if OCI image %s exists", spec.Active.Source.Value())
_, err := crane.Manifest(spec.Active.Source.Value())
if err != nil {
if strings.Contains(err.Error(), "MANIFEST_UNKNOWN") {
return nil, fmt.Errorf("oci image %s does not exist", spec.Active.Source.Value())
}
return nil, err
}
}
return spec, nil
}
func setUpgradeSourceSize(cfg *Config, spec *v1.UpgradeSpec) error {
var size int64
var err error
var originalSize uint
// Store the default given size in the spec. This includes the user specified values which have already been marshalled in the spec
if spec.RecoveryUpgrade() {
originalSize = spec.Recovery.Size
} else {
originalSize = spec.Active.Size
}
var targetSpec *v1.Image
if spec.RecoveryUpgrade() {
@ -411,16 +432,25 @@ func setUpgradeSourceSize(cfg *Config, spec *v1.UpgradeSpec) error {
}
if targetSpec.Source != nil && targetSpec.Source.IsEmpty() {
cfg.Logger.Debugf("No source specified for image, skipping size calculation")
return nil
}
size, err = GetSourceSize(cfg, targetSpec.Source)
if err != nil {
cfg.Logger.Warnf("Failed to infer size for images: %s", err.Error())
return err
}
cfg.Logger.Infof("Setting image size to %dMb", size)
targetSpec.Size = uint(size)
if uint(size) < originalSize {
cfg.Logger.Debugf("Calculated size (%dMB) is less than specified/default size (%dMB)", size, originalSize)
targetSpec.Size = originalSize
} else {
cfg.Logger.Debugf("Calculated size (%dMB) is higher than specified/default size (%dMB)", size, originalSize)
targetSpec.Size = uint(size)
}
cfg.Logger.Infof("Setting image size to %dMB", targetSpec.Size)
return nil
}
@ -788,7 +818,7 @@ func ReadUkiUpgradeSpecFromConfig(c *Config) (*v1.UpgradeUkiSpec, error) {
// getSize will calculate the size of a file or symlink and will do nothing with directories
// fileList: keeps track of the files visited to avoid counting a file more than once if it's a symlink. It could also be used as a way to filter some files
// size: will be the memory that adds up all the files sizes. Meaning it could be initialized with a value greater than 0 if needed.
func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry, err error) error {
func getSize(vfs v1.FS, size *int64, fileList map[string]bool, path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
@ -801,7 +831,7 @@ func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry,
if d.Type()&fs.ModeSymlink != 0 {
// If it's a symlink, get its target and calculate its size.
var err error
actualFilePath, err = os.Readlink(path)
actualFilePath, err = vfs.Readlink(path)
if err != nil {
return err
}
@ -812,7 +842,7 @@ func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry,
}
}
fileInfo, err := os.Stat(actualFilePath)
fileInfo, err := vfs.Stat(actualFilePath)
if os.IsNotExist(err) || fileList[actualFilePath] {
return nil
}
@ -849,17 +879,11 @@ func GetSourceSize(config *Config, source *v1.ImageSource) (int64, error) {
// , which mounts the host root into $HOST_DIR
// we should skip that dir when calculating the size as we would be doubling the calculated size
// Plus we will hit the usual things when checking a running system. Processes that go away, tmpfiles, etc...
// This is always set for pods running under kubernetes
_, underKubernetes := os.LookupEnv("KUBERNETES_SERVICE_HOST")
config.Logger.Logger.Info().Bool("status", underKubernetes).Msg("Running under kubernetes")
// Try to get the HOST_DIR in case we are not using the default one
hostDir := os.Getenv("HOST_DIR")
// If we are under kubernetes but the HOST_DIR var is empty, default to /host as system-upgrade-controller mounts
// the host in that dir by default
if underKubernetes && hostDir == "" {
hostDir = "/host"
}
hostDir := k8sutils.GetHostDirForK8s()
config.Logger.Logger.Debug().Bool("status", underKubernetes).Str("hostdir", hostDir).Msg("Kubernetes check")
err = fsutils.WalkDirFs(config.Fs, source.Value(), func(path string, d fs.DirEntry, err error) error {
// If its empty we are just not setting it, so probably out of the k8s upgrade path
if hostDir != "" && strings.HasPrefix(path, hostDir) {
@ -870,7 +894,7 @@ func GetSourceSize(config *Config, source *v1.ImageSource) (int64, error) {
// During install or upgrade outside kubernetes, we dont care about those dirs as they are not expected to be in the source dir
config.Logger.Logger.Debug().Str("path", path).Str("hostDir", hostDir).Msg("Skipping dir as it is a runtime directory under kubernetes (/proc, /dev or /run)")
} else {
v := getSize(&size, filesVisited, path, d, err)
v := getSize(config.Fs, &size, filesVisited, path, d, err)
return v
}

View File

@ -20,6 +20,7 @@ import (
"bytes"
"os"
"path/filepath"
"strings"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
@ -33,7 +34,7 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/rs/zerolog"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5/vfst"
"k8s.io/mount-utils"
)
@ -449,6 +450,45 @@ var _ = Describe("Types", Label("types", "config"), func() {
Expect(spec.Recovery.Source.IsEmpty()).To(BeTrue())
Expect(spec.Recovery.FS).To(Equal(constants.SquashFs))
})
It("sets image size to default value if not set", func() {
spec, err := config.NewUpgradeSpec(c)
Expect(err).ShouldNot(HaveOccurred())
Expect(spec.Active.Size).To(Equal(constants.ImgSize))
})
It("sets image size to provided value if set in the config and image is smaller", func() {
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n size: 666\n")))
// Set manually the config collector in the cfg file before unmarshalling the spec
c.Config = cfg.Config
spec, err := config.NewUpgradeSpec(c)
Expect(err).ShouldNot(HaveOccurred())
Expect(spec.Active.Size).To(Equal(uint(666)))
})
It("sets image size to default value if not set in the config and image is smaller", func() {
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n uri: dir:/\n")))
// Set manually the config collector in the cfg file before unmarshalling the spec
c.Config = cfg.Config
spec, err := config.NewUpgradeSpec(c)
Expect(err).ShouldNot(HaveOccurred())
Expect(spec.Active.Size).To(Equal(constants.ImgSize))
})
It("sets image size to the source if default is smaller", func() {
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n uri: file:/tmp/waka\n")))
// Set manually the config collector in the cfg file before unmarshalling the spec
c.Config = cfg.Config
Expect(c.Fs.Mkdir("/tmp", 0777)).ShouldNot(HaveOccurred())
Expect(c.Fs.WriteFile("/tmp/waka", []byte("waka"), 0777)).ShouldNot(HaveOccurred())
Expect(c.Fs.Truncate("/tmp/waka", 5120*1024*1024)).ShouldNot(HaveOccurred())
spec, err := config.NewUpgradeSpec(c)
Expect(err).ShouldNot(HaveOccurred())
f, err := c.Fs.Stat("/tmp/waka")
Expect(err).ShouldNot(HaveOccurred())
// Make the same calculation as the code
Expect(spec.Active.Size).To(Equal(uint(f.Size()/1000/1000) + 100))
})
})
})
Describe("Config from cloudconfig", Label("cloud-config"), func() {
@ -703,7 +743,8 @@ var _ = Describe("GetSourceSize", Label("GetSourceSize"), func() {
Expect(os.Mkdir(filepath.Join(tempDir, "host"), os.ModePerm)).ToNot(HaveOccurred())
Expect(createFileOfSizeInMB(filepath.Join(tempDir, "host", "what.txt"), 200)).ToNot(HaveOccurred())
// Set env var like the suc upgrade does
// Set env var like the suc upgrade and k8s does to trigger the skip
Expect(os.Setenv("KUBERNETES_SERVICE_HOST", "10.0.0.1")).ToNot(HaveOccurred())
Expect(os.Setenv("HOST_DIR", filepath.Join(tempDir, "host"))).ToNot(HaveOccurred())
sizeAfter, err := config.GetSourceSize(conf, imageSource)

View File

@ -126,7 +126,10 @@ const (
StateResetBootSuffix = " state reset (auto)"
// Error
UpgradeNoSourceError = "Could not find a proper source for the upgrade.\nThis can be configured in the cloud config files under the 'upgrade.system.uri' key or via cmdline using the '--source' flag."
UpgradeNoSourceError = "could not find a proper source for the upgrade.\nThis can be configured in the cloud config files under the 'upgrade.system.uri' key or via cmdline using the '--source' flag"
UpgradeRecoveryNoSourceError = "could not find a proper source for the recovery upgrade.\nThis can be configured in the cloud config files under the 'upgrade.recovery-system.uri' key or via cmdline using the '--source' flag"
MultipleEntriesAssessmentError = "multiple boot entries found for %s"
NoBootAssessmentWarning = "No boot assessment found in current boot entry config file"
)
func UkiDefaultMenuEntries() []string {

View File

@ -24,7 +24,6 @@ import (
"path/filepath"
"syscall"
diskfs "github.com/diskfs/go-diskfs/disk"
"github.com/diskfs/go-diskfs/partition/gpt"
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
@ -49,7 +48,7 @@ func NewElemental(config *agentConfig.Config) *Elemental {
// FormatPartition will format an already existing partition
func (e *Elemental) FormatPartition(part *types.Partition, opts ...string) error {
e.config.Logger.Infof("Formatting '%s' partition", part.FilesystemLabel)
return partitioner.FormatDevice(e.config.Runner, part.Path, part.FS, part.FilesystemLabel, opts...)
return partitioner.FormatDevice(e.config.Logger, e.config.Runner, part.Path, part.FS, part.FilesystemLabel, opts...)
}
// PartitionAndFormatDevice creates a new empty partition table on target disk
@ -73,13 +72,10 @@ func (e *Elemental) PartitionAndFormatDevice(i v1.SharedInstallSpec) error {
return err
}
// Only re-read table on devices. On files there is no need and this call will fail
if disk.Type == diskfs.Device {
err = disk.ReReadPartitionTable()
if err != nil {
e.config.Logger.Errorf("Reread table: %s", err)
return err
}
err = disk.ReReadPartitionTable()
if err != nil {
e.config.Logger.Errorf("Reread table: %s", err)
return err
}
table, err := disk.GetPartitionTable()
@ -121,7 +117,7 @@ func (e *Elemental) PartitionAndFormatDevice(i v1.SharedInstallSpec) error {
if err != nil {
e.config.Logger.Errorf("Failed finding partition %s by partition label: %s", configPart.FilesystemLabel, err)
}
err = partitioner.FormatDevice(e.config.Runner, device, configPart.FS, configPart.FilesystemLabel)
err = partitioner.FormatDevice(e.config.Logger, e.config.Runner, device, configPart.FS, configPart.FilesystemLabel)
if err != nil {
e.config.Logger.Errorf("Failed formatting partition: %s", err)
return err
@ -308,7 +304,23 @@ func (e Elemental) CreateFileSystemImage(img *v1.Image) error {
// DeployImage will deploy the given image into the target. This method
// creates the filesystem image file, mounts it and unmounts it as needed.
// Creates the default system dirs by default (/sys,/proc,/dev, etc...)
func (e *Elemental) DeployImage(img *v1.Image, leaveMounted bool) (info interface{}, err error) {
return e.deployImage(img, leaveMounted, true)
}
// DeployImageNodirs will deploy the given image into the target. This method
// creates the filesystem image file, mounts it and unmounts it as needed.
// Does not create the default system dirs so it can be used to create generic images from any source
func (e *Elemental) DeployImageNodirs(img *v1.Image, leaveMounted bool) (info interface{}, err error) {
return e.deployImage(img, leaveMounted, false)
}
// deployImage is the real function that does the actual work
// Set leaveMounted to leave the image mounted, otherwise it unmounts before returning
// Set createDirStructure to create the directory structure in the target, which creates the expected dirs
// for a running system. This is so we can reuse this method for creating random images, not only system ones
func (e *Elemental) deployImage(img *v1.Image, leaveMounted, createDirStructure bool) (info interface{}, err error) {
target := img.MountPoint
if !img.Source.IsFile() {
if img.FS != cnst.SquashFs {
@ -338,9 +350,11 @@ func (e *Elemental) DeployImage(img *v1.Image, leaveMounted bool) (info interfac
return nil, err
}
if !img.Source.IsFile() {
err = utils.CreateDirStructure(e.config.Fs, target)
if err != nil {
return nil, err
if createDirStructure {
err = utils.CreateDirStructure(e.config.Fs, target)
if err != nil {
return nil, err
}
}
if img.FS == cnst.SquashFs {
squashOptions := append(cnst.GetDefaultSquashfsOptions(), e.config.SquashFsCompressionConfig...)
@ -570,7 +584,7 @@ func (e Elemental) SetDefaultGrubEntry(partMountPoint string, imgMountPoint stri
return utils.SetPersistentVariables(
filepath.Join(partMountPoint, cnst.GrubOEMEnv),
map[string]string{"default_menu_entry": defaultEntry},
e.config.Fs,
e.config,
)
}

View File

@ -20,10 +20,10 @@ import (
"bytes"
"errors"
"fmt"
"github.com/diskfs/go-diskfs"
"golang.org/x/sys/unix"
"os"
"path/filepath"
"reflect"
"strings"
sc "syscall"
"testing"
@ -38,13 +38,13 @@ import (
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"github.com/diskfs/go-diskfs"
fileBackend "github.com/diskfs/go-diskfs/backend/file"
"github.com/diskfs/go-diskfs/partition/gpt"
"github.com/gofrs/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/sanity-io/litter"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5/vfst"
)
func TestElementalSuite(t *testing.T) {
@ -364,7 +364,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
Expect(err).To(BeNil())
Expect(os.RemoveAll(filepath.Join(tmpDir, "/test.img"))).ToNot(HaveOccurred())
// at least 2Gb in size as state is set to 1G
_, err = diskfs.Create(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024, diskfs.Raw, 512)
_, err = fileBackend.CreateFromPath(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024)
Expect(err).ToNot(HaveOccurred())
config.Install.Device = filepath.Join(tmpDir, "/test.img")
install, err = agentConfig.NewInstallSpec(config)
@ -382,17 +382,18 @@ var _ = Describe("Elemental", Label("elemental"), func() {
install.Firmware = v1.EFI
Expect(install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)).To(BeNil())
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
disk, err := diskfs.Open(filepath.Join(tmpDir, "/test.img"), diskfs.WithOpenMode(diskfs.ReadOnly))
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
defer disk.Close()
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
Expect(err).ToNot(HaveOccurred())
// check that its type GPT
Expect(reflect.TypeOf(disk.Table)).To(Equal(reflect.TypeOf(&gpt.Table{})))
Expect(table.Type()).To(Equal("gpt"))
// Expect the disk UUID to be constant
Expect(strings.ToLower(disk.Table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
// 5 partitions (boot, oem, recovery, state and persistent)
Expect(len(disk.Table.GetPartitions())).To(Equal(5))
Expect(len(table.GetPartitions())).To(Equal(5))
// Cast the boot partition into specific type to check the type and such
part := disk.Table.GetPartitions()[0]
part := table.GetPartitions()[0]
partition, ok := part.(*gpt.Partition)
Expect(ok).To(BeTrue())
// Should be efi type
@ -402,8 +403,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
// Should have predictable UUID
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
// Check the rest have the proper types
for i := 1; i < len(disk.Table.GetPartitions()); i++ {
part := disk.Table.GetPartitions()[i]
for i := 1; i < len(table.GetPartitions()); i++ {
part := table.GetPartitions()[i]
partition, ok := part.(*gpt.Partition)
Expect(ok).To(BeTrue())
// all of them should have the Linux fs type
@ -415,17 +416,19 @@ var _ = Describe("Elemental", Label("elemental"), func() {
install.Firmware = v1.BIOS
Expect(install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)).To(BeNil())
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
disk, err := diskfs.Open(filepath.Join(tmpDir, "/test.img"), diskfs.WithOpenMode(diskfs.ReadOnly))
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
defer disk.Close()
Expect(err).ToNot(HaveOccurred())
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
Expect(err).ToNot(HaveOccurred())
// check that its type GPT
Expect(reflect.TypeOf(disk.Table)).To(Equal(reflect.TypeOf(&gpt.Table{})))
Expect(table.Type()).To(Equal("gpt"))
// Expect the disk UUID to be constant
Expect(strings.ToLower(disk.Table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
// 5 partitions (boot, oem, recovery, state and persistent)
Expect(len(disk.Table.GetPartitions())).To(Equal(5))
Expect(len(table.GetPartitions())).To(Equal(5))
// Cast the boot partition into specific type to check the type and such
part := disk.Table.GetPartitions()[0]
part := table.GetPartitions()[0]
partition, ok := part.(*gpt.Partition)
Expect(ok).To(BeTrue())
// Should be BIOS boot type
@ -434,8 +437,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
Expect(partition.Name).To(Equal(cnst.BiosPartName))
// Should have predictable UUID
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
for i := 1; i < len(disk.Table.GetPartitions()); i++ {
part := disk.Table.GetPartitions()[i]
for i := 1; i < len(table.GetPartitions()); i++ {
part := table.GetPartitions()[i]
partition, ok := part.(*gpt.Partition)
Expect(ok).To(BeTrue())
// all of them should have the Linux fs type
@ -845,7 +848,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
el := elemental.NewElemental(config)
Expect(config.Fs.Mkdir("/tmp", cnst.DirPerm)).To(BeNil())
Expect(el.SetDefaultGrubEntry("/tmp", "/imgMountpoint", "dio")).To(BeNil())
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs)
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
Expect(err).To(BeNil())
Expect(varsParsed["default_menu_entry"]).To(Equal("dio"))
})
@ -853,7 +856,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
el := elemental.NewElemental(config)
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs)
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
// Because it didnt do anything due to the entry being empty, the file should not be there
Expect(err).ToNot(BeNil())
_, err = config.Fs.Stat(filepath.Join("/tmp", cnst.GrubOEMEnv))
@ -868,7 +871,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
el := elemental.NewElemental(config)
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config.Fs)
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config)
Expect(err).To(BeNil())
Expect(varsParsed["default_menu_entry"]).To(Equal("test"))

View File

@ -9,7 +9,7 @@ import (
"strings"
"github.com/Masterminds/semver/v3"
"github.com/google/go-github/v63/github"
"github.com/google/go-github/v69/github"
"golang.org/x/oauth2"
)

View File

@ -24,9 +24,11 @@ func (d *Disk) NewPartitionTable(partType string, parts sdkTypes.PartitionList)
switch partType {
case v1.GPT:
table = &gpt.Table{
ProtectiveMBR: true,
GUID: cnst.DiskUUID, // Set know predictable UUID
Partitions: kairosPartsToDiskfsGPTParts(parts, d.Size),
ProtectiveMBR: true,
GUID: cnst.DiskUUID, // Set know predictable UUID
Partitions: kairosPartsToDiskfsGPTParts(parts, d.Size, d.LogicalBlocksize),
LogicalSectorSize: int(d.LogicalBlocksize),
PhysicalSectorSize: int(d.PhysicalBlocksize),
}
default:
return fmt.Errorf("invalid partition type: %s", partType)
@ -39,11 +41,11 @@ func (d *Disk) NewPartitionTable(partType string, parts sdkTypes.PartitionList)
return nil
}
func getSectorEndFromSize(start, size uint64) uint64 {
return (size / uint64(diskfs.SectorSize512)) + start - 1
func getSectorEndFromSize(start, size uint64, sectorSize int64) uint64 {
return (size / uint64(sectorSize)) + start - 1
}
func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) []*gpt.Partition {
func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64, sectorSize int64) []*gpt.Partition {
var partitions []*gpt.Partition
for index, part := range parts {
var start uint64
@ -51,7 +53,7 @@ func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) [
var size uint64
if len(partitions) == 0 {
// first partition, align to 1Mb
start = 1024 * 1024 / uint64(diskfs.SectorSize512)
start = 1024 * 1024 / uint64(sectorSize)
} else {
// get latest partition end, sum 1
start = partitions[len(partitions)-1].End + 1
@ -78,7 +80,7 @@ func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) [
}
end = getSectorEndFromSize(start, size)
end = getSectorEndFromSize(start, size, sectorSize)
if part.Name == cnst.EfiPartName && part.FS == cnst.EfiFs {
// EFI boot partition
@ -127,7 +129,7 @@ func WithLogger(logger sdkTypes.KairosLogger) func(d *Disk) error {
}
func NewDisk(device string, opts ...DiskOptions) (*Disk, error) {
d, err := diskfs.Open(device, diskfs.WithSectorSize(512))
d, err := diskfs.Open(device)
if err != nil {
return nil, err
}

View File

@ -21,6 +21,7 @@ import (
"regexp"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/kairos-io/kairos-sdk/types"
)
type MkfsCall struct {
@ -77,11 +78,11 @@ func (mkfs MkfsCall) Apply() (string, error) {
}
// FormatDevice formats a block device with the given parameters
func FormatDevice(runner v1.Runner, device string, fileSystem string, label string, opts ...string) error {
func FormatDevice(logger types.KairosLogger, runner v1.Runner, device string, fileSystem string, label string, opts ...string) error {
mkfs := MkfsCall{fileSystem: fileSystem, label: label, customOpts: opts, dev: device, runner: runner}
out, err := mkfs.Apply()
if err != nil {
fmt.Println(out)
logger.Logger.Warn().Err(err).Str("output", out).Msg("mkfs failed")
}
return err
}

View File

@ -36,7 +36,7 @@ const (
// ImageSource represents the source from where an image is created for easy identification
type ImageSource struct {
source string `yaml:"source"`
srcType string
srcType string `yaml:"type"`
}
func (i ImageSource) Value() string {

View File

@ -190,7 +190,7 @@ func (u *UpgradeSpec) RecoveryUpgrade() bool {
func (u *UpgradeSpec) Sanitize() error {
if u.RecoveryUpgrade() {
if u.Recovery.Source.IsEmpty() {
return fmt.Errorf(constants.UpgradeNoSourceError)
return fmt.Errorf(constants.UpgradeRecoveryNoSourceError)
}
if u.Partitions.Recovery == nil || u.Partitions.Recovery.MountPoint == "" {
return fmt.Errorf("undefined recovery partition")

View File

@ -17,9 +17,10 @@ limitations under the License.
package v1_test
import (
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"path/filepath"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
. "github.com/onsi/ginkgo/v2"
@ -451,7 +452,7 @@ var _ = Describe("Types", Label("types", "config"), func() {
It("fails with empty source", func() {
err := spec.Sanitize()
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(constants.UpgradeNoSourceError))
Expect(err.Error()).To(ContainSubstring(constants.UpgradeRecoveryNoSourceError))
})
It("fails with missing recovery partition", func() {
spec.Recovery.Source = v1.NewFileSrc("/tmp")

View File

@ -31,10 +31,12 @@ type FS interface {
RemoveAll(path string) error
ReadFile(filename string) ([]byte, error)
Readlink(name string) (string, error)
Symlink(oldname, newname string) error
RawPath(name string) (string, error)
ReadDir(dirname string) ([]fs.DirEntry, error)
Remove(name string) error
OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error)
WriteFile(filename string, data []byte, perm os.FileMode) error
Rename(oldpath, newpath string) error
Truncate(name string, size int64) error
}

View File

@ -17,6 +17,7 @@ limitations under the License.
package v1
import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/kairos-io/kairos-sdk/utils"
)
@ -30,7 +31,19 @@ type OCIImageExtractor struct{}
var _ ImageExtractor = OCIImageExtractor{}
func (e OCIImageExtractor) ExtractImage(imageRef, destination, platformRef string) error {
img, err := utils.GetImage(imageRef, utils.GetCurrentPlatform(), nil, nil)
// If we pass a platform
if platformRef != "" {
// make sure its correct
_, err := v1.ParsePlatform(platformRef)
if err != nil {
// and if we cannot properly parse it, then default to the current platform
platformRef = utils.GetCurrentPlatform()
}
} else {
// if we don't pass a platform, then default to the current platform
platformRef = utils.GetCurrentPlatform()
}
img, err := utils.GetImage(imageRef, platformRef, nil, nil)
if err != nil {
return err
}

View File

@ -166,3 +166,47 @@ func copyFile(src, dst string) error {
return destinationFile.Close()
}
func AddSystemdConfSortKey(fs v1.FS, artifactDir string, log sdkTypes.KairosLogger) error {
return fsutils.WalkDirFs(fs, artifactDir, func(path string, info os.DirEntry, err error) error {
if err != nil {
return err
}
// Only do files that are conf files but dont match the loader.conf
if !info.IsDir() && filepath.Ext(path) == ".conf" && !strings.Contains(info.Name(), "loader.conf") {
log.Logger.Debug().Str("path", path).Msg("Adding sort key to file")
conf, err := sdkutils.SystemdBootConfReader(path)
if err != nil {
log.Errorf("Error reading conf file to extract values %s: %s", conf, path)
}
// Now check and put the proper sort key
var sortKey string
// If we have 2 different files that start with active, like with the extra-cmdline, how do we set this?
// Ideally if they both have the same sort key, they will be sorted by name so the single one will be first
// and the extra-cmdline will be second. This is the best we can do currently without making this a mess
// Maybe we need the bootentry command to also set the sort key somehow?
switch {
case strings.Contains(info.Name(), "active"):
sortKey = "0001"
case strings.Contains(info.Name(), "passive"):
sortKey = "0002"
case strings.Contains(info.Name(), "recovery"):
sortKey = "0003"
case strings.Contains(info.Name(), "statereset"):
sortKey = "0004"
default: // Anything that dont matches, goes to the bottom
sortKey = "0010"
}
conf["sort-key"] = sortKey
newContents := ""
for k, v := range conf {
newContents = fmt.Sprintf("%s%s %s\n", newContents, k, v)
}
log.Logger.Trace().Str("contents", litter.Sdump(conf)).Str("path", path).Msg("Final values for conf file")
return os.WriteFile(path, []byte(newContents), 0600)
}
return nil
})
}

View File

@ -10,8 +10,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
var _ = Describe("Common functions tests", func() {

View File

@ -181,13 +181,25 @@ func (i *InstallAction) Run() (err error) {
return fmt.Errorf("removing artifact set with role %s: %w", UnassignedArtifactRole, err)
}
// add sort key to all files
err = AddSystemdConfSortKey(i.cfg.Fs, i.spec.Partitions.EFI.MountPoint, i.cfg.Logger)
if err != nil {
i.cfg.Logger.Warnf("adding sort key: %s", err.Error())
}
// Add boot assessment to files by appending +3 to the name
err = utils.AddBootAssessment(i.cfg.Fs, i.spec.Partitions.EFI.MountPoint, i.cfg.Logger)
if err != nil {
i.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
}
// SelectBootEntry sets the default boot entry to the selected entry
err = action.SelectBootEntry(i.cfg, "cos")
if err != nil {
i.cfg.Logger.Warnf("selecting active boot entry: %s", err.Error())
}
err = hook.Run(*i.cfg, i.spec, hook.UKIEncryptionHooks...)
err = hook.Run(*i.cfg, i.spec, hook.PostInstall...)
if err != nil {
i.cfg.Logger.Errorf("running uki encryption hooks: %s", err.Error())
return err
@ -211,7 +223,7 @@ func (i *InstallAction) Run() (err error) {
i.cfg.Logger.Errorf("running kairos-uki-install.after hook script: %s", err.Error())
}
return hook.Run(*i.cfg, i.spec, hook.AfterUkiInstall...)
return hook.Run(*i.cfg, i.spec, hook.FinishUKIInstall...)
}
func (i *InstallAction) SkipEntry(path string, conf map[string]string) (err error) {

View File

@ -2,7 +2,6 @@ package uki
import (
"fmt"
"github.com/kairos-io/kairos-agent/v2/pkg/action"
"github.com/kairos-io/kairos-agent/v2/pkg/config"
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
@ -35,14 +34,12 @@ func (r *ResetAction) Run() (err error) {
cleanup := utils.NewCleanStack()
defer func() { err = cleanup.Cleanup(err) }()
// Unmount partitions if any is already mounted before formatting
err = e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true))
if err != nil {
r.cfg.Logger.Errorf("unmounting partitions: %s", err.Error())
return err
}
// At this point, both partitions are unlocked but they might not be mounted, like persistent
// And the /dev/disk/by-label are not pointing to the proper ones
// We need to manually trigger udev to make sure the symlinks are correct
_, err = utils.SH("udevadm trigger --type=all || udevadm trigger")
_, err = utils.SH("udevadm settle")
// Reformat persistent partition
if r.spec.FormatPersistent {
persistent := r.spec.Partitions.Persistent
if persistent != nil {
@ -58,11 +55,20 @@ func (r *ResetAction) Run() (err error) {
if r.spec.FormatOEM {
oem := r.spec.Partitions.OEM
if oem != nil {
err = e.UnmountPartition(oem)
if err != nil {
return err
}
err = e.FormatPartition(oem)
if err != nil {
r.cfg.Logger.Errorf("formatting OEM partition: %s", err.Error())
return err
}
// Mount it back, as oem is mounted during recovery, keep everything as is
err = e.MountPartition(oem)
if err != nil {
return err
}
}
}
@ -80,6 +86,19 @@ func (r *ResetAction) Run() (err error) {
r.cfg.Logger.Errorf("copying recovery to active: %s", err.Error())
return fmt.Errorf("copying recovery to active: %w", err)
}
// add sort key to all files
err = AddSystemdConfSortKey(r.cfg.Fs, r.spec.Partitions.EFI.MountPoint, r.cfg.Logger)
if err != nil {
r.cfg.Logger.Warnf("adding sort key: %s", err.Error())
}
// Add boot assessment to files by appending +3 to the name
err = elementalUtils.AddBootAssessment(r.cfg.Fs, r.spec.Partitions.EFI.MountPoint, r.cfg.Logger)
if err != nil {
r.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
}
// SelectBootEntry sets the default boot entry to the selected entry
err = action.SelectBootEntry(r.cfg, "cos")
// Should we fail? Or warn?
@ -88,14 +107,6 @@ func (r *ResetAction) Run() (err error) {
return err
}
if mnt, err := elementalUtils.IsMounted(r.cfg, r.spec.Partitions.OEM); !mnt && err == nil {
err = e.MountPartition(r.spec.Partitions.OEM)
if err != nil {
r.cfg.Logger.Errorf("mounting oem partition: %s", err.Error())
return err
}
}
err = Hook(r.cfg, constants.AfterResetHook)
if err != nil {
r.cfg.Logger.Errorf("running after install hook: %s", err.Error())

View File

@ -110,6 +110,17 @@ func (i *UpgradeAction) Run() (err error) {
return fmt.Errorf("removing artifact set: %w", err)
}
// add sort key to all files
err = AddSystemdConfSortKey(i.cfg.Fs, i.spec.EfiPartition.MountPoint, i.cfg.Logger)
if err != nil {
i.cfg.Logger.Warnf("adding sort key: %s", err.Error())
}
// Add boot assessment to files by appending +3 to the name
err = elementalUtils.AddBootAssessment(i.cfg.Fs, i.spec.EfiPartition.MountPoint, i.cfg.Logger)
if err != nil {
i.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
}
// SelectBootEntry sets the default boot entry to the selected entry
err = action.SelectBootEntry(i.cfg, "cos")
// Should we fail? Or warn?

View File

@ -28,6 +28,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
@ -45,7 +46,7 @@ import (
"github.com/joho/godotenv"
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v5"
)
func CommandExists(command string) bool {
@ -177,7 +178,14 @@ func SyncData(log sdkTypes.KairosLogger, runner v1.Runner, fs v1.FS, source stri
}
log.Infof("Starting rsync...")
args := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls"}
// TODO: copy xattr if possible or needed for selinux contexts? Or do we just relabel those on first boot?
args := []string{
"--progress",
"--partial",
"--human-readable",
"--archive", // recursive, symbolic links, permissions, owner, group, modification times, device files, special files
"--acls", // preserve ACLS and permissions
}
for _, e := range excludes {
args = append(args, fmt.Sprintf("--exclude=%s", e))
@ -495,11 +503,27 @@ func CalcFileChecksum(fs v1.FS, fileName string) (string, error) {
// FindCommand will search for the command(s) in the options given to find the current command
// If it cant find it returns the default value give. Useful for the same binaries with different names across OS
func FindCommand(defaultPath string, options []string) string {
func FindCommand(fs v1.FS, defaultPath string, options []string) string {
for _, p := range options {
path, err := exec.LookPath(p)
if err == nil {
return path
// If its a full path, check if it exists directly
if strings.Contains(p, "/") {
d, err := fs.Stat(p)
if err != nil {
continue
}
if d.IsDir() {
continue
}
m := d.Mode()
// Check if its executable
if m&0111 != 0 {
return p
}
} else {
path, err := exec.LookPath(p)
if err == nil {
return path
}
}
}
@ -614,3 +638,72 @@ func CheckFailedInstallation(stateFile string) (bool, error) {
}
return false, nil
}
// AddBootAssessment adds boot assessment to files by appending +3 to the name
// Only for files that dont have it already as those are the ones upgraded
// Existing files that have a boot assessment will be left as is
// This should be called during install, upgrade and reset
// Mainly everything that updates the config files to point to a new artifact we need to reset the boot assessment
// as its a new artifact that needs to be assessed
func AddBootAssessment(fs v1.FS, artifactDir string, logger sdkTypes.KairosLogger) error {
return fsutils.WalkDirFs(fs, artifactDir, func(path string, info os.DirEntry, err error) error {
if err != nil {
return err
}
// Only do files that are conf files but dont match the loader.conf
if !info.IsDir() && filepath.Ext(path) == ".conf" && !strings.Contains(info.Name(), "loader.conf") {
dir := filepath.Dir(path)
ext := filepath.Ext(path)
base := strings.TrimSuffix(filepath.Base(path), ext)
// Lets check if the file has a boot assessment already. If it does, we dont need to do anything
// If it matches continue
re := regexp.MustCompile(`\+\d+(-\d+)?$`)
if re.MatchString(base) {
logger.Logger.Debug().Str("file", path).Msg("Boot assessment already present in file")
return nil
}
newBase := fmt.Sprintf("%s+3%s", base, ext)
newPath := filepath.Join(dir, newBase)
logger.Logger.Debug().Str("from", path).Str("to", newPath).Msg("Enabling boot assessment")
err = fs.Rename(path, newPath)
if err != nil {
logger.Logger.Err(err).Str("from", path).Str("to", newPath).Msg("Error renaming file")
return err
}
}
return nil
})
}
func ReadAssessmentFromEntry(fs v1.FS, entry string, logger sdkTypes.KairosLogger) (string, error) {
// Read current config for boot assessment from current config. We should already have the final config name
// Fix fallback and cos pointing to passive and active
if strings.HasPrefix(entry, "fallback") {
entry = strings.Replace(entry, "fallback", "passive", 1)
}
if strings.HasPrefix(entry, "cos") {
entry = strings.Replace(entry, "cos", "active", 1)
}
efiPart, err := partitions.GetEfiPartition(&logger)
if err != nil {
return "", err
}
// We only want the ones that match the assessment
currentfile, err := fsutils.GlobFs(fs, filepath.Join(efiPart.MountPoint, "loader/entries", entry+"+*.conf"))
if err != nil {
return "", err
}
if len(currentfile) == 0 {
return "", nil
}
if len(currentfile) > 1 {
return "", fmt.Errorf(cnst.MultipleEntriesAssessmentError, entry)
}
re := regexp.MustCompile(`(\+\d+(-\d+)?)\.conf$`)
if !re.MatchString(currentfile[0]) {
logger.Logger.Debug().Str("file", currentfile[0]).Msg(cnst.NoBootAssessmentWarning)
return "", nil
}
return re.FindStringSubmatch(currentfile[0])[1], nil
}

View File

@ -33,8 +33,8 @@ import (
"time"
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
// DirSize returns the accumulated size of all files in folder
@ -273,3 +273,38 @@ func Copy(fs v1.FS, src, dst string) error {
}
return nil
}
// GlobFs returns the names of all files matching pattern or nil if there is no matching file.
// Only consider the names of files in the directory included in the pattern, not in subdirectories.
// So the pattern "dir/*" will return only the files in the directory "dir", not in "dir/subdir".
func GlobFs(fs v1.FS, pattern string) ([]string, error) {
var matches []string
// Check if the pattern is well formed.
if _, err := filepath.Match(pattern, ""); err != nil {
return nil, err
}
// Split the pattern into directory and file parts.
dir, file := filepath.Split(pattern)
if dir == "" {
dir = "."
}
// Read the directory.
entries, err := fs.ReadDir(dir)
if err != nil {
return nil, err
}
// Match the entries against the pattern.
for _, entry := range entries {
if matched, err := filepath.Match(file, entry.Name()); err != nil {
return nil, err
} else if matched {
matches = append(matches, filepath.Join(dir, entry.Name()))
}
}
return matches, nil
}

View File

@ -24,6 +24,7 @@ import (
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-sdk/utils"
"io/fs"
"os"
"path/filepath"
"sort"
"strings"
@ -46,6 +47,9 @@ func NewGrub(config *agentConfig.Config) *Grub {
}
// Install installs grub into the device, copy the config file and add any extra TTY to grub
// TODO: Make it more generic to be able to call it from other places
// i.e.: filepath.Join(cnst.ActiveDir, "etc/kairos-release") seraches for the file in the active dir, we should be looking into the rootdir?
// filepath.Join(cnst.EfiDir, "EFI/boot/grub.cfg") we also write into the efi dir directly, this should be the a var maybe?
func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, stateLabel string) (err error) { // nolint:gocyclo
var grubargs []string
var grubdir, finalContent string
@ -58,16 +62,38 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool,
if !efi {
g.config.Logger.Info("Installing GRUB..")
// Find where in the rootDir the grub2 files for i386-pc are
// Use the modinfo.sh as a marker
grubdir = findGrubDir(g.config.Fs, rootDir)
if grubdir == "" {
g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Failed to find grub dir")
return fmt.Errorf("failed to find grub dir under %s", rootDir)
}
grubargs = append(
grubargs,
fmt.Sprintf("--root-directory=%s", rootDir),
fmt.Sprintf("--directory=%s", grubdir),
fmt.Sprintf("--boot-directory=%s", bootDir),
"--target=i386-pc",
target,
)
g.config.Logger.Debugf("Running grub with the following args: %s", grubargs)
out, err := g.config.Runner.Run(FindCommand("grub2-install", []string{"grub2-install", "grub-install"}), grubargs...)
grubBin := FindCommand(g.config.Fs, "", []string{
filepath.Join(rootDir, "/usr/sbin/", "grub2-install"),
filepath.Join(rootDir, "/usr/bin/", "grub2-install"),
filepath.Join(rootDir, "/sbin/", "grub2-install"),
filepath.Join(rootDir, "/usr/sbin/", "grub-install"),
filepath.Join(rootDir, "/usr/bin/", "grub-install"),
filepath.Join(rootDir, "/sbin/", "grub-install"),
})
g.config.Logger.Logger.Debug().Str("command", grubBin).Msg("Found grub binary")
if grubBin == "" {
g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Grub binary not found in path")
return fmt.Errorf("grub binary not found in path")
}
out, err := g.config.Runner.Run(grubBin, grubargs...)
if err != nil {
g.config.Logger.Errorf(string(out))
return err
@ -254,20 +280,62 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool,
return nil
}
// SetPersistentVariables sets the given vars into the given grubEnvFile for grub to read them
func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS) error {
// findGrubDir will find the grub dir under the dir given if possible by searching for the modinfo.sh
// And it will return the full dir path where the modinfo.sh is contained
func findGrubDir(vfs v1.FS, dir string) string {
var foundPath string
_ = fsutils.WalkDirFs(vfs, dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.Name() == "modinfo.sh" && strings.Contains(path, "i386-pc") {
// We found the grub dir, return it
foundPath = filepath.Dir(path)
return nil
}
return nil
})
return foundPath
}
func SetPersistentVariables(grubEnvFile string, vars map[string]string, c *agentConfig.Config) error {
var b bytes.Buffer
// Write header
b.WriteString("# GRUB Environment Block\n")
keys := make([]string, 0, len(vars))
for k := range vars {
// First we need to read the existing values from the grubenv file if they exist
finalVars, err := ReadPersistentVariables(grubEnvFile, c)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("error reading existing grubenv file %s: %s", grubEnvFile, err)
}
}
// Check if we have a nil var
if len(finalVars) != 0 {
c.Logger.Logger.Debug().Interface("existingVars", finalVars).Msg("Existing grubenv variables")
}
// Merge the existing vars with the new ones
// existing vars will be overridden by the new ones from vars if they match
for key, newValue := range vars {
if oldValue, exists := finalVars[key]; exists {
c.Logger.Logger.Warn().Str("key", key).Str("oldValue", oldValue).Str("newValue", newValue).Msg("Overriding existing grubenv variable")
}
finalVars[key] = newValue
}
keys := make([]string, 0, len(finalVars))
for k := range finalVars {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if len(vars[k]) > 0 {
b.WriteString(fmt.Sprintf("%s=%s\n", k, vars[k]))
if len(finalVars[k]) > 0 {
b.WriteString(fmt.Sprintf("%s=%s\n", k, finalVars[k]))
}
}
@ -277,7 +345,7 @@ func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS
for i := 0; i < toBeFilled; i++ {
b.WriteByte('#')
}
return fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm)
return c.Fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm)
}
// copyGrubFonts will try to finds and copy the needed grub fonts into the system
@ -394,11 +462,12 @@ func (g Grub) copyGrub() error {
}
// ReadPersistentVariables will read a grub env file and parse the values
func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, error) {
func ReadPersistentVariables(grubEnvFile string, c *agentConfig.Config) (map[string]string, error) {
vars := make(map[string]string)
f, err := fs.ReadFile(grubEnvFile)
f, err := c.Fs.ReadFile(grubEnvFile)
if err != nil {
return nil, err
return vars, err
}
for _, a := range strings.Split(string(f), "\n") {
// comment or fillup, so skip
@ -409,7 +478,7 @@ func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, e
if len(splitted) == 2 {
vars[splitted[0]] = splitted[1]
} else {
return nil, fmt.Errorf("invalid format for %s", a)
return vars, fmt.Errorf("invalid format for %s", a)
}
}
return vars, nil

27
pkg/utils/k8s/common.go Normal file
View File

@ -0,0 +1,27 @@
package k8s
import "os"
// GetHostDirForK8s returns the base dir where the system is
// This is because under k8s, we mount the actual system under a dir
// So we need to know which paths we need to read the configs from
// if we read them from the root directly, we are actually reading the
// configs of the upgrade container
// If not found returns an empty string
func GetHostDirForK8s() string {
_, underKubernetes := os.LookupEnv("KUBERNETES_SERVICE_HOST")
// Try to get the HOST_DIR in case we are not using the default one
hostDirEnv := os.Getenv("HOST_DIR")
// If we are under kubernetes but the HOST_DIR var is empty, default to /host as system-upgrade-controller mounts
// the host in that dir by default
if underKubernetes {
if hostDirEnv != "" {
return hostDirEnv
} else {
return "/host"
}
} else {
// We return an empty string so any filepath.join does not alter the paths
return ""
}
}

View File

@ -162,7 +162,6 @@ func GetPartitionViaDM(fs v1.FS, label string) *types.Partition {
if len(slaves) == 1 {
// We got the partition this dm is associated to, now lets read that partition udev identifier
partNumber, err := fs.ReadFile(filepath.Join(lp.SysBlock, dev.Name(), "slaves", slaves[0].Name(), "dev"))
fmt.Println(string(partNumber))
// If no errors and partNumber not empty read the device from udev
if err == nil || string(partNumber) != "" {
// Now for some magic!
@ -173,7 +172,6 @@ func GetPartitionViaDM(fs v1.FS, label string) *types.Partition {
// extract the udevInfo called ID_PART_ENTRY_DISK which gives us the udev ID of the parent disk
baseID := strings.Split(strings.TrimSpace(string(partNumber)), ":")
udevID = fmt.Sprintf("b%s:0", baseID[0])
fmt.Printf("Reading udevdata of device: %s\n", filepath.Join(lp.RunUdevData, udevID))
// Read udev info about this device
udevBytes, _ = fs.ReadFile(filepath.Join(lp.RunUdevData, udevID))
udevInfo = make(map[string]string)

View File

@ -30,8 +30,8 @@ import (
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
func writeCmdline(s string, fs v1.FS) error {

View File

@ -32,14 +32,15 @@ import (
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
"github.com/kairos-io/kairos-agent/v2/tests/matchers"
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
sdkTypes "github.com/kairos-io/kairos-sdk/types"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/twpayne/go-vfs/v4"
"github.com/twpayne/go-vfs/v4/vfst"
"github.com/twpayne/go-vfs/v5"
"github.com/twpayne/go-vfs/v5/vfst"
)
func getNamesFromListFiles(list []fs.DirEntry) []string {
@ -690,6 +691,17 @@ var _ = Describe("Utils", Label("utils"), func() {
err = fs.WriteFile(filepath.Join(rootDir, constants.GrubConf), []byte("console=tty1"), 0644)
Expect(err).ShouldNot(HaveOccurred())
// Create fake grub dir in rootfs and fake grub binaries
err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "sbin"), constants.DirPerm)
Expect(err).To(BeNil())
f, err := fs.Create(filepath.Join(rootDir, "sbin", "grub2-install"))
Expect(err).To(BeNil())
Expect(f.Chmod(0755)).ToNot(HaveOccurred())
err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"), constants.DirPerm)
Expect(err).To(BeNil())
_, err = fs.Create(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
Expect(err).To(BeNil())
})
It("installs with default values", func() {
grub := utils.NewGrub(config)
@ -740,6 +752,18 @@ var _ = Describe("Utils", Label("utils"), func() {
Expect(err).To(BeNil())
})
It("fails with bios if no grub2-install file exists", func() {
Expect(fs.RemoveAll(filepath.Join(rootDir, "sbin", "grub2-install"))).ToNot(HaveOccurred())
grub := utils.NewGrub(config)
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "")
Expect(err).To(HaveOccurred())
})
It("fails with bios if no modules files exists", func() {
Expect(fs.RemoveAll(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"))).ToNot(HaveOccurred())
grub := utils.NewGrub(config)
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "")
Expect(err).To(HaveOccurred())
})
It("fails with efi if no modules files exist", Label("efi"), func() {
grub := utils.NewGrub(config)
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", true, "")
@ -803,9 +827,9 @@ var _ = Describe("Utils", Label("utils"), func() {
defer os.Remove(temp.Name())
Expect(utils.SetPersistentVariables(
temp.Name(), map[string]string{"key1": "value1", "key2": "value2"},
config.Fs,
config,
)).To(BeNil())
readVars, err := utils.ReadPersistentVariables(temp.Name(), config.Fs)
readVars, err := utils.ReadPersistentVariables(temp.Name(), config)
Expect(err).To(BeNil())
Expect(readVars["key1"]).To(Equal("value1"))
Expect(readVars["key2"]).To(Equal("value2"))
@ -813,10 +837,35 @@ var _ = Describe("Utils", Label("utils"), func() {
It("Fails setting variables", func() {
e := utils.SetPersistentVariables(
"badfilenopath", map[string]string{"key1": "value1"},
config.Fs,
config,
)
Expect(e).NotTo(BeNil())
})
It("respects existing variables", func() {
temp, err := os.CreateTemp("", "grub-*")
Expect(err).ShouldNot(HaveOccurred())
defer os.Remove(temp.Name())
Expect(utils.SetPersistentVariables(
temp.Name(), map[string]string{"key1": "value1", "key2": "value2"},
config,
)).To(BeNil())
readVars, err := utils.ReadPersistentVariables(temp.Name(), config)
Expect(err).To(BeNil())
Expect(readVars["key1"]).To(Equal("value1"))
Expect(readVars["key2"]).To(Equal("value2"))
// Now we do it again with a different value
Expect(utils.SetPersistentVariables(
temp.Name(), map[string]string{"key1": "value3", "key3": "value4"},
config,
)).To(BeNil())
// Now there should be an extra key and key1 should be updated
readVars, err = utils.ReadPersistentVariables(temp.Name(), config)
Expect(err).To(BeNil())
Expect(readVars["key1"]).To(Equal("value3"))
Expect(readVars["key2"]).To(Equal("value2"))
Expect(readVars["key3"]).To(Equal("value4"))
})
})
})
Describe("CreateSquashFS", Label("CreateSquashFS"), func() {
@ -1062,4 +1111,206 @@ var _ = Describe("Utils", Label("utils"), func() {
Expect(utils.IsUkiWithFs(fs)).To(BeFalse())
})
})
Describe("AddBootAssessment", func() {
BeforeEach(func() {
Expect(fsutils.MkdirAll(fs, "/efi/loader/entries", os.ModePerm)).ToNot(HaveOccurred())
})
It("adds the boot assessment to a file", func() {
err := fs.WriteFile("/efi/loader/entries/test.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
Expect(err).ToNot(HaveOccurred())
Expect("/efi/loader/entries/test.conf").ToNot(matchers.BeAnExistingFileFs(fs))
// Should match with the +3
Expect("/efi/loader/entries/test+3.conf").To(matchers.BeAnExistingFileFs(fs))
})
It("adds the boot assessment to several files", func() {
err := fs.WriteFile("/efi/loader/entries/test1.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test3.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
Expect(err).ToNot(HaveOccurred())
Expect("/efi/loader/entries/test1.conf").ToNot(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test2.conf").ToNot(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test3.conf").ToNot(matchers.BeAnExistingFileFs(fs))
// Should match with the +3
Expect("/efi/loader/entries/test1+3.conf").To(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test2+3.conf").To(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test3+3.conf").To(matchers.BeAnExistingFileFs(fs))
})
It("leaves assessment in place for existing files", func() {
err := fs.WriteFile("/efi/loader/entries/test1.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test2+3.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test3+1-2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
Expect(err).ToNot(HaveOccurred())
Expect("/efi/loader/entries/test1.conf").ToNot(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test3+3.conf").ToNot(matchers.BeAnExistingFileFs(fs))
// Should match with the +3 and the existing ones left in place
Expect("/efi/loader/entries/test1+3.conf").To(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test2+3.conf").To(matchers.BeAnExistingFileFs(fs))
Expect("/efi/loader/entries/test3+1-2.conf").To(matchers.BeAnExistingFileFs(fs))
})
It("fails to write the boot assessment in non existing dir", func() {
err := utils.AddBootAssessment(fs, "/fake", logger)
Expect(err).To(HaveOccurred())
})
})
Describe("ReadAssessmentFromEntry", func() {
var ghwTest ghwMock.GhwMock
BeforeEach(func() {
Expect(fsutils.MkdirAll(fs, "/efi/loader/entries", os.ModePerm)).ToNot(HaveOccurred())
mainDisk := sdkTypes.Disk{
Name: "device",
Partitions: []*sdkTypes.Partition{
{
Name: "device1",
FilesystemLabel: "COS_GRUB",
FS: "ext4",
MountPoint: "/efi",
},
},
}
ghwTest = ghwMock.GhwMock{}
ghwTest.AddDisk(mainDisk)
ghwTest.CreateDevices()
})
AfterEach(func() {
ghwTest.Clean()
})
It("reads the assessment from a file", func() {
err := fs.WriteFile("/efi/loader/entries/test+2-1.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
entry, err := utils.ReadAssessmentFromEntry(fs, "test", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+2-1"))
})
It("reads passive when using fallback", func() {
err := fs.WriteFile("/efi/loader/entries/passive+2-1.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// fallback should point to passive
entry, err := utils.ReadAssessmentFromEntry(fs, "fallback", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+2-1"))
// Should find the passive entry as well directly
entry, err = utils.ReadAssessmentFromEntry(fs, "passive", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+2-1"))
})
It("reads active when using cos", func() {
err := fs.WriteFile("/efi/loader/entries/active+1-2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// cos should point to active
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+1-2"))
// Should find the active entry as well directly
entry, err = utils.ReadAssessmentFromEntry(fs, "active", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+1-2"))
})
It("empty assessment if it doesnt match", func() {
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
// Should find the active entry as well directly
entry, err = utils.ReadAssessmentFromEntry(fs, "active", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
})
It("fails with no EFI partition", func() {
ghwTest.Clean()
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
Expect(err).To(HaveOccurred())
Expect(entry).To(Equal(""))
})
It("errors if more than one file matches", func() {
err := fs.WriteFile("/efi/loader/entries/active+1-2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/active+3-2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
entry, err := utils.ReadAssessmentFromEntry(fs, "active", logger)
Expect(err).To(HaveOccurred())
Expect(entry).To(Equal(""))
Expect(err.Error()).To(Equal(fmt.Sprintf(constants.MultipleEntriesAssessmentError, "active")))
})
It("errors if dir doesn't exist", func() {
// Remove all dirs
cleanup()
entry, err := utils.ReadAssessmentFromEntry(fs, "active", logger)
Expect(err).To(HaveOccurred())
Expect(entry).To(Equal(""))
// Check that error is os.ErrNotExist
Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
})
It("matches with weird but valid format", func() {
// This are valid values, after all the asessment is just a string at the end that starts with + and has
// and number and an optional dash after that. It has to be before the .conf so this are valid values
// even if they are weird or stupid.
// potentially the name can be this if someone is rebuilding efi files and adding the + to indicate the build number
// for example.
// We dont use this but still want to check if these are valid.
err := fs.WriteFile("/efi/loader/entries/test1++++++5.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test2+3+3-1.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
entry, err := utils.ReadAssessmentFromEntry(fs, "test1", logger)
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+5"))
entry, err = utils.ReadAssessmentFromEntry(fs, "test2", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal("+3-1"))
})
It("doesn't match assessment if format is wrong", func() {
err := fs.WriteFile("/efi/loader/entries/test1+1djnfsdjknfsdajf2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test2+1-sadfsbauhdfkj.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test3+asdasd.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test4+-2.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = fs.WriteFile("/efi/loader/entries/test5+3&4.conf", []byte(""), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
entry, err := utils.ReadAssessmentFromEntry(fs, "test1", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
entry, err = utils.ReadAssessmentFromEntry(fs, "test2", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
entry, err = utils.ReadAssessmentFromEntry(fs, "test3", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
entry, err = utils.ReadAssessmentFromEntry(fs, "test4", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
entry, err = utils.ReadAssessmentFromEntry(fs, "test5", logger)
// It actually does not error but just doesn't match
Expect(err).ToNot(HaveOccurred())
Expect(entry).To(Equal(""))
})
})
})

View File

@ -1,22 +1,23 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
"config:recommended"
],
"schedule": [
"after 11pm every weekday",
"before 7am every weekday",
"every weekend"
],
"reviewers": [ "team:maintainers" ],
"reviewers": [
"team:maintainers"
],
"timezone": "Europe/Brussels",
"rebaseWhen": "behind-base-branch",
"constraints": {
"go": "1.22"
},
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"matchUpdateTypes": [
"patch"
],
"automerge": true
}
]

44
tests/matchers/fs.go Normal file
View File

@ -0,0 +1,44 @@
package matchers
import (
"fmt"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
"github.com/twpayne/go-vfs/v5"
"os"
)
// BeAnExistingFileFs returns a matcher that checks if a file exists in the given vfs.
func BeAnExistingFileFs(fs vfs.FS) types.GomegaMatcher {
return &beAnExistingFileFsMatcher{
fs: fs,
}
}
type beAnExistingFileFsMatcher struct {
fs vfs.FS
}
func (matcher *beAnExistingFileFsMatcher) Match(actual interface{}) (success bool, err error) {
actualFilename, ok := actual.(string)
if !ok {
return false, fmt.Errorf("BeAnExistingFileFs matcher expects a file path")
}
// Here is the magic, check existence against a vfs
if _, err = matcher.fs.Stat(actualFilename); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (matcher *beAnExistingFileFsMatcher) FailureMessage(actual interface{}) (message string) {
return format.Message(actual, "to exist")
}
func (matcher *beAnExistingFileFsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
return format.Message(actual, "not to exist")
}

View File

@ -22,15 +22,22 @@ import (
)
type FakeImageExtractor struct {
Logger sdkTypes.KairosLogger
SideEffect func(imageRef, destination, platformRef string) error
Logger sdkTypes.KairosLogger
SideEffect func(imageRef, destination, platformRef string) error
ClientCalls []ExtractCall
}
func (f FakeImageExtractor) GetOCIImageSize(imageRef, platformRef string) (int64, error) {
type ExtractCall struct {
ImageRef string
Destination string
PlatformRef string
}
func (f *FakeImageExtractor) GetOCIImageSize(imageRef, platformRef string) (int64, error) {
return 0, nil
}
var _ v1.ImageExtractor = FakeImageExtractor{}
var _ v1.ImageExtractor = &FakeImageExtractor{}
func NewFakeImageExtractor(logger sdkTypes.KairosLogger) *FakeImageExtractor {
l := logger
@ -42,8 +49,9 @@ func NewFakeImageExtractor(logger sdkTypes.KairosLogger) *FakeImageExtractor {
}
}
func (f FakeImageExtractor) ExtractImage(imageRef, destination, platformRef string) error {
func (f *FakeImageExtractor) ExtractImage(imageRef, destination, platformRef string) error {
f.Logger.Debugf("extracting %s to %s in platform %s", imageRef, destination, platformRef)
f.ClientCalls = append(f.ClientCalls, ExtractCall{ImageRef: imageRef, Destination: destination, PlatformRef: platformRef})
if f.SideEffect != nil {
f.Logger.Debugf("running side effect")
return f.SideEffect(imageRef, destination, platformRef)
@ -51,3 +59,24 @@ func (f FakeImageExtractor) ExtractImage(imageRef, destination, platformRef stri
return nil
}
// WasCalledWithImageRef is a helper method to confirm that the client was called with the given image ref
func (f *FakeImageExtractor) WasCalledWithImageRef(imageRef string) bool {
for _, c := range f.ClientCalls {
if c.ImageRef == imageRef {
return true
}
}
return false
}
// WasCalledWithExtractCall is a helper method to confirm that the client was called with the given extract call
// This matches exactly the calls made to the client in all fields
func (f *FakeImageExtractor) WasCalledWithExtractCall(call ExtractCall) bool {
for _, c := range f.ClientCalls {
if c == call {
return true
}
}
return false
}