Use kairos-agent from packages (#1354)

*  Use kairos-agent from packages

Also drops all uneeded targets, code and so on for the agent
Moves profile-build into its own dir
Moves go.mod files into theyr own, one for testing and one for the
profile build
Adjusts earthly targets to the new stuff

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

* Bump repos and rework earthfile for kairos-agent package

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

* Restore missing earthly scripts

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

* Run apt update before apt install

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

* Copy go.{mod,sum} to local dir before tests

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

* Fix install tests

Is the only one not run in earthly, strange.

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>

---------

Signed-off-by: Itxaka <itxaka.garcia@spectrocloud.com>
This commit is contained in:
Itxaka 2023-04-26 16:12:58 +02:00 committed by GitHub
parent 4c5e0e93e4
commit 5147429a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 396 additions and 11497 deletions

View File

@ -1,294 +0,0 @@
version: 2.1
jobs:
release:
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Release binary assets
command: |
curl https://luet.io/install.sh | sudo sh
sudo luet repo add --type docker --yes --url quay.io/kairos/packages
sudo luet install -y utils/goreleaser
build-arm:
parameters:
flavor:
type: string
model:
type: string
image:
type: string
release:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build ARM images
command: |
mkdir -vp ~/.docker/cli-plugins/
curl --silent -L "https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64" > ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx version
sudo apt-get update && sudo apt-get install -y binfmt-support qemu-user-static
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --privileged --rm tonistiigi/binfmt --install arm64
docker context create buildcontext
docker buildx create buildcontext --use
if [ "<< parameters.release >>" == "yes" ]; then
go get github.com/tcnksm/ghr
echo $REGISTRY_PASSWORD | docker login -u $REGISTRY_USERNAME --password-stdin quay.io
export TAG=${CIRCLE_TAG}
else
export TAG=latest
fi
./earthly.sh +all-arm --IMAGE_NAME=kairos-<< parameters.flavor >>-$TAG.img --IMAGE=<< parameters.image >>-<< parameters.flavor >>:${TAG} --MODEL=<< parameters.model >> --FLAVOR=<< parameters.flavor >>
if [ "<< parameters.release >>" == "yes" ]; then
docker push << parameters.image >>-<< parameters.flavor >>:${TAG}
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${TAG} ./build/
fi
run-test:
parameters:
flavor:
type: string
test_suite:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +pull-release --FLAVOR=<< parameters.flavor >>
ls -liah
export ISO=$(ls build/*.iso)
sudo mv $ISO build/kairos.iso
./earthly.sh +run-qemu-test --FLAVOR=<< parameters.flavor >> --CONTAINER_IMAGE=ttl.sh/tests-<< parameters.flavor >>-$CIRCLE_SHA1:8h --TEST_SUITE=<< parameters.test_suite >>
run-latest-test:
parameters:
flavor:
type: string
test_suite:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +pull-release --FLAVOR=<< parameters.flavor >>
ls -liah
export ISO=$(ls build/*.iso)
sudo mv $ISO build/kairos.iso
./earthly.sh +run-qemu-test --FLAVOR=<< parameters.flavor >> --CONTAINER_IMAGE=ttl.sh/tests-<< parameters.flavor >>-$CIRCLE_SHA1:8h --TEST_SUITE=<< parameters.test_suite >>
run-datasource-test:
parameters:
flavor:
type: string
cloud_config:
type: string
test_suite:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +pull-build-artifacts --BUNDLE_IMAGE=ttl.sh/<< parameters.flavor >>-$CIRCLE_SHA1:8h
ls -liah
export ISO=$(ls build/*.iso)
sudo mv $ISO build/kairos.iso
./earthly.sh +datasource-iso --CLOUD_CONFIG=tests/assets/<< parameters.cloud_config >>.yaml
./earthly.sh +run-qemu-datasource-tests --TEST_SUITE=<< parameters.test_suite >> --FLAVOR=<< parameters.flavor >>
run-bundles-test:
parameters:
flavor:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +pull-build-artifacts --BUNDLE_IMAGE=ttl.sh/<< parameters.flavor >>-$CIRCLE_SHA1:8h
ls -liah
export ISO=$(ls build/*.iso)
sudo mv $ISO build/kairos.iso
./earthly.sh +prepare-bundles-tests
./earthly.sh +run-qemu-bundles-tests --FLAVOR=<< parameters.flavor >>
build-iso:
parameters:
flavor:
type: string
image:
type: string
release:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
if [ "<< parameters.release >>" == "yes" ]; then
go get github.com/tcnksm/ghr
echo $REGISTRY_PASSWORD | docker login -u $REGISTRY_USERNAME --password-stdin quay.io
export TAG=${CIRCLE_TAG}
else
export TAG=latest
fi
./earthly.sh +all --IMAGE=<< parameters.image >>-<< parameters.flavor >>:${TAG} --FLAVOR=<< parameters.flavor >> --ISO_NAME=kairos-<< parameters.flavor >>-${TAG}
./earthly.sh +push-build-artifacts --BUNDLE_IMAGE=ttl.sh/<< parameters.flavor >>-$CIRCLE_SHA1:8h
if [ "<< parameters.release >>" == "yes" ]; then
docker push << parameters.image >>-<< parameters.flavor >>:${TAG}
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${TAG} ./build/
fi
docker tag << parameters.image >>-<< parameters.flavor >>:${TAG} ttl.sh/tests-<< parameters.flavor >>-$CIRCLE_SHA1:8h
docker push ttl.sh/tests-<< parameters.flavor >>-$CIRCLE_SHA1:8h
lint:
machine:
image: ubuntu-2004:current
resource_class: medium
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +lint
build-bin:
machine:
image: ubuntu-2004:current
resource_class: medium
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +dist
unit-test:
machine:
image: ubuntu-2004:current
resource_class: medium
steps:
- checkout
- run:
name: Build
command: |
./earthly.sh +test
build-framework:
parameters:
flavor:
type: string
image:
type: string
release:
type: string
machine:
image: ubuntu-2004:current
resource_class: large
steps:
- checkout
- run:
name: Build
command: |
if [ "<< parameters.release >>" == "yes" ]; then
echo $REGISTRY_PASSWORD | docker login -u $REGISTRY_USERNAME --password-stdin quay.io
export TAG=${CIRCLE_TAG}
else
export TAG=latest
fi
./earthly.sh +framework-image --IMG=<< parameters.image >>:${TAG} --FLAVOR=<< parameters.flavor >> --WITH_KERNEL=false
./earthly.sh +framework-image --IMG=<< parameters.image >>-generic:${TAG} --FLAVOR=<< parameters.flavor >> --WITH_KERNEL=true
if [ "<< parameters.release >>" == "yes" ]; then
docker push << parameters.image >>-<< parameters.flavor >>:${TAG}
fi
workflows:
build:
jobs:
- build-arm:
matrix:
parameters:
image: ["quay.io/kairos/core"]
flavor: ["opensuse-tumbleweed-arm-rpi", "opensuse-leap-arm-rpi", "alpine-arm-rpi"]
model: ["rpi64"]
release: ["no"]
- build-framework:
matrix:
parameters:
image: ["quay.io/kairos/framework"]
flavor: ["opensuse-leap"]
release: ["no"]
# - run-datasource-test:
# matrix:
# parameters:
# cloud_config: ["autoinstall"]
# flavor: ["alpine","opensuse","ubuntu"]
# test_suite: ["reset-test", "autoinstall-test"]
# requires:
# - build-iso
# - run-latest-test:
# matrix:
# parameters:
# flavor: ["alpine","opensuse","ubuntu"]
# test_suite: ["upgrade-latest-with-cli"]
# requires:
# - build-iso
# - run-test:
# matrix:
# parameters:
# flavor: ["alpine","opensuse","ubuntu"]
# test_suite: ["upgrade-with-cli"]
# requires:
# - build-iso
# - run-bundles-test:
# matrix:
# parameters:
# flavor: ["opensuse"]
# requires:
# - build-iso
# - build-iso:
# matrix:
# parameters:
# image: [quay.io/kairos/core]
# flavor: ["alpine","opensuse","ubuntu", "rockylinux", "fedora"]
# release: ["no"]
# - run-datasource-test:
# matrix:
# parameters:
# cloud_config: ["autoinstall"]
# flavor: ["fedora", "rockylinux"]
# test_suite: ["reset-test", "autoinstall-test"]
# requires:
# - build-iso
lint:
jobs:
- lint
test:
jobs:
- unit-test
- build-bin

View File

@ -252,7 +252,8 @@ jobs:
export PATH=$PATH:$GOPATH/bin
export CREATE_VM=true
export FLAVOR=${{ matrix.flavor }}
go run github.com/onsi/ginkgo/v2/ginkgo --label-filter "install-test" --fail-fast -r ./tests/
cd tests
go run github.com/onsi/ginkgo/v2/ginkgo --label-filter "install-test" --fail-fast -r ./...
- uses: actions/upload-artifact@v3
if: failure()
with:

View File

@ -1,28 +0,0 @@
name: goreleaser
on:
push:
tags:
- 'v*'
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
git fetch --prune --unshallow
- name: Generate version
run: echo "VERSION=$(git describe --always --tags --dirty)" >> $GITHUB_ENV
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ^1.20
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v4
with:
version: latest
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ env.VERSION }}

View File

@ -1,45 +0,0 @@
name: Unit tests
on:
push:
branches:
- master
paths:
- '!docs/**'
pull_request:
paths:
- '**'
- '!docs/**'
env:
FORCE_COLOR: 1
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ^1.20
- name: Install earthly
uses: Luet-lab/luet-install-action@v1
with:
repository: quay.io/kairos/packages
packages: utils/earthly
- name: Run Build
run: |
earthly +dist
- name: Run tests
run: |
earthly +test
- name: Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage.out
- uses: actions/upload-artifact@v3
with:
name: build.zip
path: |
dist/*

View File

@ -1,29 +0,0 @@
name: WebUI tests
on:
push:
branches:
- master
paths:
- '**'
- '!docs/**'
pull_request:
paths:
- '**'
- '!docs/**'
concurrency:
group: ci-webui-${{ github.head_ref || github.ref }}-${{ github.repository }}
cancel-in-progress: true
jobs:
webui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install earthly
uses: Luet-lab/luet-install-action@v1
with:
repository: quay.io/kairos/packages
packages: utils/earthly
- name: WebUI tests
run: earthly +webui-tests

View File

@ -1,23 +0,0 @@
run:
timeout: 5m
tests: false
linters:
enable:
- revive # replacement for golint
- dupl # check duplicated code
- goconst # check strings that can turn into constants
- gofmt # check fmt
- goheader # Check license headers, only checks files in current year
- goimports # check imports
- gocyclo # check complexity
- govet
- gosimple
- deadcode
- ineffassign
- unused
- varcheck
- staticcheck
- typecheck
- structcheck
- godot
- misspell

View File

@ -1,39 +0,0 @@
# Make sure to check the documentation at http://goreleaser.com
project_name: kairos-agent
builds:
- ldflags:
- -w -s -X "github.com/kairos-io/kairos/v2/internal/common.VERSION={{.Env.VERSION}}"
env:
- CGO_ENABLED=0
goos:
- linux
goarch:
- amd64
- arm64
- 386
main: ./cmd/agent/
binary: '{{ .ProjectName }}'
source:
enabled: true
name_template: '{{ .ProjectName }}-{{ .Tag }}-source'
archives:
# Default template uses underscores instead of -
- name_template: "{{ .ProjectName }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
# TODO: this is deprecated -> https://goreleaser.com/deprecations#archivesreplacements
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: '{{ .ProjectName }}-{{ .Tag }}-checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- '^Merge pull request'

179
Earthfile
View File

@ -8,7 +8,6 @@ ARG ISO_NAME=kairos-${VARIANT}-${FLAVOR}
# renovate: datasource=docker depName=quay.io/luet/base
ARG LUET_VERSION=0.34.0
ARG OS_ID=kairos
ARG REPOSITORIES_FILE=framework-profile.yaml
# renovate: datasource=docker depName=aquasec/trivy
ARG TRIVY_VERSION=0.40.0
ARG COSIGN_SKIP=".*quay.io/kairos/.*"
@ -56,23 +55,14 @@ all-arm-generic:
BUILD --platform=linux/arm64 +image
BUILD --platform=linux/arm64 +iso
go-deps:
go-deps-test:
ARG GO_VERSION
FROM golang:$GO_VERSION
WORKDIR /build
COPY go.mod go.sum ./
COPY tests/go.mod tests/go.sum ./
RUN go mod download
RUN apt-get update && apt-get install -y upx
SAVE ARTIFACT go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum AS LOCAL go.sum
test:
FROM +go-deps
WORKDIR /build
COPY +luet/luet /usr/bin/luet
COPY . .
RUN go run github.com/onsi/ginkgo/v2/ginkgo --fail-fast --covermode=atomic --coverprofile=coverage.out -p -r ./pkg ./internal ./cmd ./sdk
SAVE ARTIFACT coverage.out AS LOCAL coverage.out
SAVE ARTIFACT go.mod go.mod AS LOCAL go.mod
SAVE ARTIFACT go.sum go.sum AS LOCAL go.sum
OSRELEASE:
COMMAND
@ -90,22 +80,6 @@ OSRELEASE:
# update OS-release file
RUN envsubst >>/etc/os-release </usr/lib/os-release.tmpl
BUILD_GOLANG:
COMMAND
WORKDIR /build
COPY . ./
ARG CGO_ENABLED
ARG BIN
ARG SRC
ARG VERSION
ENV CGO_ENABLED=${CGO_ENABLED}
ARG LDFLAGS="-s -w -X 'github.com/kairos-io/kairos/v2/internal/common.VERSION=${VERSION}'"
RUN --no-cache echo "Building ${BIN} from ${SRC} using ${VERSION}"
RUN echo ${LDFLAGS}
RUN go build -o ${BIN} -ldflags "${LDFLAGS}" ./cmd/${SRC} && upx ${BIN}
SAVE ARTIFACT ${BIN} ${BIN} AS LOCAL build/${BIN}
uuidgen:
FROM alpine
RUN apk add uuidgen
@ -127,42 +101,6 @@ version:
ARG VERSION=$(cat VERSION)
SAVE ARTIFACT VERSION VERSION
build-kairos-agent:
FROM +go-deps
COPY +webui-deps/node_modules ./internal/webui/public/node_modules
COPY +docs/public/local ./internal/webui/public/local
COPY +version/VERSION ./
ARG VERSION=$(cat VERSION)
RUN echo $(cat VERSION)
DO +BUILD_GOLANG --BIN=kairos-agent --SRC=agent --CGO_ENABLED=$CGO_ENABLED --VERSION=$VERSION
build:
BUILD +build-kairos-agent
dist:
ARG GO_VERSION
FROM golang:$GO_VERSION
COPY +luet/luet /usr/bin/luet
RUN mkdir -p /etc/luet/repos.conf.d/
RUN luet repo add kairos --yes --url quay.io/kairos/packages --type docker
RUN luet install -y utils/goreleaser
WORKDIR /build
COPY . .
COPY +version/VERSION ./
RUN echo $(cat VERSION)
RUN VERSION=$(cat VERSION) goreleaser build --rm-dist --skip-validate --snapshot
SAVE ARTIFACT /build/dist/* AS LOCAL dist/
golint:
ARG GO_VERSION
FROM golang:$GO_VERSION
ARG GOLINT_VERSION
RUN wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v$GOLINT_VERSION
WORKDIR /build
COPY . .
RUN golangci-lint run
hadolint:
ARG HADOLINT_VERSION
FROM hadolint/hadolint:$HADOLINT_VERSION
@ -191,7 +129,6 @@ yamllint:
RUN yamllint .github/workflows/ overlay/
lint:
BUILD +golint
BUILD +hadolint
BUILD +renovate-validate
BUILD +shellcheck-lint
@ -222,39 +159,36 @@ luet:
### Image Build targets
###
framework:
ARG COSIGN_SKIP
ARG REPOSITORIES_FILE
ARG COSIGN_EXPERIMENTAL
ARG COSIGN_REPOSITORY
ARG FLAVOR
ARG VERSION
ARG LDFLAGS="-s -w -X 'github.com/kairos-io/kairos/v2/internal/common.VERSION=$VERSION'"
# This generates the framework base by installing luet packages generated with the profile-build + framework-profile.yaml
# file
# Installs everything under the /framework dir and saves that as an artifact
framework-luet:
FROM golang:alpine
ARG FLAVOR
WORKDIR /build
COPY ./profile-build /build
COPY framework-profile.yaml /build
COPY +luet/luet /usr/bin/luet
# cosign keyless verify
ENV COSIGN_EXPERIMENTAL=${COSIGN_EXPERIMENTAL}
# Repo containing signatures
ENV COSIGN_REPOSITORY=${COSIGN_REPOSITORY}
# Skip this repo artifacts verify as they are not signed
ENV COSIGN_SKIP=${COSIGN_SKIP}
ENV USER=root
COPY . /build
RUN go run -ldflags "${LDFLAGS}" ./cmd/profile-build/main.go ${FLAVOR} $REPOSITORIES_FILE /framework
# Copy kairos binaries
COPY +build-kairos-agent/kairos-agent /framework/usr/bin/kairos-agent
COPY +luet/luet /framework/usr/bin/luet
RUN go run main.go ${FLAVOR} framework-profile.yaml /framework
RUN luet cleanup --system-target /framework
# COPY luet into the final framework
# TODO: Understand why?
COPY +luet/luet /framework/usr/bin/luet
# more cleanup
RUN rm -rf /framework/var/luet
RUN rm -rf /framework/var/cache
SAVE ARTIFACT --keep-own /framework framework-luet
framework:
FROM alpine
ARG FLAVOR
# This ARG does nothing?
ARG VERSION
COPY +framework-luet/framework-luet /framework
# Copy overlay files
# TODO: Make this also a package?
COPY overlay/files /framework
# Copy flavor-specific overlay files
IF [ "$FLAVOR" = "alpine-opensuse-leap" ] || [ "$FLAVOR" = "alpine-ubuntu" ]
@ -272,8 +206,6 @@ framework:
COPY overlay/files-ubuntu/ /framework
END
RUN rm -rf /framework/var/luet
RUN rm -rf /framework/var/cache
SAVE ARTIFACT --keep-own /framework/ framework
build-framework-image:
@ -574,30 +506,14 @@ grype-scan:
SAVE ARTIFACT /build/report.sarif report.sarif AS LOCAL build/${VARIANT}-${FLAVOR}-${VERSION}-grype.sarif
SAVE ARTIFACT /build/report.json report.json AS LOCAL build/${VARIANT}-${FLAVOR}-${VERSION}-grype.json
linux-bench:
ARG GO_VERSION
FROM golang:$GO_VERSION
GIT CLONE https://github.com/aquasecurity/linux-bench /linux-bench-src
RUN cd /linux-bench-src && CGO_ENABLED=0 go build -o linux-bench . && mv linux-bench /
SAVE ARTIFACT /linux-bench /linux-bench
# The target below should run on a live host instead.
# However, some checks are relevant as well at container level.
# It is good enough for a quick assessment.
linux-bench-scan:
FROM +image
GIT CLONE https://github.com/aquasecurity/linux-bench /build/linux-bench
WORKDIR /build/linux-bench
COPY +linux-bench/linux-bench /build/linux-bench/linux-bench
RUN /build/linux-bench/linux-bench
###
### Test targets
###
# usage e.g. ./earthly.sh +run-qemu-datasource-tests --FLAVOR=alpine-opensuse-leap --FROM_ARTIFACTS=true
run-qemu-datasource-tests:
FROM +go-deps
FROM +go-deps-test
RUN apt update
RUN apt install -y qemu-system-x86 qemu-utils golang git
WORKDIR /test
ARG FLAVOR
@ -627,12 +543,13 @@ run-qemu-datasource-tests:
ENV DATASOURCE=/test/build/datasource.iso
END
ENV CLOUD_INIT=/tests/tests/$CLOUD_CONFIG
COPY +go-deps-test/go.mod go.mod
COPY +go-deps-test/go.sum go.sum
RUN go run github.com/onsi/ginkgo/v2/ginkgo -v --label-filter "$TEST_SUITE" --fail-fast -r ./tests/
run-qemu-netboot-test:
FROM +go-deps
FROM +go-deps-test
COPY . /test
WORKDIR /test
@ -658,7 +575,8 @@ run-qemu-netboot-test:
ENV USE_QEMU=true
ARG TEST_SUITE=netboot-test
COPY +go-deps-test/go.mod go.mod
COPY +go-deps-test/go.sum go.sum
# TODO: use --pull or something to cache the python image in Earthly
WITH DOCKER
RUN docker run -d -v $PWD/build:/build --workdir=/build \
@ -667,7 +585,8 @@ run-qemu-netboot-test:
END
run-qemu-test:
FROM +go-deps
FROM +go-deps-test
RUN apt update
RUN apt install -y qemu-system-x86 qemu-utils git && apt clean
ARG FLAVOR
ARG TEST_SUITE=upgrade-with-cli
@ -686,6 +605,8 @@ run-qemu-test:
COPY +iso/kairos.iso kairos.iso
ENV ISO=/build/kairos.iso
END
COPY +go-deps-test/go.mod go.mod
COPY +go-deps-test/go.sum go.sum
RUN go run github.com/onsi/ginkgo/v2/ginkgo -v --label-filter "$TEST_SUITE" --fail-fast -r ./tests/
###
@ -778,27 +699,12 @@ examples-bundle-config:
RUN envsubst >> tests/assets/live-overlay.yaml < tests/assets/live-overlay.tmpl
SAVE ARTIFACT tests/assets/live-overlay.yaml AS LOCAL bundles-config.yaml
webui-deps:
FROM node:19-alpine
COPY . .
WORKDIR ./internal/webui/public
RUN npm install
SAVE ARTIFACT node_modules /node_modules AS LOCAL internal/webui/public/node_modules
webui-tests:
FROM ubuntu:22.10
RUN apt-get update && apt-get install -y libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb golang nodejs npm
COPY +build-kairos-agent/kairos-agent /usr/bin/kairos-agent
COPY . src/
WORKDIR src/
RUN .github/cypress_tests.sh
SAVE ARTIFACT /src/internal/webui/public/cypress/videos videos
docs:
FROM node:19-bullseye
ARG TARGETARCH
# Install dependencies
RUN apt update
RUN apt install git
# renovate: datasource=github-releases depName=gohugoio/hugo
ARG HUGO_VERSION="0.110.0"
@ -847,7 +753,10 @@ generate-schema:
FROM alpine
COPY . ./
COPY +version/VERSION ./
COPY +build-kairos-agent/kairos-agent /usr/bin/kairos-agent
COPY +luet/luet /usr/bin/luet
RUN mkdir -p /etc/luet/repos.conf.d/
RUN luet repo add kairos --yes --url quay.io/kairos/packages --type docker
RUN luet install -y system/kairos-agent
ARG RELEASE_VERSION=$(cat VERSION)
RUN mkdir "docs/static/$RELEASE_VERSION"
ARG SCHEMA_FILE="docs/static/$RELEASE_VERSION/cloud-config.json"

View File

@ -1,517 +0,0 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"github.com/kairos-io/kairos/v2/internal/agent"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/internal/webui"
"github.com/kairos-io/kairos-sdk/bundles"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/state"
"github.com/kairos-io/kairos/v2/internal/common"
"github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/Masterminds/semver/v3"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v3"
)
var configScanDir = []string{"/oem", "/usr/local/cloud-config", "/run/initramfs/live"}
// ReleasesToOutput gets a semver.Collection and outputs it in the given format
// Only used here.
func ReleasesToOutput(rels semver.Collection, output string) []string {
// Set them back to their original version number with the v in front
var stringRels []string
for _, v := range rels {
stringRels = append(stringRels, v.Original())
}
switch strings.ToLower(output) {
case "yaml":
d, _ := yaml.Marshal(stringRels)
return []string{string(d)}
case "json":
d, _ := json.Marshal(stringRels)
return []string{string(d)}
default:
return stringRels
}
}
var cmds = []*cli.Command{
{
Name: "upgrade",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Usage: "Force an upgrade",
},
&cli.BoolFlag{
Name: "debug",
Usage: "Show debug output",
},
&cli.StringFlag{
Name: "image",
Usage: "Specify an full image reference, e.g.: quay.io/some/image:tag",
},
&cli.StringFlag{Name: "auth-username", Usage: "Username to authenticate to registry"},
&cli.StringFlag{Name: "auth-password", Usage: "Password to authenticate to registry"},
&cli.StringFlag{Name: "auth-server-address", Usage: "Authentication server address"},
&cli.StringFlag{Name: "auth-type", Usage: "Auth type"},
&cli.StringFlag{Name: "auth-registry-token", Usage: "Authentication registry token"},
&cli.StringFlag{Name: "auth-identity-token", Usage: "Authentication identity token"},
&cli.BoolFlag{Name: "pre", Usage: "Include pre-releases (rc, beta, alpha)"},
},
Description: `
Manually upgrade a kairos node.
By default takes no arguments, defaulting to latest available release, to specify a version, pass it as argument:
$ kairos upgrade v1.20....
To retrieve all the available versions, use "kairos upgrade list-releases"
$ kairos upgrade list-releases
See https://kairos.io/docs/upgrade/manual/ for documentation.
`,
Subcommands: []*cli.Command{
{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Usage: "Output format (json|yaml|terminal)",
},
&cli.BoolFlag{Name: "pre", Usage: "Include pre-releases (rc, beta, alpha)"},
},
Name: "list-releases",
Description: `List all available releases versions`,
Action: func(c *cli.Context) error {
releases := agent.ListReleases(c.Bool("pre"))
list := ReleasesToOutput(releases, c.String("output"))
for _, i := range list {
fmt.Println(i)
}
return nil
},
},
},
Action: func(c *cli.Context) error {
var v string
if c.Args().Len() == 1 {
v = c.Args().First()
}
return agent.Upgrade(
v, c.String("image"), c.Bool("force"), c.Bool("debug"),
c.Bool("strict-validation"), configScanDir,
c.String("auth-username"), c.String("auth-password"), c.String("auth-server-address"),
c.String("auth-type"), c.String("auth-registry-token"), c.String("auth-identity-token"),
c.Bool("pre"),
)
},
},
{
Name: "notify",
Usage: "notify <event> <config dir>...",
UsageText: "emits the given event with a generic event payload",
Description: `
Sends a generic event payload with the configuration found in the scanned directories.
`,
Aliases: []string{},
Flags: []cli.Flag{},
Action: func(c *cli.Context) error {
dirs := []string{"/oem", "/usr/local/cloud-config"}
if c.Args().Len() > 1 {
dirs = c.Args().Slice()[1:]
}
return agent.Notify(c.Args().First(), dirs)
},
},
{
Name: "start",
Usage: "Starts the kairos agent",
UsageText: "starts the agent",
Description: `
Starts the kairos agent which automatically bootstrap and advertize to the kairos network.
`,
Aliases: []string{"s"},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "restart",
},
&cli.BoolFlag{
Name: "force",
},
&cli.StringFlag{
Name: "api",
Value: "http://127.0.0.1:8080",
},
},
Action: func(c *cli.Context) error {
dirs := []string{"/oem", "/usr/local/cloud-config"}
if c.Args().Present() {
dirs = c.Args().Slice()
}
opts := []agent.Option{
agent.WithAPI(c.String("api")),
agent.WithDirectory(dirs...),
}
if c.Bool("force") {
opts = append(opts, agent.ForceAgent)
}
if c.Bool("restart") {
opts = append(opts, agent.RestartAgent)
}
return agent.Run(opts...)
},
},
{
Name: "install-bundle",
Usage: "Installs a kairos bundle",
Description: `
Manually installs a kairos bundle.
E.g. kairos-agent install-bundle container:quay.io/kairos/kairos...
`,
Aliases: []string{"i"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "repository",
EnvVars: []string{"REPOSITORY"},
Value: "docker://quay.io/kairos/packages",
},
&cli.BoolFlag{
Name: "local-file",
EnvVars: []string{"LOCAL_FILE"},
},
},
UsageText: "Install a bundle manually in the node",
Action: func(c *cli.Context) error {
if c.Args().Len() != 1 {
return fmt.Errorf("bundle name required")
}
return bundles.RunBundles([]bundles.BundleOption{bundles.WithRepository(c.String("repository")), bundles.WithTarget(c.Args().First()), bundles.WithLocalFile(c.Bool("local-file"))})
},
},
{
Name: "uuid",
Usage: "Prints the local UUID",
Description: "Print node uuid",
Aliases: []string{"u"},
Action: func(c *cli.Context) error {
fmt.Print(machine.UUID())
return nil
},
},
{
Name: "webui",
Usage: "Starts the webui",
Description: "Starts the webui installer",
Aliases: []string{"w"},
Action: func(c *cli.Context) error {
return webui.Start(context.Background())
//return nil
},
},
{
Name: "config",
Usage: "get machine configuration",
Description: "Print machine state information, e.g. `state get uuid` returns the machine uuid",
Aliases: []string{"c"},
Action: func(c *cli.Context) error {
runtime, err := state.NewRuntime()
if err != nil {
return err
}
fmt.Print(runtime)
return err
},
Subcommands: []*cli.Command{
{
Name: "show",
Usage: "Shows the machine configuration",
Description: "Show the runtime configuration of the machine. It will scan the machine for all the configuration and will return the config file processed and found.",
Aliases: []string{"s"},
Action: func(c *cli.Context) error {
config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs)
if err != nil {
return err
}
configStr, err := config.String()
if err != nil {
return err
}
fmt.Printf("%s", configStr)
return nil
},
},
{
Name: "get",
Usage: "Get specific data from the configuration",
UsageText: `
Use it to retrieve configuration programmatically from the CLI:
$ kairos-agent config get k3s.enabled
true
or
$ kairos-agent config get k3s
enabled: true`,
Description: "It allows to navigate the YAML config file by searching with 'yq' style keywords as `config get k3s` to retrieve the k3s config block",
Aliases: []string{"g"},
Action: func(c *cli.Context) error {
config, err := config.Scan(collector.Directories(configScanDir...), collector.NoLogs, collector.StrictValidation(c.Bool("strict-validation")))
if err != nil {
return err
}
res, err := config.Query(c.Args().First())
if err != nil {
return err
}
fmt.Printf("%s", res)
return nil
},
},
},
},
{
Name: "state",
Usage: "get machine state",
Description: "Print machine state information, e.g. `state get uuid` returns the machine uuid",
Aliases: []string{"s"},
Action: func(c *cli.Context) error {
runtime, err := state.NewRuntime()
if err != nil {
return err
}
fmt.Print(runtime)
return err
},
Subcommands: []*cli.Command{
{
Name: "apply",
Usage: "Applies a machine state",
Description: "Applies machine configuration in runtimes",
Aliases: []string{"a"},
Action: func(c *cli.Context) error {
// TODO
return nil
},
},
{
Name: "get",
Usage: "get specific ",
Description: "query state data",
Aliases: []string{"g"},
Action: func(c *cli.Context) error {
runtime, err := state.NewRuntime()
if err != nil {
return err
}
res, err := runtime.Query(c.Args().First())
fmt.Print(res)
return err
},
},
},
},
{
Name: "interactive-install",
Description: `
Starts kairos in interactive mode install.
It will ask prompt for several questions and perform an install depending on the providers available in the system.
See also https://kairos.io/installation/interactive_install/ for documentation.
This command is meant to be used from the boot GRUB menu, but can be also started manually`,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "shell",
},
},
Usage: "Starts interactive installation",
Action: func(c *cli.Context) error {
return agent.InteractiveInstall(c.Bool("shell"))
},
},
{
Name: "manual-install",
Usage: "Starts the manual installation",
Description: `
`,
Aliases: []string{"m"},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "device",
},
&cli.BoolFlag{
Name: "poweroff",
},
&cli.BoolFlag{
Name: "reboot",
},
},
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
return fmt.Errorf("expect one argument. the config file - if you don't have it, use the interactive-install")
}
config := c.Args().First()
options := map[string]string{"device": c.String("device")}
if c.Bool("poweroff") {
options["poweroff"] = "true"
}
if c.Bool("reboot") {
options["reboot"] = "true"
}
return agent.ManualInstall(config, options, c.Bool("strict-validation"))
},
},
{
Name: "install",
Usage: "Starts the kairos pairing installation",
Description: `
Starts kairos in pairing mode.
It will print out a QR code which can be used with "kairos register" to send over a configuration and bootstraping a kairos node.
See also https://kairos.io/docs/installation/qrcode/ for documentation.
This command is meant to be used from the boot GRUB menu, but can be started manually`,
Aliases: []string{"i"},
Action: func(c *cli.Context) error {
return agent.Install(configScanDir...)
},
},
{
Name: "recovery",
Aliases: []string{"r"},
Action: func(c *cli.Context) error {
return agent.Recovery()
},
Usage: "Starts kairos recovery mode",
Description: `
Starts kairos recovery mode.
In recovery mode a QR code will be printed out on the screen which should be used in conjunction with "kairos bridge". Pass by the QR code as snapshot
to the bridge to connect over the machine which runs the "kairos recovery" command.
See also https://kairos.io/after_install/recovery_mode/ for documentation.
This command is meant to be used from the boot GRUB menu, but can likely be used standalone`,
},
{
Name: "reset",
Action: func(c *cli.Context) error {
return agent.Reset(configScanDir...)
},
Usage: "Starts kairos reset mode",
Description: `
Starts kairos reset mode, it will nuke completely the node data and restart fresh.
Attention ! this will delete any persistent data on the node. It is equivalent to re-init the node right after the installation.
In reset mode a the node will automatically reset
See also https://kairos.io/after_install/reset_mode/ for documentation.
This command is meant to be used from the boot GRUB menu, but can likely be used standalone`,
},
{
Name: "validate",
Action: func(c *cli.Context) error {
config := c.Args().First()
return agent.Validate(config)
},
Usage: "Validates a cloud config file",
Description: `
The validate command expects a configuration file as its only argument. Local files and URLs are accepted.
`,
},
{
Name: "print-schema",
Action: func(c *cli.Context) error {
json, err := agent.JSONSchema(common.VERSION)
if err != nil {
return err
}
fmt.Println(json)
return nil
},
Usage: "Print out Kairos' Cloud Configuration JSON Schema",
Description: `Prints out Kairos' Cloud Configuration JSON Schema`,
},
}
func main() {
bus.Manager.Initialize()
app := &cli.App{
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "strict-validation",
Usage: "Fail instead of warn on validation errors.",
EnvVars: []string{"STRICT_VALIDATIONS"},
},
},
Name: "kairos-agent",
Version: common.VERSION,
Authors: []*cli.Author{
{
Name: "Ettore Di Giacinto",
},
},
Usage: "kairos agent start",
Description: `
The kairos agent is a component to abstract away node ops, providing a common feature-set across kairos variants.
`,
UsageText: ``,
Copyright: "kairos authors",
Commands: cmds,
}
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -1,38 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/kairos-io/kairos-sdk/profile"
"github.com/kairos-io/kairos/v2/internal/common"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "profile-build",
Version: common.VERSION,
Authors: []*cli.Author{
{
Name: "Kairos authors",
},
},
Usage: "Build kairos framework images",
Description: `
Uses profile files to build kairos images`,
UsageText: ``,
Copyright: "kairos authors",
ArgsUsage: "flavor profileName profileFile outputDirectory",
Action: func(c *cli.Context) error {
return profile.BuildFlavor(c.Args().Get(0), c.Args().Get(1), c.Args().Get(2))
},
}
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -1 +1 @@
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock --rm -t -v ${pwd}:/workspace -v earthly-tmp:/tmp/earthly:rw earthly/earthly:v0.7.4 --allow-privileged @args
docker run --privileged -v /var/run/docker.sock:/var/run/docker.sock --rm -t -v ${pwd}:/workspace -v earthly-tmp:/tmp/earthly:rw earthly/earthly:v0.7.4 --allow-privileged @args

View File

@ -7,6 +7,7 @@ common:
- system/grub2-efi
- system/elemental-cli
- system/immucore
- system/kairos-agent
flavors:
debian:
- systemd-base
@ -87,9 +88,9 @@ repositories:
priority: 2
urls:
- "quay.io/kairos/packages"
reference: 20230420084320-repository.yaml
reference: 20230425110728-repository.yaml
- !!merge <<: *kairos
arch: arm64
urls:
- "quay.io/kairos/packages-arm64"
reference: 20230420090242-repository.yaml
reference: 20230425111926-repository.yaml

124
go.mod
View File

@ -1,124 +0,0 @@
module github.com/kairos-io/kairos/v2
go 1.20
require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/avast/retry-go v3.0.0+incompatible
github.com/erikgeiser/promptkit v0.8.0
github.com/google/go-github/v40 v40.0.0
github.com/google/uuid v1.3.0
github.com/itchyny/gojq v0.12.12
github.com/jaypipes/ghw v0.10.0
github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae
github.com/kairos-io/kcrypt v0.5.2
github.com/labstack/echo/v4 v4.10.2
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-20220724164624-c45b5c61312d
github.com/mudler/yip v0.11.5-0.20230124143654-91e88dfb6648
github.com/nxadm/tail v1.4.8
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.6
github.com/pterm/pterm v0.12.59
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0
github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46
github.com/swaggest/jsonschema-go v0.3.49
github.com/urfave/cli/v2 v2.25.1
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
golang.org/x/net v0.9.0
golang.org/x/oauth2 v0.7.0
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
)
require (
atomicgo.dev/cursor v0.1.1 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
github.com/aymanbagabas/go-osc52 v1.2.1 // indirect
github.com/bramvdbogaerde/go-scp v1.2.1 // indirect
github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
github.com/charmbracelet/bubbles v0.14.0 // indirect
github.com/charmbracelet/bubbletea v0.23.1 // indirect
github.com/charmbracelet/lipgloss v0.6.0 // indirect
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 // indirect
github.com/codingsince1985/checksum v1.2.6 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/eliukblau/pixterm v1.3.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/gen2brain/shm v0.0.0-20200228170931-49f9650110c5 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gookit/color v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/itchyny/timefmt-go v0.1.5 // indirect
github.com/jaypipes/pcidb v1.0.0 // indirect
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d // indirect
github.com/kbinani/screenshot v0.0.0-20210720154843-7d3a670d8329 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/labstack/gommon v0.4.0 // indirect
github.com/lithammer/fuzzysearch v1.1.5 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect
github.com/makiuchi-d/gozxing v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.13.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/qeesung/image2ascii v1.0.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/swaggest/refl v1.1.0 // indirect
github.com/twpayne/go-vfs v1.7.2 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zcalusic/sysinfo v0.9.5 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
howett.net/plist v1.0.0 // indirect
)

462
go.sum
View File

@ -1,462 +0,0 @@
atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg=
atomicgo.dev/cursor v0.1.1 h1:0t9sxQomCTRh5ug+hAMCs59x/UmC9QL6Ci5uosINKD4=
atomicgo.dev/cursor v0.1.1/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU=
atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8=
atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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=
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI=
github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c=
github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE=
github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
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/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 h1:WWB576BN5zNSZc/M9d/10pqEx5VHNhaQ/yOVAkmj5Yo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
github.com/bool64/dev v0.2.27 h1:mFT+B74mFVgUeUmm/EbfM6ELPA55lEXBjQ/AOHCwCOc=
github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E=
github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw=
github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0=
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/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
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/codingsince1985/checksum v1.2.6 h1:UjCDls6oaRQeLPG14TvjLvOos2XL1qHdMl8uGMkzpi8=
github.com/codingsince1985/checksum v1.2.6/go.mod h1:Pe5wfeiqzQC1qEXLWEFmxQ3W/OklJEJGiJO62graCJU=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
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/eliukblau/pixterm v1.3.1 h1:XeouQViH+lmzCa7sMUoK2cd7qlgHYGLIjwRKaOdJbKA=
github.com/eliukblau/pixterm v1.3.1/go.mod h1:on5ueknFt+ZFVvIVVzQ7/JXwPjv5fJd8Q1Ybh7XixfU=
github.com/erikgeiser/promptkit v0.8.0 h1:bvOzPs6RLyfRZDSgVWOghQEiBSRHQ3zmDdxcV8zOc+E=
github.com/erikgeiser/promptkit v0.8.0/go.mod h1:QxyFbCrrj20PyvV5b+ckWPozbgX11s04GeRlmTCIMTo=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
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/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/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.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v40 v40.0.0 h1:oBPVDaIhdUmwDWRRH8XJ/dZG+Rn755i08+Hp1uJHlR0=
github.com/google/go-github/v40 v40.0.0/go.mod h1:G8wWKTEjUCL0zdbaQvpwDk0hqf6KZgPQH+ssJa+/NVc=
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-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE=
github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/itchyny/gojq v0.12.12 h1:x+xGI9BXqKoJQZkr95ibpe3cdrTbY8D9lonrK433rcA=
github.com/itchyny/gojq v0.12.12/go.mod h1:j+3sVkjxwd7A7Z5jrbKibgOLn0ZfLWkV+Awxr/pyzJE=
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
github.com/jaypipes/ghw v0.10.0 h1:UHu9UX08Py315iPojADFPOkmjTsNzHj4g4adsNKKteY=
github.com/jaypipes/ghw v0.10.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g=
github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8=
github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk=
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d h1:B01GinEZbowPwbWrqDIb6n2AaHIscJVOqsh0I5gAEXw=
github.com/kairos-io/kairos v1.24.3-56.0.20230329142538-b6ae4b58c07d/go.mod h1:2aYSSCHw8csfuqA5g6BpxBJ89kZt84G5okeuJj7PH+w=
github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae h1:u/1QiU5IAJNDPxsBWBQFKQxLJKcDog1aMrN0unaP18w=
github.com/kairos-io/kairos-sdk v0.0.2-0.20230414094028-0c9d2bd9e6ae/go.mod h1:Wg/jfAQe8seka5VUXtcPvg+sA6GmQEy+DYlJmgKM8Zs=
github.com/kairos-io/kcrypt v0.5.2 h1:F9jbIjk3+nSQYEoSTDXT118Cx8AjmtDrMcU4rq/WBsI=
github.com/kairos-io/kcrypt v0.5.2/go.mod h1:FdhkFI5/gbHWr15Pvd40IAttWvxnF8/XUghMsezj1fs=
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/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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
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/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/lithammer/fuzzysearch v1.1.5 h1:Ag7aKU08wp0R9QCfF4GoGST9HbmAIeLP7xwMrOBEp1c=
github.com/lithammer/fuzzysearch v1.1.5/go.mod h1:1R1LRNk7yKid1BaQkmuLQaHruxcC4HmAH30Dh61Ih1Q=
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/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+CtW96g0Or0Fxa9IKr4uc=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
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-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
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.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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/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-20220724164624-c45b5c61312d h1:/lAg9vPAAU+s35cDMCx1IyeMn+4OYfCBPqi08Q8vXDg=
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d/go.mod h1:HGGAOJhipApckwNV8ZTliRJqxctUv3xRY+zbQEwuytc=
github.com/mudler/yip v0.11.5-0.20230124143654-91e88dfb6648 h1:+UZPjgWOTB1LyWI5qHTReIGXDSlXynGl2kIihi/lU98=
github.com/mudler/yip v0.11.5-0.20230124143654-91e88dfb6648/go.mod h1:7d0bnZ326k/bmeTvLZL5ZQx4QNi0a7mfrnGzb+2ZkrA=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
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.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI=
github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg=
github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE=
github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU=
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.59 h1:VBStvXiZL+6L+nNYjlXsD/035RJF5crqOvgqAm/Rvns=
github.com/pterm/pterm v0.12.59/go.mod h1:Lt90KhnId704siiQtMZiLS7UfoC7TRUM1HufzdM0kjk=
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/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.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
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/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 h1:uIkTLo0AGRc8l7h5l9r+GcYi9qfVPt6lD4/bhmzfiKo=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
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/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46 h1:q2T2RnISqPdZWvUpBQw0n7QWtF4cNo5RpCDTZmV732M=
github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46/go.mod h1:L2fIdtZqbQEagjOOXwkwH3t7MjJUd7fbt52cLSQGDBg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw=
github.com/swaggest/jsonschema-go v0.3.49 h1:0dB6+6/uuU9lH41evLVum9Ui1b1Pkm1mjtsHWYL+y30=
github.com/swaggest/jsonschema-go v0.3.49/go.mod h1:fZmC8juuqFTMe4Fc9tHJXwG+Uaf9BKYvF8ygL+asOuY=
github.com/swaggest/refl v1.1.0 h1:a+9a75Kv6ciMozPjVbOfcVTEQe81t2R3emvaD9oGQGc=
github.com/swaggest/refl v1.1.0/go.mod h1:g3Qa6ki0A/L2yxiuUpT+cuBURuRaltF5SDQpg1kMZSY=
github.com/twpayne/go-vfs v1.7.2 h1:ZNYMAXcu2Av8c109USrSGYm8dIIIV0xPlG19I2088Kw=
github.com/twpayne/go-vfs v1.7.2/go.mod h1:1eni2ntkiiAHZG27xfLOO4CYvMR4Kw8V7rYiLeeolsQ=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
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.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
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/wayneashleyberry/terminal-dimensions v1.1.0 h1:EB7cIzBdsOzAgmhTUtTTQXBByuPheP/Zv1zL2BRPY6g=
github.com/wayneashleyberry/terminal-dimensions v1.1.0/go.mod h1:2lc/0eWCObmhRczn2SdGSQtgBooLUzIotkkEGXqghyg=
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=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zcalusic/sysinfo v0.9.5 h1:ivoHyj9aIAYkwzo1+8QgJ5s4oeE6Etx9FmZtqa4wJjQ=
github.com/zcalusic/sysinfo v0.9.5/go.mod h1:Z/gPVufBrFc8X5sef3m6kkw3r3nlNFp+I6bvASfvBZQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
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/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
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.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43 h1:gQ6GUSD102fPgli+Yb4cR/cGaHF7tNBt+GYoRCpGC7s=
golang.org/x/image v0.0.0-20191206065243-da761ea9ff43/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/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-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-20201223074533-0d417f636930/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=

View File

@ -1,95 +0,0 @@
package agent
import (
"fmt"
"os"
"path/filepath"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
hook "github.com/kairos-io/kairos/v2/internal/agent/hooks"
"github.com/kairos-io/kairos/v2/internal/bus"
config "github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/nxadm/tail"
)
// Run starts the agent provider emitting the bootstrap event.
func Run(opts ...Option) error {
o := &Options{}
if err := o.Apply(opts...); err != nil {
return err
}
os.MkdirAll("/usr/local/.kairos", 0600) //nolint:errcheck
// Reads config
c, err := config.Scan(collector.Directories(o.Dir...))
if err != nil {
return err
}
utils.SetEnv(c.Env)
bf := machine.BootFrom()
if c.Install != nil && c.Install.Auto && (bf == machine.NetBoot || bf == machine.LiveCDBoot) {
// Don't go ahead if we are asked to install from a booting live medium
fmt.Println("Agent run aborted. Installation being performed from live medium")
return nil
}
os.MkdirAll("/var/log/kairos", 0600) //nolint:errcheck
fileName := filepath.Join("/var/log/kairos", "agent-provider.log")
// Create if not exist
if _, err := os.Stat(fileName); err != nil {
err = os.WriteFile(fileName, []byte{}, os.ModePerm)
if err != nil {
return err
}
}
// Tail to the log
t, err := tail.TailFile(fileName, tail.Config{Follow: true})
if err != nil {
return err
}
go func() {
for line := range t.Lines {
fmt.Println(line.Text)
}
}()
if !machine.SentinelExist("firstboot") {
if err := hook.Run(*c, hook.FirstBoot...); err != nil {
return err
}
// Re-load providers
bus.Reload()
err = machine.CreateSentinel("firstboot")
if c.FailOnBundleErrors && err != nil {
return err
}
// Re-read config files
c, err = config.Scan(collector.Directories(o.Dir...))
if err != nil {
return err
}
}
configStr, err := c.Config.String()
if err != nil {
panic(err)
}
_, err = bus.Manager.Publish(events.EventBootstrap, events.BootstrapPayload{APIAddress: o.APIAddress, Config: configStr, Logfile: fileName})
if o.Restart && err != nil {
fmt.Println("Warning: Agent failed, restarting: ", err.Error())
return Run(opts...)
}
return err
}

View File

@ -1,50 +0,0 @@
package agent
// Options yields the options for the running agent.
type Options struct {
APIAddress string
Dir []string
Force bool
Restart bool
}
// Apply applies option to the options struct.
func (o *Options) Apply(opts ...Option) error {
for _, oo := range opts {
if err := oo(o); err != nil {
return err
}
}
return nil
}
// Option is a generic option for the Agent.
type Option func(o *Options) error
// ForceAgent forces the agent to run.
var ForceAgent Option = func(o *Options) error {
o.Force = true
return nil
}
// RestartAgent makes the agent restart on error.
var RestartAgent Option = func(o *Options) error {
o.Restart = true
return nil
}
// WithAPI sets the API address used to talk to EdgeVPN and co-ordinate node bootstrapping.
func WithAPI(address string) Option {
return func(o *Options) error {
o.APIAddress = address
return nil
}
}
// WithDirectory sets the Agent config directories.
func WithDirectory(dirs ...string) Option {
return func(o *Options) error {
o.Dir = dirs
return nil
}
}

View File

@ -1,13 +0,0 @@
package agent_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Agent Suite")
}

View File

@ -1,52 +0,0 @@
package agent_test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
. "github.com/kairos-io/kairos/v2/internal/agent"
"github.com/kairos-io/kairos/v2/internal/bus"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
const testProvider = `#!/bin/bash
event="$1"
payload=$(</dev/stdin)
echo "Received $event with $payload" >> exec.log
echo "{}"
`
var _ = Describe("Bootstrap provider", func() {
Context("Config", func() {
It("gets entire content", func() {
f, err := ioutil.TempDir("", "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f)
wd, _ := os.Getwd()
os.WriteFile(filepath.Join(wd, "agent-provider-test"), []byte(testProvider), 0655)
defer os.RemoveAll(filepath.Join(wd, "agent-provider-test"))
err = os.WriteFile(filepath.Join(f, "test.config.yaml"), []byte(`#cloud-config
doo: bar`), 0655)
Expect(err).ToNot(HaveOccurred())
bus.Manager.Initialize()
err = Run(WithDirectory(f))
Expect(err).ToNot(HaveOccurred())
dat, err := os.ReadFile(filepath.Join(wd, "exec.log"))
Expect(err).ToNot(HaveOccurred())
fmt.Println(string(dat))
Expect(string(dat)).To(ContainSubstring("Received"), string(dat))
Expect(string(dat)).To(ContainSubstring("doo: bar"), string(dat))
})
})
})

View File

@ -1,75 +0,0 @@
package agent
import (
"os"
"github.com/kairos-io/kairos/v2/internal/kairos"
"gopkg.in/yaml.v2"
)
type BrandingText struct {
InteractiveInstall string `yaml:"interactive-install"`
Install string `yaml:"install"`
Reset string `yaml:"reset"`
Recovery string `yaml:"recovery"`
}
type WebUI struct {
Disable bool `yaml:"disable"`
ListenAddress string `yaml:"listen_address"`
}
func (w WebUI) HasAddress() bool {
return w.ListenAddress != ""
}
type Config struct {
Fast bool `yaml:"fast,omitempty"`
WebUI WebUI `yaml:"webui"`
Branding BrandingText `yaml:"branding"`
}
func LoadConfig(path ...string) (*Config, error) {
if len(path) == 0 {
path = append(path, "/etc/kairos/agent.yaml", "/etc/elemental/config.yaml")
}
cfg := &Config{}
for _, p := range path {
f, err := os.ReadFile(p)
if err == nil {
yaml.Unmarshal(f, cfg) //nolint:errcheck
}
}
if cfg.Branding.InteractiveInstall == "" {
f, err := os.ReadFile(kairos.BrandingFile("interactive_install_text"))
if err == nil {
cfg.Branding.InteractiveInstall = string(f)
}
}
if cfg.Branding.Install == "" {
f, err := os.ReadFile(kairos.BrandingFile("install_text"))
if err == nil {
cfg.Branding.Install = string(f)
}
}
if cfg.Branding.Recovery == "" {
f, err := os.ReadFile(kairos.BrandingFile("recovery_text"))
if err == nil {
cfg.Branding.Recovery = string(f)
}
}
if cfg.Branding.Reset == "" {
f, err := os.ReadFile(kairos.BrandingFile("reset_text"))
if err == nil {
cfg.Branding.Reset = string(f)
}
}
return cfg, nil
}

View File

@ -1,41 +0,0 @@
package hook
import (
"github.com/kairos-io/kairos-sdk/bundles"
"github.com/kairos-io/kairos-sdk/machine"
config "github.com/kairos-io/kairos/v2/pkg/config"
)
type BundleOption struct{}
func (b BundleOption) Run(c config.Config) error {
machine.Mount("COS_PERSISTENT", "/usr/local") //nolint:errcheck
defer func() {
machine.Umount("/usr/local") //nolint:errcheck
}()
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
defer func() {
machine.Umount("/oem") //nolint:errcheck
}()
opts := c.Install.Bundles.Options()
err := bundles.RunBundles(opts...)
if c.FailOnBundleErrors && err != nil {
return err
}
return nil
}
type BundlePostInstall struct{}
func (b BundlePostInstall) Run(c config.Config) error {
opts := c.Bundles.Options()
err := bundles.RunBundles(opts...)
if c.FailOnBundleErrors && err != nil {
return err
}
return nil
}

View File

@ -1,28 +0,0 @@
package hook
import (
"fmt"
"github.com/kairos-io/kairos-sdk/system"
config "github.com/kairos-io/kairos/v2/pkg/config"
)
type GrubOptions struct{}
func (b GrubOptions) Run(c config.Config) error {
err := system.Apply(system.SetGRUBOptions(c.Install.GrubOptions))
if err != nil {
fmt.Println(err)
}
return nil
}
type GrubPostInstallOptions struct{}
func (b GrubPostInstallOptions) Run(c config.Config) error {
err := system.Apply(system.SetGRUBOptions(c.GrubOptions))
if err != nil {
fmt.Println(err)
}
return nil
}

View File

@ -1,34 +0,0 @@
package hook
import (
config "github.com/kairos-io/kairos/v2/pkg/config"
)
type Interface interface {
Run(c config.Config) error
}
var AfterInstall = []Interface{
&RunStage{}, // Shells out to stages defined from the container image
&GrubOptions{}, // Set custom GRUB options
&BundleOption{},
&CustomMounts{},
&Kcrypt{},
&Lifecycle{}, // Handles poweroff/reboot by config options
}
var AfterReset = []Interface{}
var FirstBoot = []Interface{
&BundlePostInstall{},
&GrubPostInstallOptions{},
}
func Run(c config.Config, hooks ...Interface) error {
for _, h := range hooks {
if err := h.Run(c); err != nil {
return err
}
}
return nil
}

View File

@ -1,66 +0,0 @@
package hook
import (
"fmt"
"strings"
"time"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
config "github.com/kairos-io/kairos/v2/pkg/config"
kcryptconfig "github.com/kairos-io/kcrypt/pkg/config"
)
type Kcrypt struct{}
func (k Kcrypt) Run(c config.Config) error {
if len(c.Install.Encrypt) == 0 {
return nil
}
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
defer func() {
machine.Umount("/oem") //nolint:errcheck
}()
kcryptc, err := kcryptconfig.GetConfiguration(kcryptconfig.ConfigScanDirs)
if err != nil {
fmt.Println("Failed getting kcrypt configuration: ", err.Error())
if c.FailOnBundleErrors {
return err
}
}
for _, p := range c.Install.Encrypt {
out, err := utils.SH(fmt.Sprintf("kcrypt encrypt %s", p))
if err != nil {
fmt.Printf("could not encrypt partition: %s\n", out+err.Error())
if c.FailOnBundleErrors {
return err
}
// Give time to show the error
time.Sleep(10 * time.Second)
return nil // do not error out
}
err = kcryptc.SetMapping(strings.TrimSpace(out))
if err != nil {
fmt.Println("Failed updating the kcrypt configuration file: ", err.Error())
if c.FailOnBundleErrors {
return err
}
}
}
err = kcryptc.WriteMappings(kcryptconfig.MappingsFile)
if err != nil {
fmt.Println("Failed writing kcrypt partition mappings: ", err.Error())
if c.FailOnBundleErrors {
return err
}
}
return nil
}

View File

@ -1,19 +0,0 @@
package hook
import (
"github.com/kairos-io/kairos-sdk/utils"
"github.com/kairos-io/kairos/v2/pkg/config"
)
type Lifecycle struct{}
func (s Lifecycle) Run(c config.Config) error {
if c.Install.Reboot {
utils.Reboot()
}
if c.Install.Poweroff {
utils.PowerOFF()
}
return nil
}

View File

@ -1,59 +0,0 @@
package hook
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/kairos-io/kairos-sdk/machine"
config "github.com/kairos-io/kairos/v2/pkg/config"
"github.com/mudler/yip/pkg/schema"
yip "github.com/mudler/yip/pkg/schema"
"gopkg.in/yaml.v1"
)
type CustomMounts struct{}
func saveCloudConfig(name config.Stage, yc yip.YipConfig) error {
yipYAML, err := yaml.Marshal(yc)
if err != nil {
return err
}
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.
// 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) 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
}
machine.Mount("COS_OEM", "/oem") //nolint:errcheck
defer func() {
machine.Umount("/oem") //nolint:errcheck
}()
var mountsList = map[string]string{}
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,
}},
}}
saveCloudConfig("user_custom_mounts", config) //nolint:errcheck
return nil
}

View File

@ -1,16 +0,0 @@
package hook
import (
"github.com/kairos-io/kairos-sdk/utils"
config "github.com/kairos-io/kairos/v2/pkg/config"
events "github.com/kairos-io/kairos-sdk/bus"
)
type RunStage struct{}
func (r RunStage) Run(_ config.Config) error {
utils.SH("elemental run-stage kairos-install.after") //nolint:errcheck
events.RunHookScript("/usr/bin/kairos-agent.install.after.hook") //nolint:errcheck
return nil
}

File diff suppressed because it is too large Load Diff

View File

@ -1,365 +0,0 @@
package agent
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"strings"
"syscall"
"time"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
hook "github.com/kairos-io/kairos/v2/internal/agent/hooks"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/internal/cmd"
config "github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
"gopkg.in/yaml.v2"
)
func optsToArgs(options map[string]string) (res []string) {
for k, v := range options {
if k != "device" && k != "cc" && k != "reboot" && k != "poweroff" {
res = append(res, fmt.Sprintf("--%s", k))
if v != "" {
res = append(res, v)
}
}
}
return
}
func displayInfo(agentConfig *Config) {
fmt.Println("--------------------------")
fmt.Println("No providers found, dropping to a shell. \n -- For instructions on how to install manually, see: https://kairos.io/docs/installation/manual/")
if !agentConfig.WebUI.Disable {
if !agentConfig.WebUI.HasAddress() {
ips := machine.LocalIPs()
if len(ips) > 0 {
fmt.Print("WebUI installer running at : ")
for _, ip := range ips {
fmt.Printf("%s%s ", ip, config.DefaultWebUIListenAddress)
}
fmt.Print("\n")
}
} else {
fmt.Printf("WebUI installer running at : %s\n", agentConfig.WebUI.ListenAddress)
}
ifaces := machine.Interfaces()
fmt.Printf("Network Interfaces: %s\n", strings.Join(ifaces, " "))
}
}
func mergeOption(cloudConfig string, r map[string]string) {
c := &config.Config{}
yaml.Unmarshal([]byte(cloudConfig), c) //nolint:errcheck
for k, v := range c.Options {
if k == "cc" {
continue
}
r[k] = v
}
}
func ManualInstall(c string, options map[string]string, strictValidations bool) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
source, err := prepareConfiguration(ctx, c)
if err != nil {
return err
}
cc, err := config.Scan(collector.Directories(source), collector.MergeBootLine, collector.StrictValidation(strictValidations))
if err != nil {
return err
}
configStr, err := cc.String()
if err != nil {
return err
}
options["cc"] = configStr
// unlike Install device is already set
// options["device"] = cc.Install.Device
mergeOption(configStr, options)
if options["device"] == "" {
options["device"] = cc.Install.Device
}
return RunInstall(options)
}
func Install(dir ...string) error {
utils.OnSignal(func() {
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
}, syscall.SIGINT, syscall.SIGTERM)
tk := ""
r := map[string]string{}
bus.Manager.Response(events.EventChallenge, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
tk = r.Data
})
bus.Manager.Response(events.EventInstall, func(p *pluggable.Plugin, resp *pluggable.EventResponse) {
err := json.Unmarshal([]byte(resp.Data), &r)
if err != nil {
fmt.Println(err)
}
})
ensureDataSourceReady()
// Reads config, and if present and offline is defined,
// runs the installation
cc, err := config.Scan(collector.Directories(dir...), collector.MergeBootLine, collector.NoLogs)
if err == nil && cc.Install != nil && cc.Install.Auto {
configStr, err := cc.String()
if err != nil {
return err
}
r["cc"] = configStr
r["device"] = cc.Install.Device
mergeOption(configStr, r)
err = RunInstall(r)
if err != nil {
return err
}
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
return nil
}
if err != nil {
fmt.Printf("- config not found in the system: %s", err.Error())
}
agentConfig, err := LoadConfig()
if err != nil {
return err
}
// try to clear screen
cmd.ClearScreen()
cmd.PrintBranding(DefaultBanner)
// If there are no providers registered, we enter a shell for manual installation
// and print information about the webUI
if !bus.Manager.HasRegisteredPlugins() {
displayInfo(agentConfig)
return utils.Shell().Run()
}
configStr, err := cc.String()
if err != nil {
return err
}
_, err = bus.Manager.Publish(events.EventChallenge, events.EventPayload{Config: configStr})
if err != nil {
return err
}
cmd.PrintText(agentConfig.Branding.Install, "Installation")
if !agentConfig.Fast {
time.Sleep(5 * time.Second)
}
if tk != "" {
qr.Print(tk)
}
if _, err := bus.Manager.Publish(events.EventInstall, events.InstallPayload{Token: tk, Config: configStr}); err != nil {
return err
}
if len(r) == 0 {
return errors.New("no configuration, stopping installation")
}
// we receive a cloud config at this point
cloudConfig, exists := r["cc"]
// merge any options defined in it
mergeOption(cloudConfig, r)
// now merge cloud config from system and
// the one received from the agent-provider
ccData := map[string]interface{}{}
// make sure the config we write has at least the #cloud-config header,
// if any other was defined beforeahead
header := "#cloud-config"
if hasHeader, head := config.HasHeader(configStr, ""); hasHeader {
header = head
}
// What we receive take precedence over the one in the system. best-effort
yaml.Unmarshal([]byte(configStr), &ccData) //nolint:errcheck
if exists {
yaml.Unmarshal([]byte(cloudConfig), &ccData) //nolint:errcheck
if hasHeader, head := config.HasHeader(cloudConfig, ""); hasHeader {
header = head
}
}
out, err := yaml.Marshal(ccData)
if err != nil {
return fmt.Errorf("failed marshalling cc: %w", err)
}
r["cc"] = config.AddHeader(header, string(out))
pterm.Info.Println("Starting installation")
if err := RunInstall(r); err != nil {
return err
}
pterm.Info.Println("Installation completed, press enter to go back to the shell.")
utils.Prompt("") //nolint:errcheck
// give tty1 back
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint: errcheck
}
return nil
}
func RunInstall(options map[string]string) error {
utils.SH("elemental run-stage kairos-install.pre") //nolint:errcheck
events.RunHookScript("/usr/bin/kairos-agent.install.pre.hook") //nolint:errcheck
f, _ := os.CreateTemp("", "xxxx")
defer os.RemoveAll(f.Name())
device, ok := options["device"]
if !ok {
fmt.Println("device must be specified among options")
os.Exit(1)
}
if device == "auto" {
device = detectDevice()
}
cloudInit, ok := options["cc"]
if !ok {
fmt.Println("cloudInit must be specified among options")
os.Exit(1)
}
c := &config.Config{}
yaml.Unmarshal([]byte(cloudInit), c) //nolint:errcheck
_, reboot := options["reboot"]
_, poweroff := options["poweroff"]
if c.Install == nil {
c.Install = &config.Install{}
}
if poweroff {
c.Install.Poweroff = true
}
if reboot {
c.Install.Reboot = true
}
if c.Install.Image != "" {
options["system.uri"] = c.Install.Image
}
env := append(c.Install.Env, c.Env...)
utils.SetEnv(env)
err := os.WriteFile(f.Name(), []byte(cloudInit), os.ModePerm)
if err != nil {
fmt.Printf("could not write cloud init: %s\n", err.Error())
os.Exit(1)
}
args := []string{"install"}
args = append(args, optsToArgs(options)...)
args = append(args, "-c", f.Name(), device)
cmd := exec.Command("elemental", args...)
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
return hook.Run(*c, hook.AfterInstall...)
}
func ensureDataSourceReady() {
timeout := time.NewTimer(5 * time.Minute)
ticker := time.NewTicker(500 * time.Millisecond)
defer timeout.Stop()
defer ticker.Stop()
for {
select {
case <-timeout.C:
fmt.Println("userdata configuration failed to load after 5m, ignoring.")
return
case <-ticker.C:
if _, err := os.Stat("/run/.userdata_load"); os.IsNotExist(err) {
return
}
fmt.Println("userdata configuration has not yet completed. (waiting for /run/.userdata_load to be deleted)")
}
}
}
func prepareConfiguration(ctx context.Context, source string) (string, error) {
// if the source is not an url it is already a configuration path
if u, err := url.Parse(source); err != nil || u.Scheme == "" {
return source, nil
}
// create a configuration file with the source referenced
f, err := os.CreateTemp(os.TempDir(), "kairos-install-*.yaml")
if err != nil {
return "", err
}
// defer cleanup until after parent is done
go func() {
<-ctx.Done()
_ = os.RemoveAll(f.Name())
}()
cfg := config.Config{
ConfigURL: source,
}
if err = yaml.NewEncoder(f).Encode(cfg); err != nil {
return "", err
}
return f.Name(), nil
}

View File

@ -1,49 +0,0 @@
package agent
import (
"context"
"os"
"github.com/kairos-io/kairos/v2/pkg/config"
"gopkg.in/yaml.v3"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("prepareConfiguration", func() {
path := "/foo/bar"
url := "https://example.com"
ctx, cancel := context.WithCancel(context.Background())
It("returns a file path with no modifications", func() {
source, err := prepareConfiguration(ctx, path)
Expect(err).ToNot(HaveOccurred())
Expect(source).To(Equal(path))
})
It("creates a configuration file containing the given url", func() {
source, err := prepareConfiguration(ctx, url)
Expect(err).ToNot(HaveOccurred())
Expect(source).ToNot(Equal(path))
f, err := os.Open(source)
Expect(err).ToNot(HaveOccurred())
var cfg config.Config
err = yaml.NewDecoder(f).Decode(&cfg)
Expect(err).ToNot(HaveOccurred())
Expect(cfg.ConfigURL).To(Equal(url))
})
It("cleans up the configuration file after context is done", func() {
source, err := prepareConfiguration(ctx, url)
cancel()
_, err = os.Stat(source)
Expect(os.IsNotExist(err))
})
})

View File

@ -1,276 +0,0 @@
package agent
import (
"encoding/json"
"fmt"
"strings"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/internal/cmd"
config "github.com/kairos-io/kairos/v2/pkg/config"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/unstructured"
"github.com/erikgeiser/promptkit/textinput"
"github.com/jaypipes/ghw"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/mudler/go-pluggable"
"github.com/mudler/yip/pkg/schema"
"github.com/pterm/pterm"
)
const (
canBeEmpty = "Unset"
yesNo = "[y]es/[N]o"
)
func prompt(prompt, initialValue, placeHolder string, canBeEmpty, hidden bool) (string, error) {
input := textinput.New(prompt)
input.InitialValue = initialValue
input.Placeholder = placeHolder
if canBeEmpty {
input.Validate = func(s string) error { return nil }
}
input.Hidden = hidden
return input.RunPrompt()
}
func isYes(s string) bool {
i := strings.ToLower(s)
if i == "y" || i == "yes" {
return true
}
return false
}
const (
_ = 1 << (10 * iota)
KiB
MiB
GiB
TiB
)
func promptBool(p events.YAMLPrompt) (string, error) {
def := "n"
if p.Default != "" {
def = p.Default
}
val, err := prompt(p.Prompt, def, yesNo, true, false)
if err != nil {
return "", err
}
if isYes(val) {
val = "true"
} else {
val = "false"
}
return val, nil
}
func promptText(p events.YAMLPrompt) (string, error) {
def := ""
if p.Default != "" {
def = p.Default
}
return prompt(p.Prompt, def, p.PlaceHolder, true, false)
}
func promptToUnstructured(p events.YAMLPrompt, unstructuredYAML map[string]interface{}) (map[string]interface{}, error) {
var res string
if p.AskFirst {
ask, err := prompt(p.AskPrompt, "n", yesNo, true, false)
if err == nil && !isYes(ask) {
return unstructuredYAML, nil
}
}
if p.Bool {
val, err := promptBool(p)
if err != nil {
return unstructuredYAML, err
}
unstructuredYAML[p.YAMLSection] = val
res = val
} else {
val, err := promptText(p)
if err != nil {
return unstructuredYAML, err
}
unstructuredYAML[p.YAMLSection] = val
res = val
}
if res == "" && p.IfEmpty != "" {
res = p.IfEmpty
unstructuredYAML[p.YAMLSection] = res
}
return unstructuredYAML, nil
}
func detectDevice() string {
preferedDevice := "/dev/sda"
maxSize := float64(0)
block, err := ghw.Block()
if err == nil {
for _, disk := range block.Disks {
size := float64(disk.SizeBytes) / float64(GiB)
if size > maxSize {
maxSize = size
preferedDevice = "/dev/" + disk.Name
}
}
}
return preferedDevice
}
func InteractiveInstall(spawnShell bool) error {
bus.Manager.Initialize()
cmd.PrintBranding(DefaultBanner)
agentConfig, err := LoadConfig()
if err != nil {
return err
}
cmd.PrintText(agentConfig.Branding.InteractiveInstall, "Installation")
disks := []string{}
maxSize := float64(0)
preferedDevice := "/dev/sda"
block, err := ghw.Block()
if err == nil {
for _, disk := range block.Disks {
size := float64(disk.SizeBytes) / float64(GiB)
if size > maxSize {
maxSize = size
preferedDevice = "/dev/" + disk.Name
}
disks = append(disks, fmt.Sprintf("/dev/%s: %s (%.2f GiB) ", disk.Name, disk.Model, float64(disk.SizeBytes)/float64(GiB)))
}
}
pterm.Info.Println("Available Disks:")
for _, d := range disks {
pterm.Info.Println(" " + d)
}
device, err := prompt("What's the target install device?", preferedDevice, "Cannot be empty", false, false)
if err != nil {
return err
}
userName, err := prompt("User to setup", "kairos", canBeEmpty, true, false)
if err != nil {
return err
}
userPassword, err := prompt("Password", "", canBeEmpty, true, true)
if err != nil {
return err
}
if userPassword == "" {
userPassword = "!"
}
users, err := prompt("SSH access (rsakey, github/gitlab supported, comma-separated)", "github:someuser,github:someuser2", canBeEmpty, true, false)
if err != nil {
return err
}
sshUsers := strings.Split(users, ",")
// Prompt the user by prompts defined by the provider
r := []events.YAMLPrompt{}
bus.Manager.Response(events.EventInteractiveInstall, func(p *pluggable.Plugin, resp *pluggable.EventResponse) {
err := json.Unmarshal([]byte(resp.Data), &r)
if err != nil {
fmt.Println(err)
}
})
_, err = bus.Manager.Publish(events.EventInteractiveInstall, events.EventPayload{})
if err != nil {
return err
}
unstructuredYAML := map[string]interface{}{}
for _, p := range r {
unstructuredYAML, err = promptToUnstructured(p, unstructuredYAML)
if err != nil {
return err
}
}
result, err := unstructured.ToYAMLMap(unstructuredYAML)
if err != nil {
return err
}
allGood, err := prompt("Are settings ok?", "n", yesNo, true, false)
if err != nil {
return err
}
if !isYes(allGood) {
return InteractiveInstall(spawnShell)
}
c := &config.Config{
Install: &config.Install{
Device: device,
},
}
usersToSet := map[string]schema.User{}
if userName != "" {
user := schema.User{
Name: userName,
PasswordHash: userPassword,
Groups: []string{"admin"},
SSHAuthorizedKeys: sshUsers,
}
usersToSet = map[string]schema.User{
userName: user,
}
}
cloudConfig := schema.YipConfig{Name: "Config generated by the installer",
Stages: map[string][]schema.Stage{config.NetworkStage.String(): {
{
Users: usersToSet,
},
}}}
dat, err := config.MergeYAML(cloudConfig, c, result)
if err != nil {
return err
}
finalCloudConfig := config.AddHeader("#cloud-config", string(dat))
pterm.Info.Println("Starting installation")
pterm.Info.Println(finalCloudConfig)
err = RunInstall(map[string]string{
"device": device,
"cc": finalCloudConfig,
})
if err != nil {
pterm.Error.Println(err.Error())
}
if spawnShell {
return utils.Shell().Run()
}
return err
}

View File

@ -1,34 +0,0 @@
#!/bin/sh
if [ -z "${GOPATH}" ]; then
echo GOPATH environment variable not set
exit
fi
if [ ! -e "${GOPATH}/bin/2goarray" ]; then
echo "Installing 2goarray..."
if ! go get github.com/cratonica/2goarray; then
echo Failure executing go get github.com/cratonica/2goarray
exit
fi
fi
if [ -z "$1" ]; then
echo Please specify a PNG file
exit
fi
if [ ! -f "$1" ]; then
echo "${1} is not a valid file"
exit
fi
OUTPUT=iconunix.go
echo "Generating ${OUTPUT}"
echo "//+build linux darwin" > "${OUTPUT}"
echo >> "${OUTPUT}"
if ! "${GOPATH}"/bin/2goarray DefaultBanner agent < "${1}" >> "${OUTPUT}"; then
echo Failure generating "${OUTPUT}"
exit
fi
echo Finished

View File

@ -1,34 +0,0 @@
package agent
import (
"fmt"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/mudler/go-pluggable"
)
func Notify(event string, dirs []string) error {
bus.Manager.Initialize()
c, err := config.Scan(collector.Directories(dirs...))
if err != nil {
return err
}
if !events.IsEventDefined(event) {
return fmt.Errorf("event '%s' not defined", event)
}
configStr, err := c.String()
if err != nil {
return err
}
_, err = bus.Manager.Publish(pluggable.EventType(event), events.EventPayload{
Config: configStr,
})
return err
}

View File

@ -1,71 +0,0 @@
package agent
import (
"fmt"
"time"
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/v2/internal/bus"
"github.com/kairos-io/kairos/v2/internal/cmd"
qr "github.com/mudler/go-nodepair/qrcode"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
)
func Recovery() error {
bus.Manager.Initialize()
token := ""
msg := ""
busErr := ""
bus.Manager.Response(events.EventRecovery, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
token = r.Data
msg = r.State
busErr = r.Error
})
cmd.PrintBranding(DefaultBanner)
agentConfig, err := LoadConfig()
if err != nil {
return err
}
cmd.PrintText(agentConfig.Branding.Recovery, "Recovery")
_, err = bus.Manager.Publish(events.EventRecovery, events.EventPayload{})
if err != nil {
return err
}
if busErr != "" {
return fmt.Errorf(busErr)
}
if !agentConfig.Fast {
time.Sleep(5 * time.Second)
}
pterm.Info.Println(msg)
if token != "" {
qr.Print(token)
}
// Wait for user input and go back to shell
utils.Prompt("") //nolint:errcheck
_, err = bus.Manager.Publish(events.EventRecoveryStop, events.EventPayload{})
if err != nil {
return err
}
// give tty1 back
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
return nil
}

View File

@ -1,128 +0,0 @@
package agent
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"sync"
"time"
sdk "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/kairos-io/kairos-sdk/utils"
hook "github.com/kairos-io/kairos/v2/internal/agent/hooks"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/internal/cmd"
"github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/mudler/go-pluggable"
"github.com/pterm/pterm"
)
func Reset(dir ...string) error {
bus.Manager.Initialize()
options := map[string]string{}
bus.Manager.Response(sdk.EventBeforeReset, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
err := json.Unmarshal([]byte(r.Data), &options)
if err != nil {
fmt.Println(err)
}
})
cmd.PrintBranding(DefaultBanner)
agentConfig, err := LoadConfig()
if err != nil {
return err
}
cmd.PrintText(agentConfig.Branding.Reset, "Reset")
// We don't close the lock, as none of the following actions are expected to return
lock := sync.Mutex{}
go func() {
// Wait for user input and go back to shell
utils.Prompt("") //nolint:errcheck
// give tty1 back
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
lock.Lock()
fmt.Println("Reset aborted")
panic(utils.Shell().Run())
}()
if !agentConfig.Fast {
time.Sleep(60 * time.Second)
}
lock.Lock()
args := []string{"reset"}
ensureDataSourceReady()
bus.Manager.Publish(sdk.EventBeforeReset, sdk.EventPayload{}) //nolint:errcheck
optsArgs := optsToArgs(options)
if len(optsArgs) > 0 {
args = append(args, optsArgs...)
} else {
args = append(args, "--reset-persistent")
}
c, err := config.Scan(collector.Directories(dir...))
if err != nil {
return err
}
utils.SetEnv(c.Env)
cmd := exec.Command("elemental", args...)
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := hook.Run(*c, hook.AfterReset...); err != nil {
return err
}
bus.Manager.Publish(sdk.EventAfterReset, sdk.EventPayload{}) //nolint:errcheck
if !agentConfig.Fast {
pterm.Info.Println("Rebooting in 60 seconds, press Enter to abort...")
}
// We don't close the lock, as none of the following actions are expected to return
lock2 := sync.Mutex{}
go func() {
// Wait for user input and go back to shell
utils.Prompt("") //nolint:errcheck
// give tty1 back
svc, err := machine.Getty(1)
if err == nil {
svc.Start() //nolint:errcheck
}
lock2.Lock()
fmt.Println("Reboot aborted")
panic(utils.Shell().Run())
}()
if !agentConfig.Fast {
time.Sleep(60 * time.Second)
}
lock2.Lock()
utils.Reboot()
return nil
}

View File

@ -1,140 +0,0 @@
package agent
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"github.com/Masterminds/semver/v3"
events "github.com/kairos-io/kairos-sdk/bus"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/kairos-io/kairos/v2/internal/bus"
"github.com/kairos-io/kairos/v2/pkg/config"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
"github.com/kairos-io/kairos/v2/pkg/github"
"github.com/mudler/go-pluggable"
)
func ListReleases(includePrereleases bool) semver.Collection {
var releases semver.Collection
bus.Manager.Response(events.EventAvailableReleases, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
if err := json.Unmarshal([]byte(r.Data), &releases); err != nil {
fmt.Printf("warn: failed unmarshalling data: '%s'\n", err.Error())
}
})
if _, err := bus.Manager.Publish(events.EventAvailableReleases, events.EventPayload{}); err != nil {
fmt.Printf("warn: failed publishing event: '%s'\n", err.Error())
}
if len(releases) == 0 {
githubRepo, err := utils.OSRelease("GITHUB_REPO")
if err != nil {
return releases
}
fmt.Println("Searching for releases")
if includePrereleases {
fmt.Println("Including pre-releases")
}
releases, _ = github.FindReleases(context.Background(), "", githubRepo, includePrereleases)
}
return releases
}
func Upgrade(
version, image string, force, debug, strictValidations bool, dirs []string, authUser string,
authPass string, authServer string, authType string, registryToken string, identityToken string, preReleases bool,
) error {
bus.Manager.Initialize()
if version == "" && image == "" {
fmt.Println("Searching for releases")
if preReleases {
fmt.Println("Including pre-releases")
}
releases := ListReleases(preReleases)
if len(releases) == 0 {
return fmt.Errorf("no releases found")
}
// Using Original here because the parsing removes the v as its a semver. But it stores the original full version there
version = releases[0].Original()
if utils.Version() == version && !force {
fmt.Printf("version %s already installed. use --force to force upgrade\n", version)
return nil
}
msg := fmt.Sprintf("Latest release is %s\nAre you sure you want to upgrade to this release? (y/n)", version)
reply, err := promptBool(events.YAMLPrompt{Prompt: msg, Default: "y"})
if err != nil {
return err
}
if reply == "false" {
return nil
}
}
discoveredImage := ""
bus.Manager.Response(events.EventVersionImage, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
discoveredImage = r.Data
})
_, err := bus.Manager.Publish(events.EventVersionImage, &events.VersionImagePayload{
Version: version,
})
if err != nil {
return err
}
registry, err := utils.OSRelease("IMAGE_REPO")
if err != nil {
fmt.Printf("Cant find IMAGE_REPO key under /etc/os-release\n")
return err
}
img := fmt.Sprintf("%s:%s", registry, version)
if discoveredImage != "" {
img = discoveredImage
}
if image != "" {
img = image
}
if debug {
fmt.Printf("Upgrading to image: '%s'\n", img)
}
c, err := config.Scan(collector.Directories(dirs...), collector.StrictValidation(strictValidations))
if err != nil {
return err
}
utils.SetEnv(c.Env)
args := []string{"upgrade", "--system.uri", fmt.Sprintf("docker:%s", img)}
args = append(args,
"--auth-username", authUser,
"--auth-password", authPass,
"--auth-server-address", authServer,
"--auth-type", authType,
"--auth-registry-token", registryToken,
"--auth-identity-token", identityToken,
)
if debug {
fmt.Printf("Running command: 'elemental %s'", strings.Join(args, " "))
}
cmd := exec.Command("elemental", args...)
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
return cmd.Run()
}

View File

@ -1,73 +0,0 @@
package agent
import (
"fmt"
"io"
"net/http"
"os"
"strings"
sc "github.com/kairos-io/kairos/v2/pkg/config/schemas"
)
// JSONSchema builds a JSON Schema based on the Root Schema and the given version
// this is helpful when mapping a validation error.
func JSONSchema(version string) (string, error) {
url := fmt.Sprintf("https://kairos.io/%s/cloud-config.json", version)
schema, err := sc.GenerateSchema(sc.RootSchema{}, url)
if err != nil {
return "", err
}
return schema, nil
}
// Validate ensures that a given schema is Valid according to the Root Schema from the agent.
func Validate(source string) error {
var yaml string
if strings.HasPrefix(source, "http") {
resp, err := http.Get(source)
if err != nil {
return err
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
//Convert the body to type string
yaml = string(body)
} else {
// Maybe we should just try to read the string for the normal headers? That would identify a full yaml vs a file
dat, err := os.ReadFile(source)
if err != nil {
if strings.Contains(err.Error(), "no such file or directory") || strings.Contains(err.Error(), "file name too long") {
yaml = source
} else {
return err
}
} else {
yaml = string(dat)
}
}
config, err := sc.NewConfigFromYAML(yaml, sc.RootSchema{})
if err != nil {
return err
}
if !config.HasHeader() {
return fmt.Errorf("missing #cloud-config header")
}
if config.IsValid() {
return nil
}
err = config.ValidationError
if err != nil {
return err
}
return nil
}

View File

@ -1,107 +0,0 @@
package agent_test
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
. "github.com/kairos-io/kairos/v2/internal/agent"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Validate", func() {
Context("JSONSchema", func() {
It("returns a schema with a url to the given version", func() {
out, err := JSONSchema("0.0.0")
Expect(err).ToNot(HaveOccurred())
Expect(strings.Contains(out, `$schema": "https://kairos.io/0.0.0/cloud-config.json"`)).To(BeTrue())
})
})
Context("Validate", func() {
var yaml string
Context("With a really long config string", func() {
BeforeEach(func() {
yaml = `#cloud-config
users:
- name: kairos
passwd: kairos
vpn:
network_token: "dssdnfjkldashfkjhasdkhfkasjdhfkjhasdjkfhaksjdhfkjashjdkfhioreqwhfuihqweruifhuewrbfhuewrfuyequfhuiehuifheqrihfuiqrehfuirqheiufhreqiuhfuiqheiufhqeuihfuiqrehfiuhqreuifrhiuqehfiuhqeirhfiuewhrfhqwehfriuewhfuihewiuhfruewhrifhwiuehrfiuhweiurfhwueihrfuiwehufhweuihrfuiwerhfuihewruifhewuihfiouwehrfiouhwei"
`
})
It("validates", func() {
Expect(Validate(yaml)).ToNot(HaveOccurred())
})
})
Context("with a valid config", func() {
BeforeEach(func() {
yaml = `#cloud-config
users:
- name: kairos
passwd: kairos`
})
It("is successful reading it from file", func() {
f, err := ioutil.TempDir("", "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f)
path := filepath.Join(f, "config.yaml")
err = os.WriteFile(path, []byte(yaml), 0655)
Expect(err).ToNot(HaveOccurred())
err = Validate(path)
Expect(err).ToNot(HaveOccurred())
})
It("is successful reading it from a string", func() {
Expect(Validate(yaml)).ToNot(HaveOccurred())
})
})
Context("without a header", func() {
BeforeEach(func() {
yaml = `users:
- name: kairos
passwd: kairos`
})
It("is fails", func() {
f, err := ioutil.TempDir("", "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f)
path := filepath.Join(f, "config.yaml")
err = os.WriteFile(path, []byte(yaml), 0655)
Expect(err).ToNot(HaveOccurred())
err = Validate(path)
Expect(err).To(MatchError("missing #cloud-config header"))
})
})
Context("with an invalid rule", func() {
BeforeEach(func() {
yaml = `#cloud-config
users:
- name: 007
passwd: kairos`
})
It("is fails", func() {
f, err := ioutil.TempDir("", "tests")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(f)
path := filepath.Join(f, "config.yaml")
err = os.WriteFile(path, []byte(yaml), 0655)
Expect(err).ToNot(HaveOccurred())
err = Validate(path)
Expect(err.Error()).To(MatchRegexp("expected string, but got number"))
})
})
})
})

View File

@ -1,73 +0,0 @@
package bus
import (
"fmt"
"os"
"github.com/kairos-io/kairos-sdk/bus"
"github.com/mudler/go-pluggable"
)
// Manager is the bus instance manager, which subscribes plugins to events emitted.
var Manager = NewBus()
func NewBus() *Bus {
return &Bus{
Manager: pluggable.NewManager(
bus.AllEvents,
),
}
}
func Reload() {
Manager = NewBus()
Manager.Initialize()
}
type Bus struct {
*pluggable.Manager
registered bool
}
func (b *Bus) LoadProviders() {
wd, _ := os.Getwd()
b.Manager.Autoload("agent-provider", "/system/providers", "/usr/local/system/providers", wd).Register()
}
func (b *Bus) HasRegisteredPlugins() bool {
return len(b.Plugins) > 0
}
func (b *Bus) Initialize() {
if b.registered {
return
}
b.LoadProviders()
for i := range b.Manager.Events {
e := b.Manager.Events[i]
b.Manager.Response(e, func(p *pluggable.Plugin, r *pluggable.EventResponse) {
if os.Getenv("BUS_DEBUG") == "true" {
fmt.Println(
fmt.Sprintf("[provider event: %s]", e),
"received from",
p.Name,
"at",
p.Executable,
r,
)
}
if r.Errored() {
err := fmt.Sprintf("Provider %s at %s had an error: %s", p.Name, p.Executable, r.Error)
fmt.Println(err)
os.Exit(1)
}
if r.State != "" {
fmt.Println(fmt.Sprintf("[provider event: %s]", e), r.State)
}
})
}
b.registered = true
}

View File

@ -1,32 +0,0 @@
package cmd
import (
"fmt"
"os"
"github.com/kairos-io/kairos/v2/internal/kairos"
"github.com/kairos-io/kairos-sdk/utils"
"github.com/pterm/pterm"
)
func PrintText(f string, banner string) {
pterm.DefaultBox.WithTitle(banner).WithTitleBottomRight().WithRightPadding(0).WithBottomPadding(0).Println(
f)
}
func ClearScreen() {
fmt.Print("\033c")
}
func PrintBranding(b []byte) {
brandingFile := kairos.BrandingFile("banner")
if _, err := os.Stat(brandingFile); err == nil {
f, err := os.ReadFile(brandingFile)
if err == nil {
fmt.Println(string(f))
return
}
}
utils.PrintBanner(b)
}

View File

@ -1,3 +0,0 @@
package common
var VERSION = "0.0.0"

View File

@ -1,7 +0,0 @@
package kairos
import "path"
func BrandingFile(s string) string {
return path.Join("/etc", "kairos", "branding", s)
}

View File

@ -1,6 +0,0 @@
# Web UI
## Dependencies
All dependencies are defined in the package.json and package-lock.json files.
You can use `npm` to install or upgrade any packages.

View File

@ -1,8 +0,0 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
supportFile: false,
baseUrl: "http://localhost:8080"
},
});

View File

@ -1,49 +0,0 @@
describe('Basic Tests for webui', () => {
beforeEach(() => {
cy.visit('/')
cy.intercept({
method: 'POST',
url: '/validate',
}, {log: false}).as('validate')
})
it("basic items on the ui exist", () => {
cy.contains('#cloud-config').should("exist").should("be.visible")
cy.contains('Welcome to the Installer!').should("exist").should("be.visible")
cy.contains("p a", "cloud-config config configuration file")
.should("have.attr", "href", "/local/docs/reference/configuration/")
cy.get("#cloud-config-help a")
.should("have.attr", "href", "/local/docs/examples/")
cy.get("#installation-device").should("have.value", "auto")
// footer
cy.get("a .fa-github").should("exist").parent().should("have.attr", "href", "https://github.com/kairos-io/kairos")
cy.get("a .fa-book").should("exist").parent().should("have.attr", "href", "https://kairos.io/docs")
cy.get("#reboot-checkbox").should("exist").should("not.be.checked")
cy.get("#poweroff-checkbox").should("exist").should("not.be.checked")
cy.get("button").should("exist").invoke("text").should("equal", "Install")
})
it('validation works', () => {
cy.get('.CodeMirror')
.first()
.then((editor) => {
editor[0].CodeMirror.setValue('');
});
cy.get(".CodeMirror textarea").type("#cloud-config{enter}users:{enter} - name: itxaka", {force: true})
cy.get("#validator-alert").should("have.text", "Valid YAML syntax")
})
it('validation fails ', () => {
cy.get('.CodeMirror')
.first()
.then((editor) => {
editor[0].CodeMirror.setValue('');
});
cy.get(".CodeMirror textarea").type("blablabla", {force: true})
cy.get("#validator-alert").invoke("text").should("match", /Failed validating syntax/)
})
it('should install', function () {
cy.get("button").click()
});
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
{
"dependencies": {
"@fortawesome/fontawesome-free": "^6.2.1",
"@popperjs/core": "^2.11.6",
"alpinejs": "^3.10.5",
"bootstrap": "^5.0.0",
"codemirror": "^5.57.0",
"jquery": "^3.5.1",
"xterm": "^5.1.0",
"xterm-addon-fit": "^0.7.0",
"xterm-theme": "^1.1.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
"cypress": "^12.9.0"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,256 +0,0 @@
package webui
import (
"context"
"embed"
"io"
"io/fs"
"log"
"net/http"
"os"
"sync"
"text/template"
"time"
"github.com/kairos-io/kairos/v2/internal/agent"
"github.com/kairos-io/kairos/v2/pkg/config"
"github.com/labstack/echo/v4"
process "github.com/mudler/go-processmanager"
"github.com/nxadm/tail"
"golang.org/x/net/websocket"
)
type FormData struct {
CloudConfig string `form:"cloud-config" json:"cloud-config" query:"cloud-config"`
Reboot string `form:"reboot" json:"reboot" query:"reboot"`
PowerOff string `form:"power-off" json:"power-off" query:"power-off"`
InstallationDevice string `form:"installation-device" json:"installation-device" query:"installation-device"`
}
//go:embed public
var embededFiles embed.FS
func getFileSystem() http.FileSystem {
fsys, err := fs.Sub(embededFiles, "public")
if err != nil {
panic(err)
}
return http.FS(fsys)
}
func getFS() fs.FS {
fsys, err := fs.Sub(embededFiles, "public")
if err != nil {
panic(err)
}
return fsys
}
func streamProcess(s *state) func(c echo.Context) error {
return func(c echo.Context) error {
consumeError := func(err error) {
if err != nil {
c.Logger().Error(err)
}
}
websocket.Handler(func(ws *websocket.Conn) {
defer ws.Close()
for {
s.Lock()
if s.p == nil {
// Write
err := websocket.Message.Send(ws, "No process!")
consumeError(err)
s.Unlock()
return
}
s.Unlock()
if !s.p.IsAlive() {
errOut, err := os.ReadFile(s.p.StderrPath())
if err == nil {
err := websocket.Message.Send(ws, string(errOut))
consumeError(err)
}
out, err := os.ReadFile(s.p.StdoutPath())
if err == nil {
err = websocket.Message.Send(ws, string(out))
consumeError(err)
}
err = websocket.Message.Send(ws, "Process stopped!")
consumeError(err)
return
}
t, err := tail.TailFile(s.p.StdoutPath(), tail.Config{Follow: true})
if err != nil {
return
}
t2, err := tail.TailFile(s.p.StderrPath(), tail.Config{Follow: true})
if err != nil {
return
}
for {
select {
case line := <-t.Lines:
err = websocket.Message.Send(ws, line.Text+"\r\n")
consumeError(err)
case line := <-t2.Lines:
err = websocket.Message.Send(ws, line.Text+"\r\n")
consumeError(err)
}
}
}
}).ServeHTTP(c.Response(), c.Request())
return nil
}
}
type state struct {
p *process.Process
sync.Mutex
}
// TemplateRenderer is a custom html/template renderer for Echo framework.
type TemplateRenderer struct {
templates *template.Template
}
// Render renders a template document.
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
// Add global methods if data is a map
if viewContext, isMap := data.(map[string]interface{}); isMap {
viewContext["reverse"] = c.Echo().Reverse
}
return t.templates.ExecuteTemplate(w, name, data)
}
func Start(ctx context.Context) error {
s := state{}
listen := config.DefaultWebUIListenAddress
ec := echo.New()
assetHandler := http.FileServer(getFileSystem())
renderer := &TemplateRenderer{
templates: template.Must(template.ParseFS(getFS(), "*.html")),
}
ec.Renderer = renderer
agentConfig, err := agent.LoadConfig()
if err != nil {
return err
}
if agentConfig.WebUI.ListenAddress != "" {
listen = agentConfig.WebUI.ListenAddress
}
if agentConfig.WebUI.Disable {
log.Println("WebUI installer disabled by branding")
return nil
}
ec.GET("/*", echo.WrapHandler(http.StripPrefix("/", assetHandler)))
ec.POST("/validate", func(c echo.Context) error {
formData := new(FormData)
if err := c.Bind(formData); err != nil {
return err
}
cloudConfig := formData.CloudConfig
err := agent.Validate(cloudConfig)
if err != nil {
return c.String(http.StatusOK, err.Error())
}
return c.String(http.StatusOK, "")
})
ec.POST("/install", func(c echo.Context) error {
s.Lock()
if s.p != nil {
status, _ := s.p.ExitCode()
if s.p.IsAlive() || status == "0" {
s.Unlock()
return c.Redirect(http.StatusSeeOther, "progress.html")
}
}
s.Unlock()
formData := new(FormData)
if err := c.Bind(formData); err != nil {
return err
}
// Process the form data as necessary
cloudConfig := formData.CloudConfig
reboot := formData.Reboot
powerOff := formData.PowerOff
installationDevice := formData.InstallationDevice
args := []string{"manual-install"}
if powerOff == "on" {
args = append(args, "--poweroff")
}
if reboot == "on" {
args = append(args, "--reboot")
}
args = append(args, "--device", installationDevice)
// create tempfile to store cloud-config, bail out if we fail as we couldn't go much further
file, err := os.CreateTemp("", "install-webui-*.yaml")
if err != nil {
log.Fatalf("could not create tmpfile for cloud-config: %s", err.Error())
}
err = os.WriteFile(file.Name(), []byte(cloudConfig), 0600)
if err != nil {
log.Fatalf("could not write tmpfile for cloud-config: %s", err.Error())
}
args = append(args, file.Name())
s.Lock()
s.p = process.New(process.WithName("/usr/bin/kairos-agent"), process.WithArgs(args...), process.WithTemporaryStateDir())
s.Unlock()
err = s.p.Run()
if err != nil {
return c.Render(http.StatusOK, "message.html", map[string]interface{}{
"message": err.Error(),
"type": "danger",
})
}
// Start install process, lock with sentinel
return c.Redirect(http.StatusSeeOther, "progress.html")
})
ec.GET("/ws", streamProcess(&s))
if err := ec.Start(listen); err != nil && err != http.ErrServerClosed {
return err
}
go func() {
<-ctx.Done()
ct, cancel := context.WithTimeout(context.Background(), 10*time.Second)
err := ec.Shutdown(ct)
if err != nil {
log.Printf("shutdown failed: %s", err.Error())
}
cancel()
}()
return nil
}

View File

@ -1,471 +0,0 @@
// Package configcollector can be used to merge configuration from different
// sources into one YAML.
package collector
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"reflect"
"strings"
"time"
"unicode"
"golang.org/x/exp/slices"
"github.com/kairos-io/kairos-sdk/machine"
"github.com/avast/retry-go"
"github.com/itchyny/gojq"
"gopkg.in/yaml.v3"
)
const DefaultHeader = "#cloud-config"
var ValidFileHeaders = []string{
"#cloud-config",
"#kairos-config",
"#node-config",
}
type Configs []*Config
// We don't allow yamls that are plain arrays because is has no use in Kairos
// and there is no way to merge an array yaml with a "map" yaml.
type Config map[string]interface{}
// MergeConfigURL looks for the "config_url" key and if it's found
// it downloads the remote config and merges it with the current one.
// If the remote config also has config_url defined, it is also fetched
// recursively until a remote config no longer defines a config_url.
// NOTE: The "config_url" value of the final result is the value of the last
// config file in the chain because we replace values when we merge.
func (c *Config) MergeConfigURL() error {
// If there is no config_url, just return (do nothing)
configURL := c.ConfigURL()
if configURL == "" {
return nil
}
// fetch the remote config
remoteConfig, err := fetchRemoteConfig(configURL)
if err != nil {
return err
}
// recursively fetch remote configs
if err := remoteConfig.MergeConfigURL(); err != nil {
return err
}
// merge remoteConfig back to "c"
return c.MergeConfig(remoteConfig)
}
func (c *Config) toMap() (map[string]interface{}, error) {
var result map[string]interface{}
data, err := yaml.Marshal(c)
if err != nil {
return result, err
}
err = yaml.Unmarshal(data, &result)
return result, err
}
func (c *Config) applyMap(i interface{}) error {
data, err := yaml.Marshal(i)
if err != nil {
return err
}
err = yaml.Unmarshal(data, c)
return err
}
// MergeConfig merges the config passed as parameter back to the receiver Config.
func (c *Config) MergeConfig(newConfig *Config) error {
var err error
// convert the two configs into maps
aMap, err := c.toMap()
if err != nil {
return err
}
bMap, err := newConfig.toMap()
if err != nil {
return err
}
// deep merge the two maps
cMap, err := DeepMerge(aMap, bMap)
if err != nil {
return err
}
// apply the result of the deepmerge into the base config
return c.applyMap(cMap)
}
func deepMergeSlices(sliceA, sliceB []interface{}) ([]interface{}, error) {
// We use the first item in the slice to determine if there are maps present.
// Do we need to do the same for other types?
firstItem := sliceA[0]
if reflect.ValueOf(firstItem).Kind() == reflect.Map {
temp := make(map[string]interface{})
// first we put in temp all the keys present in a, and assign them their existing values
for _, item := range sliceA {
for k, v := range item.(map[string]interface{}) {
temp[k] = v
}
}
// then we go through b to merge each of its keys
for _, item := range sliceB {
for k, v := range item.(map[string]interface{}) {
current, ok := temp[k]
if ok {
// if the key exists, we deep merge it
dm, err := DeepMerge(current, v)
if err != nil {
return []interface{}{}, fmt.Errorf("cannot merge %s with %s", current, v)
}
temp[k] = dm
} else {
// otherwise we just set it
temp[k] = v
}
}
}
return []interface{}{temp}, nil
}
// for simple slices
for _, v := range sliceB {
i := slices.Index(sliceA, v)
if i < 0 {
sliceA = append(sliceA, v)
}
}
return sliceA, nil
}
func deepMergeMaps(a, b map[string]interface{}) (map[string]interface{}, error) {
// go through all items in b and merge them to a
for k, v := range b {
current, ok := a[k]
if ok {
// when the key is already set, we don't know what type it has, so we deep merge them in case they are maps
// or slices
res, err := DeepMerge(current, v)
if err != nil {
return a, err
}
a[k] = res
} else {
a[k] = v
}
}
return a, nil
}
// DeepMerge takes two data structures and merges them together deeply. The results can vary depending on how the
// arguments are passed since structure B will always overwrite what's on A.
func DeepMerge(a, b interface{}) (interface{}, error) {
if a == nil && b != nil {
return b, nil
}
typeA := reflect.TypeOf(a)
typeB := reflect.TypeOf(b)
// We don't support merging different data structures
if typeA.Kind() != typeB.Kind() {
return map[string]interface{}{}, fmt.Errorf("cannot merge %s with %s", typeA.String(), typeB.String())
}
if typeA.Kind() == reflect.Slice {
return deepMergeSlices(a.([]interface{}), b.([]interface{}))
}
if typeA.Kind() == reflect.Map {
return deepMergeMaps(a.(map[string]interface{}), b.(map[string]interface{}))
}
// for any other type, b should take precedence
return b, nil
}
// String returns a string which is a Yaml representation of the Config.
func (c *Config) String() (string, error) {
data, err := yaml.Marshal(c)
if err != nil {
return "", err
}
return fmt.Sprintf("%s\n\n%s", DefaultHeader, string(data)), nil
}
func (cs Configs) Merge() (*Config, error) {
result := &Config{}
for _, c := range cs {
if err := c.MergeConfigURL(); err != nil {
return result, err
}
if err := result.MergeConfig(c); err != nil {
return result, err
}
}
return result, nil
}
func Scan(o *Options, filter func(d []byte) ([]byte, error)) (*Config, error) {
configs := Configs{}
configs = append(configs, parseFiles(o.ScanDir, o.NoLogs)...)
if o.MergeBootCMDLine {
cConfig, err := ParseCmdLine(o.BootCMDLineFile, filter)
o.SoftErr("parsing cmdline", err)
if err == nil { // best-effort
configs = append(configs, cConfig)
}
}
return configs.Merge()
}
func allFiles(dir []string) []string {
files := []string{}
for _, d := range dir {
if f, err := listFiles(d); err == nil {
files = append(files, f...)
}
}
return files
}
// parseFiles returns a list of Configs parsed from files.
func parseFiles(dir []string, nologs bool) Configs {
result := Configs{}
files := allFiles(dir)
for _, f := range files {
if fileSize(f) > 1.0 {
if !nologs {
fmt.Printf("warning: skipping %s. too big (>1MB)\n", f)
}
continue
}
if strings.Contains(f, "userdata") || filepath.Ext(f) == ".yml" || filepath.Ext(f) == ".yaml" {
b, err := os.ReadFile(f)
if err != nil {
if !nologs {
fmt.Printf("warning: skipping %s. %s\n", f, err.Error())
}
continue
}
if !HasValidHeader(string(b)) {
if !nologs {
fmt.Printf("warning: skipping %s because it has no valid header\n", f)
}
continue
}
var newConfig Config
err = yaml.Unmarshal(b, &newConfig)
if err != nil && !nologs {
fmt.Printf("warning: failed to parse config:\n%s\n", err.Error())
}
result = append(result, &newConfig)
} else {
if !nologs {
fmt.Printf("warning: skipping %s (extension).\n", f)
}
}
}
return result
}
func fileSize(f string) float64 {
file, err := os.Open(f)
if err != nil {
return 0
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return 0
}
bytes := stat.Size()
kilobytes := (bytes / 1024)
megabytes := (float64)(kilobytes / 1024) // cast to type float64
return megabytes
}
func listFiles(dir string) ([]string, error) {
content := []string{}
err := filepath.Walk(dir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if !info.IsDir() {
content = append(content, path)
}
return nil
})
return content, err
}
// ParseCmdLine reads options from the kernel cmdline and returns the equivalent
// Config.
func ParseCmdLine(file string, filter func(d []byte) ([]byte, error)) (*Config, error) {
result := Config{}
dotToYAML, err := machine.DotToYAML(file)
if err != nil {
return &result, err
}
filteredYAML, err := filter(dotToYAML)
if err != nil {
return &result, err
}
err = yaml.Unmarshal(filteredYAML, &result)
if err != nil {
return &result, err
}
return &result, nil
}
// ConfigURL returns the value of config_url if set or empty string otherwise.
func (c Config) ConfigURL() string {
if val, hasKey := c["config_url"]; hasKey {
if s, isString := val.(string); isString {
return s
}
}
return ""
}
func fetchRemoteConfig(url string) (*Config, error) {
var body []byte
result := &Config{}
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
}, retry.Delay(time.Second), retry.Attempts(3),
)
if err != nil {
// TODO: improve logging
fmt.Printf("WARNING: Couldn't fetch config_url: %s", err)
return result, nil
}
if !HasValidHeader(string(body)) {
// TODO: Print a warning when we implement proper logging
fmt.Println("No valid header in remote config: %w", err)
return result, nil
}
if err := yaml.Unmarshal(body, result); err != nil {
return result, fmt.Errorf("could not unmarshal remote config to an object: %w", err)
}
return result, nil
}
func HasValidHeader(data string) bool {
header := strings.SplitN(data, "\n", 2)[0]
// Trim trailing whitespaces
header = strings.TrimRightFunc(header, unicode.IsSpace)
// NOTE: we also allow "legacy" headers. Should only allow #cloud-config at
// some point.
return (header == DefaultHeader) || (header == "#kairos-config") || (header == "#node-config")
}
func (c Config) Query(s string) (res string, err error) {
s = fmt.Sprintf(".%s", s)
var dat map[string]interface{}
var dat1 map[string]interface{}
yamlStr, err := c.String()
if err != nil {
panic(err)
}
// Marshall it so it removes the first line which cannot be parsed
err = yaml.Unmarshal([]byte(yamlStr), &dat1)
if err != nil {
panic(err)
}
// Transform it to json so its parsed correctly by gojq
b, err := json.Marshal(dat1)
if err != nil {
panic(err)
}
if err := json.Unmarshal(b, &dat); err != nil {
panic(err)
}
query, err := gojq.Parse(s)
if err != nil {
return res, err
}
iter := query.Run(dat) // or query.RunWithContext
for {
v, ok := iter.Next()
if !ok {
break
}
if err, ok := v.(error); ok {
return res, fmt.Errorf("failed parsing, error: %w", err)
}
dat, err := yaml.Marshal(v)
if err != nil {
break
}
res += string(dat)
}
return
}

View File

@ -1,754 +0,0 @@
package collector_test
import (
"fmt"
"github.com/kairos-io/kairos/v2/pkg/config"
"os"
"path"
"path/filepath"
. "github.com/kairos-io/kairos/v2/pkg/config/collector"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v1"
)
var _ = Describe("Config Collector", func() {
Describe("Options", func() {
var options *Options
BeforeEach(func() {
options = &Options{
NoLogs: false,
}
})
It("applies a defined option function", func() {
option := func(o *Options) error {
o.NoLogs = true
return nil
}
Expect(options.NoLogs).To(BeFalse())
Expect(options.Apply(option)).NotTo(HaveOccurred())
Expect(options.NoLogs).To(BeTrue())
})
})
Describe("MergeConfig", func() {
var originalConfig, newConfig *Config
BeforeEach(func() {
originalConfig = &Config{}
newConfig = &Config{}
})
Context("different keys", func() {
BeforeEach(func() {
err := yaml.Unmarshal([]byte(`#cloud-config
name: Mario`), originalConfig)
Expect(err).ToNot(HaveOccurred())
err = yaml.Unmarshal([]byte(`#cloud-config
surname: Bros`), newConfig)
Expect(err).ToNot(HaveOccurred())
})
It("gets merged together", func() {
Expect(originalConfig.MergeConfig(newConfig)).ToNot(HaveOccurred())
surname, isString := (*originalConfig)["surname"].(string)
Expect(isString).To(BeTrue())
Expect(surname).To(Equal("Bros"))
})
})
Context("same keys", func() {
Context("when the key is a map", func() {
BeforeEach(func() {
err := yaml.Unmarshal([]byte(`#cloud-config
info:
name: Mario
`), originalConfig)
Expect(err).ToNot(HaveOccurred())
err = yaml.Unmarshal([]byte(`#cloud-config
info:
surname: Bros
`), newConfig)
Expect(err).ToNot(HaveOccurred())
})
It("merges the keys", func() {
Expect(originalConfig.MergeConfig(newConfig)).ToNot(HaveOccurred())
info, isMap := (*originalConfig)["info"].(Config)
Expect(isMap).To(BeTrue())
Expect(info["name"]).To(Equal("Mario"))
Expect(info["surname"]).To(Equal("Bros"))
Expect(*originalConfig).To(HaveLen(1))
Expect(info).To(HaveLen(2))
})
})
Context("when the key is a string", func() {
BeforeEach(func() {
err := yaml.Unmarshal([]byte("#cloud-config\nname: Mario"), originalConfig)
Expect(err).ToNot(HaveOccurred())
err = yaml.Unmarshal([]byte("#cloud-config\nname: Luigi"), newConfig)
Expect(err).ToNot(HaveOccurred())
})
It("overwrites", func() {
Expect(originalConfig.MergeConfig(newConfig)).ToNot(HaveOccurred())
name, isString := (*originalConfig)["name"].(string)
Expect(isString).To(BeTrue())
Expect(name).To(Equal("Luigi"))
Expect(*originalConfig).To(HaveLen(1))
})
})
})
})
Describe("MergeConfigURL", func() {
var originalConfig *Config
BeforeEach(func() {
originalConfig = &Config{}
})
Context("when there is no config_url defined", func() {
BeforeEach(func() {
err := yaml.Unmarshal([]byte("#cloud-config\nname: Mario"), originalConfig)
Expect(err).ToNot(HaveOccurred())
})
It("does nothing", func() {
Expect(originalConfig.MergeConfigURL()).ToNot(HaveOccurred())
Expect(*originalConfig).To(HaveLen(1))
})
})
Context("when there is a chain of config_url defined", func() {
var closeFunc ServerCloseFunc
var port int
var err error
var tmpDir string
var originalConfig *Config
BeforeEach(func() {
tmpDir, err = os.MkdirTemp("", "config_url_chain")
Expect(err).ToNot(HaveOccurred())
closeFunc, port, err = startAssetServer(tmpDir)
Expect(err).ToNot(HaveOccurred())
originalConfig = &Config{}
err = yaml.Unmarshal([]byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/config1.yaml
name: Mario
surname: Bros
info:
job: plumber
`, port)), originalConfig)
Expect(err).ToNot(HaveOccurred())
err := os.WriteFile(path.Join(tmpDir, "config1.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/config2.yaml
surname: Bras
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(tmpDir, "config2.yaml"), []byte(`#cloud-config
info:
girlfriend: princess
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
closeFunc()
err := os.RemoveAll(tmpDir)
Expect(err).ToNot(HaveOccurred())
})
It("merges them all together", func() {
err := originalConfig.MergeConfigURL()
Expect(err).ToNot(HaveOccurred())
name, ok := (*originalConfig)["name"].(string)
Expect(ok).To(BeTrue())
Expect(name).To(Equal("Mario"))
surname, ok := (*originalConfig)["surname"].(string)
Expect(ok).To(BeTrue())
Expect(surname).To(Equal("Bras"))
info, ok := (*originalConfig)["info"].(Config)
Expect(ok).To(BeTrue())
Expect(info["job"]).To(Equal("plumber"))
Expect(info["girlfriend"]).To(Equal("princess"))
Expect(*originalConfig).To(HaveLen(4))
})
})
})
Describe("deepMerge", func() {
Context("different types", func() {
a := map[string]interface{}{}
b := []string{}
It("merges", func() {
_, err := DeepMerge(a, b)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("cannot merge map[string]interface {} with []string"))
_, err = DeepMerge(b, a)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("cannot merge []string with map[string]interface {}"))
})
})
Context("simple slices", func() {
a := []interface{}{"one", "three"}
b := []interface{}{"two", 4}
It("merges", func() {
c, err := DeepMerge(a, b)
Expect(err).ToNot(HaveOccurred())
Expect(c).To(Equal([]interface{}{"one", "three", "two", 4}))
})
})
Context("slices containing maps", func() {
a := []interface{}{
map[string]interface{}{
"users": []interface{}{
map[string]interface{}{
"kairos": map[string]interface{}{
"passwd": "kairos",
},
},
},
},
}
b := []interface{}{
map[string]interface{}{
"users": []interface{}{
map[string]interface{}{
"foo": map[string]interface{}{
"passwd": "bar",
},
},
},
},
}
It("merges", func() {
c, err := DeepMerge(a, b)
Expect(err).ToNot(HaveOccurred())
users := c.([]interface{})[0].(map[string]interface{})["users"]
Expect(users).To(HaveLen(1))
Expect(users).To(Equal([]interface{}{
map[string]interface{}{
"kairos": map[string]interface{}{
"passwd": "kairos",
},
"foo": map[string]interface{}{
"passwd": "bar",
},
},
}))
})
})
Context("empty map", func() {
a := map[string]interface{}{}
b := map[string]interface{}{
"foo": "bar",
}
It("merges", func() {
c, err := DeepMerge(a, b)
Expect(err).ToNot(HaveOccurred())
Expect(c).To(Equal(map[string]interface{}{
"foo": "bar",
}))
})
})
Context("simple map", func() {
a := map[string]interface{}{
"es": "uno",
"nl": "een",
"#": 0,
}
b := map[string]interface{}{
"en": "one",
"nl": "één",
"de": "Eins",
"#": 1,
}
It("merges", func() {
c, err := DeepMerge(a, b)
Expect(err).ToNot(HaveOccurred())
Expect(c).To(Equal(map[string]interface{}{
"#": 1,
"de": "Eins",
"en": "one",
"es": "uno",
"nl": "één",
}))
})
})
})
Describe("Scan", func() {
Context("duplicated configs", func() {
var cmdLinePath, tmpDir1 string
var err error
BeforeEach(func() {
tmpDir1, err = os.MkdirTemp("", "config1")
Expect(err).ToNot(HaveOccurred())
err := os.WriteFile(path.Join(tmpDir1, "local_config_1.yaml"), []byte(`#cloud-config
stages:
initramfs:
- name: "Set user and password"
users:
kairos:
passwd: "kairos"
hostname: kairos-{{ trunc 4 .Random }}
install:
auto: true
reboot: true
device: auto
grub_options:
extra_cmdline: foobarzz
bundles:
- rootfs_path: /usr/local/lib/extensions/kubo
targets:
- container://ttl.sh/97d4530c-df80-4eb4-9ae7-39f8f90c26e5:8h
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(tmpDir1, "local_config_2.yaml"), []byte(`#cloud-config
stages:
initramfs:
- name: "Set user and password"
users:
kairos:
passwd: "kairos"
hostname: kairos-{{ trunc 4 .Random }}
install:
auto: true
reboot: true
device: auto
grub_options:
extra_cmdline: foobarzz
bundles:
- rootfs_path: /usr/local/lib/extensions/kubo
targets:
- container://ttl.sh/97d4530c-df80-4eb4-9ae7-39f8f90c26e5:8h
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
err = os.RemoveAll(tmpDir1)
Expect(err).ToNot(HaveOccurred())
})
It("should be the same as just one of them", func() {
o := &Options{}
err := o.Apply(
MergeBootLine,
WithBootCMDLineFile(cmdLinePath),
Directories(tmpDir1),
)
Expect(err).ToNot(HaveOccurred())
c, err := Scan(o, config.FilterKeys)
Expect(err).ToNot(HaveOccurred())
fmt.Println(c.String())
Expect(c.String()).To(Equal(`#cloud-config
install:
auto: true
bundles:
- rootfs_path: /usr/local/lib/extensions/kubo
targets:
- container://ttl.sh/97d4530c-df80-4eb4-9ae7-39f8f90c26e5:8h
device: auto
grub_options:
extra_cmdline: foobarzz
reboot: true
stages:
initramfs:
- hostname: kairos-{{ trunc 4 .Random }}
name: Set user and password
users:
kairos:
passwd: kairos
`))
})
})
Context("Deep merge maps within arrays", func() {
var cmdLinePath, tmpDir1 string
var err error
BeforeEach(func() {
tmpDir1, err = os.MkdirTemp("", "config1")
Expect(err).ToNot(HaveOccurred())
err := os.WriteFile(path.Join(tmpDir1, "local_config_1.yaml"), []byte(`#cloud-config
install:
auto: true
reboot: false
poweroff: false
grub_options:
extra_cmdline: "console=tty0"
options:
device: /dev/sda
stages:
initramfs:
- users:
kairos:
groups:
- sudo
passwd: kairos
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(tmpDir1, "local_config_2.yaml"), []byte(`#cloud-config
stages:
initramfs:
- users:
foo:
groups:
- sudo
passwd: bar
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
err = os.RemoveAll(tmpDir1)
Expect(err).ToNot(HaveOccurred())
})
It("merges all the sources accordingly", func() {
o := &Options{}
err := o.Apply(
MergeBootLine,
WithBootCMDLineFile(cmdLinePath),
Directories(tmpDir1),
)
Expect(err).ToNot(HaveOccurred())
c, err := Scan(o, config.FilterKeys)
Expect(err).ToNot(HaveOccurred())
Expect(c.String()).To(Equal(`#cloud-config
install:
auto: true
grub_options:
extra_cmdline: console=tty0
poweroff: false
reboot: false
options:
device: /dev/sda
stages:
initramfs:
- users:
foo:
groups:
- sudo
passwd: bar
kairos:
groups:
- sudo
passwd: kairos
`))
})
})
Context("multiple sources are defined", func() {
var cmdLinePath, serverDir, tmpDir, tmpDir1, tmpDir2 string
var err error
var closeFunc ServerCloseFunc
var port int
BeforeEach(func() {
// Prepare the cmdline config_url chain
serverDir, err = os.MkdirTemp("", "config_url_chain")
Expect(err).ToNot(HaveOccurred())
closeFunc, port, err = startAssetServer(serverDir)
Expect(err).ToNot(HaveOccurred())
cmdLinePath = createRemoteConfigs(serverDir, port)
tmpDir1, err = os.MkdirTemp("", "config1")
Expect(err).ToNot(HaveOccurred())
err := os.WriteFile(path.Join(tmpDir1, "local_config_1.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_3.yaml
local_key_1: local_value_1
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(serverDir, "remote_config_3.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_4.yaml
remote_key_3: remote_value_3
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(serverDir, "remote_config_4.yaml"), []byte(`#cloud-config
options:
remote_option_1: remote_option_value_1
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
tmpDir2, err = os.MkdirTemp("", "config2")
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(tmpDir2, "local_config_2.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_5.yaml
local_key_2: local_value_2
`, port)), os.ModePerm)
err = os.WriteFile(path.Join(tmpDir2, "local_config_3.yaml"), []byte(`#cloud-config
local_key_3: local_value_3
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(serverDir, "remote_config_5.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_6.yaml
remote_key_4: remote_value_4
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(serverDir, "remote_config_6.yaml"), []byte(`#cloud-config
options:
remote_option_2: remote_option_value_2
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
err = os.RemoveAll(serverDir)
Expect(err).ToNot(HaveOccurred())
err = os.RemoveAll(tmpDir)
Expect(err).ToNot(HaveOccurred())
err = os.RemoveAll(tmpDir1)
Expect(err).ToNot(HaveOccurred())
err = os.RemoveAll(tmpDir2)
Expect(err).ToNot(HaveOccurred())
closeFunc()
})
It("merges all the sources accordingly", func() {
o := &Options{}
err := o.Apply(
MergeBootLine,
WithBootCMDLineFile(cmdLinePath),
Directories(tmpDir1, tmpDir2),
)
Expect(err).ToNot(HaveOccurred())
c, err := Scan(o, config.FilterKeys)
Expect(err).ToNot(HaveOccurred())
configURL, ok := (*c)["config_url"].(string)
Expect(ok).To(BeTrue())
Expect(configURL).To(MatchRegexp("remote_config_2.yaml"))
k := (*c)["local_key_1"].(string)
Expect(k).To(Equal("local_value_1"))
k = (*c)["local_key_2"].(string)
Expect(k).To(Equal("local_value_2"))
k = (*c)["local_key_3"].(string)
Expect(k).To(Equal("local_value_3"))
k = (*c)["remote_key_1"].(string)
Expect(k).To(Equal("remote_value_1"))
k = (*c)["remote_key_2"].(string)
Expect(k).To(Equal("remote_value_2"))
k = (*c)["remote_key_3"].(string)
Expect(k).To(Equal("remote_value_3"))
k = (*c)["remote_key_4"].(string)
Expect(k).To(Equal("remote_value_4"))
options := (*c)["options"].(Config)
Expect(options["foo"]).To(Equal("bar"))
Expect(options["remote_option_1"]).To(Equal("remote_option_value_1"))
Expect(options["remote_option_2"]).To(Equal("remote_option_value_2"))
player := (*c)["player"].(Config)
Expect(player["name"]).NotTo(Equal("Toad"))
Expect(player["surname"]).To(Equal("Bros"))
})
})
Context("when files have invalid or missing headers", func() {
var serverDir, tmpDir string
var err error
var closeFunc ServerCloseFunc
var port int
BeforeEach(func() {
// Prepare the cmdline config_url chain
serverDir, err = os.MkdirTemp("", "config_url_chain")
Expect(err).ToNot(HaveOccurred())
closeFunc, port, err = startAssetServer(serverDir)
Expect(err).ToNot(HaveOccurred())
tmpDir, err = os.MkdirTemp("", "config")
Expect(err).ToNot(HaveOccurred())
// Local configs
err = os.WriteFile(path.Join(tmpDir, "local_config.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_1.yaml
local_key_1: local_value_1
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// missing header
err = os.WriteFile(path.Join(tmpDir, "local_config_2.yaml"),
[]byte("local_key_2: local_value_2"), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// Remote config with valid header
err := os.WriteFile(path.Join(serverDir, "remote_config_1.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_2.yaml
remote_key_1: remote_value_1`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
// Remote config with invalid header
err = os.WriteFile(path.Join(serverDir, "remote_config_2.yaml"), []byte(`#invalid-header
remote_key_2: remote_value_2`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
closeFunc()
err = os.RemoveAll(serverDir)
Expect(err).ToNot(HaveOccurred())
err = os.RemoveAll(tmpDir)
})
It("ignores them", func() {
o := &Options{}
err := o.Apply(Directories(tmpDir), NoLogs)
Expect(err).ToNot(HaveOccurred())
c, err := Scan(o, config.FilterKeys)
Expect(err).ToNot(HaveOccurred())
Expect((*c)["local_key_2"]).To(BeNil())
Expect((*c)["remote_key_2"]).To(BeNil())
// sanity check, the rest should be there
v, ok := (*c)["config_url"].(string)
Expect(ok).To(BeTrue())
Expect(v).To(MatchRegexp("remote_config_2.yaml"))
v, ok = (*c)["local_key_1"].(string)
Expect(ok).To(BeTrue())
Expect(v).To(Equal("local_value_1"))
v, ok = (*c)["remote_key_1"].(string)
Expect(ok).To(BeTrue())
Expect(v).To(Equal("remote_value_1"))
})
})
})
Describe("String", func() {
var conf *Config
BeforeEach(func() {
conf = &Config{}
err := yaml.Unmarshal([]byte("name: Mario"), conf)
Expect(err).ToNot(HaveOccurred())
})
It("returns the YAML string representation of the Config", func() {
s, err := conf.String()
Expect(err).ToNot(HaveOccurred())
Expect(s).To(Equal(`#cloud-config
name: Mario
`), s)
})
})
Describe("Query", func() {
var tmpDir string
var err error
BeforeEach(func() {
tmpDir, err = os.MkdirTemp("", "config")
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(filepath.Join(tmpDir, "b"), []byte(`zz.foo="baa" options.foo=bar`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(tmpDir, "local_config.yaml"), []byte(`#cloud-config
local_key_1: local_value_1
some:
other:
key: 3
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
})
It("can query for keys", func() {
o := &Options{}
err = o.Apply(MergeBootLine, Directories(tmpDir),
WithBootCMDLineFile(filepath.Join(tmpDir, "b")),
)
Expect(err).ToNot(HaveOccurred())
c, err := Scan(o, config.FilterKeys)
Expect(err).ToNot(HaveOccurred())
v, err := c.Query("local_key_1")
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal("local_value_1\n"))
v, err = c.Query("some")
Expect(err).ToNot(HaveOccurred())
Expect(v).To(Equal("other:\n key: 3\n"))
v, err = c.Query("some.other")
Expect(v).To(Equal("key: 3\n"))
v, err = c.Query("some.other.key")
Expect(v).To(Equal("3\n"))
Expect(c.Query("options")).To(Equal("foo: bar\n"))
})
})
})
func createRemoteConfigs(serverDir string, port int) string {
err := os.WriteFile(path.Join(serverDir, "remote_config_1.yaml"), []byte(fmt.Sprintf(`#cloud-config
config_url: http://127.0.0.1:%d/remote_config_2.yaml
player:
remote_key_1: remote_value_1
`, port)), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(path.Join(serverDir, "remote_config_2.yaml"), []byte(`#cloud-config
player:
surname: Bros
remote_key_2: remote_value_2
`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
cmdLinePath := filepath.Join(serverDir, "cmdline")
// We put the cmdline in the same dir, it doesn't matter.
cmdLine := fmt.Sprintf(`config_url="http://127.0.0.1:%d/remote_config_1.yaml" player.name="Toad" options.foo=bar`, port)
err = os.WriteFile(cmdLinePath, []byte(cmdLine), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
return cmdLinePath
}

View File

@ -1,63 +0,0 @@
package collector
import "fmt"
type Options struct {
ScanDir []string
BootCMDLineFile string
MergeBootCMDLine bool
NoLogs bool
StrictValidation bool
}
type Option func(o *Options) error
var NoLogs Option = func(o *Options) error {
o.NoLogs = true
return nil
}
// SoftErr prints a warning if err is no nil and NoLogs is not true.
// It's use to wrap the same handling happening in multiple places.
//
// TODO: Switch to a standard logging library (e.g. verbose, silent mode etc).
func (o *Options) SoftErr(message string, err error) {
if !o.NoLogs && err != nil {
fmt.Printf("WARNING: %s, %s\n", message, err.Error())
}
}
func (o *Options) Apply(opts ...Option) error {
for _, oo := range opts {
if err := oo(o); err != nil {
return err
}
}
return nil
}
var MergeBootLine = func(o *Options) error {
o.MergeBootCMDLine = true
return nil
}
func WithBootCMDLineFile(s string) Option {
return func(o *Options) error {
o.BootCMDLineFile = s
return nil
}
}
func StrictValidation(v bool) Option {
return func(o *Options) error {
o.StrictValidation = v
return nil
}
}
func Directories(d ...string) Option {
return func(o *Options) error {
o.ScanDir = d
return nil
}
}

View File

@ -1,45 +0,0 @@
package collector_test
import (
"context"
"net"
"net/http"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Collector Suite")
}
type ServerCloseFunc func()
func startAssetServer(path string) (ServerCloseFunc, int, error) {
listener, err := net.Listen("tcp", ":0")
if err != nil {
return nil, 0, err
}
port := listener.Addr().(*net.TCPAddr).Port
ctx, cancelFunc := context.WithCancel(context.Background())
go func() {
defer GinkgoRecover()
err := http.Serve(listener, http.FileServer(http.Dir(path)))
select {
case <-ctx.Done(): // We closed it with the CancelFunc, ignore the error
return
default: // We didnt' close it, return the error
Expect(err).ToNot(HaveOccurred())
}
}()
stopFunc := func() {
cancelFunc()
listener.Close()
}
return stopFunc, port, nil
}

View File

@ -1,207 +0,0 @@
package config
import (
"fmt"
"os"
"path/filepath"
"strings"
"unicode"
"github.com/kairos-io/kairos-sdk/bundles"
"github.com/kairos-io/kairos/v2/pkg/config/collector"
schema "github.com/kairos-io/kairos/v2/pkg/config/schemas"
yip "github.com/mudler/yip/pkg/schema"
"gopkg.in/yaml.v3"
)
const (
DefaultWebUIListenAddress = ":8080"
FilePrefix = "file://"
)
type Install struct {
Auto bool `yaml:"auto,omitempty"`
Reboot bool `yaml:"reboot,omitempty"`
Device string `yaml:"device,omitempty"`
Poweroff bool `yaml:"poweroff,omitempty"`
GrubOptions map[string]string `yaml:"grub_options,omitempty"`
Bundles Bundles `yaml:"bundles,omitempty"`
Encrypt []string `yaml:"encrypted_partitions,omitempty"`
SkipEncryptCopyPlugins bool `yaml:"skip_copy_kcrypt_plugin,omitempty"`
Env []string `yaml:"env,omitempty"`
Image string `yaml:"image,omitempty"`
EphemeralMounts []string `yaml:"ephemeral_mounts,omitempty"`
BindMounts []string `yaml:"bind_mounts,omitempty"`
}
type Config struct {
Install *Install `yaml:"install,omitempty"`
collector.Config `yaml:"-"`
// TODO: Remove this too?
ConfigURL string `yaml:"config_url,omitempty"`
Options map[string]string `yaml:"options,omitempty"`
FailOnBundleErrors bool `yaml:"fail_on_bundles_errors,omitempty"`
Bundles Bundles `yaml:"bundles,omitempty"`
GrubOptions map[string]string `yaml:"grub_options,omitempty"`
Env []string `yaml:"env,omitempty"`
}
type Bundles []Bundle
type Bundle struct {
Repository string `yaml:"repository,omitempty"`
Rootfs string `yaml:"rootfs_path,omitempty"`
DB string `yaml:"db_path,omitempty"`
LocalFile bool `yaml:"local_file,omitempty"`
Targets []string `yaml:"targets,omitempty"`
}
const DefaultHeader = "#cloud-config"
func HasHeader(userdata, head string) (bool, string) {
header := strings.SplitN(userdata, "\n", 2)[0]
// Trim trailing whitespaces
header = strings.TrimRightFunc(header, unicode.IsSpace)
if head != "" {
return head == header, header
}
return (header == DefaultHeader) || (header == "#kairos-config") || (header == "#node-config"), header
}
func (b Bundles) Options() (res [][]bundles.BundleOption) {
for _, bundle := range b {
for _, t := range bundle.Targets {
opts := []bundles.BundleOption{bundles.WithRepository(bundle.Repository), bundles.WithTarget(t)}
if bundle.Rootfs != "" {
opts = append(opts, bundles.WithRootFS(bundle.Rootfs))
}
if bundle.DB != "" {
opts = append(opts, bundles.WithDBPath(bundle.DB))
}
if bundle.LocalFile {
opts = append(opts, bundles.WithLocalFile(true))
}
res = append(res, opts)
}
}
return
}
// HasConfigURL returns true if ConfigURL has been set and false if it's empty.
func (c Config) HasConfigURL() bool {
return c.ConfigURL != ""
}
// FilterKeys is used to pass to any other pkg which might want to see which part of the config matches the Kairos config.
func FilterKeys(d []byte) ([]byte, error) {
cmdLineFilter := Config{}
err := yaml.Unmarshal(d, &cmdLineFilter)
if err != nil {
return []byte{}, err
}
out, err := yaml.Marshal(cmdLineFilter)
if err != nil {
return []byte{}, err
}
return out, nil
}
func Scan(opts ...collector.Option) (c *Config, err error) {
result := &Config{}
o := &collector.Options{}
if err := o.Apply(opts...); err != nil {
return result, err
}
genericConfig, err := collector.Scan(o, FilterKeys)
if err != nil {
return result, err
}
result.Config = *genericConfig
configStr, err := genericConfig.String()
if err != nil {
return result, err
}
err = yaml.Unmarshal([]byte(configStr), result)
if err != nil {
return result, err
}
kc, err := schema.NewConfigFromYAML(configStr, schema.RootSchema{})
if err != nil {
if !o.NoLogs && !o.StrictValidation {
fmt.Printf("WARNING: %s\n", err.Error())
}
if o.StrictValidation {
return result, fmt.Errorf("ERROR: %s", err.Error())
}
}
if !kc.IsValid() {
if !o.NoLogs && !o.StrictValidation {
fmt.Printf("WARNING: %s\n", kc.ValidationError.Error())
}
if o.StrictValidation {
return result, fmt.Errorf("ERROR: %s", kc.ValidationError.Error())
}
}
return result, nil
}
type Stage string
const (
NetworkStage Stage = "network"
)
func (n Stage) String() string {
return string(n)
}
func SaveCloudConfig(name Stage, yc yip.YipConfig) error {
dnsYAML, err := yaml.Marshal(yc)
if err != nil {
return err
}
return os.WriteFile(filepath.Join("usr", "local", "cloud-config", fmt.Sprintf("100_%s.yaml", name)), dnsYAML, 0700)
}
func FromString(s string, o interface{}) error {
return yaml.Unmarshal([]byte(s), o)
}
func MergeYAML(objs ...interface{}) ([]byte, error) {
content := [][]byte{}
for _, o := range objs {
dat, err := yaml.Marshal(o)
if err != nil {
return []byte{}, err
}
content = append(content, dat)
}
finalData := make(map[string]interface{})
for _, c := range content {
if err := yaml.Unmarshal(c, &finalData); err != nil {
return []byte{}, err
}
}
return yaml.Marshal(finalData)
}
func AddHeader(header, data string) string {
return fmt.Sprintf("%s\n%s", header, data)
}

View File

@ -1,13 +0,0 @@
package config_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}

View File

@ -1,44 +0,0 @@
// Copyright © 2022 Ettore Di Giacinto <mudler@c3os.io>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package config_test
import (
"os"
// . "github.com/kairos-io/kairos/v2/pkg/config"
. "github.com/onsi/ginkgo/v2"
// . "github.com/onsi/gomega"
)
type TConfig struct {
Kairos struct {
OtherKey string `yaml:"other_key"`
NetworkToken string `yaml:"network_token"`
} `yaml:"kairos"`
}
var _ = Describe("Config", func() {
var d string
BeforeEach(func() {
d, _ = os.MkdirTemp("", "xxxx")
})
AfterEach(func() {
if d != "" {
os.RemoveAll(d)
}
})
})

View File

@ -1,79 +0,0 @@
package config
import (
jsonschemago "github.com/swaggest/jsonschema-go"
)
// InstallSchema represents the install block in the Kairos configuration. It is used to drive automatic installations without user interaction.
type InstallSchema struct {
_ struct{} `title:"Kairos Schema: Install block" description:"The install block is to drive automatic installations without user interaction."`
Auto bool `json:"auto,omitempty" description:"Set to true when installing without Pairing"`
BindMounts []string `json:"bind_mounts,omitempty"`
Bundles []BundleSchema `json:"bundles,omitempty" description:"Add bundles in runtime"`
Device string `json:"device,omitempty" pattern:"^(auto|/|(/[a-zA-Z0-9_-]+)+)$" description:"Device for automated installs" examples:"[\"auto\",\"/dev/sda\"]"`
EphemeralMounts []string `json:"ephemeral_mounts,omitempty"`
EncryptedPartitions []string `json:"encrypted_partitions,omitempty"`
Env []interface{} `json:"env,omitempty"`
GrubOptionsSchema `json:"grub_options,omitempty"`
Image string `json:"image,omitempty" description:"Use a different container image for the installation"`
PowerManagement
SkipEncryptCopyPlugins bool `json:"skip_copy_kcrypt_plugin,omitempty"`
}
// BundleSchema represents the bundle block which can be used in different places of the Kairos configuration. It is used to reference a bundle and its confguration.
type BundleSchema struct {
DB string `json:"db_path,omitempty"`
LocalFile bool `json:"local_file,omitempty"`
Repository string `json:"repository,omitempty"`
Rootfs string `json:"rootfs_path,omitempty"`
Targets []string `json:"targets,omitempty"`
}
// GrubOptionsSchema represents the grub options block which can be used in different places of the Kairos configuration. It is used to configure grub.
type GrubOptionsSchema struct {
DefaultFallback string `json:"default_fallback,omitempty" description:"Sets default fallback logic"`
DefaultMenuEntry string `json:"default_menu_entry,omitempty" description:"Change GRUB menu entry"`
ExtraActiveCmdline string `json:"extra_active_cmdline,omitempty" description:"Additional Kernel option cmdline to apply just for active"`
ExtraCmdline string `json:"extra_cmdline,omitempty" description:"Additional Kernel option cmdline to apply"`
ExtraPassiveCmdline string `json:"extra_passive_cmdline,omitempty" description:"Additional Kernel option cmdline to apply just for passive"`
ExtraRecoveryCmdline string `json:"extra_recovery_cmdline,omitempty" description:"Set additional boot commands when booting into recovery"`
NextEntry string `json:"next_entry,omitempty" description:"Set the next reboot entry."`
SavedEntry string `json:"saved_entry,omitempty" description:"Set the default boot entry."`
}
// PowerManagement is a meta structure to hold the different rules for managing power, which are not compatible between each other.
type PowerManagement struct {
}
// NoPowerManagement is a meta structure used when the user does not define any power management options or when the user does not want to reboot or poweroff the machine.
type NoPowerManagement struct {
Reboot bool `json:"reboot,omitempty" const:"false" default:"false" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"false" default:"false" description:"Power off after installation"`
}
// RebootOnly is a meta structure used to enforce that when the reboot option is set, the poweroff option is not set.
type RebootOnly struct {
Reboot bool `json:"reboot,omitempty" const:"true" default:"false" required:"true" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"false" default:"false" description:"Power off after installation"`
}
// PowerOffOnly is a meta structure used to enforce that when the poweroff option is set, the reboot option is not set.
type PowerOffOnly struct {
Reboot bool `json:"reboot,omitempty" const:"false" default:"false" description:"Reboot after installation"`
Poweroff bool `json:"poweroff,omitempty" const:"true" default:"false" required:"true" description:"Power off after installation"`
}
var _ jsonschemago.OneOfExposer = PowerManagement{}
// The OneOfModel interface is only needed for the tests that check the new schemas contain all needed fields
// it can be removed once the new schema is the single source of truth.
type OneOfModel interface {
JSONSchemaOneOf() []interface{}
}
// JSONSchemaOneOf defines that different which are the different valid power management rules and states that one and only one of them needs to be validated for the entire schema to be valid.
func (PowerManagement) JSONSchemaOneOf() []interface{} {
return []interface{}{
NoPowerManagement{}, RebootOnly{}, PowerOffOnly{},
}
}

View File

@ -1,134 +0,0 @@
package config_test
import (
"strings"
. "github.com/kairos-io/kairos/v2/pkg/config/schemas"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Install Schema", func() {
var config *KConfig
var err error
var yaml string
JustBeforeEach(func() {
config, err = NewConfigFromYAML(yaml, InstallSchema{})
Expect(err).ToNot(HaveOccurred())
})
Context("when device is auto", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: auto`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("when device is a path", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("when device is other than a path or auto", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: foobar`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(
strings.Contains(config.ValidationError.Error(),
"does not match pattern '^(auto|/|(/[a-zA-Z0-9_-]+)+)$'",
),
).To(BeTrue())
})
})
Context("when reboot and poweroff are true", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: true
poweroff: true`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("value must be false"))
})
})
Context("when reboot is true and poweroff is false", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: true
poweroff: false`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("when reboot is false and poweroff is true", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda
reboot: false
poweroff: true`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("with no power management set", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: /dev/sda`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("with all possible options", func() {
BeforeEach(func() {
yaml = `#cloud-config
device: "/dev/sda"
reboot: true
auto: true
image: "docker:.."
bundles:
- rootfs_path: /usr/local/lib/extensions/<name>
targets:
- container://<image>
grub_options:
extra_cmdline: "config_url=http://"
extra_active_cmdline: "config_url=http://"
extra_passive_cmdline: "config_url=http://"
default_menu_entry: "foobar"
env:
- foo=barevice: /dev/sda`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
})

View File

@ -1,68 +0,0 @@
package config
import (
jsonschemago "github.com/swaggest/jsonschema-go"
)
// P2PSchema represents the P2P block in the Kairos configuration. It is used to enables and configure the p2p full-mesh functionalities.
type P2PSchema struct {
_ struct{} `title:"Kairos Schema: P2P block" description:"The p2p block enables the p2p full-mesh functionalities."`
Role string `json:"role,omitempty" default:"none" enum:"[\"master\",\"worker\",\"none\"]"`
NetworkID string `json:"network_id,omitempty" description:"User defined network-id. Can be used to have multiple clusters in the same network"`
DNS bool `json:"dns,omitempty" description:"Enable embedded DNS See also: https://mudler.github.io/edgevpn/docs/concepts/overview/dns/"`
DisableDHT bool `json:"disable_dht,omitempty" default:"true" description:"Disabling DHT makes co-ordination to discover nodes only in the local network"`
P2PNetworkExtended
VPN `json:"vpn,omitempty"`
}
// KubeVIPSchema represents the kubevip block in the Kairos configuration. It sets the Elastic IP used in KubeVIP. Only valid with p2p.
type KubeVIPSchema struct {
_ struct{} `title:"Kairos Schema: KubeVIP block" description:"Sets the Elastic IP used in KubeVIP. Only valid with p2p"`
EIP string `json:"eip,omitempty" example:"192.168.1.110"`
ManifestURL string `json:"manifest_url,omitempty" description:"Specify a manifest URL for KubeVIP." default:""`
Enable bool `json:"enable,omitempty" description:"Enables KubeVIP"`
Interface bool `json:"interface,omitempty" description:"Specifies a KubeVIP Interface" example:"ens18"`
}
// P2PNetworkExtended is a meta structure to hold the different rules for managing the P2P network, which are not compatible between each other.
type P2PNetworkExtended struct {
}
// P2PAutoDisabled is used to validate that when p2p.auto is disabled, then neither p2p.auto.ha not p2p.network_token can be set.
type P2PAutoDisabled struct {
NetworkToken string `json:"network_token,omitempty" const:"" required:"true"`
Auto struct {
Enable bool `json:"enable" const:"false" required:"true"`
Ha struct {
Enable bool `json:"enable" const:"false"`
} `json:"ha"`
} `json:"auto"`
}
// P2PAutoEnabled is used to validate that when p2p.auto is set, p2p.network_token has to be set.
type P2PAutoEnabled struct {
NetworkToken string `json:"network_token" required:"true" minLength:"1" description:"network_token is the shared secret used by the nodes to co-ordinate with p2p"`
Auto struct {
Enable bool `json:"enable,omitempty" const:"true"`
Ha struct {
Enable bool `json:"enable" const:"true"`
MasterNodes int `json:"master_nodes,omitempty" minimum:"1" description:"Number of HA additional master nodes. A master node is always required for creating the cluster and is implied."`
} `json:"ha"`
} `json:"auto,omitempty"`
}
var _ jsonschemago.OneOfExposer = P2PNetworkExtended{}
// JSONSchemaOneOf defines that different which are the different valid p2p network rules and states that one and only one of them needs to be validated for the entire schema to be valid.
func (P2PNetworkExtended) JSONSchemaOneOf() []interface{} {
return []interface{}{
P2PAutoEnabled{}, P2PAutoDisabled{},
}
}
// VPN represents the vpn block in the Kairos configuration.
type VPN struct {
Create bool `json:"vpn,omitempty" default:"true"`
Use bool `json:"use,omitempty" default:"true"`
Envs []interface{} `json:"env,omitempty"`
}

View File

@ -1,181 +0,0 @@
package config_test
import (
"strings"
. "github.com/kairos-io/kairos/v2/pkg/config/schemas"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("P2P Schema", func() {
var config *KConfig
var err error
var yaml string
JustBeforeEach(func() {
config, err = NewConfigFromYAML(yaml, P2PSchema{})
Expect(err).ToNot(HaveOccurred())
})
Context("with role master", func() {
BeforeEach(func() {
yaml = `#cloud-config
role: master
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("with role worker", func() {
BeforeEach(func() {
yaml = `#cloud-config
role: worker
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("with role none", func() {
BeforeEach(func() {
yaml = `#cloud-config
role: none
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("with other role", func() {
BeforeEach(func() {
yaml = `#cloud-config
role: foobar
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp(`value must be one of "master", "worker", "none"`))
})
})
Context("With a network_token and p2p.auto.enable = false", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="
auto:
enable: false`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(
strings.Contains(config.ValidationError.Error(), `value must be true`),
).To(BeTrue())
})
})
Context("With an empty network_token and p2p.auto.enable = true", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: ""
auto:
enable: true`
})
It("Fails", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(
strings.Contains(config.ValidationError.Error(),
"length must be >= 1, but got 0",
),
).To(BeTrue())
})
})
Context("With a network_token and p2p.auto.enable = true", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="
auto:
enable: true`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("With a p2p.auto.enable = false and ha.enable = true", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: ""
auto:
enable: false
ha:
enable: true`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("(length must be >= 1, but got 0|value must be true)"))
})
})
Context("HA with 0 master nodes", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="
auto:
enable: true
ha:
enable: true
master_nodes: 0`
})
It("fails", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("must be >= 1 but found 0"))
})
})
Context("HA", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="
auto:
enable: true
ha:
enable: true
master_nodes: 2`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("kubevip", func() {
BeforeEach(func() {
yaml = `#cloud-config
network_token: "b3RwOgogIGRoYWdlX3NpemU6IDIwOTcxNTIwCg=="
auto:
enable: true
ha:
enable: true
master_nodes: 2`
})
It("succeedes", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
})

View File

@ -1,106 +0,0 @@
package config
import (
"encoding/json"
"strings"
"github.com/santhosh-tekuri/jsonschema/v5"
jsonschemago "github.com/swaggest/jsonschema-go"
"gopkg.in/yaml.v3"
)
// RootSchema groups all the different schemas of the Kairos configuration together.
type RootSchema struct {
_ struct{} `title:"Kairos Schema" description:"Defines all valid Kairos configuration attributes."`
Bundles []BundleSchema `json:"bundles,omitempty" description:"Add bundles in runtime"`
ConfigURL string `json:"config_url,omitempty" description:"URL download configuration from."`
Env []string `json:"env,omitempty"`
FailOnBundleErrors bool `json:"fail_on_bundles_errors,omitempty"`
GrubOptionsSchema `json:"grub_options,omitempty"`
Install InstallSchema `json:"install,omitempty"`
Options []interface{} `json:"options,omitempty" description:"Various options."`
Users []UserSchema `json:"users,omitempty" minItems:"1" required:"true"`
P2P P2PSchema `json:"p2p,omitempty"`
}
// KConfig is used to parse and validate Kairos configuration files.
type KConfig struct {
Source string
parsed interface{}
ValidationError error
schemaType interface{}
}
// GenerateSchema takes the given schema type and builds a JSON Schema out of it
// if a URL is passed it will also add it as the $schema key, which is useful when
// defining a version of a Root Schema which will be available online.
func GenerateSchema(schemaType interface{}, url string) (string, error) {
reflector := jsonschemago.Reflector{}
generatedSchema, err := reflector.Reflect(schemaType)
if err != nil {
return "", err
}
if url != "" {
generatedSchema.WithSchema(url)
}
generatedSchemaJSON, err := json.MarshalIndent(generatedSchema, "", " ")
if err != nil {
return "", err
}
return string(generatedSchemaJSON), nil
}
func (kc *KConfig) validate() {
generatedSchemaJSON, err := GenerateSchema(kc.schemaType, "")
if err != nil {
kc.ValidationError = err
return
}
sch, err := jsonschema.CompileString("schema.json", string(generatedSchemaJSON))
if err != nil {
kc.ValidationError = err
return
}
if err = sch.Validate(kc.parsed); err != nil {
kc.ValidationError = err
}
}
// IsValid returns true if the schema rules of the configuration are valid.
func (kc *KConfig) IsValid() bool {
kc.validate()
return kc.ValidationError == nil
}
// HasHeader returns true if the config has one of the valid headers.
func (kc *KConfig) HasHeader() bool {
var found bool
availableHeaders := []string{"#cloud-config", "#kairos-config", "#node-config"}
for _, header := range availableHeaders {
if strings.HasPrefix(kc.Source, header) {
found = true
}
}
return found
}
// NewConfigFromYAML is a constructor for KConfig instances. The source of the configuration is passed in YAML and if there are any issues unmarshaling it will return an error.
func NewConfigFromYAML(s string, st interface{}) (*KConfig, error) {
kc := &KConfig{
Source: s,
schemaType: st,
}
err := yaml.Unmarshal([]byte(s), &kc.parsed)
if err != nil {
return kc, err
}
return kc, nil
}

View File

@ -1,190 +0,0 @@
package config_test
import (
"fmt"
"reflect"
"strings"
. "github.com/kairos-io/kairos/v2/pkg/config"
. "github.com/kairos-io/kairos/v2/pkg/config/schemas"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func getTagName(s string) string {
if len(s) < 1 {
return ""
}
if s == "-" {
return ""
}
f := func(c rune) bool {
return c == '"' || c == ','
}
return s[:strings.IndexFunc(s, f)]
}
func structContainsField(f, t string, str interface{}) bool {
values := reflect.ValueOf(str)
types := values.Type()
for j := 0; j < values.NumField(); j++ {
tagName := getTagName(types.Field(j).Tag.Get("json"))
if types.Field(j).Name == f || tagName == t {
return true
} else {
if types.Field(j).Type.Kind() == reflect.Struct {
if types.Field(j).Type.Name() != "" {
model := reflect.New(types.Field(j).Type)
if instance, ok := model.Interface().(OneOfModel); ok {
for _, childSchema := range instance.JSONSchemaOneOf() {
if structContainsField(f, t, childSchema) {
return true
}
}
}
}
}
}
}
return false
}
func structFieldsContainedInOtherStruct(left, right interface{}) {
leftValues := reflect.ValueOf(left)
leftTypes := leftValues.Type()
for i := 0; i < leftValues.NumField(); i++ {
leftTagName := getTagName(leftTypes.Field(i).Tag.Get("yaml"))
leftFieldName := leftTypes.Field(i).Name
if leftTypes.Field(i).IsExported() {
It(fmt.Sprintf("Checks that the new schema contians the field %s", leftFieldName), func() {
Expect(
structContainsField(leftFieldName, leftTagName, right),
).To(BeTrue())
})
}
}
}
var _ = Describe("Schema", func() {
Context("NewConfigFromYAML", func() {
var config *KConfig
var err error
var yaml string
JustBeforeEach(func() {
config, err = NewConfigFromYAML(yaml, RootSchema{})
})
Context("While the new Schema is not the single source of truth", func() {
structFieldsContainedInOtherStruct(Config{}, RootSchema{})
})
Context("While the new InstallSchema is not the single source of truth", func() {
structFieldsContainedInOtherStruct(Install{}, InstallSchema{})
})
Context("While the new BundleSchema is not the single source of truth", func() {
structFieldsContainedInOtherStruct(Bundle{}, BundleSchema{})
})
Context("With invalid YAML syntax", func() {
BeforeEach(func() {
yaml = `#cloud-config
this is:
- invalid
yaml`
})
It("errors", func() {
Expect(err.Error()).To(MatchRegexp("yaml: line 4: could not find expected ':'"))
})
})
Context("When `users` is empty", func() {
BeforeEach(func() {
yaml = `#cloud-config
users: []`
})
It("errors", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("minimum 1 items required, but found 0 items"))
})
})
Context("without a valid header", func() {
BeforeEach(func() {
yaml = `---
users:
- name: kairos
passwd: kairos`
})
It("is successful but HasHeader returns false", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config.HasHeader()).To(BeFalse())
})
})
Context("With a valid config", func() {
BeforeEach(func() {
yaml = `#cloud-config
users:
- name: kairos
passwd: kairos`
})
It("is successful", func() {
Expect(err).ToNot(HaveOccurred())
Expect(config.HasHeader()).To(BeTrue())
})
})
})
Context("GenerateSchema", func() {
var url string
var schema string
var err error
type TestSchema struct {
Key interface{} `json:"key,omitemtpy" required:"true"`
}
JustBeforeEach(func() {
schema, err = GenerateSchema(TestSchema{}, url)
Expect(err).ToNot(HaveOccurred())
})
It("does not include the $schema key by default", func() {
Expect(strings.Contains(schema, `$schema`)).To(BeFalse())
})
It("can use any type of schma", func() {
wants := `{
"required": [
"key"
],
"properties": {
"key": {}
},
"type": "object"
}`
Expect(schema).To(Equal(wants))
})
Context("with a URL", func() {
BeforeEach(func() {
url = "http://foobar"
})
It("appends the $schema key", func() {
Expect(strings.Contains(schema, `$schema": "http://foobar"`)).To(BeTrue())
})
})
})
})

View File

@ -1,13 +0,0 @@
package config_test
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Schemas Suite")
}

View File

@ -1,11 +0,0 @@
package config
// UserSchema represents the users block in the Kairos configuration. It allows the creation of users in the system.
type UserSchema struct {
_ struct{} `title:"Kairos Schema: Users block" description:"The users block allows you to create users in the system."`
Name string `json:"name,omitempty" pattern:"([a-z_][a-z0-9_]{0,30})" required:"true" example:"kairos"`
Passwd string `json:"passwd,omitempty" example:"kairos"`
LockPasswd bool `json:"lockPasswd,omitempty" example:"true"`
Groups []string `json:"groups,omitempty" example:"admin"`
SSHAuthorizedKeys []string `json:"ssh_authorized_keys,omitempty" examples:"[\"github:USERNAME\",\"ssh-ed25519 AAAF00BA5\"]"`
}

View File

@ -1,77 +0,0 @@
package config_test
import (
"strings"
. "github.com/kairos-io/kairos/v2/pkg/config/schemas"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Users Schema", func() {
var config *KConfig
var err error
var yaml string
JustBeforeEach(func() {
config, err = NewConfigFromYAML(yaml, UserSchema{})
Expect(err).ToNot(HaveOccurred())
})
Context("When a user has no name", func() {
BeforeEach(func() {
yaml = `#cloud-config
passwd: foobar`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(config.ValidationError.Error()).To(MatchRegexp("missing properties: 'name'"))
})
})
Context("When a user name doesn't fit the pattern", func() {
BeforeEach(func() {
yaml = `#cloud-config
name: "007"
passwd: "bond"`
})
It("errors", func() {
Expect(config.IsValid()).NotTo(BeTrue())
Expect(
strings.Contains(config.ValidationError.Error(),
"does not match pattern '([a-z_][a-z0-9_]{0,30})'",
),
).To(BeTrue())
})
})
Context("With only the required attributes", func() {
BeforeEach(func() {
yaml = `#cloud-config
name: "kairos"`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
Context("With all possible attributes", func() {
BeforeEach(func() {
yaml = `#cloud-config
name: "kairos"
passwd: "kairos"
lock_passwd: true
groups:
- "admin"
ssh_authorized_keys:
- github:mudler`
})
It("succeeds", func() {
Expect(config.IsValid()).To(BeTrue())
})
})
})

View File

@ -1,63 +0,0 @@
package github
import (
"context"
"fmt"
"log"
"net/http"
"sort"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/google/go-github/v40/github"
"golang.org/x/oauth2"
)
func newHTTPClient(ctx context.Context, token string) *http.Client {
if token == "" {
return http.DefaultClient
}
src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
return oauth2.NewClient(ctx, src)
}
// FindReleases finds the releases from the given repo (slug) and returns a parsed semver.Collection
// where the first item is the highest version as its sorted.
func FindReleases(ctx context.Context, token, slug string, preReleases bool) (semver.Collection, error) {
hc := newHTTPClient(ctx, token)
cli := github.NewClient(hc)
repo := strings.Split(slug, "/")
if len(repo) != 2 || repo[0] == "" || repo[1] == "" {
return nil, fmt.Errorf("Invalid slug format. It should be 'owner/name': %s", slug)
}
// Get at least 30 releases
opts := github.ListOptions{PerPage: 30}
rels, res, err := cli.Repositories.ListReleases(ctx, repo[0], repo[1], &opts)
if err != nil {
log.Println("API returned an error response:", err)
if res != nil && res.StatusCode == 404 {
// 404 means repository not found or release not found. It's not an error here.
err = nil
log.Println("API returned 404. Repository or release not found")
}
return nil, err
}
var versions semver.Collection
for _, rel := range rels {
if strings.HasPrefix(*rel.Name, "v") {
v := semver.MustParse(*rel.Name)
if v.Prerelease() == "" {
versions = append(versions, v)
}
if v.Prerelease() != "" && preReleases {
versions = append(versions, v)
}
}
}
// Return them reversed sorted so the higher is the first one in the collection!
sort.Sort(sort.Reverse(versions))
return versions, nil
}

View File

@ -1,32 +0,0 @@
package github_test
import (
"context"
"testing"
"github.com/kairos-io/kairos/v2/pkg/github"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestReleases(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Releases Suite")
}
var _ = Describe("Releases", func() {
It("can find the proper releases in order", func() {
releases, err := github.FindReleases(context.Background(), "", "kairos-io/kairos", false)
Expect(err).ToNot(HaveOccurred())
Expect(len(releases)).To(BeNumerically(">", 0))
// Expect the first one to be greater than the last one
Expect(releases[0].GreaterThan(releases[len(releases)-1]))
})
It("can find the proper releases in order with prereleases", func() {
releases, err := github.FindReleases(context.Background(), "", "kairos-io/kairos", true)
Expect(err).ToNot(HaveOccurred())
Expect(len(releases)).To(BeNumerically(">", 0))
// Expect the first one to be greater than the last one
Expect(releases[0].GreaterThan(releases[len(releases)-1]))
})
})

5
profile-build/README.md Normal file
View File

@ -0,0 +1,5 @@
# profile-builder
--------------------
This is used in conjunction with the framework-profile files to install the required luet packages depending on the flavor

17
profile-build/go.mod Normal file
View File

@ -0,0 +1,17 @@
module main
go 1.20
require (
github.com/kairos-io/kairos-sdk v0.0.1
github.com/urfave/cli v1.22.12
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/denisbrodbeck/machineid v1.0.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

33
profile-build/go.sum Normal file
View File

@ -0,0 +1,33 @@
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kairos-io/kairos-sdk v0.0.1 h1:obJw0/5amn+/wWNuDTVq81HcPuX5TwHbGS4xxEy9Ju4=
github.com/kairos-io/kairos-sdk v0.0.1/go.mod h1:E70cYgGQpu1MXI8ddhH4CHVIvNi3w7l6MQlxLTeBTXY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8=
github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=

34
profile-build/main.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"fmt"
"os"
"github.com/kairos-io/kairos-sdk/profile"
"github.com/urfave/cli"
)
var VERSION = "0.0.0"
func main() {
app := &cli.App{
Name: "profile-build",
Version: VERSION,
Authors: []cli.Author{{Name: "Kairos authors"}},
Usage: "Build kairos framework images",
Description: `Uses profile files to build kairos images`,
UsageText: ``,
Copyright: "kairos authors",
ArgsUsage: "flavor profileName profileFile outputDirectory",
Action: func(c *cli.Context) error {
return profile.BuildFlavor(c.Args().Get(0), c.Args().Get(1), c.Args().Get(2))
},
}
err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

41
tests/go.mod Normal file
View File

@ -0,0 +1,41 @@
module github.com/kairos-io/kairos/v2
go 1.20
require (
github.com/google/uuid v1.3.0
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d
github.com/onsi/ginkgo/v2 v2.9.2
github.com/onsi/gomega v1.27.6
github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46
)
require (
github.com/bramvdbogaerde/go-scp v1.2.1 // indirect
github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
github.com/codingsince1985/checksum v1.2.6 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
github.com/pkg/errors v0.9.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/term v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

216
tests/go.sum Normal file
View File

@ -0,0 +1,216 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bramvdbogaerde/go-scp v1.2.1 h1:BKTqrqXiQYovrDlfuVFaEGz0r4Ou6EED8L7jCXw6Buw=
github.com/bramvdbogaerde/go-scp v1.2.1/go.mod h1:s4ZldBoRAOgUg8IrRP2Urmq5qqd2yPXQTPshACY8vQ0=
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/codingsince1985/checksum v1.2.6 h1:UjCDls6oaRQeLPG14TvjLvOos2XL1qHdMl8uGMkzpi8=
github.com/codingsince1985/checksum v1.2.6/go.mod h1:Pe5wfeiqzQC1qEXLWEFmxQ3W/OklJEJGiJO62graCJU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8=
github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo=
github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
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/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
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/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d h1:/lAg9vPAAU+s35cDMCx1IyeMn+4OYfCBPqi08Q8vXDg=
github.com/mudler/go-processmanager v0.0.0-20220724164624-c45b5c61312d/go.mod h1:HGGAOJhipApckwNV8ZTliRJqxctUv3xRY+zbQEwuytc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
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.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46 h1:q2T2RnISqPdZWvUpBQw0n7QWtF4cNo5RpCDTZmV732M=
github.com/spectrocloud/peg v0.0.0-20230407121159-2e15270c4a46/go.mod h1:L2fIdtZqbQEagjOOXwkwH3t7MjJUd7fbt52cLSQGDBg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
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/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
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.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/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-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
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=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=