mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-06-03 01:44:53 +00:00
Compare commits
209 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a6f34820fb | ||
![]() |
75edc8b146 | ||
![]() |
7842ad8059 | ||
![]() |
530e2df7cf | ||
![]() |
7fb6ee0ff7 | ||
![]() |
e3d2a860d0 | ||
![]() |
8aaf0b9cac | ||
![]() |
53c1b6c9ea | ||
![]() |
2b1e5e66fb | ||
![]() |
5ec00e6ec1 | ||
![]() |
a708ffd175 | ||
![]() |
6d1b5c0c9d | ||
![]() |
86f54cf5c3 | ||
![]() |
e8bca1dcf0 | ||
![]() |
24d365e19b | ||
![]() |
62c2b834c6 | ||
![]() |
69e0ef7631 | ||
![]() |
599d53de34 | ||
![]() |
79cd5620c5 | ||
![]() |
30596d666b | ||
![]() |
4368a15d79 | ||
![]() |
3129aa16ea | ||
![]() |
251934559b | ||
![]() |
75a45c762f | ||
![]() |
d1c439c2dc | ||
![]() |
103789cb69 | ||
![]() |
d0f0710c78 | ||
![]() |
5d5a52930f | ||
![]() |
fb3e245554 | ||
![]() |
1a1f738903 | ||
![]() |
685dd0090c | ||
![]() |
cccda67781 | ||
![]() |
5d444a7ab9 | ||
![]() |
19b0348659 | ||
![]() |
db0f21164e | ||
![]() |
af8c24846c | ||
![]() |
e5b98de8b3 | ||
![]() |
e61dc8f00a | ||
![]() |
1182776075 | ||
![]() |
ad825b1308 | ||
![]() |
80d6f064c3 | ||
![]() |
7a39098c13 | ||
![]() |
32d3026be3 | ||
![]() |
97a7806148 | ||
![]() |
1c50dd2584 | ||
![]() |
d18bedd2de | ||
![]() |
b7ecc30b8f | ||
![]() |
c407692c10 | ||
![]() |
c94cd8c685 | ||
![]() |
86d710dd02 | ||
![]() |
9cd047ed88 | ||
![]() |
cf0afb0cce | ||
![]() |
62b6a63f57 | ||
![]() |
b2ced7173f | ||
![]() |
2f50886ba2 | ||
![]() |
c62f26884e | ||
![]() |
2b9a3359db | ||
![]() |
d6a9cd869c | ||
![]() |
cb1bda7e3c | ||
![]() |
bce1cdce45 | ||
![]() |
d83f78047f | ||
![]() |
f0bdaaacce | ||
![]() |
ea9ca53912 | ||
![]() |
8393b4401f | ||
![]() |
d4d1aac9ec | ||
![]() |
cb3d349fc8 | ||
![]() |
dc78072602 | ||
![]() |
96501020b3 | ||
![]() |
4975b9b914 | ||
![]() |
db703db5e5 | ||
![]() |
06aa2ce4e4 | ||
![]() |
030204d466 | ||
![]() |
2f3f2ce8ac | ||
![]() |
44a397a758 | ||
![]() |
796252bdc0 | ||
![]() |
bea5878fd2 | ||
![]() |
ca05938938 | ||
![]() |
eb9a8d1736 | ||
![]() |
6845373bfd | ||
![]() |
50fde398bc | ||
![]() |
9bf5c602ab | ||
![]() |
5c9e4dd7b9 | ||
![]() |
694ecdc63d | ||
![]() |
fd0471143f | ||
![]() |
a77b041b23 | ||
![]() |
b5869b4017 | ||
![]() |
e8e05379a5 | ||
![]() |
8695ba7526 | ||
![]() |
cc82369b30 | ||
![]() |
11cdddc452 | ||
![]() |
bb4225884e | ||
![]() |
e2f2b53b99 | ||
![]() |
127df512c0 | ||
![]() |
09c138ee8b | ||
![]() |
12d6227008 | ||
![]() |
9e9042fee3 | ||
![]() |
21d9d48947 | ||
![]() |
081014bd1a | ||
![]() |
013f24dfd5 | ||
![]() |
b3f6db40ab | ||
![]() |
6eea367c2a | ||
![]() |
445da19c7e | ||
![]() |
615cd97436 | ||
![]() |
0323e7d59d | ||
![]() |
d11dc122ae | ||
![]() |
d9381c3ae3 | ||
![]() |
3db8924ce8 | ||
![]() |
ae7b2d08a6 | ||
![]() |
a3dca67856 | ||
![]() |
edcf8723ab | ||
![]() |
a91fb13e39 | ||
![]() |
9b7b5db760 | ||
![]() |
892ad54e6f | ||
![]() |
163fa3d08b | ||
![]() |
ea105176e4 | ||
![]() |
eb36d8b6c1 | ||
![]() |
2955e3275b | ||
![]() |
477f0e6323 | ||
![]() |
a942a90dc4 | ||
![]() |
e2ff555707 | ||
![]() |
3412d7a2a2 | ||
![]() |
900779f98e | ||
![]() |
626fbe4ec0 | ||
![]() |
e2c6ef5a9b | ||
![]() |
1b6c87d1c1 | ||
![]() |
2e667634f5 | ||
![]() |
0266183421 | ||
![]() |
18911b7231 | ||
![]() |
488841ebf2 | ||
![]() |
347a30b261 | ||
![]() |
8a8ef8ff45 | ||
![]() |
63067dcbcb | ||
![]() |
d57bea8263 | ||
![]() |
235440e393 | ||
![]() |
c572314bcc | ||
![]() |
2a7be43e74 | ||
![]() |
e6ed19177f | ||
![]() |
195fb484d2 | ||
![]() |
f111e10906 | ||
![]() |
d8d75ebd37 | ||
![]() |
9e6d7f8b5c | ||
![]() |
d34d7e834b | ||
![]() |
78c109040d | ||
![]() |
71e8e5b801 | ||
![]() |
a8c24b7534 | ||
![]() |
1d7321fe00 | ||
![]() |
1197f49c00 | ||
![]() |
a5a55c636d | ||
![]() |
92ca5a6d53 | ||
![]() |
248c622260 | ||
![]() |
9d9da86e69 | ||
![]() |
22c65b3ac8 | ||
![]() |
a15e7746b1 | ||
![]() |
d80f7d60a4 | ||
![]() |
163b28729f | ||
![]() |
c6166c9b7a | ||
![]() |
6e1d1bbed3 | ||
![]() |
694cdff23e | ||
![]() |
bb11c5759e | ||
![]() |
704cba1d69 | ||
![]() |
70a5d04e99 | ||
![]() |
697b9edec4 | ||
![]() |
45aedb89f1 | ||
![]() |
49c6f0bdde | ||
![]() |
953b40cafd | ||
![]() |
5a1c6de530 | ||
![]() |
a2071ac740 | ||
![]() |
6b57b8dd93 | ||
![]() |
d45a645de5 | ||
![]() |
724a671607 | ||
![]() |
07480abde2 | ||
![]() |
7be897c1d5 | ||
![]() |
8516a19ff9 | ||
![]() |
a6dd348055 | ||
![]() |
8a726b1076 | ||
![]() |
c726263c27 | ||
![]() |
895e571bb3 | ||
![]() |
800b7a0246 | ||
![]() |
15a85eb614 | ||
![]() |
00409e2357 | ||
![]() |
04ef1ff989 | ||
![]() |
4f284cc117 | ||
![]() |
94cb4c9a7c | ||
![]() |
1e81cdef38 | ||
![]() |
785d9d2300 | ||
![]() |
b421676aec | ||
![]() |
9fb65f43d3 | ||
![]() |
9ea89f7610 | ||
![]() |
dcad8beac2 | ||
![]() |
6634e18aa3 | ||
![]() |
87fca9570f | ||
![]() |
00d0447757 | ||
![]() |
527ed05b1c | ||
![]() |
0f4b83d8c8 | ||
![]() |
3425759377 | ||
![]() |
7cb2219042 | ||
![]() |
c60b35f187 | ||
![]() |
3f2458d42c | ||
![]() |
a288b14e48 | ||
![]() |
4573dffbb6 | ||
![]() |
9f193d8d2e | ||
![]() |
8184c366ad | ||
![]() |
c7df8f3f0e | ||
![]() |
1e7cfe438c | ||
![]() |
f24511b2dd | ||
![]() |
e2c3a0e638 | ||
![]() |
d5ded907a1 | ||
![]() |
97d25b8993 | ||
![]() |
a3aadbbaa9 |
2
.github/workflows/dependabot_auto.yml
vendored
2
.github/workflows/dependabot_auto.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
- name: Dependabot metadata
|
||||
id: metadata
|
||||
uses: dependabot/fetch-metadata@v2.2.0
|
||||
uses: dependabot/fetch-metadata@v2.4.0
|
||||
with:
|
||||
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
skip-commit-verification: true
|
||||
|
2
.github/workflows/osv-scanner-pr.yaml
vendored
2
.github/workflows/osv-scanner-pr.yaml
vendored
@ -18,4 +18,4 @@ permissions:
|
||||
|
||||
jobs:
|
||||
scan-pr:
|
||||
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@v1.9.0"
|
||||
uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@v2.0.2"
|
||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@ -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:
|
||||
|
2
.github/workflows/secscan.yaml
vendored
2
.github/workflows/secscan.yaml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Gosec Security Scanner
|
||||
uses: securego/gosec@v2.21.4
|
||||
uses: securego/gosec@v2.22.4
|
||||
with:
|
||||
# we let the report trigger content trigger a failure using the GitHub Security features.
|
||||
args: '-no-fail -fmt sarif -out results.sarif ./...'
|
||||
|
11
.github/workflows/unit-tests.yml
vendored
11
.github/workflows/unit-tests.yml
vendored
@ -11,9 +11,6 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
unit-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ "1.23" ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -21,15 +18,15 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go environment
|
||||
uses: actions/setup-go@v5.0.2
|
||||
uses: actions/setup-go@v5.5.0
|
||||
with:
|
||||
go-version: '${{ matrix.go-version }}'
|
||||
go-version-file: go.mod
|
||||
- name: Run tests
|
||||
run: |
|
||||
go run github.com/onsi/ginkgo/v2/ginkgo run -p --github-output --covermode=atomic --coverprofile=coverage.out --race -r ./...
|
||||
- name: Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
file: ./coverage.out
|
||||
files: ./coverage.out
|
||||
|
7
.github/workflows/webui.yaml
vendored
7
.github/workflows/webui.yaml
vendored
@ -12,14 +12,13 @@ jobs:
|
||||
webui:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ "1.23-bookworm" ]
|
||||
go-version: [ "1.24-bookworm" ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install earthly
|
||||
uses: Luet-lab/luet-install-action@v1
|
||||
uses: earthly/actions-setup@v1
|
||||
with:
|
||||
repository: quay.io/kairos/packages
|
||||
packages: utils/earthly
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: WebUI tests
|
||||
run: earthly +webui-tests --GO_VERSION=${{ matrix.go-version }}
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
project_name: kairos-agent
|
||||
version: 2
|
||||
builds:
|
||||
- ldflags:
|
||||
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}"
|
||||
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
@ -11,17 +11,53 @@ builds:
|
||||
- amd64
|
||||
- arm64
|
||||
binary: '{{ .ProjectName }}'
|
||||
id: default
|
||||
- ldflags:
|
||||
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GOEXPERIMENT=boringcrypto
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
binary: '{{ .ProjectName }}'
|
||||
id: fips-amd64
|
||||
hooks:
|
||||
post:
|
||||
- bash -c 'set -e; go version {{.Path}} | grep boringcrypto || (echo "boringcrypto not found" && exit 1)'
|
||||
- ldflags:
|
||||
- -w -s -X "github.com/kairos-io/kairos-agent/v2/internal/common.VERSION={{.Tag}}" -X "github.com/kairos-io/kairos-agent/v2/internal/common.gitCommit={{.Commit}}"
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GOEXPERIMENT=boringcrypto
|
||||
- CC=aarch64-linux-gnu-gcc
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- arm64
|
||||
binary: '{{ .ProjectName }}'
|
||||
id: fips-arm64
|
||||
hooks:
|
||||
post:
|
||||
- bash -c 'set -e; go version {{.Path}} | grep boringcrypto || (echo "boringcrypto not found" && exit 1)'
|
||||
source:
|
||||
enabled: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Tag }}-source'
|
||||
archives:
|
||||
# Default template uses underscores instead of -
|
||||
- name_template: >-
|
||||
{{ .ProjectName }}-{{ .Tag }}-{{- title .Os }}-{{- if eq .Arch "amd64" }}x86_64{{- else if eq .Arch "386" }}i386{{- else }}{{ .Arch }}{{ end }}{{- if .Arm }}v{{ .Arm }}{{ end }}
|
||||
- id: default-archive
|
||||
ids:
|
||||
- default
|
||||
name_template: '{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
- id: fips-archive
|
||||
ids:
|
||||
- fips-arm64
|
||||
- fips-amd64
|
||||
name_template: '{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-fips'
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}-{{ .Tag }}-checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
version_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
|
196
go.mod
196
go.mod
@ -1,46 +1,47 @@
|
||||
module github.com/kairos-io/kairos-agent/v2
|
||||
|
||||
go 1.23.1
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/Masterminds/semver/v3 v3.3.0
|
||||
github.com/Masterminds/semver/v3 v3.3.1
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1
|
||||
github.com/diskfs/go-diskfs v1.4.1
|
||||
github.com/diskfs/go-diskfs v1.6.0
|
||||
github.com/erikgeiser/promptkit v0.9.0
|
||||
github.com/google/go-containerregistry v0.20.2
|
||||
github.com/google/go-containerregistry v0.20.5
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jaypipes/ghw v0.13.0 // indirect
|
||||
github.com/jaypipes/ghw v0.16.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kairos-io/kairos-sdk v0.6.0
|
||||
github.com/kairos-io/kcrypt v0.12.2
|
||||
github.com/labstack/echo/v4 v4.12.0
|
||||
github.com/kairos-io/go-nodepair v0.3.0
|
||||
github.com/kairos-io/kairos-sdk v0.9.3
|
||||
github.com/labstack/echo/v4 v4.13.4
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb
|
||||
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82
|
||||
github.com/mudler/yip v1.11.0
|
||||
github.com/mudler/yip v1.16.1
|
||||
github.com/nxadm/tail v1.4.11
|
||||
github.com/onsi/ginkgo/v2 v2.20.2
|
||||
github.com/onsi/gomega v1.34.2
|
||||
github.com/pterm/pterm v0.12.79
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
github.com/pterm/pterm v0.12.80
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/sanity-io/litter v1.5.8
|
||||
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/urfave/cli/v2 v2.27.4
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sys v0.25.0
|
||||
golang.org/x/net v0.40.0
|
||||
golang.org/x/oauth2 v0.30.0
|
||||
golang.org/x/sys v0.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/mount-utils v0.31.1
|
||||
k8s.io/mount-utils v0.33.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/google/go-github/v63 v63.0.0
|
||||
github.com/twpayne/go-vfs/v4 v4.3.0
|
||||
github.com/google/go-github/v69 v69.2.0
|
||||
github.com/google/go-github/v72 v72.0.0
|
||||
github.com/twpayne/go-vfs/v5 v5.0.4
|
||||
github.com/urfave/cli/v2 v2.27.6
|
||||
)
|
||||
|
||||
require (
|
||||
@ -50,12 +51,11 @@ require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.11.7 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.0.0 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.9 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.5 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/alecthomas/assert/v2 v2.6.0 // indirect
|
||||
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba // indirect
|
||||
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98 // indirect
|
||||
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
|
||||
@ -65,66 +65,65 @@ require (
|
||||
github.com/charmbracelet/bubbletea v0.24.2 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.7.1 // indirect
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 // indirect
|
||||
github.com/containerd/containerd v1.7.22 // indirect
|
||||
github.com/containerd/continuity v0.4.2 // indirect
|
||||
github.com/containerd/errdefs v0.1.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
||||
github.com/containerd/console v1.0.4 // indirect
|
||||
github.com/containerd/containerd v1.7.27 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
|
||||
github.com/containerd/typeurl/v2 v2.2.3 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/denisbrodbeck/machineid v1.0.1 // indirect
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/djherbis/times v1.6.0 // indirect
|
||||
github.com/docker/cli v27.1.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v27.3.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/cli v28.1.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v28.1.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/go-connections v0.5.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/edsrzf/mmap-go v1.1.0 // indirect
|
||||
github.com/eliukblau/pixterm v1.3.1 // indirect
|
||||
github.com/edsrzf/mmap-go v1.2.0 // indirect
|
||||
github.com/eliukblau/pixterm v1.3.2 // indirect
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-git/go-git/v5 v5.12.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
github.com/go-git/go-git/v5 v5.14.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/itchyny/gojq v0.12.16 // indirect
|
||||
github.com/itchyny/gojq v0.12.17 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
||||
github.com/jaypipes/pcidb v1.0.1 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
|
||||
github.com/jezek/xgb v1.1.0 // indirect
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 // indirect
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c // indirect
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 // indirect
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
@ -132,45 +131,41 @@ require (
|
||||
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/makiuchi-d/gozxing v0.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mauromorales/xpasswd v0.4.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mauromorales/xpasswd v0.4.1 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.1 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/mudler/entities v0.8.1 // indirect
|
||||
github.com/mudler/entities v0.8.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/runc v1.1.14 // indirect
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/otiai10/copy v1.14.0 // indirect
|
||||
github.com/packethost/packngo v0.29.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.17 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/xattr v0.4.9 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/qeesung/image2ascii v1.0.1 // indirect
|
||||
github.com/rancher-sandbox/linuxkit v1.0.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/saferwall/pe v1.5.4 // indirect
|
||||
github.com/saferwall/pe v1.5.6 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/secDre4mer/pkcs7 v0.0.0-20240322103146-665324a4461d // indirect
|
||||
@ -178,58 +173,59 @@ require (
|
||||
github.com/shirou/gopsutil/v4 v4.24.7 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/skeema/knownhosts v1.2.2 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spectrocloud-labs/herd v0.4.2 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/cast v1.7.1 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/swaggest/jsonschema-go v0.3.62 // indirect
|
||||
github.com/swaggest/refl v1.3.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/tredoe/osutil v1.5.0 // indirect
|
||||
github.com/twpayne/go-vfs/v4 v4.3.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/vbatts/tar-split v0.12.1 // indirect
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 // indirect
|
||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
github.com/zcalusic/sysinfo v1.1.2 // indirect
|
||||
github.com/zcalusic/sysinfo v1.1.3 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/image v0.18.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect
|
||||
google.golang.org/grpc v1.62.1 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
|
||||
golang.org/x/image v0.20.0 // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b // indirect
|
||||
google.golang.org/grpc v1.70.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
|
||||
pault.ag/go/modprobe v0.1.2 // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
pault.ag/go/modprobe v0.2.0 // indirect
|
||||
pault.ag/go/topsort v0.1.1 // indirect
|
||||
)
|
||||
|
527
go.sum
527
go.sum
@ -9,10 +9,9 @@ atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8p
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||
@ -24,17 +23,17 @@ github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi
|
||||
github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
|
||||
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
|
||||
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
|
||||
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
|
||||
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
|
||||
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
|
||||
github.com/ProtonMail/go-crypto v1.1.5 h1:eoAQfK2dwL+tFSFpr7TbOaPNUbPiJj4fLYwwGE1FQO4=
|
||||
github.com/ProtonMail/go-crypto v1.1.5/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
|
||||
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
@ -43,8 +42,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba h1:LJ/tQNki21ep58+YZElkXQVpswENcK66NMNv4JGZf7w=
|
||||
github.com/anatol/devmapper.go v0.0.0-20230829043248-59ac2b9706ba/go.mod h1:yZpXZj/k3rAZDY43DteaEzbnnxiz9OYijJqRcqWMKSw=
|
||||
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98 h1:SML/05friOcB5ohyBaVC1TZPaMsnLFU7OTcTBhTdygk=
|
||||
github.com/anatol/luks.go v0.0.0-20240507052915-92f8bb765f98/go.mod h1:71hQWy01rC95qOpZ315jMB69d4pI/PU6HnZhpnemx90=
|
||||
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f h1:4tLJrnm3h3biCFsXHQ9w6DVGwuZXW4KMfiKV/atSYXg=
|
||||
github.com/anatol/luks.go v0.0.0-20250316021219-8cd744c3576f/go.mod h1:kEOnWwULAKOORfFvE4dEkdRZJS7+NMJKxRb/vWvmARk=
|
||||
github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc h1:xMQuzBhj6hXQZufedPQM2OiGX2UcQHSptXtG3+28S8Q=
|
||||
github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc/go.mod h1:NC+g66bgkUjV1unIJXhHO35RHxVViWUzNeeKAkkO7DU=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@ -66,13 +65,12 @@ github.com/bool64/dev v0.2.31 h1:OS57EqYaYe2M/2bw9uhDCIFiZZwywKFS/4qMLN6JUmQ=
|
||||
github.com/bool64/dev v0.2.31/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
|
||||
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
|
||||
github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cavaliergopher/grab v2.0.0+incompatible h1:XLeGNAc7MIRTMb8RlRbN76uO8vx1/AeNMWWN7FYpDw8=
|
||||
github.com/cavaliergopher/grab v2.0.0+incompatible/go.mod h1:6ICNRTQPwkMP0m2sKIDv/9XkhFJJwiEOQyZ+8E4H7Yg=
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4=
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
@ -83,32 +81,37 @@ github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNW
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
|
||||
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
|
||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5 h1:Ig+OPkE3XQrrl+SKsOqAjlkrBN/zrr+Qpw7rCuDjRCE=
|
||||
github.com/containerd/console v1.0.4-0.20230706203907-8f6c4e4faef5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.22 h1:nZuNnNRA6T6jB975rx2RRNqqH2k6ELYKDZfqTHqwyy0=
|
||||
github.com/containerd/containerd v1.7.22/go.mod h1:e3Jz1rYRUZ2Lt51YrH9Rz0zPyJBOlSvB3ghr2jbVD8g=
|
||||
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
|
||||
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
|
||||
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
|
||||
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
|
||||
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
|
||||
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/containerd/containerd v1.7.27 h1:yFyEyojddO3MIGVER2xJLWoCIn+Up4GaHFquP7hsFII=
|
||||
github.com/containerd/containerd v1.7.27/go.mod h1:xZmPnl75Vc+BLGt4MIfu6bp+fy03gdHAn9bz+FreFR0=
|
||||
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
|
||||
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
|
||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
||||
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
|
||||
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -120,31 +123,37 @@ github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGwe
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/diskfs/go-diskfs v1.4.1 h1:iODgkzHLmvXS+1VDztpW53T+dQm8GQzi20y9yUd5UCA=
|
||||
github.com/diskfs/go-diskfs v1.4.1/go.mod h1:+tOkQs8CMMog6Nvljg8DGIxEXrgL48iyT3OM3IlSz74=
|
||||
github.com/diskfs/go-diskfs v1.6.0 h1:YmK5+vLSfkwC6kKKRTRPGaDGNF+Xh8FXeiNHwryDfu4=
|
||||
github.com/diskfs/go-diskfs v1.6.0/go.mod h1:bRFumZeGFCO8C2KNswrQeuj2m1WCVr4Ms5IjWMczMDk=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/cli v27.5.0+incompatible h1:aMphQkcGtpHixwwhAXJT1rrK/detk2JIvDaFkLctbGM=
|
||||
github.com/docker/cli v27.5.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v28.1.1+incompatible h1:eyUemzeI45DY7eDPuwUcmDyDj1pM98oD5MdSpiItp8k=
|
||||
github.com/docker/cli v28.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8=
|
||||
github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
|
||||
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ=
|
||||
github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/eliukblau/pixterm v1.3.1 h1:XeouQViH+lmzCa7sMUoK2cd7qlgHYGLIjwRKaOdJbKA=
|
||||
github.com/eliukblau/pixterm v1.3.1/go.mod h1:on5ueknFt+ZFVvIVVzQ7/JXwPjv5fJd8Q1Ybh7XixfU=
|
||||
github.com/edsrzf/mmap-go v1.2.0 h1:hXLYlkbaPzt1SaQk+anYwKSRNhufIDCchSPkUD6dD84=
|
||||
github.com/edsrzf/mmap-go v1.2.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/eliukblau/pixterm v1.3.2 h1:kAF9qvbaDV3emb9LPHw1Bvd9D5o4y28U0e8Q9vfl24I=
|
||||
github.com/eliukblau/pixterm v1.3.2/go.mod h1:CgaInx2l92Xo3GTldly4UQeNghSFXmIQNk3zL77Xo/A=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY=
|
||||
github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@ -157,8 +166,8 @@ github.com/erikgeiser/promptkit v0.9.0 h1:3qL1mS/ntCrXdb8sTP/ka82CJ9kEQaGuYXNrYJ
|
||||
github.com/erikgeiser/promptkit v0.9.0/go.mod h1:pU9dtogSe3Jlc2AY77EP7R4WFP/vgD4v+iImC83KsCo=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca h1:ErxkaWK5AIt8gQf3KpAuQQBdZI4ps72HzEe123kh+So=
|
||||
github.com/foxboron/go-uefi v0.0.0-20240805124652-e2076f0e58ca/go.mod h1:ffg/fkDeOYicEQLoO2yFFGt00KUTYVXI+rfnc8il6vQ=
|
||||
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f h1:SGo7y1xmmGWiQzp7QU3ueehmdMVkjj9Yyo1IDEuHbYw=
|
||||
github.com/foxboron/go-uefi v0.0.0-20250207204325-69fb7dba244f/go.mod h1:q85c4IRlhhwdRJgGIUWrisDjU8dgcMj8dnXZCXo3hus=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
@ -166,18 +175,18 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 h1:Y5Q2mEwfzjMt5+3u70Gtw93ZOu2UuPeeeTBDntF7FoY=
|
||||
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7 h1:VLEKvjGJYAMCXw0/32r9io61tEXnMWDRxMk+peyRVFc=
|
||||
github.com/gen2brain/shm v0.0.0-20230802011745-f2460f5984f7/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -191,18 +200,16 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -224,16 +231,22 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
|
||||
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
|
||||
github.com/google/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE=
|
||||
github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
|
||||
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
|
||||
github.com/google/go-containerregistry v0.20.4 h1:w/Fdj3ef046SdV/GJU69cCnreaLpqbTo1X9XPyHbkd4=
|
||||
github.com/google/go-containerregistry v0.20.4/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
|
||||
github.com/google/go-containerregistry v0.20.5 h1:4RnlYcDs5hoA++CeFjlbZ/U9Yp1EuWr+UhhTyYQjOP0=
|
||||
github.com/google/go-containerregistry v0.20.5/go.mod h1:Q14vdOOzug02bwnhMkZKD4e30pDaD9W65qzXpyzF49E=
|
||||
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
|
||||
github.com/google/go-github/v69 v69.2.0/go.mod h1:xne4jymxLR6Uj9b7J7PyTpkMYstEMMwGZa0Aehh1azM=
|
||||
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
|
||||
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -259,46 +272,53 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
|
||||
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
|
||||
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
|
||||
github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g=
|
||||
github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM=
|
||||
github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg=
|
||||
github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY=
|
||||
github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q=
|
||||
github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg=
|
||||
github.com/jaypipes/ghw v0.13.0 h1:log8MXuB8hzTNnSktqpXMHc0c/2k/WgjOMSUtnI1RV4=
|
||||
github.com/jaypipes/ghw v0.13.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
|
||||
github.com/jaypipes/ghw v0.16.0 h1:3HurCTS38VNpeQLo5fIdZsySuo/qAfpPSJ5t05QBFPM=
|
||||
github.com/jaypipes/ghw v0.16.0/go.mod h1:In8SsaDqlb1oTyrbmTC14uy+fbBMvp+xdqX51MidlD8=
|
||||
github.com/jaypipes/pcidb v1.0.1 h1:WB2zh27T3nwg8AE8ei81sNRb9yWBii3JGNJtT7K9Oic=
|
||||
github.com/jaypipes/pcidb v1.0.1/go.mod h1:6xYUz/yYEyOkIkUt2t2J2folIuZ4Yg6uByCGFXMCeE4=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||
github.com/jezek/xgb v1.1.0 h1:wnpxJzP1+rkbGclEkmwpVFQWpuE2PUGNUzP8SbfFobk=
|
||||
github.com/jezek/xgb v1.1.0/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
||||
github.com/kairos-io/kairos-sdk v0.4.6 h1:6dbKozJTku99P2vytz9M0xAnpkKKiAggSMPfT2vpw68=
|
||||
github.com/kairos-io/kairos-sdk v0.4.6/go.mod h1:QXYmZ2BMrJ0Iyp7I3+rvCYpZRMvwOtK/6IGCLhNL4tY=
|
||||
github.com/kairos-io/kairos-sdk v0.6.0 h1:A096lZVHE4rkvA5kG0Oss0085T0noUcf7AeppWGySR8=
|
||||
github.com/kairos-io/kairos-sdk v0.6.0/go.mod h1:7Y6Y/McniCyAJcmQfoTfKd09cwmwS40URaIVbJn8V2k=
|
||||
github.com/kairos-io/kcrypt v0.12.2 h1:+lr8FGS0AW6D5dWSmaR3+AobL1TBTnOFgCSYctKY+5I=
|
||||
github.com/kairos-io/kcrypt v0.12.2/go.mod h1:7SPiHzNMYl4MlxeB30s1YlHDYByTusu7u1mU5Nvicm0=
|
||||
github.com/kairos-io/go-nodepair v0.3.0 h1:JIMBAtbNhIAsx89aP61mQDGMuGFoIQH/woK2tMDYD6k=
|
||||
github.com/kairos-io/go-nodepair v0.3.0/go.mod h1:7i905W/KmR9DAcMSVJr/Wdb84E5Yyu9YLgj7chwX1xs=
|
||||
github.com/kairos-io/kairos-sdk v0.9.0 h1:Bcpf3nUwGvzreIdXBIZZRnS2LDPs496C0Reo+dpbkMs=
|
||||
github.com/kairos-io/kairos-sdk v0.9.0/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
|
||||
github.com/kairos-io/kairos-sdk v0.9.1 h1:5MagNf3ghNsQaH6sVXXLVSjClrDQ9UZrxjHYRghk26Q=
|
||||
github.com/kairos-io/kairos-sdk v0.9.1/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
|
||||
github.com/kairos-io/kairos-sdk v0.9.2 h1:A/9rbRpjZsBWniXSPzvT7I2dbbukgveUjrvk9iXH4AE=
|
||||
github.com/kairos-io/kairos-sdk v0.9.2/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
|
||||
github.com/kairos-io/kairos-sdk v0.9.3 h1:je3Q0mfm1p4y3jO3k0P/SUp4NEax8IwLveDlnZBB8Yc=
|
||||
github.com/kairos-io/kairos-sdk v0.9.3/go.mod h1:O3si3aCkYsOyjjLF2jKKTKUYW9948WcB7xR0ivKbB6M=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 h1:qq2nCpSrXrmvDGRxW0ruW9BVEV1CN2a9YDOExdt+U0o=
|
||||
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329/go.mod h1:2VPVQDR4wO7KXHwP+DAypEy67rXf+okUx2zjgpCxZw4=
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c h1:eKb4PqwAMhlqwXw0W3atpKaYaPGlXE/Fwh+xpCEYaPk=
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c/go.mod h1:VOfm8h1NySetVlpHDSnbpCMsvCgYaU+YDn4XezUy2+4=
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237 h1:YOp8St+CM/AQ9Vp4XYm4272E77MptJDHkwypQHIRl9Q=
|
||||
github.com/kbinani/screenshot v0.0.0-20230812210009-b87d31814237/go.mod h1:e7qQlOY68wOz4b82D7n+DdaptZAi+SHW0+yKiWZzEYE=
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767 h1:Ds6xHRvL0yjG4kZD05leRKt70mM18Fjt0+B5gIqqe1g=
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20230809052043-4d1c7e9d1767/go.mod h1:VOfm8h1NySetVlpHDSnbpCMsvCgYaU+YDn4XezUy2+4=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@ -306,13 +326,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
|
||||
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
|
||||
github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
|
||||
github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
@ -325,6 +346,8 @@ github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9
|
||||
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
@ -333,10 +356,10 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mauromorales/xpasswd v0.4.0 h1:Jf6mfA8lwQsYzwgfQADPDGV7l/liAvRrnG+nQTPy0j8=
|
||||
github.com/mauromorales/xpasswd v0.4.0/go.mod h1:Z3+aY19mhNfcGi3st0+RAVSz2vC+pyoju2S/FPN8kEg=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mauromorales/xpasswd v0.4.1 h1:b2UalYW/p4CN6D01+ZMXvMLoPAFnTwUuWxPvQBckGSw=
|
||||
github.com/mauromorales/xpasswd v0.4.1/go.mod h1:Imb54ard3hSA6jTEBLdues/Q4e1IfDzu9fPw6Eip28w=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@ -347,28 +370,26 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g=
|
||||
github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/mudler/entities v0.8.1 h1:/iZ3VrhZy8bSVr39IqoSwL4jphna2rgSYnJCUZakZ3s=
|
||||
github.com/mudler/entities v0.8.1/go.mod h1:exnXZF6qVnu4b9dEiH3sLEyxYBTknfkcJ3UCxyc/dwE=
|
||||
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb h1:F6TP0DW7C0U9sgm9g4uAs0Vp2JSkhn2umlyrNlxUKXw=
|
||||
github.com/mudler/go-nodepair v0.0.0-20221223092639-ba399a66fdfb/go.mod h1:6QsBHK5vY/wDnSpueKek3/qfoCZmdJ9pz7FnzQbP/gE=
|
||||
github.com/mudler/entities v0.8.2 h1:a7MHgXFC05IqTPJA4dVbm2TpMsmb3zmnvYsIZmaQTcQ=
|
||||
github.com/mudler/entities v0.8.2/go.mod h1:exnXZF6qVnu4b9dEiH3sLEyxYBTknfkcJ3UCxyc/dwE=
|
||||
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5 h1:FaZD86+A9mVt7lh9glAryzQblMsbJYU2VnrdZ8yHlTs=
|
||||
github.com/mudler/go-pluggable v0.0.0-20230126220627-7710299a0ae5/go.mod h1:WmKcT8ONmhDQIqQ+HxU+tkGWjzBEyY/KFO8LTGCu4AI=
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82 h1:FVT07EI8njvsD4tC2Hw8Xhactp5AWhsQWD4oTeQuSAU=
|
||||
github.com/mudler/go-processmanager v0.0.0-20240820160718-8b802d3ecf82/go.mod h1:Urp7LG5jylKoDq0663qeBh0pINGcRl35nXdKx82PSoU=
|
||||
github.com/mudler/yip v1.10.0 h1:MwEIySEfSRRwTUz2BmQQpRn6+M7jqVGf/OldsepBvz0=
|
||||
github.com/mudler/yip v1.10.0/go.mod h1:gwH7iGcr1Jimox2xKtN2AprEO00GzY7smvuycqCL7+Y=
|
||||
github.com/mudler/yip v1.11.0 h1:h+npjzSKM9VbShHxa+ywWZzpGIolKvN/e2FOT+rxKkI=
|
||||
github.com/mudler/yip v1.11.0/go.mod h1:gwH7iGcr1Jimox2xKtN2AprEO00GzY7smvuycqCL7+Y=
|
||||
github.com/mudler/yip v1.16.0 h1:TZr9zLghe5CJXRdvBK6f5uHe6RJtotweDU+m/GNT+gY=
|
||||
github.com/mudler/yip v1.16.0/go.mod h1:Wk3CIZCqdK58+1CzamA87atJD2y/dhDKXrguUyYipCc=
|
||||
github.com/mudler/yip v1.16.1 h1:SUq136jJ6QnX0FgP87IoIvkLT8OdEp3DYQUlKzI/gOQ=
|
||||
github.com/mudler/yip v1.16.1/go.mod h1:Wk3CIZCqdK58+1CzamA87atJD2y/dhDKXrguUyYipCc=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
@ -387,35 +408,30 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
||||
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
|
||||
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
|
||||
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
|
||||
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w=
|
||||
github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA=
|
||||
github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=
|
||||
github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
|
||||
github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
|
||||
github.com/packethost/packngo v0.29.0 h1:gRIhciVZQ/zLNrIdIdbOUyB/Tw5IgoaXyhP4bvE+D2s=
|
||||
github.com/packethost/packngo v0.29.0/go.mod h1:/UHguFdPs6Lf6FOkkSEPnRY5tgS0fsVM+Zv/bvBrmt0=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs=
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
@ -426,6 +442,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
|
||||
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
|
||||
@ -434,33 +452,32 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
|
||||
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
|
||||
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
|
||||
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
|
||||
github.com/pterm/pterm v0.12.79 h1:lH3yrYMhdpeqX9y5Ep1u7DejyHy7NSQg9qrBjF9dFT4=
|
||||
github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo=
|
||||
github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg=
|
||||
github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo=
|
||||
github.com/qeesung/image2ascii v1.0.1 h1:Fe5zTnX/v/qNC3OC4P/cfASOXS501Xyw2UUcgrLgtp4=
|
||||
github.com/qeesung/image2ascii v1.0.1/go.mod h1:kZKhyX0h2g/YXa/zdJR3JnLnJ8avHjZ3LrvEKSYyAyU=
|
||||
github.com/rancher-sandbox/linuxkit v1.0.2 h1:mUFPL2Mgl1XZ5H82ABR57t5H2G2Qd+lu3gMYvUGmeZo=
|
||||
github.com/rancher-sandbox/linuxkit v1.0.2/go.mod h1:n6Fkjc5qoMeWrnLSA5oqUF8ZzFKMrM960CtBwfvH1ZM=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
|
||||
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/saferwall/pe v1.5.4 h1:tLmMggEMUfeqrpJ25zS/okUQmyFdD5xWKL2+z9njCqg=
|
||||
github.com/saferwall/pe v1.5.4/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
|
||||
github.com/saferwall/pe v1.5.6 h1:DrRLnoQFxHWJ5lJUmrH7X2L0xeUu6SUS95Dc61eW2Yc=
|
||||
github.com/saferwall/pe v1.5.6/go.mod h1:mJx+PuptmNpoPFBNhWs/uDMFL/kTHVZIkg0d4OUJFbQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||
github.com/sanity-io/litter v1.5.8 h1:uM/2lKrWdGbRXDrIq08Lh9XtVYoeGtcQxk9rtQ7+rYg=
|
||||
github.com/sanity-io/litter v1.5.8/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b h1:gQZ0qzfKHQIybLANtM3mBXNUtOfsCFXeTsnBqCsx1KM=
|
||||
@ -479,11 +496,10 @@ github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnj
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448 h1:4T/wluVIsyQ0Kqamo3he0Q0FhZG7CBd5LJgb4KOmftM=
|
||||
github.com/sirupsen/logrus v1.9.4-0.20241118143825-d1e633264448/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
@ -492,10 +508,10 @@ github.com/spectrocloud-labs/herd v0.4.2 h1:90cYZmW0yxNw4PEbqNGSerrtqKSx1nvRbSwA
|
||||
github.com/spectrocloud-labs/herd v0.4.2/go.mod h1:WBlMIs1QZ7XtVrt9rAAFZpkh/fZYA4l2gGOCUS1LDHE=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -506,15 +522,15 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ=
|
||||
@ -533,28 +549,28 @@ github.com/tredoe/osutil v1.5.0 h1:UGVxbbHRoZi8xXVmbNZ2vgG6XoJ15ndE4LniiQ3rJKg=
|
||||
github.com/tredoe/osutil v1.5.0/go.mod h1:TEzphzUUunysbdDRfdOgqkg10POQbnfIPV50ynqOfIg=
|
||||
github.com/twpayne/go-vfs/v4 v4.3.0 h1:rTqFzzOQ/6ESKTSiwVubHlCBedJDOhQyVSnw8rQNZhU=
|
||||
github.com/twpayne/go-vfs/v4 v4.3.0/go.mod h1:tq2UVhnUepesc0lSnPJH/jQ8HruGhzwZe2r5kDFpEIw=
|
||||
github.com/twpayne/go-vfs/v5 v5.0.4 h1:/ne3h+rW7f5YOyOFguz+3ztfUwzOLR0Vts3y0mMAitg=
|
||||
github.com/twpayne/go-vfs/v5 v5.0.4/go.mod h1:zTPFJUbgsEMFNSWnWQlLq9wh4AN83edZzx3VXbxrS1w=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
|
||||
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
|
||||
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||
github.com/vbatts/tar-split v0.11.6 h1:4SjTW5+PU11n6fZenf2IPoV8/tz3AaYHMWjf23envGs=
|
||||
github.com/vbatts/tar-split v0.11.6/go.mod h1:dqKNtesIOr2j2Qv3W/cHjnvk9I8+G7oAkFDFN6TCBEI=
|
||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 h1:v6jG/tdl4O07LNVp74Nt7/OyL+1JsIW1M2f/nSvQheY=
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3/go.mod h1:CSBTxrhePCm0cmXNKDGeu+6bOQzpaEklfCqEpn89JWk=
|
||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g=
|
||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
@ -569,50 +585,57 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zcalusic/sysinfo v1.1.2 h1:38KUgZQmCxlN9vUTt4miis4rU5ISJXGXOJ2rY7bMC8g=
|
||||
github.com/zcalusic/sysinfo v1.1.2/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
|
||||
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=
|
||||
github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
|
||||
go.mozilla.org/pkcs7 v0.9.0 h1:yM4/HS9dYv7ri2biPtxt8ikvB37a980dg69/pKmS+eI=
|
||||
go.mozilla.org/pkcs7 v0.9.0/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0 h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.33.0/go.mod h1:wAy0T/dUbs468uOlkT31xjvqQgEVXv58BRFWEgn5v/0=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
|
||||
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
|
||||
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200420201142-3c4aac89819a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
@ -620,8 +643,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -637,14 +660,16 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -652,8 +677,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -663,16 +690,13 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -685,40 +709,40 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -729,31 +753,33 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47 h1:5iw9XJTD4thFidQmFVvx0wi4g5yOHk76rNRUxz1ZG5g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47/go.mod h1:AfA77qWLcidQWywD0YgqfpJzf50w2VjzBml3TybHeJU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b h1:FQtJ1MxbXoIIrZHZ33M+w5+dAP9o86rgpjoKr/ZmT7k=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250212204824-5a70512c5d8b/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
|
||||
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -763,8 +789,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
@ -786,20 +812,21 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
|
||||
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
|
||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
|
||||
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/mount-utils v0.31.1 h1:f8UrH9kRynljmdNGM6BaCvFUON5ZPKDgE+ltmYqI4wA=
|
||||
k8s.io/mount-utils v0.31.1/go.mod h1:HV/VYBUGqYUj4vt82YltzpWvgv8FPg0G9ItyInT3NPU=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
|
||||
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
pault.ag/go/modprobe v0.1.2 h1:bblunaPhqpTxGDJ5TVFW/4gheohBPleF2dIV6j6sWkI=
|
||||
pault.ag/go/modprobe v0.1.2/go.mod h1:afr2STC/2Maz/qi4+Bma1s0dszZgO/PcM8AKar9DWhM=
|
||||
pault.ag/go/topsort v0.0.0-20160530003732-f98d2ad46e1a/go.mod h1:INqx0ClF7kmPAMk2zVTX8DRnhZ/yaA/Mg52g8KFKE7k=
|
||||
k8s.io/mount-utils v0.33.0 h1:hH6EcCcax4lFNIERaGMj6d7oGMW1qW3eTCwHUuLtLog=
|
||||
k8s.io/mount-utils v0.33.0/go.mod h1:1JR4rKymg8B8bCPo618hpSAdrpO6XLh0Acqok/xVwPE=
|
||||
k8s.io/mount-utils v0.33.1 h1:hodPhfyoK+gG0SgnYwx1iPrlnpaESZiJ9GFzF5V/imE=
|
||||
k8s.io/mount-utils v0.33.1/go.mod h1:1JR4rKymg8B8bCPo618hpSAdrpO6XLh0Acqok/xVwPE=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
pault.ag/go/modprobe v0.2.0 h1:VF04w/Lez7qFHZX9QUZo2jVyTZINvYZG9dfvgZVXwXU=
|
||||
pault.ag/go/modprobe v0.2.0/go.mod h1:GDTMW6GviL6BFKkaYuzIPUCaPizqO1XD7Uf9pnNO6sg=
|
||||
pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4=
|
||||
pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=
|
||||
|
@ -3,12 +3,15 @@ package hook
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-sdk/bundles"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
)
|
||||
|
||||
// BundlePostInstall install bundles just after installation
|
||||
@ -28,33 +31,45 @@ func (b BundlePostInstall) Run(c config.Config, _ v1.Spec) error {
|
||||
// Note that the binding of /usr/local/.state/var-lib-extensions.bind to /var/lib/extensions on active/passive its done by inmmucore based on the
|
||||
// 00_rootfs.yaml config which sets the bind and ephemeral paths.
|
||||
c.Logger.Logger.Debug().Msg("Running BundlePostInstall hook")
|
||||
_ = machine.Umount(constants.PersistentDir)
|
||||
|
||||
machine.Mount("COS_PERSISTENT", "/usr/local") //nolint:errcheck
|
||||
_ = machine.Mount(constants.OEMLabel, constants.OEMPath)
|
||||
defer func() {
|
||||
machine.Umount("/usr/local") //nolint:errcheck
|
||||
_ = machine.Umount(constants.OEMPath)
|
||||
}()
|
||||
|
||||
err := os.MkdirAll("/usr/local/.state/var-lib-extensions.bind", os.ModeDir|os.ModePerm)
|
||||
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
|
||||
syscall.Sync()
|
||||
err := c.Syscall.Mount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel), constants.UsrLocalPath, "ext4", 0, "")
|
||||
if err != nil {
|
||||
c.Logger.Logger.Err(err).Msg("could not mount persistent")
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
c.Logger.Debugf("Unmounting persistent partition")
|
||||
err := machine.Umount(constants.UsrLocalPath)
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not unmount persistent partition: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = os.MkdirAll("/usr/local/.state/var-lib-extensions.bind", os.ModeDir|os.ModePerm)
|
||||
if c.FailOnBundleErrors && err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd := exec.Command("rsync", "-aqAX", "/var/lib/extensions", "/usr/local/.state/var-lib-extensions.bind")
|
||||
cmd := exec.Command("rsync", "-aqAX", "/var/lib/extensions/", "/usr/local/.state/var-lib-extensions.bind")
|
||||
_, err = cmd.CombinedOutput()
|
||||
if c.FailOnBundleErrors && err != nil {
|
||||
return err
|
||||
}
|
||||
err = syscall.Mount("/usr/local/.state/var-lib-extensions.bind", "/var/lib/extensions", "", syscall.MS_BIND, "")
|
||||
err = c.Syscall.Mount("/usr/local/.state/var-lib-extensions.bind", "/var/lib/extensions", "", syscall.MS_BIND, "")
|
||||
if c.FailOnBundleErrors && err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = syscall.Unmount("/var/lib/extensions", 0)
|
||||
}()
|
||||
|
||||
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
|
||||
defer func() {
|
||||
machine.Umount("/oem") //nolint:errcheck
|
||||
_ = machine.Umount("/var/lib/extensions")
|
||||
}()
|
||||
|
||||
opts := c.Install.Bundles.Options()
|
||||
|
@ -2,25 +2,127 @@ package hook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-sdk/kcrypt"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KcryptUKI struct{}
|
||||
// Finish is a hook that runs after the install process.
|
||||
// It is used to encrypt partitions and run the BundlePostInstall, CustomMounts and CopyLogs hooks
|
||||
type Finish struct{}
|
||||
|
||||
func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
func (k Finish) Run(c config.Config, spec v1.Spec) error {
|
||||
var err error
|
||||
if len(c.Install.Encrypt) != 0 || internalutils.IsUki() {
|
||||
c.Logger.Logger.Info().Msg("Running encrypt hook")
|
||||
|
||||
if internalutils.IsUki() {
|
||||
err = EncryptUKI(c, spec)
|
||||
} else {
|
||||
err = Encrypt(c, spec)
|
||||
}
|
||||
|
||||
// They return with partitions unlocked so make sure to lock them before we end
|
||||
defer lockPartitions(c)
|
||||
|
||||
if err != nil {
|
||||
c.Logger.Logger.Error().Err(err).Msg("could not encrypt partitions")
|
||||
return err
|
||||
}
|
||||
c.Logger.Logger.Info().Msg("Finished encrypt hook")
|
||||
}
|
||||
|
||||
// Now that we have everything encrypted and ready to mount if needed
|
||||
err = GrubPostInstallOptions{}.Run(c, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = BundlePostInstall{}.Run(c, spec)
|
||||
if err != nil {
|
||||
c.Logger.Logger.Warn().Err(err).Msg("could not copy run bundles post install")
|
||||
if c.FailOnBundleErrors {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = CustomMounts{}.Run(c, spec)
|
||||
if err != nil {
|
||||
c.Logger.Logger.Warn().Err(err).Msg("could not create custom mounts")
|
||||
}
|
||||
err = CopyLogs{}.Run(c, spec)
|
||||
if err != nil {
|
||||
c.Logger.Logger.Warn().Err(err).Msg("could not copy logs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt is a hook that encrypts partitions using kcrypt for non uki.
|
||||
// It will unmount OEM and PERSISTENT and return with them unmounted
|
||||
func Encrypt(c config.Config, _ v1.Spec) error {
|
||||
// Start with unmounted partitions
|
||||
_ = machine.Umount(constants.OEMDir) //nolint:errcheck
|
||||
_ = machine.Umount(constants.PersistentDir) //nolint:errcheck
|
||||
|
||||
// Config passed during install ends up here, so we need to read it, try to mount it
|
||||
_ = machine.Mount("COS_OEM", "/oem")
|
||||
defer func() {
|
||||
err := syscall.Unmount(constants.OEMPath, 0)
|
||||
if err != nil {
|
||||
c.Logger.Warnf("could not unmount Oem partition: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, p := range c.Install.Encrypt {
|
||||
_, err := kcrypt.Encrypt(p, c.Logger)
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not encrypt partition: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_ = kcrypt.UnlockAll(false, c.Logger)
|
||||
|
||||
for _, p := range c.Install.Encrypt {
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Logger.Infof("Waiting for unlocked partition %s to appear", p)
|
||||
_, _ = utils.SH("sync")
|
||||
part, _ := utils.SH(fmt.Sprintf("blkid -L %s", p))
|
||||
if part == "" {
|
||||
c.Logger.Infof("Partition %s not found, waiting %d seconds before retrying", p, i)
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
// Retry the unlock as well, because maybe the partition was not refreshed on time for unlock to unlock it
|
||||
// So no matter how many tries we do, it will still be locked and will never appear
|
||||
err := kcrypt.UnlockAll(false, c.Logger)
|
||||
if err != nil {
|
||||
c.Logger.Debugf("UnlockAll returned: %s", err)
|
||||
}
|
||||
if i == 9 {
|
||||
c.Logger.Errorf("Partition %s not unlocked/found after 10 retries", p)
|
||||
return fmt.Errorf("partition %s not unlocked/found after 10 retries", p)
|
||||
}
|
||||
continue
|
||||
}
|
||||
c.Logger.Infof("Partition found, continuing")
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// EncryptUKI encrypts the partitions using kcrypt in uki mode
|
||||
// It will unmount OEM and PERSISTENT and return with them unmounted
|
||||
func EncryptUKI(c config.Config, spec v1.Spec) error {
|
||||
// pre-check for systemd version, we need something higher or equal to 252
|
||||
run, err := utils.SH("systemctl --version | head -1 | awk '{ print $2}'")
|
||||
systemdVersion := strings.TrimSpace(string(run))
|
||||
@ -48,7 +150,7 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
// If systemd version is less than 252 return
|
||||
if systemdVersionInt < 252 {
|
||||
c.Logger.Infof("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
|
||||
return nil
|
||||
return fmt.Errorf("systemd version is %s, we need 252 or higher for encrypting partitions", systemdVersion)
|
||||
}
|
||||
|
||||
// Check for a TPM 2.0 device as its needed to encrypt
|
||||
@ -57,11 +159,9 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
_, err = os.Stat("/dev/tpmrm0")
|
||||
if err != nil {
|
||||
c.Logger.Warnf("Skipping partition encryption, could not find TPM 2.0 device at /dev/tpmrm0")
|
||||
return nil
|
||||
return fmt.Errorf("Skipping partition encryption, could not find TPM 2.0 device at /dev/tpmrm0")
|
||||
}
|
||||
|
||||
c.Logger.Logger.Debug().Msg("Running KcryptUKI hook")
|
||||
|
||||
// We always encrypt OEM and PERSISTENT under UKI
|
||||
// If mounted, unmount it
|
||||
_ = machine.Umount(constants.OEMDir) //nolint:errcheck
|
||||
@ -92,16 +192,11 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
|
||||
c.Logger.Infof("Encrypting %s", p)
|
||||
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
|
||||
err := kcrypt.LuksifyMeasurements(p, []string{"11"}, []string{}, c.Logger.Logger)
|
||||
err = kcrypt.EncryptWithPcrs(p, c.BindPublicPCRs, c.BindPCRs, c.Logger)
|
||||
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not encrypt partition: %s", err)
|
||||
if c.FailOnBundleErrors {
|
||||
return err
|
||||
}
|
||||
// Give time to show the error
|
||||
time.Sleep(10 * time.Second)
|
||||
return nil // do not error out
|
||||
return err
|
||||
}
|
||||
c.Logger.Infof("Done encrypting %s", p)
|
||||
}
|
||||
@ -109,31 +204,20 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
_, _ = utils.SH("sync")
|
||||
|
||||
_ = os.Setenv("SYSTEMD_LOG_LEVEL", "debug")
|
||||
err = kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
|
||||
|
||||
err = kcrypt.UnlockAll(true, c.Logger)
|
||||
|
||||
_ = os.Unsetenv("SYSTEMD_LOG_LEVEL")
|
||||
if err != nil {
|
||||
lockPartitions(c)
|
||||
c.Logger.Errorf("could not unlock partitions: %s", err)
|
||||
return err
|
||||
}
|
||||
// Close the unlocked partitions after dealing with them, otherwise we leave them open and they can be mounted by anyone
|
||||
defer func() {
|
||||
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}, c.Install.Encrypt...) {
|
||||
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
|
||||
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
|
||||
// There is a known error with cryptsetup that it can't close the device because of a semaphore
|
||||
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
|
||||
// output of the error
|
||||
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
|
||||
c.Logger.Errorf("could not close /dev/disk/by-label/%s: %s", p, out)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Here it can take the oem partition a bit of time to appear after unlocking so we need to retry a couple of time with some waiting
|
||||
// retry + backoff
|
||||
// Check all encrypted partitions are unlocked
|
||||
for _, p := range append([]string{constants.OEMLabel, constants.PersistentLabel}) {
|
||||
for _, p := range []string{constants.OEMLabel, constants.PersistentLabel} {
|
||||
for i := 0; i < 10; i++ {
|
||||
c.Logger.Infof("Waiting for unlocked partition %s to appear", p)
|
||||
_, _ = utils.SH("sync")
|
||||
@ -143,7 +227,7 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
// Retry the unlock as well, because maybe the partition was not refreshed on time for unlock to unlock it
|
||||
// So no matter how many tries we do, it will still be locked and will never appear
|
||||
err := kcrypt.UnlockAllWithLogger(true, c.Logger.Logger)
|
||||
err := kcrypt.UnlockAll(true, c.Logger)
|
||||
if err != nil {
|
||||
c.Logger.Debugf("UnlockAll returned: %s", err)
|
||||
}
|
||||
@ -158,29 +242,21 @@ func (k KcryptUKI) Run(c config.Config, spec v1.Spec) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Mount the unlocked oem partition
|
||||
err = machine.Mount(constants.OEMLabel, constants.OEMDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Copy back the contents of the oem partition that we saved before encrypting
|
||||
err = internalutils.SyncData(c.Logger, c.Runner, c.Fs, tmpDir, constants.OEMDir, []string{}...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Unmount the oem partition and leave everything unmounted
|
||||
err = machine.Umount(constants.OEMDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Logger.Logger.Debug().Msg("Finish KcryptUKI hook")
|
||||
// We now have the partitions unlocked and ready, lets call the other hooks here instead of closing and reopening them each time
|
||||
err = BundlePostInstall{}.Run(c, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = CustomMounts{}.Run(c, spec)
|
||||
err = CopyLogs{}.Run(c, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -1,48 +1,53 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"fmt"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Run(c config.Config, spec v1.Spec) error
|
||||
}
|
||||
|
||||
var AfterInstall = []Interface{
|
||||
&GrubOptions{}, // Set custom GRUB options
|
||||
&BundlePostInstall{},
|
||||
&CustomMounts{},
|
||||
&CopyLogs{},
|
||||
// FinishInstall is a list of hooks that run when the install process is finished completely.
|
||||
// Its mean for options that are not related to the install process itself
|
||||
var FinishInstall = []Interface{
|
||||
&Lifecycle{}, // Handles poweroff/reboot by config options
|
||||
}
|
||||
|
||||
var AfterReset = []Interface{
|
||||
&CopyLogs{},
|
||||
&Lifecycle{},
|
||||
// FinishReset is a list of hooks that run when the reset process is finished completely.
|
||||
var FinishReset = []Interface{
|
||||
&CopyLogs{}, // Try to copy the reset logs to the persistent partition
|
||||
&Lifecycle{}, // Handles poweroff/reboot by config options
|
||||
}
|
||||
|
||||
var AfterUpgrade = []Interface{
|
||||
&Lifecycle{},
|
||||
// FinishUpgrade is a list of hooks that run when the upgrade process is finished completely.
|
||||
var FinishUpgrade = []Interface{
|
||||
&Lifecycle{}, // Handles poweroff/reboot by config options
|
||||
}
|
||||
|
||||
// FirstBoot is a list of hooks that run on the first boot of the node.
|
||||
var FirstBoot = []Interface{
|
||||
&BundleFirstBoot{},
|
||||
&GrubPostInstallOptions{},
|
||||
}
|
||||
|
||||
// AfterUkiInstall sets which Hooks to run after uki runs the install action
|
||||
var AfterUkiInstall = []Interface{
|
||||
&SysExtPostInstall{},
|
||||
&Lifecycle{},
|
||||
// FinishUKIInstall is a list of hooks that run when the install process is finished completely.
|
||||
// Its mean for options that are not related to the install process itself
|
||||
var FinishUKIInstall = []Interface{
|
||||
&SysExtPostInstall{}, // Installs sysexts into the EFI partition
|
||||
&Lifecycle{}, // Handles poweroff/reboot by config options
|
||||
}
|
||||
|
||||
var UKIEncryptionHooks = []Interface{
|
||||
&KcryptUKI{},
|
||||
}
|
||||
|
||||
var EncryptionHooks = []Interface{
|
||||
&Kcrypt{},
|
||||
// PostInstall is a list of hooks that run after the install process has run.
|
||||
// Runs things that need to be done before we run other post install stages like
|
||||
// encrypting partitions, copying the install logs or installing bundles
|
||||
// Most of this options are optional so they are not run by default unless specified in the config
|
||||
var PostInstall = []Interface{
|
||||
&Finish{},
|
||||
}
|
||||
|
||||
func Run(c config.Config, spec v1.Spec, hooks ...Interface) error {
|
||||
@ -53,3 +58,18 @@ func Run(c config.Config, spec v1.Spec, hooks ...Interface) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockPartitions will try to close all the partitions that are unencrypted.
|
||||
func lockPartitions(c config.Config) {
|
||||
for _, p := range c.Install.Encrypt {
|
||||
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
|
||||
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
|
||||
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
|
||||
// There is a known error with cryptsetup that it can't close the device because of a semaphore
|
||||
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
|
||||
// output of the error
|
||||
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
|
||||
c.Logger.Debugf("could not close /dev/disk/by-label/%s: %s", p, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
|
@ -1,45 +0,0 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Kcrypt struct{}
|
||||
|
||||
func (k Kcrypt) Run(c config.Config, _ v1.Spec) error {
|
||||
if len(c.Install.Encrypt) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.Logger.Logger.Info().Msg("Running encrypt hook")
|
||||
|
||||
// We need to unmount the persistent partition to encrypt it
|
||||
// we dont know the state here so we better try
|
||||
err := machine.Umount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel)) //nolint:errcheck
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not unmount persistent partition: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Config passed during install ends up here, so we need to read it
|
||||
_ = machine.Mount("COS_OEM", "/oem")
|
||||
defer func() {
|
||||
_ = machine.Umount("/oem") //nolint:errcheck
|
||||
}()
|
||||
|
||||
for _, p := range c.Install.Encrypt {
|
||||
_, err := kcrypt.Luksify(p, c.Logger.Logger)
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not encrypt partition: %s", err)
|
||||
if c.FailOnBundleErrors {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Logger.Logger.Info().Msg("Finished encrypt hook")
|
||||
return nil
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
@ -9,49 +11,44 @@ import (
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
kcrypt "github.com/kairos-io/kcrypt/pkg/lib"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// CopyLogs copies all current logs to the persistent partition
|
||||
// useful during install to keep the livecd logs
|
||||
// best effort, no error handling
|
||||
type CopyLogs struct{}
|
||||
|
||||
// Run for CopyLogs copies all current logs to the persistent partition.
|
||||
// useful during install to keep the livecd logs. Its also run during reset
|
||||
// best effort, no error handling
|
||||
func (k CopyLogs) Run(c config.Config, _ v1.Spec) error {
|
||||
// TODO: If we have encryption under RESET we need to make sure to:
|
||||
// - Unlock the partitions
|
||||
// - Mount OEM so we can read the config for encryption (remote server)
|
||||
// - Mount the persistent partition
|
||||
c.Logger.Logger.Debug().Msg("Running CopyLogs hook")
|
||||
c.Logger.Debugf("Copying logs to persistent partition")
|
||||
_ = machine.Umount(constants.PersistentDir)
|
||||
_ = machine.Umount(constants.OEMDir)
|
||||
_ = machine.Umount(constants.OEMPath)
|
||||
|
||||
// Path if we have encrypted persistent
|
||||
if len(c.Install.Encrypt) != 0 {
|
||||
err := kcrypt.UnlockAll(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Close the unencrypted persistent partition at the end!
|
||||
defer func() {
|
||||
for _, p := range []string{constants.PersistentLabel} {
|
||||
c.Logger.Debugf("Closing unencrypted /dev/disk/by-label/%s", p)
|
||||
out, err := utils.SH(fmt.Sprintf("cryptsetup close /dev/disk/by-label/%s", p))
|
||||
// There is a known error with cryptsetup that it can't close the device because of a semaphore
|
||||
// doesnt seem to affect anything as the device is closed as expected so we ignore it if it matches the
|
||||
// output of the error
|
||||
if err != nil && !strings.Contains(out, "incorrect semaphore state") {
|
||||
c.Logger.Errorf("could not close /dev/disk/by-label/%s: %s", p, out)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Config passed during install ends up here, kcrypt challenger needs to read it if we are using a server for encryption
|
||||
_ = machine.Mount(constants.OEMLabel, constants.OEMPath)
|
||||
defer func() {
|
||||
_ = machine.Umount(constants.OEMPath)
|
||||
}()
|
||||
|
||||
err := machine.Mount(constants.PersistentLabel, constants.PersistentDir)
|
||||
_, _ = utils.SH("udevadm trigger --type=all || udevadm trigger")
|
||||
_ = utils.MkdirAll(c.Fs, constants.PersistentDir, 0755)
|
||||
err := c.Syscall.Mount(filepath.Join("/dev/disk/by-label", constants.PersistentLabel), constants.PersistentDir, "ext4", 0, "")
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not mount persistent partition: %s", err)
|
||||
c.Logger.Logger.Warn().Err(err).Msg("could not mount persistent")
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := machine.Umount(constants.PersistentDir)
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not unmount persistent partition: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Create the directory on persistent
|
||||
varLog := filepath.Join(constants.PersistentDir, ".state", "var-log.bind")
|
||||
|
||||
@ -66,11 +63,6 @@ func (k CopyLogs) Run(c config.Config, _ v1.Spec) error {
|
||||
c.Logger.Errorf("could not copy logs to persistent partition: %s", err)
|
||||
return nil
|
||||
}
|
||||
err = machine.Umount(constants.PersistentDir)
|
||||
if err != nil {
|
||||
c.Logger.Errorf("could not unmount persistent partition: %s", err)
|
||||
return nil
|
||||
}
|
||||
syscall.Sync()
|
||||
c.Logger.Debugf("Logs copied to persistent partition")
|
||||
c.Logger.Logger.Debug().Msg("Finish CopyLogs hook")
|
||||
|
@ -24,23 +24,21 @@ func saveCloudConfig(name config.Stage, yc yip.YipConfig) error {
|
||||
return os.WriteFile(filepath.Join("/oem", fmt.Sprintf("10_%s.yaml", name)), yipYAML, 0400)
|
||||
}
|
||||
|
||||
// Read the keys sections ephemeral_mounts and bind mounts from install key in the cloud config.
|
||||
// Run Read the keys sections ephemeral_mounts and bind mounts from install key in the cloud config.
|
||||
// If not empty write an environment file to /run/cos/custom-layout.env.
|
||||
// That env file is in turn read by /overlay/files/system/oem/11_persistency.yaml in fs.after stage.
|
||||
func (cm CustomMounts) Run(c config.Config, _ v1.Spec) error {
|
||||
|
||||
//fmt.Println("Custom mounts hook")
|
||||
//fmt.Println(strings.Join(c.Install.BindMounts, " "))
|
||||
//fmt.Println(strings.Join(c.Install.EphemeralMounts, " "))
|
||||
|
||||
if len(c.Install.BindMounts) == 0 && len(c.Install.EphemeralMounts) == 0 {
|
||||
return nil
|
||||
}
|
||||
c.Logger.Logger.Debug().Msg("Running CustomMounts hook")
|
||||
|
||||
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
|
||||
err := machine.Mount("COS_OEM", "/oem")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
machine.Umount("/oem") //nolint:errcheck
|
||||
_ = machine.Umount("/oem")
|
||||
}()
|
||||
|
||||
var mountsList = map[string]string{}
|
||||
@ -48,15 +46,22 @@ func (cm CustomMounts) Run(c config.Config, _ v1.Spec) error {
|
||||
mountsList["CUSTOM_BIND_MOUNTS"] = strings.Join(c.Install.BindMounts, " ")
|
||||
mountsList["CUSTOM_EPHEMERAL_MOUNTS"] = strings.Join(c.Install.EphemeralMounts, " ")
|
||||
|
||||
config := yip.YipConfig{Stages: map[string][]schema.Stage{
|
||||
"rootfs": []yip.Stage{{
|
||||
Name: "user_custom_mounts",
|
||||
EnvironmentFile: "/run/cos/custom-layout.env",
|
||||
Environment: mountsList,
|
||||
}},
|
||||
}}
|
||||
cfg := yip.YipConfig{
|
||||
Stages: map[string][]schema.Stage{
|
||||
"rootfs": {
|
||||
{
|
||||
Name: "user_custom_mounts",
|
||||
EnvironmentFile: "/run/cos/custom-layout.env",
|
||||
Environment: mountsList,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
saveCloudConfig("user_custom_mounts", config) //nolint:errcheck
|
||||
err = saveCloudConfig("user_custom_mounts", cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Logger.Logger.Debug().Msg("Finish CustomMounts hook")
|
||||
return nil
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/sanity-io/litter"
|
||||
|
||||
qr "github.com/kairos-io/go-nodepair/qrcode"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/bus"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
@ -27,7 +28,6 @@ import (
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
qr "github.com/mudler/go-nodepair/qrcode"
|
||||
"github.com/mudler/go-pluggable"
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
@ -215,6 +215,11 @@ func RunInstall(c *config.Config) error {
|
||||
utils.SetEnv(c.Env)
|
||||
utils.SetEnv(c.Install.Env)
|
||||
|
||||
err := c.CheckForUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// UKI path. Check if we are on UKI AND if we are running off a cd, otherwise it makes no sense to run the install
|
||||
// From the installed system
|
||||
if internalutils.IsUkiWithFs(c.Fs) {
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
|
@ -152,31 +152,45 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
userName, err := prompt("User to setup", "kairos", canBeEmpty, true, false)
|
||||
createUser, err := prompt("Do you want to create any users? If not, system will not be accesible via terminal or ssh", "y", yesNo, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userPassword, err := prompt("Password", "", canBeEmpty, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var userName, userPassword, sshKeys, makeAdmin string
|
||||
|
||||
if userPassword == "" {
|
||||
userPassword = "!"
|
||||
}
|
||||
if isYes(createUser) {
|
||||
userName, err = prompt("User to setup", "kairos", canBeEmpty, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
users, err := prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userPassword, err = prompt("Password", "", canBeEmpty, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup the users if we selected the default values as they are not valid users
|
||||
if users == "github:someuser,github:someuser2" {
|
||||
users = ""
|
||||
}
|
||||
if users != "" {
|
||||
sshUsers = strings.Split(users, ",")
|
||||
if userPassword == "" {
|
||||
userPassword = "!"
|
||||
}
|
||||
|
||||
sshKeys, err = prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
makeAdmin, err = prompt("Make the user an admin (with sudo permissions)?", "y", yesNo, true, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Cleanup the users if we selected the default values as they are not valid users
|
||||
if sshKeys == "github:someuser,github:someuser2" {
|
||||
sshKeys = ""
|
||||
}
|
||||
if sshKeys != "" {
|
||||
sshUsers = strings.Split(sshKeys, ",")
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt the user by prompts defined by the provider
|
||||
@ -216,41 +230,49 @@ func InteractiveInstall(debug, spawnShell bool, sourceImgURL string) error {
|
||||
return InteractiveInstall(debug, spawnShell, sourceImgURL)
|
||||
}
|
||||
|
||||
usersToSet := map[string]schema.User{}
|
||||
|
||||
stage := config.NetworkStage.String()
|
||||
|
||||
if userName != "" {
|
||||
user := schema.User{
|
||||
Name: userName,
|
||||
PasswordHash: userPassword,
|
||||
Groups: []string{"admin"},
|
||||
SSHAuthorizedKeys: sshUsers,
|
||||
}
|
||||
|
||||
// If we got no ssh keys, we don't need network, do the user as soon as possible
|
||||
if len(sshUsers) == 0 {
|
||||
stage = config.InitramfsStage.String()
|
||||
}
|
||||
|
||||
usersToSet = map[string]schema.User{
|
||||
userName: user,
|
||||
}
|
||||
}
|
||||
|
||||
cloudConfig := schema.YipConfig{Name: "Config generated by the installer",
|
||||
Stages: map[string][]schema.Stage{stage: {
|
||||
{
|
||||
Users: usersToSet,
|
||||
},
|
||||
}}}
|
||||
|
||||
// This is temporal to generate a valid cc file, no need to properly initialize everything
|
||||
cc := &config.Config{
|
||||
Install: &config.Install{
|
||||
Device: device,
|
||||
},
|
||||
}
|
||||
|
||||
var cloudConfig schema.YipConfig
|
||||
|
||||
// Only add the user stage if we have any users
|
||||
if userName != "" {
|
||||
var isAdmin []string
|
||||
|
||||
if isYes(makeAdmin) {
|
||||
isAdmin = append(isAdmin, "admin")
|
||||
}
|
||||
|
||||
user := schema.User{
|
||||
Name: userName,
|
||||
PasswordHash: userPassword,
|
||||
Groups: isAdmin,
|
||||
SSHAuthorizedKeys: sshUsers,
|
||||
}
|
||||
|
||||
stage := config.NetworkStage.String()
|
||||
// If we got no ssh keys, we don't need network, do the user as soon as possible
|
||||
if len(sshUsers) == 0 {
|
||||
stage = config.InitramfsStage.String()
|
||||
}
|
||||
|
||||
cloudConfig = schema.YipConfig{Name: "Config generated by the installer",
|
||||
Stages: map[string][]schema.Stage{stage: {
|
||||
{
|
||||
Users: map[string]schema.User{
|
||||
userName: user,
|
||||
},
|
||||
},
|
||||
}}}
|
||||
} else {
|
||||
// If no users, we need to set this option to skip the user validation and confirm that we want a system with no users.
|
||||
cc.Install.NoUsers = true
|
||||
}
|
||||
|
||||
// Merge all yamls into one
|
||||
dat, err := config.MergeYAML(cloudConfig, cc, result)
|
||||
if err != nil {
|
||||
|
@ -4,12 +4,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
qr "github.com/kairos-io/go-nodepair/qrcode"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/bus"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
|
||||
events "github.com/kairos-io/kairos-sdk/bus"
|
||||
"github.com/kairos-io/kairos-sdk/machine"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/bus"
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/cmd"
|
||||
qr "github.com/mudler/go-nodepair/qrcode"
|
||||
"github.com/mudler/go-pluggable"
|
||||
"github.com/pterm/pterm"
|
||||
)
|
||||
@ -42,7 +42,7 @@ func Recovery() error {
|
||||
}
|
||||
|
||||
if busErr != "" {
|
||||
return fmt.Errorf(busErr)
|
||||
return fmt.Errorf("%s", busErr)
|
||||
}
|
||||
|
||||
if !agentConfig.Fast {
|
||||
|
@ -38,6 +38,10 @@ func reset(reboot, unattended, resetOem bool, dir ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cfg.CheckForUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Load the installation Config from the cloud-config data
|
||||
resetSpec, err := config.ReadResetSpecFromConfig(cfg)
|
||||
if err != nil {
|
||||
@ -57,7 +61,7 @@ func reset(reboot, unattended, resetOem bool, dir ...string) error {
|
||||
|
||||
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
|
||||
|
||||
return hook.Run(*cfg, resetSpec, hook.AfterReset...)
|
||||
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
|
||||
}
|
||||
|
||||
func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
|
||||
@ -65,6 +69,10 @@ func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = cfg.CheckForUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Load the installation Config from the cloud-config data
|
||||
resetSpec, err := config.ReadUkiResetSpecFromConfig(cfg)
|
||||
if err != nil {
|
||||
@ -84,7 +92,7 @@ func resetUki(reboot, unattended, resetOem bool, dir ...string) error {
|
||||
|
||||
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
|
||||
|
||||
return hook.Run(*cfg, resetSpec, hook.AfterReset...)
|
||||
return hook.Run(*cfg, resetSpec, hook.FinishReset...)
|
||||
}
|
||||
|
||||
// sharedReset is the common reset code for both uki and non-uki
|
||||
|
@ -3,6 +3,7 @@ package agent
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
hook "github.com/kairos-io/kairos-agent/v2/internal/agent/hooks"
|
||||
@ -11,9 +12,9 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/bus"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
config "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/uki"
|
||||
internalutils "github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
k8sutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/k8s"
|
||||
events "github.com/kairos-io/kairos-sdk/bus"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
@ -64,23 +65,44 @@ func ListNewerReleases(includePrereleases bool) ([]string, error) {
|
||||
return tagList.FullImages()
|
||||
}
|
||||
|
||||
// TODO: Check where force and preReleases is being used? They dont seem to be used anywhere?
|
||||
func Upgrade(
|
||||
source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error {
|
||||
bus.Manager.Initialize()
|
||||
|
||||
fixedDirs := make([]string, len(dirs))
|
||||
// Check and fix dirs if we are under k8s, so we read the actual running system configs instead of only
|
||||
// the container configs
|
||||
// we can run it blindly as it will return an empty string if not under k8s
|
||||
hostdir := k8sutils.GetHostDirForK8s()
|
||||
for _, dir := range dirs {
|
||||
fixedDirs = append(fixedDirs, filepath.Join(hostdir, dir))
|
||||
}
|
||||
|
||||
if internalutils.UkiBootMode() == internalutils.UkiHDD {
|
||||
return upgradeUki(source, dirs, upgradeEntry, strictValidations)
|
||||
return upgradeUki(source, fixedDirs, upgradeEntry, strictValidations)
|
||||
} else {
|
||||
return upgrade(source, force, strictValidations, dirs, upgradeEntry, preReleases)
|
||||
return upgrade(source, fixedDirs, upgradeEntry, strictValidations)
|
||||
}
|
||||
}
|
||||
|
||||
func upgrade(source string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) error {
|
||||
upgradeSpec, c, err := generateUpgradeSpec(source, force, strictValidations, dirs, upgradeEntry, preReleases)
|
||||
func upgrade(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error {
|
||||
c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
err = c.CheckForUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = upgradeSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -93,7 +115,56 @@ func upgrade(source string, force, strictValidations bool, dirs []string, upgrad
|
||||
return err
|
||||
}
|
||||
|
||||
return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
|
||||
return hook.Run(*c, upgradeSpec, hook.FinishUpgrade...)
|
||||
}
|
||||
|
||||
func upgradeUki(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) error {
|
||||
c, err := getConfig(sourceImageURL, dirs, upgradeEntry, strictValidations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
err = c.CheckForUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = upgradeSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upgradeAction := uki.NewUpgradeAction(c, upgradeSpec)
|
||||
|
||||
err = upgradeAction.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return hook.Run(*c, upgradeSpec, hook.FinishUpgrade...)
|
||||
}
|
||||
|
||||
func getConfig(sourceImageURL string, dirs []string, upgradeEntry string, strictValidations bool) (*config.Config, error) {
|
||||
cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := config.Scan(collector.Directories(dirs...),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.StrictValidation(strictValidations))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
|
||||
}
|
||||
|
||||
func allReleases() (versioneer.TagList, error) {
|
||||
@ -155,30 +226,6 @@ func generateUpgradeConfForCLIArgs(source, upgradeEntry string) (string, error)
|
||||
return string(d), err
|
||||
}
|
||||
|
||||
func generateUpgradeSpec(sourceImageURL string, force, strictValidations bool, dirs []string, upgradeEntry string, preReleases bool) (*v1.UpgradeSpec, *config.Config, error) {
|
||||
cliConf, err := generateUpgradeConfForCLIArgs(sourceImageURL, upgradeEntry)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
c, err := config.Scan(collector.Directories(dirs...),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.StrictValidation(strictValidations))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return upgradeSpec, c, nil
|
||||
}
|
||||
|
||||
func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
|
||||
var result []string
|
||||
bus.Manager.Response(events.EventAvailableReleases, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
|
||||
@ -199,42 +246,6 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func upgradeUki(source string, dirs []string, upgradeEntry string, strictValidations bool) error {
|
||||
cliConf, err := generateUpgradeConfForCLIArgs(source, upgradeEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := config.Scan(collector.Directories(dirs...),
|
||||
collector.Readers(strings.NewReader(cliConf)),
|
||||
collector.StrictValidation(strictValidations))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utils.SetEnv(c.Env)
|
||||
|
||||
// Load the upgrade Config from the system
|
||||
upgradeSpec, err := config.ReadUkiUpgradeSpecFromConfig(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = upgradeSpec.Sanitize()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upgradeAction := uki.NewUpgradeAction(c, upgradeSpec)
|
||||
|
||||
err = upgradeAction.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return hook.Run(*c, upgradeSpec, hook.AfterUpgrade...)
|
||||
}
|
||||
|
||||
// ExtraConfigUpgrade is the struct that holds the upgrade options that come from flags and events
|
||||
type ExtraConfigUpgrade struct {
|
||||
Upgrade struct {
|
||||
|
2481
internal/webui/public/package-lock.json
generated
2481
internal/webui/public/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,6 @@
|
||||
"yamljs": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^13.0.0"
|
||||
"cypress": "^14.0.0"
|
||||
}
|
||||
}
|
||||
|
328
main.go
328
main.go
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@ -74,12 +75,13 @@ var cmds = []*cli.Command{
|
||||
&cli.BoolFlag{Name: "recovery", Usage: "Upgrade recovery"},
|
||||
},
|
||||
Description: `
|
||||
Manually upgrade a kairos node Active image. Does not upgrade passive or recovery images.
|
||||
Manually upgrade a kairos node Active image. Does not upgrade the passive image. It upgrades the recovery image when the --recovery flag is passed.
|
||||
|
||||
With no arguments, it defaults to latest available release. To specify a version, pass it as argument using the --source flag.
|
||||
Passing just the Kairos version as the first argument is no longer supported. If you speficy a positional argument, it will be treated
|
||||
To specify a version, pass it as argument using the --source flag. Passing just the Kairos version as the first argument is no longer supported. If you speficy a positional argument, it will be treated
|
||||
as a value for the --source flag.
|
||||
|
||||
You can also specify the upgrade image by setting "upgrade.system.uri" for the active image or "upgrade.recovery-system.uri" for the recovery image, in the cloud config.
|
||||
|
||||
To retrieve all the available versions, use "kairos upgrade list-releases"
|
||||
|
||||
$ kairos upgrade list-releases
|
||||
@ -166,24 +168,21 @@ See https://kairos.io/docs/upgrade/manual/ for documentation.
|
||||
return checkRoot()
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
var v string
|
||||
var source string
|
||||
if c.Args().Len() == 1 {
|
||||
v = c.Args().First()
|
||||
fmt.Println("Warning: Passing a version as a positional argument is deprecated. Use --source flag instead.")
|
||||
fmt.Println("The value will be used as a value for the --source flag")
|
||||
source = c.Args().First()
|
||||
}
|
||||
|
||||
if v := c.String("source"); v != "" {
|
||||
source = v
|
||||
}
|
||||
|
||||
image := c.String("image")
|
||||
if v := c.String("source"); v != "" {
|
||||
source = c.String("source")
|
||||
}
|
||||
|
||||
if image != "" {
|
||||
if v := c.String("image"); v != "" {
|
||||
fmt.Println("--image flag is deprecated, please use --source")
|
||||
// override source with image for now until we drop it
|
||||
source = fmt.Sprintf("oci:%s", image)
|
||||
source = fmt.Sprintf("oci:%s", v)
|
||||
}
|
||||
|
||||
if c.Bool("recovery") && c.String("boot-entry") != "" {
|
||||
@ -667,6 +666,10 @@ The validate command expects a configuration file as its only argument. Local fi
|
||||
Name: "cloud-init-paths",
|
||||
Usage: "Extra paths to add to the run stage",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "override-cloud-init-paths",
|
||||
Usage: "Override paths to use when running the stage, removing defaults. Supercedes --cloud-init-paths",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "analyze",
|
||||
Usage: "Only print the modules that would run in the order they would run",
|
||||
@ -690,6 +693,11 @@ The validate command expects a configuration file as its only argument. Local fi
|
||||
if len(c.StringSlice("cloud-init-paths")) > 0 {
|
||||
config.CloudInitPaths = append(config.CloudInitPaths, c.StringSlice("cloud-init-paths")...)
|
||||
}
|
||||
|
||||
if len(c.StringSlice("override-cloud-init-paths")) > 0 {
|
||||
config.CloudInitPaths = c.StringSlice("override-cloud-init-paths")
|
||||
}
|
||||
|
||||
if c.Bool("debug") {
|
||||
config.Logger.SetLevel("debug")
|
||||
}
|
||||
@ -796,6 +804,261 @@ The validate command expects a configuration file as its only argument. Local fi
|
||||
return action.ListBootEntries(cfg)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "sysext",
|
||||
Usage: "sysext subcommands",
|
||||
Description: "sysext subcommands",
|
||||
Before: func(c *cli.Context) error {
|
||||
_, err := exec.LookPath("systemd-sysext")
|
||||
if err != nil {
|
||||
return fmt.Errorf("systemd-sysext not found in PATH")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "list",
|
||||
Usage: "List all the installed system extensions",
|
||||
Description: "List all the installed system extensions",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "List the system extensions for the active boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "passive",
|
||||
Usage: "List the system extensions for the passive boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "recovery",
|
||||
Usage: "List the system extensions for the recovery boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "common",
|
||||
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
|
||||
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
|
||||
}
|
||||
|
||||
if err := checkRoot(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bootState string
|
||||
|
||||
if c.Bool("active") {
|
||||
bootState = "active"
|
||||
}
|
||||
if c.Bool("passive") {
|
||||
bootState = "passive"
|
||||
}
|
||||
out, err := action.ListSystemExtensions(cfg, bootState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(out) == 0 {
|
||||
cfg.Logger.Logger.Info().Msg("No system extensions found")
|
||||
return nil
|
||||
}
|
||||
for _, ext := range out {
|
||||
cfg.Logger.Info(litter.Sdump(ext))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Usage: "Enable a installed system extension for a give entry",
|
||||
UsageText: "enable [--active|--passive] EXTENSION",
|
||||
Description: "Enable a system extension for a given boot entry (active or passive)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "Enable the system extension for the active boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "passive",
|
||||
Usage: "Enable the system extension for the passive boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "recovery",
|
||||
Usage: "List the system extensions for the recovery boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "common",
|
||||
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "now",
|
||||
Usage: "Enable the system extension now and reload systemd-sysext",
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
if c.Args().Len() != 1 {
|
||||
return fmt.Errorf("extension name required")
|
||||
}
|
||||
|
||||
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
|
||||
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
|
||||
}
|
||||
|
||||
if noneOfEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
|
||||
return fmt.Errorf("either --active, --passive, --recovery or --common must be set")
|
||||
}
|
||||
|
||||
if err := checkRoot(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bootState string
|
||||
if c.Bool("active") {
|
||||
bootState = "active"
|
||||
}
|
||||
if c.Bool("passive") {
|
||||
bootState = "passive"
|
||||
}
|
||||
ext := c.Args().First()
|
||||
if err := action.EnableSystemExtension(cfg, ext, bootState, c.Bool("now")); err != nil {
|
||||
cfg.Logger.Logger.Error().Err(err).Msg("failed enabling system extension")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Usage: "Disable a installed system extension for a give entry",
|
||||
UsageText: "disable [--active|--passive] EXTENSION",
|
||||
Description: "Disable a system extension for a given boot entry (active or passive)",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "active",
|
||||
Usage: "Disable the system extension for the active boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "passive",
|
||||
Usage: "Disable the system extension for the passive boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "recovery",
|
||||
Usage: "List the system extensions for the recovery boot entry",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "common",
|
||||
Usage: "List the system extensions for the common boot entry (applies to all boot states)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "now",
|
||||
Usage: "Disable the system extension now and reload systemd-sysext",
|
||||
},
|
||||
},
|
||||
Before: func(c *cli.Context) error {
|
||||
if c.Args().Len() != 1 {
|
||||
return fmt.Errorf("extension name required")
|
||||
}
|
||||
|
||||
if moreThanOneEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
|
||||
return fmt.Errorf("only one of --active, --passive, --recovery or --common can be set")
|
||||
}
|
||||
|
||||
if noneOfEnabled(c.Bool("active"), c.Bool("passive"), c.Bool("recovery"), c.Bool("common")) {
|
||||
return fmt.Errorf("either --active, --passive, --recovery or --common must be set")
|
||||
}
|
||||
if err := checkRoot(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bootState string
|
||||
if c.Bool("active") {
|
||||
bootState = "active"
|
||||
}
|
||||
if c.Bool("passive") {
|
||||
bootState = "passive"
|
||||
}
|
||||
ext := c.Args().First()
|
||||
if err := action.DisableSystemExtension(cfg, ext, bootState, c.Bool("now")); err != nil {
|
||||
cfg.Logger.Logger.Error().Err(err).Msg("failed disabling system extension")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install a system extension",
|
||||
UsageText: "install URI",
|
||||
Description: "Install a system extension from a given URI",
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Args().Len() != 1 {
|
||||
return fmt.Errorf("extension URI required")
|
||||
}
|
||||
uri := c.Args().First()
|
||||
if err := validateSourceSysext(uri); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := action.InstallSystemExtension(cfg, uri); err != nil {
|
||||
cfg.Logger.Logger.Error().Err(err).Msg("failed installing system extension")
|
||||
return err
|
||||
}
|
||||
cfg.Logger.Logger.Info().Msgf("System extension %s installed", uri)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "Remove a system extension",
|
||||
UsageText: "remove EXTENSION",
|
||||
Description: "Remove a installed system extension",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "now",
|
||||
Usage: "Remove the system extension now and reload systemd-sysext",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Args().Len() != 1 {
|
||||
return fmt.Errorf("extension required")
|
||||
}
|
||||
extension := c.Args().First()
|
||||
cfg, err := agentConfig.Scan(collector.Directories(constants.GetUserConfigDirs()...), collector.NoLogs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := action.RemoveSystemExtension(cfg, extension, c.Bool("now")); err != nil {
|
||||
cfg.Logger.Logger.Error().Err(err).Msg("failed removing system extension")
|
||||
return err
|
||||
}
|
||||
cfg.Logger.Logger.Info().Msgf("System extension %s removed", extension)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -876,7 +1139,7 @@ func validateSource(source string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := regexp.Compile(`^oci:|dir:|file:`)
|
||||
r, err := regexp.Compile(`^oci:|^dir:|^file:`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -887,6 +1150,22 @@ func validateSource(source string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSourceSysext(source string) error {
|
||||
if source == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
r, err := regexp.Compile(`^oci:|^file:|^http:|^https:`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r.MatchString(source) {
|
||||
return fmt.Errorf("source %s does not match any of oci:, file: or http(s): ", source)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check
|
||||
func bootFromLiveMedia() bool {
|
||||
// Check if the system is booted from a LIVE media by checking if the file /run/cos/livecd is present
|
||||
@ -920,3 +1199,26 @@ func getReleasesFromProvider(includePrereleases bool) ([]string, error) {
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func moreThanOneEnabled(bools ...bool) bool {
|
||||
count := 0
|
||||
for _, b := range bools {
|
||||
if b {
|
||||
count++
|
||||
}
|
||||
if count > 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func noneOfEnabled(bools ...bool) bool {
|
||||
count := 0
|
||||
for _, b := range bools {
|
||||
if b {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count == 0
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func selectBootEntryGrub(cfg *config.Config, entry string) error {
|
||||
vars := map[string]string{
|
||||
"next_entry": entry,
|
||||
}
|
||||
err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg.Fs)
|
||||
err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg)
|
||||
if err != nil {
|
||||
cfg.Logger.Errorf("could not set default boot entry: %s\n", err)
|
||||
return err
|
||||
@ -129,10 +129,16 @@ func selectBootEntrySystemd(cfg *config.Config, entry string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
bootName, err := bootNameToSystemdConf(entry)
|
||||
bootFileName, err := bootNameToSystemdConf(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assessment, err := utils.ReadAssessmentFromEntry(cfg.Fs, bootFileName, cfg.Logger)
|
||||
if err != nil {
|
||||
cfg.Logger.Logger.Err(err).Str("entry", entry).Str("boot file name", bootFileName).Msg("could not read assessment from entry")
|
||||
return err
|
||||
}
|
||||
bootName := fmt.Sprintf("%s%s.conf", bootFileName, assessment)
|
||||
// Set the default entry to the selected entry
|
||||
systemdConf["default"] = bootName
|
||||
err = utils.SystemdBootConfWriter(cfg.Fs, filepath.Join(efiPartition.MountPoint, "loader/loader.conf"), systemdConf)
|
||||
@ -173,6 +179,10 @@ func systemdConfToBootName(conf string) (string, error) {
|
||||
|
||||
fileName := strings.TrimSuffix(conf, ".conf")
|
||||
|
||||
// Remove the boot assesment from the name we show
|
||||
re := regexp.MustCompile(`\+\d+(-\d+)?$`)
|
||||
fileName = re.ReplaceAllString(fileName, "")
|
||||
|
||||
if strings.HasPrefix(fileName, "active") {
|
||||
bootName := "cos"
|
||||
confName := strings.TrimPrefix(fileName, "active")
|
||||
@ -220,6 +230,8 @@ func systemdConfToBootName(conf string) (string, error) {
|
||||
return strings.ReplaceAll(fileName, "_", " "), nil
|
||||
}
|
||||
|
||||
// bootNameToSystemdConf converts a boot name to a systemd-boot conf file name
|
||||
// skips the .conf extension
|
||||
func bootNameToSystemdConf(name string) (string, error) {
|
||||
differenciator := ""
|
||||
|
||||
@ -227,38 +239,38 @@ func bootNameToSystemdConf(name string) (string, error) {
|
||||
if name != "cos" {
|
||||
differenciator = "_" + strings.TrimPrefix(name, "cos ")
|
||||
}
|
||||
return "active" + differenciator + ".conf", nil
|
||||
return "active" + differenciator, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "active") {
|
||||
if name != "active" {
|
||||
differenciator = "_" + strings.TrimPrefix(name, "active ")
|
||||
}
|
||||
return "active" + differenciator + ".conf", nil
|
||||
return "active" + differenciator, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "fallback") {
|
||||
if name != "fallback" {
|
||||
differenciator = "_" + strings.TrimPrefix(name, "fallback ")
|
||||
}
|
||||
return "passive" + differenciator + ".conf", nil
|
||||
return "passive" + differenciator, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "recovery") {
|
||||
if name != "recovery" {
|
||||
differenciator = "_" + strings.TrimPrefix(name, "recovery ")
|
||||
}
|
||||
return "recovery" + differenciator + ".conf", nil
|
||||
return "recovery" + differenciator, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "statereset") {
|
||||
if name != "statereset" {
|
||||
differenciator = "_" + strings.TrimPrefix(name, "statereset ")
|
||||
}
|
||||
return "statereset" + differenciator + ".conf", nil
|
||||
return "statereset" + differenciator, nil
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(name, " ", "_") + ".conf", nil
|
||||
return strings.ReplaceAll(name, " ", "_"), nil
|
||||
}
|
||||
|
||||
// listBootEntriesSystemd lists the boot entries available in the systemd-boot config files
|
||||
|
@ -14,8 +14,8 @@ import (
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
@ -138,14 +138,42 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("does not exist"))
|
||||
})
|
||||
It("selects the boot entry in a default installation", func() {
|
||||
It("works without boot assessment", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/active.conf", []byte("title kairos\nefi /EFI/kairos/active.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
|
||||
err = fs.WriteFile("/efi/loader/loader.conf", []byte("default active.conf"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery.conf", []byte("title kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
|
||||
|
||||
err = SelectBootEntry(config, "active")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/statereset.conf", []byte("title kairos state reset (auto)\nefi /EFI/kairos/statereset.efi\n"), os.ModePerm)
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active"))
|
||||
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("active.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
"/efi",
|
||||
"",
|
||||
syscall.MS_REMOUNT,
|
||||
"")).To(BeTrue())
|
||||
// Should have called a remount to make it RO
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
"/efi",
|
||||
"",
|
||||
syscall.MS_REMOUNT|syscall.MS_RDONLY,
|
||||
"")).To(BeTrue())
|
||||
})
|
||||
|
||||
It("selects the boot entry in a default installation", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/active+2-1.conf", []byte("title kairos\nefi /EFI/kairos/active.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/passive+3.conf", []byte("title kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery+1-2.conf", []byte("title kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/statereset+2-1.conf", []byte("title kairos state reset (auto)\nefi /EFI/kairos/statereset.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/loader.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -155,7 +183,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
|
||||
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("passive.conf"))
|
||||
Expect(reader["default"]).To(Equal("passive+3.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -176,7 +204,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to recovery"))
|
||||
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("recovery.conf"))
|
||||
Expect(reader["default"]).To(Equal("recovery+1-2.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -197,7 +225,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to statereset"))
|
||||
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("statereset.conf"))
|
||||
Expect(reader["default"]).To(Equal("statereset+2-1.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -218,7 +246,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to cos"))
|
||||
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("active.conf"))
|
||||
Expect(reader["default"]).To(Equal("active+2-1.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -240,7 +268,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to active"))
|
||||
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("active.conf"))
|
||||
Expect(reader["default"]).To(Equal("active+2-1.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -260,7 +288,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
It("selects the boot entry in a extend-cmdline installation with boot branding", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/active_install-mode_awesomeos.conf", []byte("title awesomeos\nefi /EFI/kairos/active_install-mode_awesomeos.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/passive_install-mode_awesomeos.conf", []byte("title awesomeos (fallback)\nefi /EFI/kairos/passive_install-mode_awesomeos.efi\n"), os.ModePerm)
|
||||
err = fs.WriteFile("/efi/loader/entries/passive_install-mode_awesomeos+3.conf", []byte("title awesomeos (fallback)\nefi /EFI/kairos/passive_install-mode_awesomeos.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery_install-mode_awesomeos.conf", []byte("title awesomeos recovery\nefi /EFI/kairos/recovery_install-mode_awesomeos.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -274,7 +302,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
|
||||
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("passive_install-mode_awesomeos.conf"))
|
||||
Expect(reader["default"]).To(Equal("passive_install-mode_awesomeos+3.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -381,11 +409,11 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/active_foobar.conf", []byte("title Kairos\nefi /EFI/kairos/active_foobar.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/passive.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
|
||||
err = fs.WriteFile("/efi/loader/entries/passive+3.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/passive_foobar.conf", []byte("title Kairos (fallback)\nefi /EFI/kairos/passive_foobar.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery+3.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/recovery_foobar.conf", []byte("title Kairos recovery\nefi /EFI/kairos/recovery_foobar.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@ -393,7 +421,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/statereset_foobar.conf", []byte("title Kairos state reset (auto)\nefi /EFI/kairos/state_reset_foobar.efi\n"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/loader.conf", []byte(""), os.ModePerm)
|
||||
err = fs.WriteFile("/efi/loader/loader.conf", []byte("default active.conf"), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = SelectBootEntry(config, "fallback")
|
||||
@ -401,7 +429,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to fallback"))
|
||||
reader, err := utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("passive.conf"))
|
||||
Expect(reader["default"]).To(Equal("passive+3.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -443,7 +471,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to recovery"))
|
||||
reader, err = utils.SystemdBootConfReader(fs, "/efi/loader/loader.conf")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(reader["default"]).To(Equal("recovery.conf"))
|
||||
Expect(reader["default"]).To(Equal("recovery+3.conf"))
|
||||
// Should have called a remount to make it RW
|
||||
Expect(syscallMock.WasMountCalledWith(
|
||||
"",
|
||||
@ -666,7 +694,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() {
|
||||
err = SelectBootEntry(config, "kairos")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(memLog.String()).To(ContainSubstring("Default boot entry set to kairos"))
|
||||
variables, err := utils.ReadPersistentVariables("/oem/grubenv", fs)
|
||||
variables, err := utils.ReadPersistentVariables("/oem/grubenv", config)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(variables["next_entry"]).To(Equal("kairos"))
|
||||
})
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
@ -270,7 +270,7 @@ func (i InstallAction) Run() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = hook.Run(*i.cfg, i.spec, hook.EncryptionHooks...)
|
||||
err = hook.Run(*i.cfg, i.spec, hook.PostInstall...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -307,5 +307,5 @@ func (i InstallAction) Run() (err error) {
|
||||
_ = utils.RunStage(i.cfg, "kairos-install.after")
|
||||
_ = events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck
|
||||
|
||||
return hook.Run(*i.cfg, i.spec, hook.AfterInstall...)
|
||||
return hook.Run(*i.cfg, i.spec, hook.FinishInstall...)
|
||||
}
|
||||
|
@ -19,17 +19,15 @@ package action_test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/diskfs/go-diskfs"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
fileBackend "github.com/diskfs/go-diskfs/backend/file"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
@ -38,8 +36,8 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("Install action tests", func() {
|
||||
@ -105,7 +103,8 @@ var _ = Describe("Install action tests", func() {
|
||||
device = filepath.Join(tmpdir, "test.img")
|
||||
Expect(os.RemoveAll(device)).Should(Succeed())
|
||||
// at least 2Gb in size as state is set to 1G
|
||||
_, err = diskfs.Create(device, 2*1024*1024*1024, diskfs.Raw, 512)
|
||||
|
||||
_, err = fileBackend.CreateFromPath(device, 2*1024*1024*1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
config.Install.Device = device
|
||||
@ -150,6 +149,16 @@ var _ = Describe("Install action tests", func() {
|
||||
Expect(err).To(BeNil())
|
||||
_, err = fs.Create(grubCfg)
|
||||
Expect(err).To(BeNil())
|
||||
// Create fake grub dir in rootfs and fake grub binaries
|
||||
err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "sbin"), constants.DirPerm)
|
||||
Expect(err).To(BeNil())
|
||||
f, err := fs.Create(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install"))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(f.Chmod(0755)).ToNot(HaveOccurred())
|
||||
err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc"), constants.DirPerm)
|
||||
Expect(err).To(BeNil())
|
||||
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
mainDisk := sdkTypes.Disk{
|
||||
Name: "device",
|
||||
@ -349,9 +358,10 @@ var _ = Describe("Install action tests", func() {
|
||||
Expect(cl.WasGetCalledWith("http://my.config.org")).To(BeTrue())
|
||||
})
|
||||
|
||||
It("Fails on grub2-install errors", Label("grub"), func() {
|
||||
It("Fails to find grub2-install", Label("grub"), func() {
|
||||
spec.Target = device
|
||||
cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"})
|
||||
err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install"))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(installer.Run()).NotTo(BeNil())
|
||||
Expect(runner.MatchMilestones([][]string{{"grub2-install"}}))
|
||||
})
|
||||
@ -362,5 +372,12 @@ var _ = Describe("Install action tests", func() {
|
||||
Expect(installer.Run()).NotTo(BeNil())
|
||||
Expect(runner.MatchMilestones([][]string{{"tune2fs", "-L", constants.PassiveLabel}}))
|
||||
})
|
||||
It("Fails if there is no grub2 artifacts", Label("grub"), func() {
|
||||
spec.Target = device
|
||||
err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(installer.Run()).NotTo(BeNil())
|
||||
Expect(runner.MatchMilestones([][]string{{"grub2-install"}}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -199,6 +199,16 @@ func (r ResetAction) Run() (err error) {
|
||||
// Create extra dirs in rootfs as afterwards this will be impossible due to RO system
|
||||
createExtraDirsInRootfs(r.cfg, r.spec.ExtraDirsRootfs, r.spec.Active.MountPoint)
|
||||
|
||||
// Mount EFI partition before installing grub as under EFI this copies stuff in there
|
||||
if r.spec.Efi {
|
||||
err = e.MountPartition(r.spec.Partitions.EFI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanup.Push(func() error { return e.UnmountPartition(r.spec.Partitions.EFI) })
|
||||
}
|
||||
//TODO: does bios needs to be mounted here?
|
||||
|
||||
// install grub
|
||||
grub := utils.NewGrub(r.cfg)
|
||||
err = grub.Install(
|
||||
|
@ -20,23 +20,20 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("Reset action tests", func() {
|
||||
@ -133,7 +130,8 @@ var _ = Describe("Reset action tests", func() {
|
||||
ghwTest.AddDisk(mainDisk)
|
||||
ghwTest.CreateDevices()
|
||||
|
||||
fs.Create(constants.EfiDevice)
|
||||
Expect(fsutils.MkdirAll(fs, constants.EfiDevice, constants.DirPerm)).ToNot(HaveOccurred())
|
||||
|
||||
bootedFrom = constants.SystemLabel
|
||||
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
||||
if cmd == cmdFail {
|
||||
@ -159,76 +157,82 @@ var _ = Describe("Reset action tests", func() {
|
||||
_, err = fs.Create(grubCfg)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
||||
regexCmd := regexp.MustCompile(cmdFail)
|
||||
if cmdFail != "" && regexCmd.MatchString(cmd) {
|
||||
return []byte{}, errors.New("command failed")
|
||||
}
|
||||
return []byte{}, nil
|
||||
// create the fake grub dir with modules, it needs an arch in the path
|
||||
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, constants.Archx86), constants.DirPerm)).ToNot(HaveOccurred())
|
||||
for _, mod := range constants.GetGrubModules() {
|
||||
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, constants.Archx86, mod))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
// create fake shim
|
||||
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/"), constants.DirPerm)).ToNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/", "shim.efi"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// create fake grub
|
||||
Expect(fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/"), constants.DirPerm)).ToNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(spec.Active.MountPoint, "/usr/share/efi/x86_64/", "grub.efi"))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
reset = action.NewResetAction(config, spec)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
ghwTest.Clean()
|
||||
})
|
||||
Describe("With EFI", func() {
|
||||
It("Successfully resets on non-squashfs recovery", func() {
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets on non-squashfs recovery including persistent data", func() {
|
||||
spec.FormatPersistent = true
|
||||
spec.FormatOEM = true
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets from a squashfs recovery image", Label("channel"), func() {
|
||||
err := fsutils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
spec.Active.Source = v1.NewDirSrc(constants.IsoBaseTree)
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets despite having errors on hooks", func() {
|
||||
cloudInit.Error = true
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets from a docker image", Label("docker"), func() {
|
||||
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
|
||||
It("Successfully resets on non-squashfs recovery", func() {
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets on non-squashfs recovery including persistent data", func() {
|
||||
spec.FormatPersistent = true
|
||||
spec.FormatOEM = true
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets from a squashfs recovery image", Label("channel"), func() {
|
||||
err := fsutils.MkdirAll(config.Fs, constants.IsoBaseTree, constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
spec.Active.Source = v1.NewDirSrc(constants.IsoBaseTree)
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets despite having errors on hooks", func() {
|
||||
cloudInit.Error = true
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
})
|
||||
It("Successfully resets from a docker image", Label("docker"), func() {
|
||||
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
|
||||
Expect(reset.Run()).To(BeNil())
|
||||
|
||||
})
|
||||
It("Fails installing grub", func() {
|
||||
cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"})
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{cmdFail}}))
|
||||
})
|
||||
It("Fails formatting state partition", func() {
|
||||
cmdFail = "mkfs.ext4"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{"mkfs.ext4"}}))
|
||||
})
|
||||
It("Fails setting the active label on non-squashfs recovery", func() {
|
||||
cmdFail = "tune2fs"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails setting the passive label on squashfs recovery", func() {
|
||||
cmdFail = "tune2fs"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{"tune2fs"}}))
|
||||
})
|
||||
It("Fails mounting partitions", func() {
|
||||
mounter.ErrorOnMount = true
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails unmounting partitions", func() {
|
||||
mounter.ErrorOnUnmount = true
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails unpacking docker image ", func() {
|
||||
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
|
||||
extractor.SideEffect = func(imageRef, destination, platformRef string) error {
|
||||
return fmt.Errorf("error")
|
||||
}
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails formatting state partition", func() {
|
||||
cmdFail = "mkfs.ext4"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{"mkfs.ext4"}}))
|
||||
})
|
||||
It("Fails setting the active label on non-squashfs recovery", func() {
|
||||
cmdFail = "tune2fs"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails setting the passive label on squashfs recovery", func() {
|
||||
cmdFail = "tune2fs"
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
Expect(runner.IncludesCmds([][]string{{"tune2fs"}}))
|
||||
})
|
||||
It("Fails mounting partitions", func() {
|
||||
mounter.ErrorOnMount = true
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails unmounting partitions", func() {
|
||||
mounter.ErrorOnUnmount = true
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
It("Fails unpacking docker image ", func() {
|
||||
spec.Active.Source = v1.NewDockerSrc("my/image:latest")
|
||||
extractor.SideEffect = func(imageRef, destination, platformRef string) error {
|
||||
return fmt.Errorf("error")
|
||||
}
|
||||
Expect(reset.Run()).NotTo(BeNil())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
433
pkg/action/sysext.go
Normal file
433
pkg/action/sysext.go
Normal 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
588
pkg/action/sysext_test.go
Normal 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())
|
||||
})
|
||||
})
|
||||
})
|
@ -18,27 +18,22 @@ package action_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/internal/agent"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("Upgrade Actions test", func() {
|
||||
@ -55,10 +50,8 @@ var _ = Describe("Upgrade Actions test", func() {
|
||||
var ghwTest ghwMock.GhwMock
|
||||
var extractor *v1mock.FakeImageExtractor
|
||||
var dummySourceFile string
|
||||
var dummySourceSizeMb int64
|
||||
|
||||
BeforeEach(func() {
|
||||
dummySourceSizeMb = 20
|
||||
runner = v1mock.NewFakeRunner()
|
||||
syscall = &v1mock.FakeSyscall{}
|
||||
mounter = v1mock.NewErrorMounter()
|
||||
@ -153,34 +146,6 @@ var _ = Describe("Upgrade Actions test", func() {
|
||||
AfterEach(func() {
|
||||
ghwTest.Clean()
|
||||
})
|
||||
It("calculates the recovery source size correctly", func() {
|
||||
dummySourceFile = createDummyFile(fs, dummySourceSizeMb)
|
||||
upgradeConfig := agent.ExtraConfigUpgrade{}
|
||||
upgradeConfig.Upgrade.Entry = constants.BootEntryRecovery
|
||||
upgradeConfig.Upgrade.RecoverySystem.URI = fmt.Sprintf("file:%s", dummySourceFile)
|
||||
d, err := json.Marshal(upgradeConfig)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
cliConfig := string(d)
|
||||
|
||||
config, err := agentConfig.Scan(collector.Readers(strings.NewReader(cliConfig)))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
agentConfig.WithFs(fs)(config)
|
||||
agentConfig.WithRunner(runner)(config)
|
||||
agentConfig.WithLogger(logger)(config)
|
||||
agentConfig.WithMounter(mounter)(config)
|
||||
agentConfig.WithSyscall(syscall)(config)
|
||||
agentConfig.WithClient(client)(config)
|
||||
agentConfig.WithCloudInitRunner(cloudInit)(config)
|
||||
agentConfig.WithImageExtractor(extractor)(config)
|
||||
agentConfig.WithPlatform("linux/amd64")(config)
|
||||
config.ImageExtractor = extractor
|
||||
|
||||
spec, err = agentConfig.NewUpgradeSpec(config)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(spec.Entry).To(Equal(constants.BootEntryRecovery))
|
||||
Expect(spec.Recovery.Size).To(Equal(uint(100 + dummySourceSizeMb))) // We adding 100Mb on top
|
||||
})
|
||||
Describe(fmt.Sprintf("Booting from %s", constants.ActiveLabel), Label("active_label"), func() {
|
||||
var err error
|
||||
BeforeEach(func() {
|
||||
@ -659,25 +624,3 @@ var _ = Describe("Upgrade Actions test", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func createDummyFile(fs vfs.FS, sizeMb int64) string {
|
||||
fileSize := int64(sizeMb * 1024 * 1024)
|
||||
|
||||
tmpFile, err := os.CreateTemp("", "dummyfile_*.tmp")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
tmpName := tmpFile.Name()
|
||||
tmpFile.Close()
|
||||
os.RemoveAll(tmpName)
|
||||
|
||||
dir := filepath.Dir(tmpName)
|
||||
err = fs.Mkdir(dir, os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
f, err := fs.Create(tmpName)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = f.Truncate(fileSize)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
f.Close()
|
||||
|
||||
return tmpName
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"github.com/mudler/yip/pkg/executor"
|
||||
"github.com/mudler/yip/pkg/plugins"
|
||||
"github.com/mudler/yip/pkg/schema"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
)
|
||||
|
||||
type YipCloudInitRunner struct {
|
||||
|
@ -34,7 +34,7 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
// Parted print sample output
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
yip "github.com/mudler/yip/pkg/schema"
|
||||
"github.com/sanity-io/litter"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/mount-utils"
|
||||
)
|
||||
@ -55,10 +55,15 @@ type Install struct {
|
||||
ExtraPartitions sdkTypes.PartitionList `yaml:"extra-partitions,omitempty" mapstructure:"extra-partitions"`
|
||||
ExtraDirsRootfs []string `yaml:"extra-dirs-rootfs,omitempty" mapstructure:"extra-dirs-rootfs"`
|
||||
Force bool `yaml:"force,omitempty" mapstructure:"force"`
|
||||
NoUsers bool `yaml:"nousers,omitempty" mapstructure:"nousers"`
|
||||
}
|
||||
|
||||
func NewConfig(opts ...GenericOptions) *Config {
|
||||
log := sdkTypes.NewKairosLogger("agent", "info", false)
|
||||
// Get the viper config in case something in command line or env var has set it and set the level asap
|
||||
if viper.GetBool("debug") {
|
||||
log.SetLevel("debug")
|
||||
}
|
||||
|
||||
hostPlatform, err := v1.NewPlatformFromArch(runtime.GOARCH)
|
||||
if err != nil {
|
||||
@ -150,6 +155,8 @@ type Config struct {
|
||||
SquashFsCompressionConfig []string `yaml:"squash-compression,omitempty" mapstructure:"squash-compression"`
|
||||
SquashFsNoCompression bool `yaml:"squash-no-compression,omitempty" mapstructure:"squash-no-compression"`
|
||||
UkiMaxEntries int `yaml:"uki-max-entries,omitempty" mapstructure:"uki-max-entries"`
|
||||
BindPCRs []string `yaml:"bind-pcrs,omitempty" mapstructure:"bind-pcrs"`
|
||||
BindPublicPCRs []string `yaml:"bind-public-pcrs,omitempty" mapstructure:"bind-public-pcrs"`
|
||||
}
|
||||
|
||||
// WriteInstallState writes the state.yaml file to the given state and recovery paths
|
||||
@ -188,6 +195,61 @@ func (c Config) LoadInstallState() (*v1.InstallState, error) {
|
||||
return installState, nil
|
||||
}
|
||||
|
||||
func contains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckForUsers will check the config for any users and validate that at least we have 1 admin.
|
||||
// Since Kairos 3.3.x we don't ship a default user with the system, so before a system with no specific users
|
||||
// was relying in our default cloud-configs which created a kairos user ALWAYS (with SUDO!)
|
||||
// But now we don't ship it anymore. So a user upgrading from 3.2.x to 3.3.x that created no users, will end up with a blocked
|
||||
// system.
|
||||
// So we need to see if they are setting a user in their config and if not refuse to continue
|
||||
func (c Config) CheckForUsers() (err error) {
|
||||
// If nousers is enabled we do not check for the validity of the users and such
|
||||
// At this point, the config should be fully parsed and the yip stages ready
|
||||
|
||||
// Check if the sentinel is present
|
||||
_, sentinel := c.Fs.Stat("/etc/kairos/.nousers")
|
||||
if sentinel == nil {
|
||||
c.Logger.Logger.Debug().Msg("Sentinel file found, skipping user check")
|
||||
return nil
|
||||
}
|
||||
if !c.Install.NoUsers {
|
||||
anyAdmin := false
|
||||
cc, _ := c.Config.String()
|
||||
yamlConfig, err := yip.Load(cc, vfs.OSFS, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stage := range yamlConfig.Stages {
|
||||
for _, x := range stage {
|
||||
if len(x.Users) > 0 {
|
||||
for _, user := range x.Users {
|
||||
if contains(user.Groups, "admin") || user.PrimaryGroup == "admin" {
|
||||
anyAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if !anyAdmin {
|
||||
return fmt.Errorf("No users found in any stage that are part of the 'admin' group.\n" +
|
||||
"In Kairos 3.3.x we no longer ship a default hardcoded user with the system configs and require users to provide their own user." +
|
||||
"Please provide at least 1 user that is part of the 'admin' group(for sudo) with your cloud configs." +
|
||||
"If you still want to continue without creating any users in the system, set 'install.nousers: true' to be in the config in order to allow a system with no users.")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Sanitize checks the consistency of the struct, returns error
|
||||
// if unsolvable inconsistencies are found
|
||||
func (c *Config) Sanitize() error {
|
||||
@ -339,10 +401,11 @@ func FilterKeys(d []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// ScanNoLogs is a wrapper around Scan that sets the logger to null
|
||||
// Also sets the NoLogs option to true by default
|
||||
func ScanNoLogs(opts ...collector.Option) (c *Config, err error) {
|
||||
log := sdkTypes.NewNullLogger()
|
||||
result := NewConfig(WithLogger(log))
|
||||
return scan(result, opts...)
|
||||
return scan(result, append(opts, collector.NoLogs)...)
|
||||
}
|
||||
|
||||
// Scan is a wrapper around collector.Scan that sets the logger to the default Kairos logger
|
||||
|
@ -22,12 +22,13 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
pkgConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
v1mocks "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
. "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
@ -93,7 +94,7 @@ func structFieldsContainedInOtherStruct(left, right interface{}) {
|
||||
leftFieldName := leftTypes.Field(i).Name
|
||||
if leftTypes.Field(i).IsExported() {
|
||||
It(fmt.Sprintf("Checks that the new schema contians the field %s", leftFieldName), func() {
|
||||
if leftFieldName == "Source" {
|
||||
if leftFieldName == "Source" || leftFieldName == "NoUsers" || leftFieldName == "BindPublicPCRs" || leftFieldName == "BindPCRs" {
|
||||
Skip("Schema not updated yet")
|
||||
}
|
||||
Expect(
|
||||
@ -227,7 +228,7 @@ var _ = Describe("Schema", func() {
|
||||
cleanup()
|
||||
})
|
||||
It("Scan can override options", func() {
|
||||
c, err := Scan(collector.Readers(strings.NewReader(`uki-max-entries: 34`)), collector.NoLogs)
|
||||
c, err := ScanNoLogs(collector.Readers(strings.NewReader(`uki-max-entries: 34`)))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(c.UkiMaxEntries).To(Equal(34))
|
||||
})
|
||||
@ -265,4 +266,40 @@ var _ = Describe("Schema", func() {
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Validate users in config", func() {
|
||||
It("Validates a existing user in the system", func() {
|
||||
cc := `#cloud-config
|
||||
stages:
|
||||
initramfs:
|
||||
- name: "Set user and password"
|
||||
users:
|
||||
kairos:
|
||||
passwd: "kairos"
|
||||
groups:
|
||||
- "admin"
|
||||
`
|
||||
config, err := pkgConfig.ScanNoLogs(collector.Readers(strings.NewReader(cc)))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(config.CheckForUsers()).ToNot(HaveOccurred())
|
||||
})
|
||||
It("Fails if there is no user", func() {
|
||||
config, err := pkgConfig.ScanNoLogs(collector.NoLogs)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(config.CheckForUsers()).To(HaveOccurred())
|
||||
})
|
||||
It("Fails if there is user but its not admin", func() {
|
||||
cc := `#cloud-config
|
||||
stages:
|
||||
initramfs:
|
||||
- name: "Set user and password"
|
||||
users:
|
||||
kairos:
|
||||
passwd: "kairos"
|
||||
`
|
||||
config, err := pkgConfig.ScanNoLogs(collector.Readers(strings.NewReader(cc)))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(config.CheckForUsers()).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
k8sutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/k8s"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
|
||||
"github.com/kairos-io/kairos-sdk/collector"
|
||||
"github.com/kairos-io/kairos-sdk/ghw"
|
||||
@ -396,12 +397,32 @@ func NewUpgradeSpec(cfg *Config) (*v1.UpgradeSpec, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed calculating size: %w", err)
|
||||
}
|
||||
|
||||
if spec.Active.Source.IsDocker() {
|
||||
cfg.Logger.Infof("Checking if OCI image %s exists", spec.Active.Source.Value())
|
||||
_, err := crane.Manifest(spec.Active.Source.Value())
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "MANIFEST_UNKNOWN") {
|
||||
return nil, fmt.Errorf("oci image %s does not exist", spec.Active.Source.Value())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func setUpgradeSourceSize(cfg *Config, spec *v1.UpgradeSpec) error {
|
||||
var size int64
|
||||
var err error
|
||||
var originalSize uint
|
||||
|
||||
// Store the default given size in the spec. This includes the user specified values which have already been marshalled in the spec
|
||||
if spec.RecoveryUpgrade() {
|
||||
originalSize = spec.Recovery.Size
|
||||
} else {
|
||||
originalSize = spec.Active.Size
|
||||
}
|
||||
|
||||
var targetSpec *v1.Image
|
||||
if spec.RecoveryUpgrade() {
|
||||
@ -411,16 +432,25 @@ func setUpgradeSourceSize(cfg *Config, spec *v1.UpgradeSpec) error {
|
||||
}
|
||||
|
||||
if targetSpec.Source != nil && targetSpec.Source.IsEmpty() {
|
||||
cfg.Logger.Debugf("No source specified for image, skipping size calculation")
|
||||
return nil
|
||||
}
|
||||
|
||||
size, err = GetSourceSize(cfg, targetSpec.Source)
|
||||
if err != nil {
|
||||
cfg.Logger.Warnf("Failed to infer size for images: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.Logger.Infof("Setting image size to %dMb", size)
|
||||
targetSpec.Size = uint(size)
|
||||
if uint(size) < originalSize {
|
||||
cfg.Logger.Debugf("Calculated size (%dMB) is less than specified/default size (%dMB)", size, originalSize)
|
||||
targetSpec.Size = originalSize
|
||||
} else {
|
||||
cfg.Logger.Debugf("Calculated size (%dMB) is higher than specified/default size (%dMB)", size, originalSize)
|
||||
targetSpec.Size = uint(size)
|
||||
}
|
||||
|
||||
cfg.Logger.Infof("Setting image size to %dMB", targetSpec.Size)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -788,7 +818,7 @@ func ReadUkiUpgradeSpecFromConfig(c *Config) (*v1.UpgradeUkiSpec, error) {
|
||||
// getSize will calculate the size of a file or symlink and will do nothing with directories
|
||||
// fileList: keeps track of the files visited to avoid counting a file more than once if it's a symlink. It could also be used as a way to filter some files
|
||||
// size: will be the memory that adds up all the files sizes. Meaning it could be initialized with a value greater than 0 if needed.
|
||||
func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry, err error) error {
|
||||
func getSize(vfs v1.FS, size *int64, fileList map[string]bool, path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -801,7 +831,7 @@ func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry,
|
||||
if d.Type()&fs.ModeSymlink != 0 {
|
||||
// If it's a symlink, get its target and calculate its size.
|
||||
var err error
|
||||
actualFilePath, err = os.Readlink(path)
|
||||
actualFilePath, err = vfs.Readlink(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -812,7 +842,7 @@ func getSize(size *int64, fileList map[string]bool, path string, d fs.DirEntry,
|
||||
}
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(actualFilePath)
|
||||
fileInfo, err := vfs.Stat(actualFilePath)
|
||||
if os.IsNotExist(err) || fileList[actualFilePath] {
|
||||
return nil
|
||||
}
|
||||
@ -849,17 +879,11 @@ func GetSourceSize(config *Config, source *v1.ImageSource) (int64, error) {
|
||||
// , which mounts the host root into $HOST_DIR
|
||||
// we should skip that dir when calculating the size as we would be doubling the calculated size
|
||||
// Plus we will hit the usual things when checking a running system. Processes that go away, tmpfiles, etc...
|
||||
|
||||
// This is always set for pods running under kubernetes
|
||||
_, underKubernetes := os.LookupEnv("KUBERNETES_SERVICE_HOST")
|
||||
config.Logger.Logger.Info().Bool("status", underKubernetes).Msg("Running under kubernetes")
|
||||
// Try to get the HOST_DIR in case we are not using the default one
|
||||
hostDir := os.Getenv("HOST_DIR")
|
||||
// If we are under kubernetes but the HOST_DIR var is empty, default to /host as system-upgrade-controller mounts
|
||||
// the host in that dir by default
|
||||
if underKubernetes && hostDir == "" {
|
||||
hostDir = "/host"
|
||||
}
|
||||
hostDir := k8sutils.GetHostDirForK8s()
|
||||
config.Logger.Logger.Debug().Bool("status", underKubernetes).Str("hostdir", hostDir).Msg("Kubernetes check")
|
||||
err = fsutils.WalkDirFs(config.Fs, source.Value(), func(path string, d fs.DirEntry, err error) error {
|
||||
// If its empty we are just not setting it, so probably out of the k8s upgrade path
|
||||
if hostDir != "" && strings.HasPrefix(path, hostDir) {
|
||||
@ -870,7 +894,7 @@ func GetSourceSize(config *Config, source *v1.ImageSource) (int64, error) {
|
||||
// During install or upgrade outside kubernetes, we dont care about those dirs as they are not expected to be in the source dir
|
||||
config.Logger.Logger.Debug().Str("path", path).Str("hostDir", hostDir).Msg("Skipping dir as it is a runtime directory under kubernetes (/proc, /dev or /run)")
|
||||
} else {
|
||||
v := getSize(&size, filesVisited, path, d, err)
|
||||
v := getSize(config.Fs, &size, filesVisited, path, d, err)
|
||||
return v
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
@ -33,7 +34,7 @@ import (
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
"k8s.io/mount-utils"
|
||||
)
|
||||
|
||||
@ -449,6 +450,45 @@ var _ = Describe("Types", Label("types", "config"), func() {
|
||||
Expect(spec.Recovery.Source.IsEmpty()).To(BeTrue())
|
||||
Expect(spec.Recovery.FS).To(Equal(constants.SquashFs))
|
||||
})
|
||||
|
||||
It("sets image size to default value if not set", func() {
|
||||
spec, err := config.NewUpgradeSpec(c)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(spec.Active.Size).To(Equal(constants.ImgSize))
|
||||
})
|
||||
|
||||
It("sets image size to provided value if set in the config and image is smaller", func() {
|
||||
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n size: 666\n")))
|
||||
// Set manually the config collector in the cfg file before unmarshalling the spec
|
||||
c.Config = cfg.Config
|
||||
spec, err := config.NewUpgradeSpec(c)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(spec.Active.Size).To(Equal(uint(666)))
|
||||
})
|
||||
It("sets image size to default value if not set in the config and image is smaller", func() {
|
||||
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n uri: dir:/\n")))
|
||||
// Set manually the config collector in the cfg file before unmarshalling the spec
|
||||
c.Config = cfg.Config
|
||||
spec, err := config.NewUpgradeSpec(c)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(spec.Active.Size).To(Equal(constants.ImgSize))
|
||||
})
|
||||
|
||||
It("sets image size to the source if default is smaller", func() {
|
||||
cfg, err := config.ScanNoLogs(collector.Readers(strings.NewReader("#cloud-config\nupgrade:\n system:\n uri: file:/tmp/waka\n")))
|
||||
// Set manually the config collector in the cfg file before unmarshalling the spec
|
||||
c.Config = cfg.Config
|
||||
Expect(c.Fs.Mkdir("/tmp", 0777)).ShouldNot(HaveOccurred())
|
||||
Expect(c.Fs.WriteFile("/tmp/waka", []byte("waka"), 0777)).ShouldNot(HaveOccurred())
|
||||
Expect(c.Fs.Truncate("/tmp/waka", 5120*1024*1024)).ShouldNot(HaveOccurred())
|
||||
spec, err := config.NewUpgradeSpec(c)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
f, err := c.Fs.Stat("/tmp/waka")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
// Make the same calculation as the code
|
||||
Expect(spec.Active.Size).To(Equal(uint(f.Size()/1000/1000) + 100))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
Describe("Config from cloudconfig", Label("cloud-config"), func() {
|
||||
@ -703,7 +743,8 @@ var _ = Describe("GetSourceSize", Label("GetSourceSize"), func() {
|
||||
|
||||
Expect(os.Mkdir(filepath.Join(tempDir, "host"), os.ModePerm)).ToNot(HaveOccurred())
|
||||
Expect(createFileOfSizeInMB(filepath.Join(tempDir, "host", "what.txt"), 200)).ToNot(HaveOccurred())
|
||||
// Set env var like the suc upgrade does
|
||||
// Set env var like the suc upgrade and k8s does to trigger the skip
|
||||
Expect(os.Setenv("KUBERNETES_SERVICE_HOST", "10.0.0.1")).ToNot(HaveOccurred())
|
||||
Expect(os.Setenv("HOST_DIR", filepath.Join(tempDir, "host"))).ToNot(HaveOccurred())
|
||||
|
||||
sizeAfter, err := config.GetSourceSize(conf, imageSource)
|
||||
|
@ -126,7 +126,10 @@ const (
|
||||
StateResetBootSuffix = " state reset (auto)"
|
||||
|
||||
// Error
|
||||
UpgradeNoSourceError = "Could not find a proper source for the upgrade.\nThis can be configured in the cloud config files under the 'upgrade.system.uri' key or via cmdline using the '--source' flag."
|
||||
UpgradeNoSourceError = "could not find a proper source for the upgrade.\nThis can be configured in the cloud config files under the 'upgrade.system.uri' key or via cmdline using the '--source' flag"
|
||||
UpgradeRecoveryNoSourceError = "could not find a proper source for the recovery upgrade.\nThis can be configured in the cloud config files under the 'upgrade.recovery-system.uri' key or via cmdline using the '--source' flag"
|
||||
MultipleEntriesAssessmentError = "multiple boot entries found for %s"
|
||||
NoBootAssessmentWarning = "No boot assessment found in current boot entry config file"
|
||||
)
|
||||
|
||||
func UkiDefaultMenuEntries() []string {
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
diskfs "github.com/diskfs/go-diskfs/disk"
|
||||
"github.com/diskfs/go-diskfs/partition/gpt"
|
||||
agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
@ -49,7 +48,7 @@ func NewElemental(config *agentConfig.Config) *Elemental {
|
||||
// FormatPartition will format an already existing partition
|
||||
func (e *Elemental) FormatPartition(part *types.Partition, opts ...string) error {
|
||||
e.config.Logger.Infof("Formatting '%s' partition", part.FilesystemLabel)
|
||||
return partitioner.FormatDevice(e.config.Runner, part.Path, part.FS, part.FilesystemLabel, opts...)
|
||||
return partitioner.FormatDevice(e.config.Logger, e.config.Runner, part.Path, part.FS, part.FilesystemLabel, opts...)
|
||||
}
|
||||
|
||||
// PartitionAndFormatDevice creates a new empty partition table on target disk
|
||||
@ -73,13 +72,10 @@ func (e *Elemental) PartitionAndFormatDevice(i v1.SharedInstallSpec) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only re-read table on devices. On files there is no need and this call will fail
|
||||
if disk.Type == diskfs.Device {
|
||||
err = disk.ReReadPartitionTable()
|
||||
if err != nil {
|
||||
e.config.Logger.Errorf("Reread table: %s", err)
|
||||
return err
|
||||
}
|
||||
err = disk.ReReadPartitionTable()
|
||||
if err != nil {
|
||||
e.config.Logger.Errorf("Reread table: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
table, err := disk.GetPartitionTable()
|
||||
@ -121,7 +117,7 @@ func (e *Elemental) PartitionAndFormatDevice(i v1.SharedInstallSpec) error {
|
||||
if err != nil {
|
||||
e.config.Logger.Errorf("Failed finding partition %s by partition label: %s", configPart.FilesystemLabel, err)
|
||||
}
|
||||
err = partitioner.FormatDevice(e.config.Runner, device, configPart.FS, configPart.FilesystemLabel)
|
||||
err = partitioner.FormatDevice(e.config.Logger, e.config.Runner, device, configPart.FS, configPart.FilesystemLabel)
|
||||
if err != nil {
|
||||
e.config.Logger.Errorf("Failed formatting partition: %s", err)
|
||||
return err
|
||||
@ -308,7 +304,23 @@ func (e Elemental) CreateFileSystemImage(img *v1.Image) error {
|
||||
|
||||
// DeployImage will deploy the given image into the target. This method
|
||||
// creates the filesystem image file, mounts it and unmounts it as needed.
|
||||
// Creates the default system dirs by default (/sys,/proc,/dev, etc...)
|
||||
func (e *Elemental) DeployImage(img *v1.Image, leaveMounted bool) (info interface{}, err error) {
|
||||
return e.deployImage(img, leaveMounted, true)
|
||||
}
|
||||
|
||||
// DeployImageNodirs will deploy the given image into the target. This method
|
||||
// creates the filesystem image file, mounts it and unmounts it as needed.
|
||||
// Does not create the default system dirs so it can be used to create generic images from any source
|
||||
func (e *Elemental) DeployImageNodirs(img *v1.Image, leaveMounted bool) (info interface{}, err error) {
|
||||
return e.deployImage(img, leaveMounted, false)
|
||||
}
|
||||
|
||||
// deployImage is the real function that does the actual work
|
||||
// Set leaveMounted to leave the image mounted, otherwise it unmounts before returning
|
||||
// Set createDirStructure to create the directory structure in the target, which creates the expected dirs
|
||||
// for a running system. This is so we can reuse this method for creating random images, not only system ones
|
||||
func (e *Elemental) deployImage(img *v1.Image, leaveMounted, createDirStructure bool) (info interface{}, err error) {
|
||||
target := img.MountPoint
|
||||
if !img.Source.IsFile() {
|
||||
if img.FS != cnst.SquashFs {
|
||||
@ -338,9 +350,11 @@ func (e *Elemental) DeployImage(img *v1.Image, leaveMounted bool) (info interfac
|
||||
return nil, err
|
||||
}
|
||||
if !img.Source.IsFile() {
|
||||
err = utils.CreateDirStructure(e.config.Fs, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if createDirStructure {
|
||||
err = utils.CreateDirStructure(e.config.Fs, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if img.FS == cnst.SquashFs {
|
||||
squashOptions := append(cnst.GetDefaultSquashfsOptions(), e.config.SquashFsCompressionConfig...)
|
||||
@ -570,7 +584,7 @@ func (e Elemental) SetDefaultGrubEntry(partMountPoint string, imgMountPoint stri
|
||||
return utils.SetPersistentVariables(
|
||||
filepath.Join(partMountPoint, cnst.GrubOEMEnv),
|
||||
map[string]string{"default_menu_entry": defaultEntry},
|
||||
e.config.Fs,
|
||||
e.config,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,10 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/diskfs/go-diskfs"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
sc "syscall"
|
||||
"testing"
|
||||
@ -38,13 +38,13 @@ import (
|
||||
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
|
||||
"github.com/diskfs/go-diskfs"
|
||||
fileBackend "github.com/diskfs/go-diskfs/backend/file"
|
||||
"github.com/diskfs/go-diskfs/partition/gpt"
|
||||
"github.com/gofrs/uuid"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sanity-io/litter"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
func TestElementalSuite(t *testing.T) {
|
||||
@ -364,7 +364,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(err).To(BeNil())
|
||||
Expect(os.RemoveAll(filepath.Join(tmpDir, "/test.img"))).ToNot(HaveOccurred())
|
||||
// at least 2Gb in size as state is set to 1G
|
||||
_, err = diskfs.Create(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024, diskfs.Raw, 512)
|
||||
_, err = fileBackend.CreateFromPath(filepath.Join(tmpDir, "/test.img"), 2*1024*1024*1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
config.Install.Device = filepath.Join(tmpDir, "/test.img")
|
||||
install, err = agentConfig.NewInstallSpec(config)
|
||||
@ -382,17 +382,18 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
install.Firmware = v1.EFI
|
||||
Expect(install.Partitions.SetFirmwarePartitions(v1.EFI, v1.GPT)).To(BeNil())
|
||||
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
||||
disk, err := diskfs.Open(filepath.Join(tmpDir, "/test.img"), diskfs.WithOpenMode(diskfs.ReadOnly))
|
||||
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
||||
defer disk.Close()
|
||||
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// check that its type GPT
|
||||
Expect(reflect.TypeOf(disk.Table)).To(Equal(reflect.TypeOf(&gpt.Table{})))
|
||||
Expect(table.Type()).To(Equal("gpt"))
|
||||
// Expect the disk UUID to be constant
|
||||
Expect(strings.ToLower(disk.Table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
||||
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
||||
// 5 partitions (boot, oem, recovery, state and persistent)
|
||||
Expect(len(disk.Table.GetPartitions())).To(Equal(5))
|
||||
Expect(len(table.GetPartitions())).To(Equal(5))
|
||||
// Cast the boot partition into specific type to check the type and such
|
||||
part := disk.Table.GetPartitions()[0]
|
||||
part := table.GetPartitions()[0]
|
||||
partition, ok := part.(*gpt.Partition)
|
||||
Expect(ok).To(BeTrue())
|
||||
// Should be efi type
|
||||
@ -402,8 +403,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
// Should have predictable UUID
|
||||
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
||||
// Check the rest have the proper types
|
||||
for i := 1; i < len(disk.Table.GetPartitions()); i++ {
|
||||
part := disk.Table.GetPartitions()[i]
|
||||
for i := 1; i < len(table.GetPartitions()); i++ {
|
||||
part := table.GetPartitions()[i]
|
||||
partition, ok := part.(*gpt.Partition)
|
||||
Expect(ok).To(BeTrue())
|
||||
// all of them should have the Linux fs type
|
||||
@ -415,17 +416,19 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
install.Firmware = v1.BIOS
|
||||
Expect(install.Partitions.SetFirmwarePartitions(v1.BIOS, v1.GPT)).To(BeNil())
|
||||
Expect(el.PartitionAndFormatDevice(install)).To(BeNil())
|
||||
disk, err := diskfs.Open(filepath.Join(tmpDir, "/test.img"), diskfs.WithOpenMode(diskfs.ReadOnly))
|
||||
disk, err := fileBackend.OpenFromPath(filepath.Join(tmpDir, "/test.img"), true)
|
||||
defer disk.Close()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
table, err := gpt.Read(disk, int(diskfs.SectorSize512), int(diskfs.SectorSize512))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// check that its type GPT
|
||||
Expect(reflect.TypeOf(disk.Table)).To(Equal(reflect.TypeOf(&gpt.Table{})))
|
||||
Expect(table.Type()).To(Equal("gpt"))
|
||||
// Expect the disk UUID to be constant
|
||||
Expect(strings.ToLower(disk.Table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
||||
Expect(strings.ToLower(table.UUID())).To(Equal(strings.ToLower(cnst.DiskUUID)))
|
||||
// 5 partitions (boot, oem, recovery, state and persistent)
|
||||
Expect(len(disk.Table.GetPartitions())).To(Equal(5))
|
||||
Expect(len(table.GetPartitions())).To(Equal(5))
|
||||
// Cast the boot partition into specific type to check the type and such
|
||||
part := disk.Table.GetPartitions()[0]
|
||||
part := table.GetPartitions()[0]
|
||||
partition, ok := part.(*gpt.Partition)
|
||||
Expect(ok).To(BeTrue())
|
||||
// Should be BIOS boot type
|
||||
@ -434,8 +437,8 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
Expect(partition.Name).To(Equal(cnst.BiosPartName))
|
||||
// Should have predictable UUID
|
||||
Expect(strings.ToLower(partition.UUID())).To(Equal(strings.ToLower(uuid.NewV5(uuid.NamespaceURL, cnst.EfiLabel).String())))
|
||||
for i := 1; i < len(disk.Table.GetPartitions()); i++ {
|
||||
part := disk.Table.GetPartitions()[i]
|
||||
for i := 1; i < len(table.GetPartitions()); i++ {
|
||||
part := table.GetPartitions()[i]
|
||||
partition, ok := part.(*gpt.Partition)
|
||||
Expect(ok).To(BeTrue())
|
||||
// all of them should have the Linux fs type
|
||||
@ -845,7 +848,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
el := elemental.NewElemental(config)
|
||||
Expect(config.Fs.Mkdir("/tmp", cnst.DirPerm)).To(BeNil())
|
||||
Expect(el.SetDefaultGrubEntry("/tmp", "/imgMountpoint", "dio")).To(BeNil())
|
||||
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs)
|
||||
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(varsParsed["default_menu_entry"]).To(Equal("dio"))
|
||||
})
|
||||
@ -853,7 +856,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
el := elemental.NewElemental(config)
|
||||
Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil())
|
||||
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
||||
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs)
|
||||
_, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config)
|
||||
// Because it didnt do anything due to the entry being empty, the file should not be there
|
||||
Expect(err).ToNot(BeNil())
|
||||
_, err = config.Fs.Stat(filepath.Join("/tmp", cnst.GrubOEMEnv))
|
||||
@ -868,7 +871,7 @@ var _ = Describe("Elemental", Label("elemental"), func() {
|
||||
|
||||
el := elemental.NewElemental(config)
|
||||
Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil())
|
||||
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config.Fs)
|
||||
varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(varsParsed["default_menu_entry"]).To(Equal("test"))
|
||||
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/google/go-github/v63/github"
|
||||
"github.com/google/go-github/v69/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
|
@ -24,9 +24,11 @@ func (d *Disk) NewPartitionTable(partType string, parts sdkTypes.PartitionList)
|
||||
switch partType {
|
||||
case v1.GPT:
|
||||
table = &gpt.Table{
|
||||
ProtectiveMBR: true,
|
||||
GUID: cnst.DiskUUID, // Set know predictable UUID
|
||||
Partitions: kairosPartsToDiskfsGPTParts(parts, d.Size),
|
||||
ProtectiveMBR: true,
|
||||
GUID: cnst.DiskUUID, // Set know predictable UUID
|
||||
Partitions: kairosPartsToDiskfsGPTParts(parts, d.Size, d.LogicalBlocksize),
|
||||
LogicalSectorSize: int(d.LogicalBlocksize),
|
||||
PhysicalSectorSize: int(d.PhysicalBlocksize),
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid partition type: %s", partType)
|
||||
@ -39,11 +41,11 @@ func (d *Disk) NewPartitionTable(partType string, parts sdkTypes.PartitionList)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSectorEndFromSize(start, size uint64) uint64 {
|
||||
return (size / uint64(diskfs.SectorSize512)) + start - 1
|
||||
func getSectorEndFromSize(start, size uint64, sectorSize int64) uint64 {
|
||||
return (size / uint64(sectorSize)) + start - 1
|
||||
}
|
||||
|
||||
func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) []*gpt.Partition {
|
||||
func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64, sectorSize int64) []*gpt.Partition {
|
||||
var partitions []*gpt.Partition
|
||||
for index, part := range parts {
|
||||
var start uint64
|
||||
@ -51,7 +53,7 @@ func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) [
|
||||
var size uint64
|
||||
if len(partitions) == 0 {
|
||||
// first partition, align to 1Mb
|
||||
start = 1024 * 1024 / uint64(diskfs.SectorSize512)
|
||||
start = 1024 * 1024 / uint64(sectorSize)
|
||||
} else {
|
||||
// get latest partition end, sum 1
|
||||
start = partitions[len(partitions)-1].End + 1
|
||||
@ -78,7 +80,7 @@ func kairosPartsToDiskfsGPTParts(parts sdkTypes.PartitionList, diskSize int64) [
|
||||
|
||||
}
|
||||
|
||||
end = getSectorEndFromSize(start, size)
|
||||
end = getSectorEndFromSize(start, size, sectorSize)
|
||||
|
||||
if part.Name == cnst.EfiPartName && part.FS == cnst.EfiFs {
|
||||
// EFI boot partition
|
||||
@ -127,7 +129,7 @@ func WithLogger(logger sdkTypes.KairosLogger) func(d *Disk) error {
|
||||
}
|
||||
|
||||
func NewDisk(device string, opts ...DiskOptions) (*Disk, error) {
|
||||
d, err := diskfs.Open(device, diskfs.WithSectorSize(512))
|
||||
d, err := diskfs.Open(device)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ const (
|
||||
// ImageSource represents the source from where an image is created for easy identification
|
||||
type ImageSource struct {
|
||||
source string `yaml:"source"`
|
||||
srcType string
|
||||
srcType string `yaml:"type"`
|
||||
}
|
||||
|
||||
func (i ImageSource) Value() string {
|
||||
|
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -31,10 +31,12 @@ type FS interface {
|
||||
RemoveAll(path string) error
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
Readlink(name string) (string, error)
|
||||
Symlink(oldname, newname string) error
|
||||
RawPath(name string) (string, error)
|
||||
ReadDir(dirname string) ([]fs.DirEntry, error)
|
||||
Remove(name string) error
|
||||
OpenFile(name string, flag int, perm fs.FileMode) (*os.File, error)
|
||||
WriteFile(filename string, data []byte, perm os.FileMode) error
|
||||
Rename(oldpath, newpath string) error
|
||||
Truncate(name string, size int64) error
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -166,3 +166,47 @@ func copyFile(src, dst string) error {
|
||||
|
||||
return destinationFile.Close()
|
||||
}
|
||||
|
||||
func AddSystemdConfSortKey(fs v1.FS, artifactDir string, log sdkTypes.KairosLogger) error {
|
||||
return fsutils.WalkDirFs(fs, artifactDir, func(path string, info os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Only do files that are conf files but dont match the loader.conf
|
||||
if !info.IsDir() && filepath.Ext(path) == ".conf" && !strings.Contains(info.Name(), "loader.conf") {
|
||||
log.Logger.Debug().Str("path", path).Msg("Adding sort key to file")
|
||||
conf, err := sdkutils.SystemdBootConfReader(path)
|
||||
if err != nil {
|
||||
log.Errorf("Error reading conf file to extract values %s: %s", conf, path)
|
||||
}
|
||||
// Now check and put the proper sort key
|
||||
var sortKey string
|
||||
// If we have 2 different files that start with active, like with the extra-cmdline, how do we set this?
|
||||
// Ideally if they both have the same sort key, they will be sorted by name so the single one will be first
|
||||
// and the extra-cmdline will be second. This is the best we can do currently without making this a mess
|
||||
// Maybe we need the bootentry command to also set the sort key somehow?
|
||||
switch {
|
||||
case strings.Contains(info.Name(), "active"):
|
||||
sortKey = "0001"
|
||||
case strings.Contains(info.Name(), "passive"):
|
||||
sortKey = "0002"
|
||||
case strings.Contains(info.Name(), "recovery"):
|
||||
sortKey = "0003"
|
||||
case strings.Contains(info.Name(), "statereset"):
|
||||
sortKey = "0004"
|
||||
default: // Anything that dont matches, goes to the bottom
|
||||
sortKey = "0010"
|
||||
}
|
||||
conf["sort-key"] = sortKey
|
||||
newContents := ""
|
||||
for k, v := range conf {
|
||||
newContents = fmt.Sprintf("%s%s %s\n", newContents, k, v)
|
||||
}
|
||||
log.Logger.Trace().Str("contents", litter.Sdump(conf)).Str("path", path).Msg("Final values for conf file")
|
||||
|
||||
return os.WriteFile(path, []byte(newContents), 0600)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("Common functions tests", func() {
|
||||
|
@ -181,13 +181,25 @@ func (i *InstallAction) Run() (err error) {
|
||||
return fmt.Errorf("removing artifact set with role %s: %w", UnassignedArtifactRole, err)
|
||||
}
|
||||
|
||||
// add sort key to all files
|
||||
err = AddSystemdConfSortKey(i.cfg.Fs, i.spec.Partitions.EFI.MountPoint, i.cfg.Logger)
|
||||
if err != nil {
|
||||
i.cfg.Logger.Warnf("adding sort key: %s", err.Error())
|
||||
}
|
||||
|
||||
// Add boot assessment to files by appending +3 to the name
|
||||
err = utils.AddBootAssessment(i.cfg.Fs, i.spec.Partitions.EFI.MountPoint, i.cfg.Logger)
|
||||
if err != nil {
|
||||
i.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
|
||||
}
|
||||
|
||||
// SelectBootEntry sets the default boot entry to the selected entry
|
||||
err = action.SelectBootEntry(i.cfg, "cos")
|
||||
if err != nil {
|
||||
i.cfg.Logger.Warnf("selecting active boot entry: %s", err.Error())
|
||||
}
|
||||
|
||||
err = hook.Run(*i.cfg, i.spec, hook.UKIEncryptionHooks...)
|
||||
err = hook.Run(*i.cfg, i.spec, hook.PostInstall...)
|
||||
if err != nil {
|
||||
i.cfg.Logger.Errorf("running uki encryption hooks: %s", err.Error())
|
||||
return err
|
||||
@ -211,7 +223,7 @@ func (i *InstallAction) Run() (err error) {
|
||||
i.cfg.Logger.Errorf("running kairos-uki-install.after hook script: %s", err.Error())
|
||||
}
|
||||
|
||||
return hook.Run(*i.cfg, i.spec, hook.AfterUkiInstall...)
|
||||
return hook.Run(*i.cfg, i.spec, hook.FinishUKIInstall...)
|
||||
}
|
||||
|
||||
func (i *InstallAction) SkipEntry(path string, conf map[string]string) (err error) {
|
||||
|
@ -2,7 +2,6 @@ package uki
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/action"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/config"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
@ -35,14 +34,12 @@ func (r *ResetAction) Run() (err error) {
|
||||
cleanup := utils.NewCleanStack()
|
||||
defer func() { err = cleanup.Cleanup(err) }()
|
||||
|
||||
// Unmount partitions if any is already mounted before formatting
|
||||
err = e.UnmountPartitions(r.spec.Partitions.PartitionsByMountPoint(true))
|
||||
if err != nil {
|
||||
r.cfg.Logger.Errorf("unmounting partitions: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
// At this point, both partitions are unlocked but they might not be mounted, like persistent
|
||||
// And the /dev/disk/by-label are not pointing to the proper ones
|
||||
// We need to manually trigger udev to make sure the symlinks are correct
|
||||
_, err = utils.SH("udevadm trigger --type=all || udevadm trigger")
|
||||
_, err = utils.SH("udevadm settle")
|
||||
|
||||
// Reformat persistent partition
|
||||
if r.spec.FormatPersistent {
|
||||
persistent := r.spec.Partitions.Persistent
|
||||
if persistent != nil {
|
||||
@ -58,11 +55,20 @@ func (r *ResetAction) Run() (err error) {
|
||||
if r.spec.FormatOEM {
|
||||
oem := r.spec.Partitions.OEM
|
||||
if oem != nil {
|
||||
err = e.UnmountPartition(oem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = e.FormatPartition(oem)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Errorf("formatting OEM partition: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
// Mount it back, as oem is mounted during recovery, keep everything as is
|
||||
err = e.MountPartition(oem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,6 +86,19 @@ func (r *ResetAction) Run() (err error) {
|
||||
r.cfg.Logger.Errorf("copying recovery to active: %s", err.Error())
|
||||
return fmt.Errorf("copying recovery to active: %w", err)
|
||||
}
|
||||
|
||||
// add sort key to all files
|
||||
err = AddSystemdConfSortKey(r.cfg.Fs, r.spec.Partitions.EFI.MountPoint, r.cfg.Logger)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Warnf("adding sort key: %s", err.Error())
|
||||
}
|
||||
|
||||
// Add boot assessment to files by appending +3 to the name
|
||||
err = elementalUtils.AddBootAssessment(r.cfg.Fs, r.spec.Partitions.EFI.MountPoint, r.cfg.Logger)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
|
||||
}
|
||||
|
||||
// SelectBootEntry sets the default boot entry to the selected entry
|
||||
err = action.SelectBootEntry(r.cfg, "cos")
|
||||
// Should we fail? Or warn?
|
||||
@ -88,14 +107,6 @@ func (r *ResetAction) Run() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if mnt, err := elementalUtils.IsMounted(r.cfg, r.spec.Partitions.OEM); !mnt && err == nil {
|
||||
err = e.MountPartition(r.spec.Partitions.OEM)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Errorf("mounting oem partition: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = Hook(r.cfg, constants.AfterResetHook)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Errorf("running after install hook: %s", err.Error())
|
||||
|
@ -110,6 +110,17 @@ func (i *UpgradeAction) Run() (err error) {
|
||||
return fmt.Errorf("removing artifact set: %w", err)
|
||||
}
|
||||
|
||||
// add sort key to all files
|
||||
err = AddSystemdConfSortKey(i.cfg.Fs, i.spec.EfiPartition.MountPoint, i.cfg.Logger)
|
||||
if err != nil {
|
||||
i.cfg.Logger.Warnf("adding sort key: %s", err.Error())
|
||||
}
|
||||
|
||||
// Add boot assessment to files by appending +3 to the name
|
||||
err = elementalUtils.AddBootAssessment(i.cfg.Fs, i.spec.EfiPartition.MountPoint, i.cfg.Logger)
|
||||
if err != nil {
|
||||
i.cfg.Logger.Warnf("adding boot assesment: %s", err.Error())
|
||||
}
|
||||
// SelectBootEntry sets the default boot entry to the selected entry
|
||||
err = action.SelectBootEntry(i.cfg, "cos")
|
||||
// Should we fail? Or warn?
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -45,7 +46,7 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
)
|
||||
|
||||
func CommandExists(command string) bool {
|
||||
@ -177,7 +178,14 @@ func SyncData(log sdkTypes.KairosLogger, runner v1.Runner, fs v1.FS, source stri
|
||||
}
|
||||
|
||||
log.Infof("Starting rsync...")
|
||||
args := []string{"--progress", "--partial", "--human-readable", "--archive", "--xattrs", "--acls"}
|
||||
// TODO: copy xattr if possible or needed for selinux contexts? Or do we just relabel those on first boot?
|
||||
args := []string{
|
||||
"--progress",
|
||||
"--partial",
|
||||
"--human-readable",
|
||||
"--archive", // recursive, symbolic links, permissions, owner, group, modification times, device files, special files
|
||||
"--acls", // preserve ACLS and permissions
|
||||
}
|
||||
|
||||
for _, e := range excludes {
|
||||
args = append(args, fmt.Sprintf("--exclude=%s", e))
|
||||
@ -495,11 +503,27 @@ func CalcFileChecksum(fs v1.FS, fileName string) (string, error) {
|
||||
|
||||
// FindCommand will search for the command(s) in the options given to find the current command
|
||||
// If it cant find it returns the default value give. Useful for the same binaries with different names across OS
|
||||
func FindCommand(defaultPath string, options []string) string {
|
||||
func FindCommand(fs v1.FS, defaultPath string, options []string) string {
|
||||
for _, p := range options {
|
||||
path, err := exec.LookPath(p)
|
||||
if err == nil {
|
||||
return path
|
||||
// If its a full path, check if it exists directly
|
||||
if strings.Contains(p, "/") {
|
||||
d, err := fs.Stat(p)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if d.IsDir() {
|
||||
continue
|
||||
}
|
||||
m := d.Mode()
|
||||
// Check if its executable
|
||||
if m&0111 != 0 {
|
||||
return p
|
||||
}
|
||||
} else {
|
||||
path, err := exec.LookPath(p)
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,3 +638,72 @@ func CheckFailedInstallation(stateFile string) (bool, error) {
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddBootAssessment adds boot assessment to files by appending +3 to the name
|
||||
// Only for files that dont have it already as those are the ones upgraded
|
||||
// Existing files that have a boot assessment will be left as is
|
||||
// This should be called during install, upgrade and reset
|
||||
// Mainly everything that updates the config files to point to a new artifact we need to reset the boot assessment
|
||||
// as its a new artifact that needs to be assessed
|
||||
func AddBootAssessment(fs v1.FS, artifactDir string, logger sdkTypes.KairosLogger) error {
|
||||
return fsutils.WalkDirFs(fs, artifactDir, func(path string, info os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Only do files that are conf files but dont match the loader.conf
|
||||
if !info.IsDir() && filepath.Ext(path) == ".conf" && !strings.Contains(info.Name(), "loader.conf") {
|
||||
dir := filepath.Dir(path)
|
||||
ext := filepath.Ext(path)
|
||||
base := strings.TrimSuffix(filepath.Base(path), ext)
|
||||
// Lets check if the file has a boot assessment already. If it does, we dont need to do anything
|
||||
// If it matches continue
|
||||
re := regexp.MustCompile(`\+\d+(-\d+)?$`)
|
||||
if re.MatchString(base) {
|
||||
logger.Logger.Debug().Str("file", path).Msg("Boot assessment already present in file")
|
||||
return nil
|
||||
}
|
||||
newBase := fmt.Sprintf("%s+3%s", base, ext)
|
||||
newPath := filepath.Join(dir, newBase)
|
||||
logger.Logger.Debug().Str("from", path).Str("to", newPath).Msg("Enabling boot assessment")
|
||||
err = fs.Rename(path, newPath)
|
||||
if err != nil {
|
||||
logger.Logger.Err(err).Str("from", path).Str("to", newPath).Msg("Error renaming file")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ReadAssessmentFromEntry(fs v1.FS, entry string, logger sdkTypes.KairosLogger) (string, error) {
|
||||
// Read current config for boot assessment from current config. We should already have the final config name
|
||||
// Fix fallback and cos pointing to passive and active
|
||||
if strings.HasPrefix(entry, "fallback") {
|
||||
entry = strings.Replace(entry, "fallback", "passive", 1)
|
||||
}
|
||||
if strings.HasPrefix(entry, "cos") {
|
||||
entry = strings.Replace(entry, "cos", "active", 1)
|
||||
}
|
||||
efiPart, err := partitions.GetEfiPartition(&logger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// We only want the ones that match the assessment
|
||||
currentfile, err := fsutils.GlobFs(fs, filepath.Join(efiPart.MountPoint, "loader/entries", entry+"+*.conf"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(currentfile) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if len(currentfile) > 1 {
|
||||
return "", fmt.Errorf(cnst.MultipleEntriesAssessmentError, entry)
|
||||
}
|
||||
re := regexp.MustCompile(`(\+\d+(-\d+)?)\.conf$`)
|
||||
if !re.MatchString(currentfile[0]) {
|
||||
logger.Logger.Debug().Str("file", currentfile[0]).Msg(cnst.NoBootAssessmentWarning)
|
||||
return "", nil
|
||||
}
|
||||
return re.FindStringSubmatch(currentfile[0])[1], nil
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ import (
|
||||
"time"
|
||||
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
// DirSize returns the accumulated size of all files in folder
|
||||
@ -273,3 +273,38 @@ func Copy(fs v1.FS, src, dst string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GlobFs returns the names of all files matching pattern or nil if there is no matching file.
|
||||
// Only consider the names of files in the directory included in the pattern, not in subdirectories.
|
||||
// So the pattern "dir/*" will return only the files in the directory "dir", not in "dir/subdir".
|
||||
func GlobFs(fs v1.FS, pattern string) ([]string, error) {
|
||||
var matches []string
|
||||
|
||||
// Check if the pattern is well formed.
|
||||
if _, err := filepath.Match(pattern, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Split the pattern into directory and file parts.
|
||||
dir, file := filepath.Split(pattern)
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
// Read the directory.
|
||||
entries, err := fs.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Match the entries against the pattern.
|
||||
for _, entry := range entries {
|
||||
if matched, err := filepath.Match(file, entry.Name()); err != nil {
|
||||
return nil, err
|
||||
} else if matched {
|
||||
matches = append(matches, filepath.Join(dir, entry.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-sdk/utils"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -46,6 +47,9 @@ func NewGrub(config *agentConfig.Config) *Grub {
|
||||
}
|
||||
|
||||
// Install installs grub into the device, copy the config file and add any extra TTY to grub
|
||||
// TODO: Make it more generic to be able to call it from other places
|
||||
// i.e.: filepath.Join(cnst.ActiveDir, "etc/kairos-release") seraches for the file in the active dir, we should be looking into the rootdir?
|
||||
// filepath.Join(cnst.EfiDir, "EFI/boot/grub.cfg") we also write into the efi dir directly, this should be the a var maybe?
|
||||
func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, stateLabel string) (err error) { // nolint:gocyclo
|
||||
var grubargs []string
|
||||
var grubdir, finalContent string
|
||||
@ -58,16 +62,38 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool,
|
||||
if !efi {
|
||||
g.config.Logger.Info("Installing GRUB..")
|
||||
|
||||
// Find where in the rootDir the grub2 files for i386-pc are
|
||||
// Use the modinfo.sh as a marker
|
||||
grubdir = findGrubDir(g.config.Fs, rootDir)
|
||||
if grubdir == "" {
|
||||
g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Failed to find grub dir")
|
||||
return fmt.Errorf("failed to find grub dir under %s", rootDir)
|
||||
}
|
||||
|
||||
grubargs = append(
|
||||
grubargs,
|
||||
fmt.Sprintf("--root-directory=%s", rootDir),
|
||||
fmt.Sprintf("--directory=%s", grubdir),
|
||||
fmt.Sprintf("--boot-directory=%s", bootDir),
|
||||
"--target=i386-pc",
|
||||
target,
|
||||
)
|
||||
|
||||
g.config.Logger.Debugf("Running grub with the following args: %s", grubargs)
|
||||
out, err := g.config.Runner.Run(FindCommand("grub2-install", []string{"grub2-install", "grub-install"}), grubargs...)
|
||||
|
||||
grubBin := FindCommand(g.config.Fs, "", []string{
|
||||
filepath.Join(rootDir, "/usr/sbin/", "grub2-install"),
|
||||
filepath.Join(rootDir, "/usr/bin/", "grub2-install"),
|
||||
filepath.Join(rootDir, "/sbin/", "grub2-install"),
|
||||
filepath.Join(rootDir, "/usr/sbin/", "grub-install"),
|
||||
filepath.Join(rootDir, "/usr/bin/", "grub-install"),
|
||||
filepath.Join(rootDir, "/sbin/", "grub-install"),
|
||||
})
|
||||
g.config.Logger.Logger.Debug().Str("command", grubBin).Msg("Found grub binary")
|
||||
if grubBin == "" {
|
||||
g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Grub binary not found in path")
|
||||
return fmt.Errorf("grub binary not found in path")
|
||||
}
|
||||
out, err := g.config.Runner.Run(grubBin, grubargs...)
|
||||
if err != nil {
|
||||
g.config.Logger.Errorf(string(out))
|
||||
return err
|
||||
@ -254,20 +280,62 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool,
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPersistentVariables sets the given vars into the given grubEnvFile for grub to read them
|
||||
func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS) error {
|
||||
// findGrubDir will find the grub dir under the dir given if possible by searching for the modinfo.sh
|
||||
// And it will return the full dir path where the modinfo.sh is contained
|
||||
func findGrubDir(vfs v1.FS, dir string) string {
|
||||
var foundPath string
|
||||
_ = fsutils.WalkDirFs(vfs, dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.Name() == "modinfo.sh" && strings.Contains(path, "i386-pc") {
|
||||
// We found the grub dir, return it
|
||||
foundPath = filepath.Dir(path)
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return foundPath
|
||||
|
||||
}
|
||||
|
||||
func SetPersistentVariables(grubEnvFile string, vars map[string]string, c *agentConfig.Config) error {
|
||||
var b bytes.Buffer
|
||||
// Write header
|
||||
b.WriteString("# GRUB Environment Block\n")
|
||||
|
||||
keys := make([]string, 0, len(vars))
|
||||
for k := range vars {
|
||||
// First we need to read the existing values from the grubenv file if they exist
|
||||
finalVars, err := ReadPersistentVariables(grubEnvFile, c)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("error reading existing grubenv file %s: %s", grubEnvFile, err)
|
||||
}
|
||||
}
|
||||
// Check if we have a nil var
|
||||
if len(finalVars) != 0 {
|
||||
c.Logger.Logger.Debug().Interface("existingVars", finalVars).Msg("Existing grubenv variables")
|
||||
}
|
||||
|
||||
// Merge the existing vars with the new ones
|
||||
// existing vars will be overridden by the new ones from vars if they match
|
||||
for key, newValue := range vars {
|
||||
if oldValue, exists := finalVars[key]; exists {
|
||||
c.Logger.Logger.Warn().Str("key", key).Str("oldValue", oldValue).Str("newValue", newValue).Msg("Overriding existing grubenv variable")
|
||||
}
|
||||
finalVars[key] = newValue
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(finalVars))
|
||||
|
||||
for k := range finalVars {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, k := range keys {
|
||||
if len(vars[k]) > 0 {
|
||||
b.WriteString(fmt.Sprintf("%s=%s\n", k, vars[k]))
|
||||
if len(finalVars[k]) > 0 {
|
||||
b.WriteString(fmt.Sprintf("%s=%s\n", k, finalVars[k]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,7 +345,7 @@ func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS
|
||||
for i := 0; i < toBeFilled; i++ {
|
||||
b.WriteByte('#')
|
||||
}
|
||||
return fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm)
|
||||
return c.Fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm)
|
||||
}
|
||||
|
||||
// copyGrubFonts will try to finds and copy the needed grub fonts into the system
|
||||
@ -394,11 +462,12 @@ func (g Grub) copyGrub() error {
|
||||
}
|
||||
|
||||
// ReadPersistentVariables will read a grub env file and parse the values
|
||||
func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, error) {
|
||||
func ReadPersistentVariables(grubEnvFile string, c *agentConfig.Config) (map[string]string, error) {
|
||||
vars := make(map[string]string)
|
||||
f, err := fs.ReadFile(grubEnvFile)
|
||||
|
||||
f, err := c.Fs.ReadFile(grubEnvFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return vars, err
|
||||
}
|
||||
for _, a := range strings.Split(string(f), "\n") {
|
||||
// comment or fillup, so skip
|
||||
@ -409,7 +478,7 @@ func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, e
|
||||
if len(splitted) == 2 {
|
||||
vars[splitted[0]] = splitted[1]
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid format for %s", a)
|
||||
return vars, fmt.Errorf("invalid format for %s", a)
|
||||
}
|
||||
}
|
||||
return vars, nil
|
||||
|
27
pkg/utils/k8s/common.go
Normal file
27
pkg/utils/k8s/common.go
Normal file
@ -0,0 +1,27 @@
|
||||
package k8s
|
||||
|
||||
import "os"
|
||||
|
||||
// GetHostDirForK8s returns the base dir where the system is
|
||||
// This is because under k8s, we mount the actual system under a dir
|
||||
// So we need to know which paths we need to read the configs from
|
||||
// if we read them from the root directly, we are actually reading the
|
||||
// configs of the upgrade container
|
||||
// If not found returns an empty string
|
||||
func GetHostDirForK8s() string {
|
||||
_, underKubernetes := os.LookupEnv("KUBERNETES_SERVICE_HOST")
|
||||
// Try to get the HOST_DIR in case we are not using the default one
|
||||
hostDirEnv := os.Getenv("HOST_DIR")
|
||||
// If we are under kubernetes but the HOST_DIR var is empty, default to /host as system-upgrade-controller mounts
|
||||
// the host in that dir by default
|
||||
if underKubernetes {
|
||||
if hostDirEnv != "" {
|
||||
return hostDirEnv
|
||||
} else {
|
||||
return "/host"
|
||||
}
|
||||
} else {
|
||||
// We return an empty string so any filepath.join does not alter the paths
|
||||
return ""
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -30,8 +30,8 @@ import (
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
func writeCmdline(s string, fs v1.FS) error {
|
||||
|
@ -32,14 +32,15 @@ import (
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/fs"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/utils/partitions"
|
||||
"github.com/kairos-io/kairos-agent/v2/tests/matchers"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
ghwMock "github.com/kairos-io/kairos-sdk/ghw/mocks"
|
||||
sdkTypes "github.com/kairos-io/kairos-sdk/types"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs/v4"
|
||||
"github.com/twpayne/go-vfs/v4/vfst"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"github.com/twpayne/go-vfs/v5/vfst"
|
||||
)
|
||||
|
||||
func getNamesFromListFiles(list []fs.DirEntry) []string {
|
||||
@ -690,6 +691,17 @@ var _ = Describe("Utils", Label("utils"), func() {
|
||||
|
||||
err = fs.WriteFile(filepath.Join(rootDir, constants.GrubConf), []byte("console=tty1"), 0644)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
// Create fake grub dir in rootfs and fake grub binaries
|
||||
err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "sbin"), constants.DirPerm)
|
||||
Expect(err).To(BeNil())
|
||||
f, err := fs.Create(filepath.Join(rootDir, "sbin", "grub2-install"))
|
||||
Expect(err).To(BeNil())
|
||||
Expect(f.Chmod(0755)).ToNot(HaveOccurred())
|
||||
err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"), constants.DirPerm)
|
||||
Expect(err).To(BeNil())
|
||||
_, err = fs.Create(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc", "modinfo.sh"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
})
|
||||
It("installs with default values", func() {
|
||||
grub := utils.NewGrub(config)
|
||||
@ -740,6 +752,18 @@ var _ = Describe("Utils", Label("utils"), func() {
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
})
|
||||
It("fails with bios if no grub2-install file exists", func() {
|
||||
Expect(fs.RemoveAll(filepath.Join(rootDir, "sbin", "grub2-install"))).ToNot(HaveOccurred())
|
||||
grub := utils.NewGrub(config)
|
||||
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
It("fails with bios if no modules files exists", func() {
|
||||
Expect(fs.RemoveAll(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"))).ToNot(HaveOccurred())
|
||||
grub := utils.NewGrub(config)
|
||||
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "")
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
It("fails with efi if no modules files exist", Label("efi"), func() {
|
||||
grub := utils.NewGrub(config)
|
||||
err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", true, "")
|
||||
@ -803,9 +827,9 @@ var _ = Describe("Utils", Label("utils"), func() {
|
||||
defer os.Remove(temp.Name())
|
||||
Expect(utils.SetPersistentVariables(
|
||||
temp.Name(), map[string]string{"key1": "value1", "key2": "value2"},
|
||||
config.Fs,
|
||||
config,
|
||||
)).To(BeNil())
|
||||
readVars, err := utils.ReadPersistentVariables(temp.Name(), config.Fs)
|
||||
readVars, err := utils.ReadPersistentVariables(temp.Name(), config)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(readVars["key1"]).To(Equal("value1"))
|
||||
Expect(readVars["key2"]).To(Equal("value2"))
|
||||
@ -813,10 +837,35 @@ var _ = Describe("Utils", Label("utils"), func() {
|
||||
It("Fails setting variables", func() {
|
||||
e := utils.SetPersistentVariables(
|
||||
"badfilenopath", map[string]string{"key1": "value1"},
|
||||
config.Fs,
|
||||
config,
|
||||
)
|
||||
Expect(e).NotTo(BeNil())
|
||||
})
|
||||
It("respects existing variables", func() {
|
||||
temp, err := os.CreateTemp("", "grub-*")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
defer os.Remove(temp.Name())
|
||||
Expect(utils.SetPersistentVariables(
|
||||
temp.Name(), map[string]string{"key1": "value1", "key2": "value2"},
|
||||
config,
|
||||
)).To(BeNil())
|
||||
readVars, err := utils.ReadPersistentVariables(temp.Name(), config)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(readVars["key1"]).To(Equal("value1"))
|
||||
Expect(readVars["key2"]).To(Equal("value2"))
|
||||
|
||||
// Now we do it again with a different value
|
||||
Expect(utils.SetPersistentVariables(
|
||||
temp.Name(), map[string]string{"key1": "value3", "key3": "value4"},
|
||||
config,
|
||||
)).To(BeNil())
|
||||
// Now there should be an extra key and key1 should be updated
|
||||
readVars, err = utils.ReadPersistentVariables(temp.Name(), config)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(readVars["key1"]).To(Equal("value3"))
|
||||
Expect(readVars["key2"]).To(Equal("value2"))
|
||||
Expect(readVars["key3"]).To(Equal("value4"))
|
||||
})
|
||||
})
|
||||
})
|
||||
Describe("CreateSquashFS", Label("CreateSquashFS"), func() {
|
||||
@ -1062,4 +1111,206 @@ var _ = Describe("Utils", Label("utils"), func() {
|
||||
Expect(utils.IsUkiWithFs(fs)).To(BeFalse())
|
||||
})
|
||||
})
|
||||
Describe("AddBootAssessment", func() {
|
||||
BeforeEach(func() {
|
||||
Expect(fsutils.MkdirAll(fs, "/efi/loader/entries", os.ModePerm)).ToNot(HaveOccurred())
|
||||
})
|
||||
It("adds the boot assessment to a file", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/test.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect("/efi/loader/entries/test.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
// Should match with the +3
|
||||
Expect("/efi/loader/entries/test+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
})
|
||||
It("adds the boot assessment to several files", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/test1.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test3.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect("/efi/loader/entries/test1.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test2.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test3.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
// Should match with the +3
|
||||
Expect("/efi/loader/entries/test1+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test2+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test3+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
})
|
||||
It("leaves assessment in place for existing files", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/test1.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test2+3.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test3+1-2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = utils.AddBootAssessment(fs, "/efi/loader/entries", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect("/efi/loader/entries/test1.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test3+3.conf").ToNot(matchers.BeAnExistingFileFs(fs))
|
||||
// Should match with the +3 and the existing ones left in place
|
||||
Expect("/efi/loader/entries/test1+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test2+3.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
Expect("/efi/loader/entries/test3+1-2.conf").To(matchers.BeAnExistingFileFs(fs))
|
||||
})
|
||||
It("fails to write the boot assessment in non existing dir", func() {
|
||||
err := utils.AddBootAssessment(fs, "/fake", logger)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
Describe("ReadAssessmentFromEntry", func() {
|
||||
var ghwTest ghwMock.GhwMock
|
||||
BeforeEach(func() {
|
||||
Expect(fsutils.MkdirAll(fs, "/efi/loader/entries", os.ModePerm)).ToNot(HaveOccurred())
|
||||
mainDisk := sdkTypes.Disk{
|
||||
Name: "device",
|
||||
Partitions: []*sdkTypes.Partition{
|
||||
{
|
||||
Name: "device1",
|
||||
FilesystemLabel: "COS_GRUB",
|
||||
FS: "ext4",
|
||||
MountPoint: "/efi",
|
||||
},
|
||||
},
|
||||
}
|
||||
ghwTest = ghwMock.GhwMock{}
|
||||
ghwTest.AddDisk(mainDisk)
|
||||
ghwTest.CreateDevices()
|
||||
})
|
||||
AfterEach(func() {
|
||||
ghwTest.Clean()
|
||||
})
|
||||
It("reads the assessment from a file", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/test+2-1.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "test", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+2-1"))
|
||||
})
|
||||
It("reads passive when using fallback", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/passive+2-1.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// fallback should point to passive
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "fallback", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+2-1"))
|
||||
|
||||
// Should find the passive entry as well directly
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "passive", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+2-1"))
|
||||
})
|
||||
It("reads active when using cos", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/active+1-2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
// cos should point to active
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+1-2"))
|
||||
|
||||
// Should find the active entry as well directly
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "active", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+1-2"))
|
||||
})
|
||||
|
||||
It("empty assessment if it doesnt match", func() {
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
|
||||
// Should find the active entry as well directly
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "active", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
})
|
||||
|
||||
It("fails with no EFI partition", func() {
|
||||
ghwTest.Clean()
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "cos", logger)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
})
|
||||
|
||||
It("errors if more than one file matches", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/active+1-2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/active+3-2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "active", logger)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
Expect(err.Error()).To(Equal(fmt.Sprintf(constants.MultipleEntriesAssessmentError, "active")))
|
||||
})
|
||||
|
||||
It("errors if dir doesn't exist", func() {
|
||||
// Remove all dirs
|
||||
cleanup()
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "active", logger)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
// Check that error is os.ErrNotExist
|
||||
Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
|
||||
})
|
||||
|
||||
It("matches with weird but valid format", func() {
|
||||
// This are valid values, after all the asessment is just a string at the end that starts with + and has
|
||||
// and number and an optional dash after that. It has to be before the .conf so this are valid values
|
||||
// even if they are weird or stupid.
|
||||
// potentially the name can be this if someone is rebuilding efi files and adding the + to indicate the build number
|
||||
// for example.
|
||||
// We dont use this but still want to check if these are valid.
|
||||
err := fs.WriteFile("/efi/loader/entries/test1++++++5.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test2+3+3-1.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "test1", logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+5"))
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "test2", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal("+3-1"))
|
||||
})
|
||||
It("doesn't match assessment if format is wrong", func() {
|
||||
err := fs.WriteFile("/efi/loader/entries/test1+1djnfsdjknfsdajf2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test2+1-sadfsbauhdfkj.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test3+asdasd.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test4+-2.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = fs.WriteFile("/efi/loader/entries/test5+3&4.conf", []byte(""), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
entry, err := utils.ReadAssessmentFromEntry(fs, "test1", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "test2", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "test3", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "test4", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
entry, err = utils.ReadAssessmentFromEntry(fs, "test5", logger)
|
||||
// It actually does not error but just doesn't match
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(entry).To(Equal(""))
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -1,22 +1,23 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
"config:recommended"
|
||||
],
|
||||
"schedule": [
|
||||
"after 11pm every weekday",
|
||||
"before 7am every weekday",
|
||||
"every weekend"
|
||||
],
|
||||
"reviewers": [ "team:maintainers" ],
|
||||
"reviewers": [
|
||||
"team:maintainers"
|
||||
],
|
||||
"timezone": "Europe/Brussels",
|
||||
"rebaseWhen": "behind-base-branch",
|
||||
"constraints": {
|
||||
"go": "1.22"
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"matchUpdateTypes": [
|
||||
"patch"
|
||||
],
|
||||
"automerge": true
|
||||
}
|
||||
]
|
||||
|
44
tests/matchers/fs.go
Normal file
44
tests/matchers/fs.go
Normal file
@ -0,0 +1,44 @@
|
||||
package matchers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/onsi/gomega/format"
|
||||
"github.com/onsi/gomega/types"
|
||||
"github.com/twpayne/go-vfs/v5"
|
||||
"os"
|
||||
)
|
||||
|
||||
// BeAnExistingFileFs returns a matcher that checks if a file exists in the given vfs.
|
||||
func BeAnExistingFileFs(fs vfs.FS) types.GomegaMatcher {
|
||||
return &beAnExistingFileFsMatcher{
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
type beAnExistingFileFsMatcher struct {
|
||||
fs vfs.FS
|
||||
}
|
||||
|
||||
func (matcher *beAnExistingFileFsMatcher) Match(actual interface{}) (success bool, err error) {
|
||||
actualFilename, ok := actual.(string)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("BeAnExistingFileFs matcher expects a file path")
|
||||
}
|
||||
// Here is the magic, check existence against a vfs
|
||||
if _, err = matcher.fs.Stat(actualFilename); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (matcher *beAnExistingFileFsMatcher) FailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "to exist")
|
||||
}
|
||||
|
||||
func (matcher *beAnExistingFileFsMatcher) NegatedFailureMessage(actual interface{}) (message string) {
|
||||
return format.Message(actual, "not to exist")
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user