Compare commits

...

69 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
37 changed files with 1889 additions and 300 deletions

View File

@ -14,7 +14,7 @@ jobs:
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v2.3.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@v2.0.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.22.2
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,9 +18,9 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go environment
uses: actions/setup-go@v5.4.0
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 ./...
@ -32,4 +29,4 @@ jobs:
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
file: ./coverage.out
files: ./coverage.out

View File

@ -12,7 +12,7 @@ jobs:
webui:
strategy:
matrix:
go-version: [ "1.23-bookworm" ]
go-version: [ "1.24-bookworm" ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

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:

81
go.mod
View File

@ -1,49 +1,47 @@
module github.com/kairos-io/kairos-agent/v2
go 1.23.6
toolchain go1.24.1
go 1.24.2
require (
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.2
github.com/diskfs/go-diskfs v1.6.0
github.com/erikgeiser/promptkit v0.9.0
github.com/google/go-containerregistry v0.20.3
github.com/google/go-containerregistry v0.20.5
github.com/hashicorp/go-multierror v1.1.1
github.com/jaypipes/ghw v0.16.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/kairos-io/go-nodepair v0.3.0
github.com/kairos-io/kairos-sdk v0.7.3
github.com/kairos-io/kcrypt v0.14.1
github.com/labstack/echo/v4 v4.13.3
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-pluggable v0.0.0-20230126220627-7710299a0ae5
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82
github.com/mudler/yip v1.15.0
github.com/mudler/yip v1.16.1
github.com/nxadm/tail v1.4.11
github.com/onsi/ginkgo/v2 v2.23.3
github.com/onsi/gomega v1.36.3
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.6
golang.org/x/net v0.37.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sys v0.31.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.32.3
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/v69 v69.2.0
github.com/google/go-github/v70 v70.0.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 (
@ -77,16 +75,17 @@ require (
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // 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.5.0+incompatible // indirect
github.com/docker/cli v28.1.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.5.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // 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.2.0 // indirect
@ -94,12 +93,11 @@ require (
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-20241219185318-19dc140271bf // indirect
github.com/fsnotify/fsnotify v1.7.0 // 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.6.2 // indirect
github.com/go-git/go-git/v5 v5.13.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
@ -109,7 +107,7 @@ require (
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-20250208200701-d0013a598941 // 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
@ -125,7 +123,7 @@ require (
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.11 // 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
@ -133,7 +131,7 @@ 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.16 // indirect
@ -152,10 +150,7 @@ require (
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/otiai10/copy v1.14.1 // indirect
github.com/otiai10/mint v1.6.3 // 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
@ -165,7 +160,6 @@ require (
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.6 // indirect
@ -196,9 +190,7 @@ require (
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.6 // indirect
github.com/vishvananda/netlink v1.3.0 // 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
@ -209,19 +201,20 @@ require (
github.com/zcalusic/sysinfo v1.1.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.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.36.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.23.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/tools v0.30.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

179
go.sum
View File

@ -42,8 +42,6 @@ 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=
@ -105,9 +103,12 @@ github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRcc
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.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=
@ -122,21 +123,26 @@ 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.2 h1:khBr9RTkqAZFaMYK7PP8NooL30hqj3bSgRmj3Ouguls=
github.com/diskfs/go-diskfs v1.4.2/go.mod h1:ss1uAUBhgDdEOewZFDWWpYqJFjNPbK7hYSjRoQE+D94=
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.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=
@ -144,8 +150,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
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.4.0 h1:4GyuSbFa+s26+3rmYNSuUVsx+HgPrV1bk1jXI0l9wjM=
github.com/elazarl/goproxy v1.4.0/go.mod h1:X/5W/t+gzDyLfHW4DrMdpjqYjpXsURlBt9lpBDxZZZQ=
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=
@ -160,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-20241219185318-19dc140271bf h1:eKPYdh9Dq7P/Tc6GRt4HqqsVK8b2vt0IGP+xmZ8dMjo=
github.com/foxboron/go-uefi v0.0.0-20241219185318-19dc140271bf/go.mod h1:q85c4IRlhhwdRJgGIUWrisDjU8dgcMj8dnXZCXo3hus=
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=
@ -179,8 +185,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
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.13.2 h1:7O7xvsK7K+rZPKW6AQR1YyNhfywkv7B8/FsP3ki6Zv0=
github.com/go-git/go-git/v5 v5.13.2/go.mod h1:hWdW5P4YZRjmpGHwRH2v3zkWcNl6HeXaXQEMGb3NJ9A=
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=
@ -230,13 +236,17 @@ 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/v70 v70.0.0/go.mod h1:xBUZgo8MI3lUL/hwxl3hlceJW1U8MVnXP3zUyI+rhQY=
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-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc=
github.com/google/pprof v0.0.0-20250208200701-d0013a598941/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=
@ -266,8 +276,6 @@ 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.15.0 h1:kjn+8fWVtB/DKfwMwpojLFMM6a3zdBF1OnBhAbvJ1BI=
github.com/jaypipes/ghw v0.15.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=
@ -283,12 +291,14 @@ github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
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.7.3 h1:OyDSEQVtc1MnRrP3M8d+wref0RA3eZof/FTL5ETOlXM=
github.com/kairos-io/kairos-sdk v0.7.3/go.mod h1:ZSxP3VgOE2+f/3IdPNcGK7qcYLWl44zV+gq0m+9ovoo=
github.com/kairos-io/kcrypt v0.14.0 h1:7dEg/gLDJkT06XutnLFwjp8ExfxtgYImF8OaCoVRbpY=
github.com/kairos-io/kcrypt v0.14.0/go.mod h1:qz/QBCg7phprxHogsjouhlish2Jz92JsmFbJVg2M5cI=
github.com/kairos-io/kcrypt v0.14.1 h1:dkZ+dJO9reAlZ60R/aS5iDyg/ZC3MtWTPUEfiYvSPyE=
github.com/kairos-io/kcrypt v0.14.1/go.mod h1:Bn0a2d09xrRvL/n5CTtqsCFKu900vtOpdgxAhDRJJu8=
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-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
@ -302,6 +312,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
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=
@ -316,6 +328,8 @@ 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.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=
@ -332,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=
@ -370,8 +386,10 @@ github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5 h1:FaZD86+A9mV
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.15.0 h1:msYgf+ZGFgresmdo9xB6+eVj6EnpRqelTU2ph1qKCs8=
github.com/mudler/yip v1.15.0/go.mod h1:xBWq88HvpJ5aJGJy4fCuhJ6lePG+uiE4z2RKstqO3n8=
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=
@ -390,31 +408,19 @@ 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.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/ginkgo/v2 v2.23.1 h1:Ox0cOPv/t8RzKJUfDo9ZKtRvBOJY369sFJnl00CjqwY=
github.com/onsi/ginkgo/v2 v2.23.1/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/ginkgo/v2 v2.23.2 h1:LYLd7Wz401p0N7xR8y7WL6D2QZwKpbirDg0EVIvzvMM=
github.com/onsi/ginkgo/v2 v2.23.2/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0=
github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
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.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU=
github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
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/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
github.com/otiai10/mint v1.6.3/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=
@ -436,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=
@ -448,19 +456,14 @@ 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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
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.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
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=
@ -519,7 +522,6 @@ 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=
@ -559,10 +561,8 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
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/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
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.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=
@ -595,30 +595,41 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS
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-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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
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-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
@ -632,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.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.23.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=
@ -650,11 +661,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
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.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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=
@ -662,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.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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=
@ -694,23 +711,25 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.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.10.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.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
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=
@ -718,8 +737,10 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
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=
@ -732,8 +753,10 @@ 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.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
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=
@ -797,8 +820,10 @@ 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.32.3 h1:ZPXXHblfBhYP89OnaozpFg9Ojl6HhDfxBLcdWNkaxW8=
k8s.io/mount-utils v0.32.3/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0=
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=

View File

@ -1,7 +1,6 @@
package hook
import (
"fmt"
"os"
"os/exec"
"path/filepath"
@ -43,7 +42,7 @@ func (b BundlePostInstall) Run(c config.Config, _ v1.Spec) error {
syscall.Sync()
err := c.Syscall.Mount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel), constants.UsrLocalPath, "ext4", 0, "")
if err != nil {
fmt.Printf("could not mount persistent: %s\n", err)
c.Logger.Logger.Err(err).Msg("could not mount persistent")
return err
}

View File

@ -7,9 +7,9 @@ import (
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"
@ -43,7 +43,11 @@ func (k Finish) Run(c config.Config, spec v1.Spec) error {
c.Logger.Logger.Info().Msg("Finished encrypt hook")
}
// Now that we have everything encrypted and ready if needed
// 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")
@ -79,14 +83,14 @@ func Encrypt(c config.Config, _ v1.Spec) error {
}()
for _, p := range c.Install.Encrypt {
_, err := kcrypt.Luksify(p, c.Logger)
_, err := kcrypt.Encrypt(p, c.Logger)
if err != nil {
c.Logger.Errorf("could not encrypt partition: %s", err)
return err
}
}
_ = kcrypt.UnlockAllWithLogger(false, c.Logger)
_ = kcrypt.UnlockAll(false, c.Logger)
for _, p := range c.Install.Encrypt {
for i := 0; i < 10; i++ {
@ -98,7 +102,7 @@ func Encrypt(c config.Config, _ 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(false, c.Logger)
err := kcrypt.UnlockAll(false, c.Logger)
if err != nil {
c.Logger.Debugf("UnlockAll returned: %s", err)
}
@ -188,7 +192,7 @@ func EncryptUKI(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, c.BindPublicPCRs, c.BindPCRs, c.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)
@ -201,7 +205,7 @@ func EncryptUKI(c config.Config, spec v1.Spec) error {
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
err = kcrypt.UnlockAllWithLogger(true, c.Logger)
err = kcrypt.UnlockAll(true, c.Logger)
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
if err != nil {
@ -223,7 +227,7 @@ func EncryptUKI(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)
err := kcrypt.UnlockAll(true, c.Logger)
if err != nil {
c.Logger.Debugf("UnlockAll returned: %s", err)
}

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

@ -2,7 +2,7 @@ package hook
import (
"fmt"
config "github.com/kairos-io/kairos-agent/v2/pkg/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-sdk/utils"
"strings"
@ -15,8 +15,7 @@ type Interface interface {
// 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{
&GrubOptions{}, // Set custom GRUB options in OEM partition
&Lifecycle{}, // Handles poweroff/reboot by config options
&Lifecycle{}, // Handles poweroff/reboot by config options
}
// FinishReset is a list of hooks that run when the reset process is finished completely.
@ -46,7 +45,7 @@ var FinishUKIInstall = []Interface{
// 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 int he config
// Most of this options are optional so they are not run by default unless specified in the config
var PostInstall = []Interface{
&Finish{},
}

View File

@ -1,7 +1,6 @@
package hook
import (
"fmt"
"path/filepath"
"syscall"
@ -39,7 +38,7 @@ func (k CopyLogs) Run(c config.Config, _ v1.Spec) error {
_ = 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 {
fmt.Printf("could not mount persistent: %s\n", err)
c.Logger.Logger.Warn().Err(err).Msg("could not mount persistent")
return nil
}

View File

@ -42,7 +42,7 @@ func Recovery() error {
}
if busErr != "" {
return fmt.Errorf(busErr)
return fmt.Errorf("%s", busErr)
}
if !agentConfig.Fast {

View File

@ -20,21 +20,10 @@
"cypress": "^14.0.0"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@cypress/request": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz",
"integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==",
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz",
"integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -51,7 +40,7 @@
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"performance-now": "^2.1.0",
"qs": "6.13.1",
"qs": "6.14.0",
"safe-buffer": "^5.1.2",
"tough-cookie": "^5.0.0",
"tunnel-agent": "^0.6.0",
@ -380,9 +369,9 @@
"license": "MIT"
},
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"version": "5.3.6",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
"funding": [
{
"type": "github",
@ -454,9 +443,9 @@
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
"integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -468,14 +457,14 @@
}
},
"node_modules/call-bound": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
"integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"get-intrinsic": "^1.2.6"
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
@ -571,9 +560,9 @@
}
},
"node_modules/cli-table3": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
"integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz",
"integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -583,7 +572,7 @@
"node": "10.* || >= 12.*"
},
"optionalDependencies": {
"@colors/colors": "1.5.0"
"colors": "1.4.0"
}
},
"node_modules/cli-truncate": {
@ -636,6 +625,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -698,14 +698,14 @@
}
},
"node_modules/cypress": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.2.0.tgz",
"integrity": "sha512-u7fuc9JEpSYLOdu8mzZDZ/JWsHUzR5pc8i1TeSqMz/bafXp+6IweMAeyphsEJ6/13qbB6nwTEY1m+GUAp6GqCQ==",
"version": "14.4.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-14.4.0.tgz",
"integrity": "sha512-/I59Fqxo7fqdiDi3IM2QKA65gZ7+PVejXg404/I8ZSq+NOnrmw+2pnMUJzpoNyg7KABcEBmgpkfAqhV98p7wJA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@cypress/request": "^3.0.7",
"@cypress/request": "^3.0.8",
"@cypress/xvfb": "^1.2.4",
"@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2",
@ -718,7 +718,7 @@
"check-more-types": "^2.24.0",
"ci-info": "^4.1.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.5",
"cli-table3": "0.6.1",
"commander": "^6.2.1",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
@ -742,7 +742,7 @@
"process": "^0.11.10",
"proxy-from-env": "1.0.0",
"request-progress": "^3.0.0",
"semver": "^7.5.3",
"semver": "^7.7.1",
"supports-color": "^8.1.1",
"tmp": "~0.2.3",
"tree-kill": "1.2.2",
@ -1070,18 +1070,18 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.0",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
@ -1819,13 +1819,13 @@
}
},
"node_modules/qs": {
"version": "6.13.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz",
"integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.6"
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@ -1904,9 +1904,9 @@
"license": "MIT"
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
"dev": true,
"license": "ISC",
"bin": {

317
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") != "" {
@ -805,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() {
@ -896,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
@ -929,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

View File

@ -694,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

@ -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"
@ -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

@ -130,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 {

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

@ -213,6 +213,13 @@ func contains(s []string, str string) bool {
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()

View File

@ -126,7 +126,8 @@ 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"
)

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
@ -588,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,7 +38,7 @@ 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"
@ -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

@ -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

@ -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,6 +31,7 @@ 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

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

@ -185,7 +185,6 @@ func SyncData(log sdkTypes.KairosLogger, runner v1.Runner, fs v1.FS, source stri
"--human-readable",
"--archive", // recursive, symbolic links, permissions, owner, group, modification times, device files, special files
"--acls", // preserve ACLS and permissions
"--atimes", // preserve access times
}
for _, e := range excludes {
@ -504,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
}
}
}

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"
@ -61,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
@ -257,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]))
}
}
@ -280,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
@ -397,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
@ -412,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

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

@ -691,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)
@ -741,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, "")
@ -804,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"))
@ -814,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() {

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
}