Compare commits

...

14 Commits

Author SHA1 Message Date
Dimitris Karakasilis
d4ce271c3a Migrate legacy (already removed) scripts to auroraboot commands (#201)
* Migrate legacy (already removed) scripts to auroraboot commands

Since we switched to using auroraboot as the tool-image, these scripts
no longer exist. The functionality is provided by auroraboot itself.

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

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

* Add e2e tests for various cloud formats

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

* Fix "PVC already exists" error in tests

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

* Fix auroraboot commands

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

* Make build-iso an init container

so that the netboot container finds the ISO in place when it needs it

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

* Fix tests

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

* Increase timeout for namespace deletion

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

* Refactor commands

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

* Refactor auroraboot command strings to use strings.Builder (#203)

* Initial plan

* Refactor command string to use strings.Builder for better readability

Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>

* Complete refactoring of command string

Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>

* Refactor azureCmd and gceCmd to use strings.Builder

Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>

* Revert unintended changes to generated files

Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Apply suggestion from @jimmidyson

Co-authored-by: Jimmi Dyson <jimmi.dyson@nutanix.com>
Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>

* Refactor auroraboot invocation for consistenty with the rest

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

* Use string builder everywhere (consistency)

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

* Make sure we don't match multiple files (PR suggestion)

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

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jimmykarily <2794419+jimmykarily@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jimmi Dyson <jimmi.dyson@nutanix.com>
2025-12-16 20:57:42 +02:00
Dimitris Karakasilis
19b3a57878 The flag --name has been renamed to --override-name in newer auroraboot (#200)
* The flag --name has been renamed to --override-name in newer auroraboot

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

* Set the default tool-image to a compatible one

We'd better set this to the compatible auroraboot version each time
because changing the flags of auroraboot in the future means it may not always
work with `latest`.

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

* Use newer image to fix tests

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

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2025-11-20 13:36:57 +02:00
dependabot[bot]
f3ee5c3dc1 Bump golang.org/x/crypto in the go_modules group across 1 directory (#193)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-15 08:36:00 +02:00
Dimitris Karakasilis
4aa7211f96 Remove tools image references and opt for auroraboot (#192)
* Remove references to osbuilder-tools image

because it has been merged with auroraboot here:

https://github.com/kairos-io/AuroraBoot/pull/110

as part of https://github.com/kairos-io/kairos/issues/1633

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

* WIP

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

* Fix test command

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

* go mod tidy

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

* Bump linting action

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

* Don't look for artifacts in build/ subdir

because it has been fixed here:

https://github.com/kairos-io/AuroraBoot/pull/115

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

---------

Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me>
2024-11-15 16:16:13 +02:00
Itxaka
3aa1fa1104 Merge pull request #188 from kairos-io/renovate/major-repositories 2024-10-23 11:52:08 +02:00
renovate[bot]
8813eb4809 Update repositories 2024-10-23 09:51:38 +00:00
Itxaka
95509370f6 Merge pull request #187 from kairos-io/Itxaka-patch-1 2024-10-19 12:53:52 +02:00
Itxaka
42fde4a1fb bump enki
Signed-off-by: Itxaka <itxaka@kairos.io>
2024-10-19 12:50:24 +02:00
Itxaka
169ba969f3 Merge pull request #186 from kairos-io/renovate/major-repositories 2024-10-18 15:34:22 +02:00
renovate[bot]
6b7aac0e4b Update repositories 2024-10-16 18:09:03 +00:00
Itxaka
405eda716a Merge pull request #185 from kairos-io/Itxaka-patch-1 2024-10-14 12:25:21 +02:00
Itxaka
c77f6c564a Bump enki version
Signed-off-by: Itxaka <itxaka@kairos.io>
2024-10-14 11:57:37 +02:00
Itxaka
ac3bc8bcc6 Merge pull request #175 from kairos-io/renovate/major-repositories 2024-10-14 11:56:33 +02:00
renovate[bot]
cf125b7371 Update repositories 2024-10-11 09:42:31 +00:00
41 changed files with 3967 additions and 5256 deletions

View File

@@ -15,7 +15,7 @@ env:
FORCE_COLOR: 1 FORCE_COLOR: 1
jobs: jobs:
call-workflow: call-workflow:
uses: kairos-io/linting-composite-action/.github/workflows/reusable-linting.yaml@v0.0.9 uses: kairos-io/linting-composite-action/.github/workflows/reusable-linting.yaml@v0.0.10
with: with:
yamldirs: ".github/workflows/ config/ tools-image/" yamldirs: ".github/workflows/ config/"
is-go: true is-go: true

View File

@@ -1,77 +0,0 @@
---
name: 'build tools container images'
on:
pull_request:
push:
branches:
- master
tags:
- '*'
concurrency:
group: tool-image-${{ github.ref || github.head_ref }}
cancel-in-progress: true
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=quay.io/kairos/osbuilder-tools
VERSION=latest
SHORTREF=${GITHUB_SHA::8}
# If this is git tag, use the tag name as a docker tag
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
fi
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:${SHORTREF}"
# If the VERSION looks like a version number, assume that
# this is the most recent version of the image and also
# tag it 'latest'.
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
TAGS="$TAGS,${DOCKER_IMAGE}:latest"
fi
# Set output parameters.
echo ::set-output name=tags::${TAGS}
echo ::set-output name=docker_image::${DOCKER_IMAGE}
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_PASSWORD }}
- name: Build
if: github.event_name == 'pull_request'
uses: docker/build-push-action@v6
with:
builder: ${{ steps.buildx.outputs.name }}
context: ./tools-image
file: ./tools-image/Dockerfile
platforms: linux/amd64,linux/arm64
push: false
tags: ${{ steps.prep.outputs.tags }}
- name: Build and push
if: github.event_name != 'pull_request'
uses: docker/build-push-action@v6
with:
builder: ${{ steps.buildx.outputs.name }}
context: ./tools-image
file: ./tools-image/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.prep.outputs.tags }}

View File

@@ -1,5 +1,5 @@
# Build the manager binary # Build the manager binary
FROM golang:1.22 as builder FROM golang:1.23 AS builder
WORKDIR /workspace WORKDIR /workspace
# Copy the Go Modules manifests # Copy the Go Modules manifests

View File

@@ -1,25 +0,0 @@
VERSION 0.6
last-commit-packages:
FROM quay.io/skopeo/stable
RUN dnf install -y jq
WORKDIR build
RUN skopeo list-tags docker://quay.io/kairos/packages | jq -rc '.Tags | map(select( (. | contains("-repository.yaml")) )) | sort_by(. | sub("v";"") | sub("-repository.yaml";"") | sub("-";"") | split(".") | map(tonumber) ) | .[-1]' > REPO_AMD64
RUN skopeo list-tags docker://quay.io/kairos/packages-arm64 | jq -rc '.Tags | map(select( (. | contains("-repository.yaml")) )) | sort_by(. | sub("v";"") | sub("-repository.yaml";"") | sub("-";"") | split(".") | map(tonumber) ) | .[-1]' > REPO_ARM64
SAVE ARTIFACT REPO_AMD64 REPO_AMD64
SAVE ARTIFACT REPO_ARM64 REPO_ARM64
bump-repositories:
FROM mikefarah/yq
WORKDIR build
COPY +last-commit-packages/REPO_AMD64 REPO_AMD64
COPY +last-commit-packages/REPO_ARM64 REPO_ARM64
ARG REPO_AMD64=$(cat REPO_AMD64)
ARG REPO_ARM64=$(cat REPO_ARM64)
COPY tools-image/luet-amd64.yaml luet-amd64.yaml
COPY tools-image/luet-arm64.yaml luet-arm64.yaml
RUN yq eval ".repositories[0] |= . * { \"reference\": \"${REPO_AMD64}\" }" -i luet-amd64.yaml
RUN yq eval ".repositories[0] |= . * { \"reference\": \"${REPO_ARM64}\" }" -i luet-arm64.yaml
SAVE ARTIFACT luet-arm64.yaml AS LOCAL tools-image/luet-arm64.yaml
SAVE ARTIFACT luet-amd64.yaml AS LOCAL tools-image/luet-amd64.yaml

View File

@@ -177,13 +177,15 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest
## Tool Versions ## Tool Versions
KUSTOMIZE_VERSION ?= v3.8.7 KUSTOMIZE_VERSION ?= v3.8.7
CONTROLLER_TOOLS_VERSION ?= v0.9.0 CONTROLLER_TOOLS_VERSION ?= v0.16.5
KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"
.PHONY: kustomize .PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN) $(KUSTOMIZE): $(LOCALBIN)
curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN) @if [ ! -f $(KUSTOMIZE) ]; then \
curl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); \
fi
.PHONY: controller-gen .PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,6 @@
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole kind: ClusterRole
metadata: metadata:
creationTimestamp: null
name: manager-role name: manager-role
rules: rules:
- apiGroups: - apiGroups:
@@ -16,15 +15,6 @@ rules:
- "" - ""
resources: resources:
- persistentvolumeclaims - persistentvolumeclaims
verbs:
- create
- delete
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods - pods
verbs: verbs:
- create - create

View File

@@ -18,6 +18,7 @@ package controllers
import ( import (
"fmt" "fmt"
"strings"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -156,10 +157,10 @@ func (r *OSArtifactReconciler) newArtifactPVC(artifact *osbuilder.OSArtifact) *c
} }
func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder.OSArtifact) *corev1.Pod { func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder.OSArtifact) *corev1.Pod {
cmd := fmt.Sprintf( var cmd strings.Builder
"/entrypoint.sh --debug --name %s build-iso --date=false --output /artifacts dir:/rootfs", cmd.WriteString("auroraboot --debug build-iso")
artifact.Name, cmd.WriteString(fmt.Sprintf(" --override-name %s", artifact.Name))
) cmd.WriteString(" --date=false")
volumeMounts := []corev1.VolumeMount{ volumeMounts := []corev1.VolumeMount{
{ {
@@ -180,27 +181,29 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
}) })
} }
cloudImgCmd := fmt.Sprintf( var cloudImgCmd strings.Builder
"/raw-images.sh /rootfs /artifacts/%s.raw", cloudImgCmd.WriteString("auroraboot --debug")
artifact.Name, cloudImgCmd.WriteString(" --set 'disk.raw=true'")
) cloudImgCmd.WriteString(" --set 'disable_netboot=true'")
cloudImgCmd.WriteString(" --set 'disable_http_server=true'")
cloudImgCmd.WriteString(" --set 'state_dir=/artifacts'")
cloudImgCmd.WriteString(" --set 'container_image=dir:/rootfs'")
if artifact.Spec.CloudConfigRef != nil { if artifact.Spec.CloudConfigRef != nil {
volumeMounts = append(volumeMounts, corev1.VolumeMount{ volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: "cloudconfig", Name: "cloudconfig",
MountPath: "/iso/iso-overlay/cloud_config.yaml", MountPath: "/cloud-config.yaml",
SubPath: artifact.Spec.CloudConfigRef.Key, SubPath: artifact.Spec.CloudConfigRef.Key,
}) })
cloudImgCmd.WriteString(" --cloud-config /cloud-config.yaml")
cloudImgCmd += " /iso/iso-overlay/cloud_config.yaml"
} }
cloudImgCmd.WriteString(fmt.Sprintf(" && file=$(ls /artifacts/*.raw 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.raw", artifact.Name))
if artifact.Spec.CloudConfigRef != nil || artifact.Spec.GRUBConfig != "" { if artifact.Spec.CloudConfigRef != nil || artifact.Spec.GRUBConfig != "" {
cmd = fmt.Sprintf( cmd.WriteString(" --cloud-config /cloud-config.yaml")
"/entrypoint.sh --debug --name %s build-iso --date=false --overlay-iso /iso/iso-overlay --output /artifacts dir:/rootfs",
artifact.Name,
)
} }
cmd.WriteString(" --output /artifacts dir:/rootfs")
buildIsoContainer := corev1.Container{ buildIsoContainer := corev1.Container{
ImagePullPolicy: corev1.PullAlways, ImagePullPolicy: corev1.PullAlways,
@@ -209,7 +212,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
Image: r.ToolImage, Image: r.ToolImage,
Command: []string{"/bin/bash", "-cxe"}, Command: []string{"/bin/bash", "-cxe"},
Args: []string{ Args: []string{
cmd, cmd.String(),
}, },
VolumeMounts: volumeMounts, VolumeMounts: volumeMounts,
} }
@@ -222,7 +225,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
Command: []string{"/bin/bash", "-cxe"}, Command: []string{"/bin/bash", "-cxe"},
Args: []string{ Args: []string{
cloudImgCmd, cloudImgCmd.String(),
}, },
VolumeMounts: volumeMounts, VolumeMounts: volumeMounts,
} }
@@ -234,6 +237,12 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
}} }}
} }
var netbootCmd strings.Builder
netbootCmd.WriteString("auroraboot --debug netboot")
netbootCmd.WriteString(fmt.Sprintf(" /artifacts/%s.iso", artifact.Name))
netbootCmd.WriteString(" /artifacts")
netbootCmd.WriteString(fmt.Sprintf(" %s", artifact.Name))
extractNetboot := corev1.Container{ extractNetboot := corev1.Container{
ImagePullPolicy: corev1.PullAlways, ImagePullPolicy: corev1.PullAlways,
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)}, SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
@@ -245,15 +254,24 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
Value: artifact.Spec.NetbootURL, Value: artifact.Spec.NetbootURL,
}}, }},
Args: []string{ Args: []string{
fmt.Sprintf( netbootCmd.String(),
"/netboot.sh /artifacts/%s.iso /artifacts/%s",
artifact.Name,
artifact.Name,
),
}, },
VolumeMounts: volumeMounts, VolumeMounts: volumeMounts,
} }
var azureCmd strings.Builder
azureCmd.WriteString("auroraboot --debug")
azureCmd.WriteString(" --set 'disk.vhd=true'")
azureCmd.WriteString(" --set 'disable_netboot=true'")
azureCmd.WriteString(" --set 'disable_http_server=true'")
azureCmd.WriteString(" --set 'state_dir=/artifacts'")
azureCmd.WriteString(" --set 'container_image=dir:/rootfs'")
if artifact.Spec.CloudConfigRef != nil {
azureCmd.WriteString(" --cloud-config /cloud-config.yaml")
}
azureCmd.WriteString(fmt.Sprintf(" && file=$(ls /artifacts/*.vhd 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.vhd", artifact.Name))
buildAzureCloudImageContainer := corev1.Container{ buildAzureCloudImageContainer := corev1.Container{
ImagePullPolicy: corev1.PullAlways, ImagePullPolicy: corev1.PullAlways,
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)}, SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
@@ -261,15 +279,24 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
Image: r.ToolImage, Image: r.ToolImage,
Command: []string{"/bin/bash", "-cxe"}, Command: []string{"/bin/bash", "-cxe"},
Args: []string{ Args: []string{
fmt.Sprintf( azureCmd.String(),
"/azure.sh /artifacts/%s.raw /artifacts/%s.vhd",
artifact.Name,
artifact.Name,
),
}, },
VolumeMounts: volumeMounts, VolumeMounts: volumeMounts,
} }
var gceCmd strings.Builder
gceCmd.WriteString("auroraboot --debug")
gceCmd.WriteString(" --set 'disk.gce=true'")
gceCmd.WriteString(" --set 'disable_netboot=true'")
gceCmd.WriteString(" --set 'disable_http_server=true'")
gceCmd.WriteString(" --set 'state_dir=/artifacts'")
gceCmd.WriteString(" --set 'container_image=dir:/rootfs'")
if artifact.Spec.CloudConfigRef != nil {
gceCmd.WriteString(" --cloud-config /cloud-config.yaml")
}
gceCmd.WriteString(fmt.Sprintf(" && file=$(ls /artifacts/*.raw.gce.tar.gz 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.gce.tar.gz", artifact.Name))
buildGCECloudImageContainer := corev1.Container{ buildGCECloudImageContainer := corev1.Container{
ImagePullPolicy: corev1.PullAlways, ImagePullPolicy: corev1.PullAlways,
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)}, SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
@@ -277,11 +304,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
Image: r.ToolImage, Image: r.ToolImage,
Command: []string{"/bin/bash", "-cxe"}, Command: []string{"/bin/bash", "-cxe"},
Args: []string{ Args: []string{
fmt.Sprintf( gceCmd.String(),
"/gce.sh /artifacts/%s.raw /artifacts/%s.gce.raw",
artifact.Name,
artifact.Name,
),
}, },
VolumeMounts: volumeMounts, VolumeMounts: volumeMounts,
} }
@@ -385,8 +408,11 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
podSpec.InitContainers = append(podSpec.InitContainers, kairosReleaseContainer(r.ToolImage)) podSpec.InitContainers = append(podSpec.InitContainers, kairosReleaseContainer(r.ToolImage))
} }
// build-iso runs as an init container to ensure it completes before build-netboot
// (which extracts artifacts from the ISO). Init containers run sequentially and must
// succeed before regular containers start.
if artifact.Spec.ISO || artifact.Spec.Netboot { if artifact.Spec.ISO || artifact.Spec.Netboot {
podSpec.Containers = append(podSpec.Containers, buildIsoContainer) podSpec.InitContainers = append(podSpec.InitContainers, buildIsoContainer)
} }
if artifact.Spec.Netboot { if artifact.Spec.Netboot {

View File

@@ -40,6 +40,7 @@ import (
const ( const (
FinalizerName = "build.kairos.io/osbuilder-finalizer" FinalizerName = "build.kairos.io/osbuilder-finalizer"
CompatibleAurorabootVersion = "v0.14.0"
artifactLabel = "build.kairos.io/artifact" artifactLabel = "build.kairos.io/artifact"
artifactExporterIndexAnnotation = "build.kairos.io/export-index" artifactExporterIndexAnnotation = "build.kairos.io/export-index"
) )
@@ -127,6 +128,14 @@ func (r *OSArtifactReconciler) createPVC(ctx context.Context, artifact *osbuilde
return pvc, err return pvc, err
} }
if err := r.Create(ctx, pvc); err != nil { if err := r.Create(ctx, pvc); err != nil {
if apierrors.IsAlreadyExists(err) {
// PVC already exists, fetch and return it
existingPVC := &corev1.PersistentVolumeClaim{}
if err := r.Get(ctx, client.ObjectKeyFromObject(pvc), existingPVC); err != nil {
return pvc, err
}
return existingPVC, nil
}
return pvc, err return pvc, err
} }

View File

@@ -8,7 +8,6 @@ import (
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2" osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/phayes/freeport"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -20,6 +19,7 @@ import (
clientgoscheme "k8s.io/client-go/kubernetes/scheme" clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
) )
var _ = Describe("OSArtifactReconciler", func() { var _ = Describe("OSArtifactReconciler", func() {
@@ -51,20 +51,15 @@ var _ = Describe("OSArtifactReconciler", func() {
utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(clientgoscheme.AddToScheme(scheme))
utilruntime.Must(osbuilder.AddToScheme(scheme)) utilruntime.Must(osbuilder.AddToScheme(scheme))
metricsPort, err := freeport.GetFreePort()
Expect(err).ToNot(HaveOccurred())
fmt.Printf("metricsPort = %+v\n", metricsPort)
mgr, err := ctrl.NewManager(restConfig, ctrl.Options{
Scheme: scheme,
MetricsBindAddress: fmt.Sprintf("127.0.0.1:%d", metricsPort),
})
Expect(err).ToNot(HaveOccurred())
r = &OSArtifactReconciler{ r = &OSArtifactReconciler{
ToolImage: "quay.io/kairos/osbuilder-tools:latest", ToolImage: fmt.Sprintf("quay.io/kairos/auroraboot:%s", CompatibleAurorabootVersion),
} }
err = (r).SetupWithManager(mgr)
// Create a direct client (no cache) for tests - we don't need reconciliation
// This avoids the complexity of managing a running manager
directClient, err := client.New(restConfig, client.Options{Scheme: scheme})
Expect(err).ToNot(HaveOccurred())
err = r.InjectClient(directClient)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
}) })
@@ -170,4 +165,247 @@ var _ = Describe("OSArtifactReconciler", func() {
}) })
}) })
}) })
Describe("Auroraboot Commands", func() {
BeforeEach(func() {
artifact.Spec.ImageName = "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0"
})
When("CloudImage is enabled", func() {
BeforeEach(func() {
artifact.Spec.CloudImage = true
})
It("creates build-cloud-image container with correct auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var cloudImageContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-cloud-image" {
cloudImageContainer = &pod.Spec.Containers[i]
break
}
}
Expect(cloudImageContainer).ToNot(BeNil())
Expect(cloudImageContainer.Args).To(HaveLen(1))
Expect(cloudImageContainer.Args[0]).To(ContainSubstring("auroraboot --debug --set 'disk.raw=true'"))
Expect(cloudImageContainer.Args[0]).To(ContainSubstring("--set 'state_dir=/artifacts'"))
Expect(cloudImageContainer.Args[0]).To(ContainSubstring("dir:/rootfs"))
Expect(cloudImageContainer.Args[0]).To(ContainSubstring(fmt.Sprintf("file=$(ls /artifacts/*.raw 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.raw", artifact.Name)))
})
When("CloudConfigRef is set", func() {
BeforeEach(func() {
secretName := artifact.Name + "-cloudconfig"
_, err := clientset.CoreV1().Secrets(namespace).Create(context.TODO(),
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{
"cloud-config.yaml": "#cloud-config\nusers:\n - name: test",
},
Type: "Opaque",
}, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
artifact.Spec.CloudConfigRef = &osbuilder.SecretKeySelector{
Name: secretName,
Key: "cloud-config.yaml",
}
})
It("includes cloud-config flag in auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var cloudImageContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-cloud-image" {
cloudImageContainer = &pod.Spec.Containers[i]
break
}
}
Expect(cloudImageContainer).ToNot(BeNil())
Expect(cloudImageContainer.Args[0]).To(ContainSubstring("--cloud-config /cloud-config.yaml"))
})
})
})
When("Netboot is enabled", func() {
BeforeEach(func() {
artifact.Spec.Netboot = true
artifact.Spec.ISO = true
artifact.Spec.NetbootURL = "http://example.com"
})
It("creates build-netboot container with correct auroraboot netboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var netbootContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-netboot" {
netbootContainer = &pod.Spec.Containers[i]
break
}
}
Expect(netbootContainer).ToNot(BeNil())
Expect(netbootContainer.Args).To(HaveLen(1))
Expect(netbootContainer.Args[0]).To(ContainSubstring("auroraboot --debug netboot"))
Expect(netbootContainer.Args[0]).To(ContainSubstring(fmt.Sprintf("/artifacts/%s.iso", artifact.Name)))
Expect(netbootContainer.Args[0]).To(ContainSubstring("/artifacts"))
Expect(netbootContainer.Args[0]).To(ContainSubstring(artifact.Name))
})
})
When("AzureImage is enabled", func() {
BeforeEach(func() {
artifact.Spec.AzureImage = true
})
It("creates build-azure-cloud-image container with correct auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var azureContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-azure-cloud-image" {
azureContainer = &pod.Spec.Containers[i]
break
}
}
Expect(azureContainer).ToNot(BeNil())
Expect(azureContainer.Args).To(HaveLen(1))
Expect(azureContainer.Args[0]).To(ContainSubstring("auroraboot --debug --set 'disk.vhd=true'"))
Expect(azureContainer.Args[0]).To(ContainSubstring("--set 'state_dir=/artifacts'"))
Expect(azureContainer.Args[0]).To(ContainSubstring("dir:/rootfs"))
Expect(azureContainer.Args[0]).To(ContainSubstring(fmt.Sprintf("file=$(ls /artifacts/*.vhd 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.vhd", artifact.Name)))
})
When("CloudConfigRef is set", func() {
BeforeEach(func() {
secretName := artifact.Name + "-cloudconfig"
_, err := clientset.CoreV1().Secrets(namespace).Create(context.TODO(),
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{
"cloud-config.yaml": "#cloud-config\nusers:\n - name: test",
},
Type: "Opaque",
}, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
artifact.Spec.CloudConfigRef = &osbuilder.SecretKeySelector{
Name: secretName,
Key: "cloud-config.yaml",
}
})
It("includes cloud-config flag in auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var azureContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-azure-cloud-image" {
azureContainer = &pod.Spec.Containers[i]
break
}
}
Expect(azureContainer).ToNot(BeNil())
Expect(azureContainer.Args[0]).To(ContainSubstring("--cloud-config /cloud-config.yaml"))
})
})
})
When("GCEImage is enabled", func() {
BeforeEach(func() {
artifact.Spec.GCEImage = true
})
It("creates build-gce-cloud-image container with correct auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var gceContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-gce-cloud-image" {
gceContainer = &pod.Spec.Containers[i]
break
}
}
Expect(gceContainer).ToNot(BeNil())
Expect(gceContainer.Args).To(HaveLen(1))
Expect(gceContainer.Args[0]).To(ContainSubstring("auroraboot --debug --set 'disk.gce=true'"))
Expect(gceContainer.Args[0]).To(ContainSubstring("--set 'state_dir=/artifacts'"))
Expect(gceContainer.Args[0]).To(ContainSubstring("dir:/rootfs"))
Expect(gceContainer.Args[0]).To(ContainSubstring(fmt.Sprintf("file=$(ls /artifacts/*.raw.gce.tar.gz 2>/dev/null | head -n1) && [ -n \"$file\" ] && mv \"$file\" /artifacts/%s.gce.tar.gz", artifact.Name)))
})
When("CloudConfigRef is set", func() {
BeforeEach(func() {
secretName := artifact.Name + "-cloudconfig"
_, err := clientset.CoreV1().Secrets(namespace).Create(context.TODO(),
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
StringData: map[string]string{
"cloud-config.yaml": "#cloud-config\nusers:\n - name: test",
},
Type: "Opaque",
}, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
artifact.Spec.CloudConfigRef = &osbuilder.SecretKeySelector{
Name: secretName,
Key: "cloud-config.yaml",
}
})
It("includes cloud-config flag in auroraboot command", func() {
pvc, err := r.createPVC(context.TODO(), artifact)
Expect(err).ToNot(HaveOccurred())
pod, err := r.createBuilderPod(context.TODO(), artifact, pvc)
Expect(err).ToNot(HaveOccurred())
var gceContainer *corev1.Container
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == "build-gce-cloud-image" {
gceContainer = &pod.Spec.Containers[i]
break
}
}
Expect(gceContainer).ToNot(BeNil())
Expect(gceContainer.Args[0]).To(ContainSubstring("--cloud-config /cloud-config.yaml"))
})
})
})
})
}) })

View File

@@ -21,10 +21,12 @@ import (
"math/rand" "math/rand"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/kubernetes/scheme"
@@ -73,7 +75,6 @@ var _ = BeforeSuite(func() {
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil()) Expect(k8sClient).NotTo(BeNil())
}) })
var _ = AfterSuite(func() { var _ = AfterSuite(func() {
@@ -100,10 +101,27 @@ func createRandomNamespace(clientset *kubernetes.Clientset) string {
}, metav1.CreateOptions{}) }, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Create default service account to avoid pod creation errors
_, err = clientset.CoreV1().ServiceAccounts(name).Create(context.Background(), &v1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: "default",
Namespace: name,
},
}, metav1.CreateOptions{})
if err != nil && !apierrors.IsAlreadyExists(err) {
Expect(err).ToNot(HaveOccurred())
}
return name return name
} }
func deleteNamepace(clientset *kubernetes.Clientset, name string) { func deleteNamepace(clientset *kubernetes.Clientset, name string) {
err := clientset.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{}) err := clientset.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
// Wait for the namespace to be fully deleted to ensure clean test isolation
Eventually(func() bool {
_, err := clientset.CoreV1().Namespaces().Get(context.Background(), name, metav1.GetOptions{})
return apierrors.IsNotFound(err)
}, 2*time.Minute, 1*time.Second).Should(BeTrue(), "namespace should be deleted")
} }

11
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/kairos-io/osbuilder module github.com/kairos-io/osbuilder
go 1.22 go 1.23.3
require ( require (
github.com/onsi/ginkgo/v2 v2.20.2 github.com/onsi/ginkgo/v2 v2.20.2
@@ -60,13 +60,12 @@ require (
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.21.0 // indirect go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.26.0 // indirect golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.28.0 // indirect golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sys v0.24.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.23.0 // indirect golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.17.0 // indirect golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.24.0 // indirect golang.org/x/tools v0.24.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect

62
go.sum
View File

@@ -165,8 +165,6 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
@@ -269,8 +267,6 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@@ -392,23 +388,11 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0=
github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA=
github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo=
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 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.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os=
github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@@ -560,12 +544,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -576,8 +556,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -653,10 +631,6 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -754,22 +728,12 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/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.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -779,12 +743,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -852,10 +812,6 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -992,8 +948,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=

View File

@@ -18,6 +18,7 @@ package main
import ( import (
"flag" "flag"
"fmt"
"os" "os"
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
@@ -57,7 +58,7 @@ func main() {
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
// It needs luet inside // It needs luet inside
flag.StringVar(&toolImage, "tool-image", "quay.io/kairos/osbuilder-tools:latest", "Tool image.") flag.StringVar(&toolImage, "tool-image", fmt.Sprintf("quay.io/kairos/auroraboot:%s", controllers.CompatibleAurorabootVersion), "Tool image.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false, flag.BoolVar(&enableLeaderElection, "leader-elect", false,

View File

@@ -22,16 +22,5 @@
"matchUpdateTypes": ["patch"], "matchUpdateTypes": ["patch"],
"automerge": true "automerge": true
}, },
],
"regexManagers": [
{
"fileMatch": [
"tools-image/luet-amd64.yaml$",
"tools-image/luet-arm64.yaml$"
],
"matchStrings": [
"#\\s*renovate:\\s*datasource=(?<datasource>.*?) depName=(?<depName>.*?)?\\s+reference:\\s(?<currentValue>.*?)\\s"
]
}
] ]
} }

View File

@@ -1,5 +1,6 @@
#!/bin/bash #!/bin/bash
# TODO: Bump to some recent kubernetes version
KUBE_VERSION=${KUBE_VERSION:-v1.22.7} KUBE_VERSION=${KUBE_VERSION:-v1.22.7}
CLUSTER_NAME="${CLUSTER_NAME:-kairos-osbuilder-e2e}" CLUSTER_NAME="${CLUSTER_NAME:-kairos-osbuilder-e2e}"

View File

@@ -0,0 +1,356 @@
package e2e_test
import (
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
. "github.com/onsi/ginkgo/v2"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
)
var _ = Describe("Artifact Format Tests", func() {
var tc *TestClients
BeforeEach(func() {
tc = SetupTestClients()
})
Describe("CloudImage (Raw Disk)", func() {
var artifactName string
var artifactLabelSelector labels.Selector
BeforeEach(func() {
artifact := &osbuilder.OSArtifact{
TypeMeta: metav1.TypeMeta{
Kind: "OSArtifact",
APIVersion: osbuilder.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: "cloudimage-",
},
Spec: osbuilder.OSArtifactSpec{
ImageName: "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0",
CloudImage: true,
Exporters: []batchv1.JobSpec{
{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "verify",
Image: "debian:latest",
Command: []string{"bash"},
Args: []string{
"-xec",
`
set -e
# Check that raw file exists
raw_file=$(ls /artifacts/*.raw 2>/dev/null | head -n1)
if [ -z "$raw_file" ]; then
echo "No .raw file found"
exit 1
fi
# Check that it's a valid disk image (has non-zero size)
if [ ! -s "$raw_file" ]; then
echo "Raw file is empty"
exit 1
fi
# Check file size is reasonable (at least 100MB)
size=$(stat -c%s "$raw_file")
if [ "$size" -lt 104857600 ]; then
echo "Raw file too small: $size bytes"
exit 1
fi
echo "Raw disk verification passed: $raw_file"
`,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "artifacts",
ReadOnly: true,
MountPath: "/artifacts",
},
},
},
},
},
},
},
},
},
}
artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
})
It("builds a valid raw disk image", func() {
tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
tc.WaitForExportCompletion(artifactLabelSelector)
tc.Cleanup(artifactName, artifactLabelSelector)
})
})
Describe("Netboot", func() {
var artifactName string
var artifactLabelSelector labels.Selector
BeforeEach(func() {
artifact := &osbuilder.OSArtifact{
TypeMeta: metav1.TypeMeta{
Kind: "OSArtifact",
APIVersion: osbuilder.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: "netboot-",
},
Spec: osbuilder.OSArtifactSpec{
ImageName: "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0",
ISO: true,
Netboot: true,
NetbootURL: "http://example.com",
Exporters: []batchv1.JobSpec{
{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "verify",
Image: "debian:latest",
Command: []string{"bash"},
Args: []string{
"-xec",
`
set -e
# Check for kernel file (pattern: *-kernel)
kernel_file=$(ls /artifacts/*-kernel 2>/dev/null | head -n1)
if [ -z "$kernel_file" ]; then
echo "No kernel file found (pattern: *-kernel)"
ls -la /artifacts/ || true
exit 1
fi
# Check for initrd file (pattern: *-initrd)
initrd_file=$(ls /artifacts/*-initrd 2>/dev/null | head -n1)
if [ -z "$initrd_file" ]; then
echo "No initrd file found (pattern: *-initrd)"
ls -la /artifacts/ || true
exit 1
fi
# Check for squashfs file (pattern: *.squashfs)
squashfs_file=$(ls /artifacts/*.squashfs 2>/dev/null | head -n1)
if [ -z "$squashfs_file" ]; then
echo "No squashfs file found (pattern: *.squashfs)"
ls -la /artifacts/ || true
exit 1
fi
# Verify files are non-empty
for file in "$kernel_file" "$initrd_file" "$squashfs_file"; do
if [ ! -s "$file" ]; then
echo "File is empty: $file"
exit 1
fi
done
echo "Netboot artifacts verification passed"
echo "Kernel: $kernel_file"
echo "Initrd: $initrd_file"
echo "Squashfs: $squashfs_file"
`,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "artifacts",
ReadOnly: true,
MountPath: "/artifacts",
},
},
},
},
},
},
},
},
},
}
artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
})
It("builds valid netboot artifacts", func() {
tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
tc.WaitForExportCompletion(artifactLabelSelector)
tc.Cleanup(artifactName, artifactLabelSelector)
})
})
Describe("AzureImage (VHD)", func() {
var artifactName string
var artifactLabelSelector labels.Selector
BeforeEach(func() {
artifact := &osbuilder.OSArtifact{
TypeMeta: metav1.TypeMeta{
Kind: "OSArtifact",
APIVersion: osbuilder.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: "azure-",
},
Spec: osbuilder.OSArtifactSpec{
ImageName: "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0",
AzureImage: true,
Exporters: []batchv1.JobSpec{
{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "verify",
Image: "debian:latest",
Command: []string{"bash"},
Args: []string{
"-xec",
`
set -e
# Check that VHD file exists
vhd_file=$(ls /artifacts/*.vhd 2>/dev/null | head -n1)
if [ -z "$vhd_file" ]; then
echo "No .vhd file found"
exit 1
fi
# Check that it's non-empty
if [ ! -s "$vhd_file" ]; then
echo "VHD file is empty"
exit 1
fi
# Check file size is reasonable (at least 100MB)
size=$(stat -c%s "$vhd_file")
if [ "$size" -lt 104857600 ]; then
echo "VHD file too small: $size bytes"
exit 1
fi
# Check VHD footer (last 512 bytes should contain VHD signature)
# VHD footer starts at offset -512 and contains "conectix" string
tail -c 512 "$vhd_file" | grep -q "conectix" || {
echo "VHD file does not have valid VHD footer"
exit 1
}
echo "VHD verification passed: $vhd_file"
`,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "artifacts",
ReadOnly: true,
MountPath: "/artifacts",
},
},
},
},
},
},
},
},
},
}
artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
})
It("builds a valid Azure VHD image", func() {
tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
tc.WaitForExportCompletion(artifactLabelSelector)
tc.Cleanup(artifactName, artifactLabelSelector)
})
})
Describe("GCEImage", func() {
var artifactName string
var artifactLabelSelector labels.Selector
BeforeEach(func() {
artifact := &osbuilder.OSArtifact{
TypeMeta: metav1.TypeMeta{
Kind: "OSArtifact",
APIVersion: osbuilder.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
GenerateName: "gce-",
},
Spec: osbuilder.OSArtifactSpec{
ImageName: "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0",
GCEImage: true,
Exporters: []batchv1.JobSpec{
{
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "verify",
Image: "debian:latest",
Command: []string{"bash"},
Args: []string{
"-xec",
`
set -e
# Check that GCE tar.gz file exists
gce_file=$(ls /artifacts/*.gce.tar.gz 2>/dev/null | head -n1)
if [ -z "$gce_file" ]; then
echo "No .gce.tar.gz file found"
exit 1
fi
# Check that it's non-empty
if [ ! -s "$gce_file" ]; then
echo "GCE tar.gz file is empty"
exit 1
fi
# Extract and verify it contains disk.raw
temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT
tar -xzf "$gce_file" -C "$temp_dir"
if [ ! -f "$temp_dir/disk.raw" ]; then
echo "GCE archive does not contain disk.raw"
exit 1
fi
# Verify disk.raw is non-empty and reasonable size
if [ ! -s "$temp_dir/disk.raw" ]; then
echo "disk.raw in archive is empty"
exit 1
fi
size=$(stat -c%s "$temp_dir/disk.raw")
if [ "$size" -lt 104857600 ]; then
echo "disk.raw too small: $size bytes"
exit 1
fi
echo "GCE verification passed: $gce_file"
`,
},
VolumeMounts: []corev1.VolumeMount{
{
Name: "artifacts",
ReadOnly: true,
MountPath: "/artifacts",
},
},
},
},
},
},
},
},
},
}
artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
})
It("builds a valid GCE image", func() {
tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
tc.WaitForExportCompletion(artifactLabelSelector)
tc.Cleanup(artifactName, artifactLabelSelector)
})
})
})

View File

@@ -1,41 +1,21 @@
package e2e_test package e2e_test
import ( import (
"context"
"time"
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2" osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
ctrl "sigs.k8s.io/controller-runtime"
) )
var _ = Describe("ISO build test", func() { var _ = Describe("ISO build test", func() {
var artifactName string var artifactName string
var artifacts, pods, pvcs, jobs dynamic.ResourceInterface
var scheme *runtime.Scheme
var artifactLabelSelector labels.Selector var artifactLabelSelector labels.Selector
var tc *TestClients
BeforeEach(func() { BeforeEach(func() {
k8s := dynamic.NewForConfigOrDie(ctrl.GetConfigOrDie()) tc = SetupTestClients()
scheme = runtime.NewScheme()
err := osbuilder.AddToScheme(scheme)
Expect(err).ToNot(HaveOccurred())
artifacts = k8s.Resource(schema.GroupVersionResource{Group: osbuilder.GroupVersion.Group, Version: osbuilder.GroupVersion.Version, Resource: "osartifacts"}).Namespace("default")
pods = k8s.Resource(schema.GroupVersionResource{Group: corev1.GroupName, Version: corev1.SchemeGroupVersion.Version, Resource: "pods"}).Namespace("default")
pvcs = k8s.Resource(schema.GroupVersionResource{Group: corev1.GroupName, Version: corev1.SchemeGroupVersion.Version, Resource: "persistentvolumeclaims"}).Namespace("default")
jobs = k8s.Resource(schema.GroupVersionResource{Group: batchv1.GroupName, Version: batchv1.SchemeGroupVersion.Version, Resource: "jobs"}).Namespace("default")
artifact := &osbuilder.OSArtifact{ artifact := &osbuilder.OSArtifact{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
@@ -46,7 +26,7 @@ var _ = Describe("ISO build test", func() {
GenerateName: "simple-", GenerateName: "simple-",
}, },
Spec: osbuilder.OSArtifactSpec{ Spec: osbuilder.OSArtifactSpec{
ImageName: "quay.io/kairos/core-opensuse:latest", ImageName: "quay.io/kairos/opensuse:leap-15.6-core-amd64-generic-v3.6.0",
ISO: true, ISO: true,
DiskSize: "", DiskSize: "",
Exporters: []batchv1.JobSpec{ Exporters: []batchv1.JobSpec{
@@ -76,87 +56,12 @@ var _ = Describe("ISO build test", func() {
}, },
} }
uArtifact := unstructured.Unstructured{} artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
uArtifact.Object, _ = runtime.DefaultUnstructuredConverter.ToUnstructured(artifact)
resp, err := artifacts.Create(context.TODO(), &uArtifact, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
artifactName = resp.GetName()
artifactLabelSelectorReq, err := labels.NewRequirement("build.kairos.io/artifact", selection.Equals, []string{artifactName})
Expect(err).ToNot(HaveOccurred())
artifactLabelSelector = labels.NewSelector().Add(*artifactLabelSelectorReq)
}) })
It("works", func() { It("works", func() {
By("starting the build") tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
Eventually(func(g Gomega) { tc.WaitForExportCompletion(artifactLabelSelector)
w, err := pods.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()}) tc.Cleanup(artifactName, artifactLabelSelector)
Expect(err).ToNot(HaveOccurred())
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
}
}).WithTimeout(time.Hour).Should(Succeed())
By("exporting the artifacts")
Eventually(func(g Gomega) {
w, err := jobs.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
Expect(err).ToNot(HaveOccurred())
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
}
}).WithTimeout(time.Hour).Should(Succeed())
By("building the artifacts successfully")
Eventually(func(g Gomega) {
w, err := artifacts.Watch(context.TODO(), metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
var artifact osbuilder.OSArtifact
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = !ok
if event.Type == watch.Modified && event.Object.(*unstructured.Unstructured).GetName() == artifactName {
err := scheme.Convert(event.Object, &artifact, nil)
Expect(err).ToNot(HaveOccurred())
stopped = artifact.Status.Phase == osbuilder.Ready
}
}
}).WithTimeout(time.Hour).Should(Succeed())
By("cleaning up resources on deletion")
err := artifacts.Delete(context.TODO(), artifactName, metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
Eventually(func(g Gomega) int {
res, err := artifacts.List(context.TODO(), metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := pods.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := pvcs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := jobs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
}) })
}) })

View File

@@ -1,13 +1,160 @@
package e2e_test package e2e_test
import ( import (
"context"
"testing" "testing"
"time"
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/selection"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
ctrl "sigs.k8s.io/controller-runtime"
) )
func TestE2e(t *testing.T) { func TestE2e(t *testing.T) {
RegisterFailHandler(Fail) RegisterFailHandler(Fail)
RunSpecs(t, "kairos-operator e2e test Suite") RunSpecs(t, "kairos-operator e2e test Suite")
} }
// TestClients holds common Kubernetes clients used across e2e tests
type TestClients struct {
Artifacts dynamic.ResourceInterface
Pods dynamic.ResourceInterface
PVCs dynamic.ResourceInterface
Jobs dynamic.ResourceInterface
Scheme *runtime.Scheme
}
// SetupTestClients initializes and returns common Kubernetes clients
func SetupTestClients() *TestClients {
k8s := dynamic.NewForConfigOrDie(ctrl.GetConfigOrDie())
scheme := runtime.NewScheme()
err := osbuilder.AddToScheme(scheme)
Expect(err).ToNot(HaveOccurred())
return &TestClients{
Artifacts: k8s.Resource(schema.GroupVersionResource{
Group: osbuilder.GroupVersion.Group,
Version: osbuilder.GroupVersion.Version,
Resource: "osartifacts",
}).Namespace("default"),
Pods: k8s.Resource(schema.GroupVersionResource{
Group: corev1.GroupName,
Version: corev1.SchemeGroupVersion.Version,
Resource: "pods",
}).Namespace("default"),
PVCs: k8s.Resource(schema.GroupVersionResource{
Group: corev1.GroupName,
Version: corev1.SchemeGroupVersion.Version,
Resource: "persistentvolumeclaims",
}).Namespace("default"),
Jobs: k8s.Resource(schema.GroupVersionResource{
Group: batchv1.GroupName,
Version: batchv1.SchemeGroupVersion.Version,
Resource: "jobs",
}).Namespace("default"),
Scheme: scheme,
}
}
// CreateArtifact creates an OSArtifact and returns its name and label selector
func (tc *TestClients) CreateArtifact(artifact *osbuilder.OSArtifact) (string, labels.Selector) {
uArtifact := unstructured.Unstructured{}
uArtifact.Object, _ = runtime.DefaultUnstructuredConverter.ToUnstructured(artifact)
resp, err := tc.Artifacts.Create(context.TODO(), &uArtifact, metav1.CreateOptions{})
Expect(err).ToNot(HaveOccurred())
artifactName := resp.GetName()
artifactLabelSelectorReq, err := labels.NewRequirement("build.kairos.io/artifact", selection.Equals, []string{artifactName})
Expect(err).ToNot(HaveOccurred())
artifactLabelSelector := labels.NewSelector().Add(*artifactLabelSelectorReq)
return artifactName, artifactLabelSelector
}
// WaitForBuildCompletion waits for the build pod to complete and artifact to be ready
func (tc *TestClients) WaitForBuildCompletion(artifactName string, artifactLabelSelector labels.Selector) {
By("waiting for build pod to complete")
Eventually(func(g Gomega) {
w, err := tc.Pods.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
g.Expect(err).ToNot(HaveOccurred())
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
}
}).WithTimeout(time.Hour).Should(Succeed())
By("waiting for artifact to be ready")
Eventually(func(g Gomega) {
w, err := tc.Artifacts.Watch(context.TODO(), metav1.ListOptions{})
g.Expect(err).ToNot(HaveOccurred())
var artifact osbuilder.OSArtifact
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = !ok
if event.Type == watch.Modified && event.Object.(*unstructured.Unstructured).GetName() == artifactName {
err := tc.Scheme.Convert(event.Object, &artifact, nil)
g.Expect(err).ToNot(HaveOccurred())
stopped = artifact.Status.Phase == osbuilder.Ready
}
}
}).WithTimeout(time.Hour).Should(Succeed())
}
// WaitForExportCompletion waits for the export job to complete
func (tc *TestClients) WaitForExportCompletion(artifactLabelSelector labels.Selector) {
By("waiting for export job to complete")
Eventually(func(g Gomega) {
w, err := tc.Jobs.Watch(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
g.Expect(err).ToNot(HaveOccurred())
var stopped bool
for !stopped {
event, ok := <-w.ResultChan()
stopped = event.Type != watch.Deleted && event.Type != watch.Error || !ok
}
}).WithTimeout(time.Hour).Should(Succeed())
}
// Cleanup deletes the artifact and waits for all related resources to be cleaned up
func (tc *TestClients) Cleanup(artifactName string, artifactLabelSelector labels.Selector) {
By("cleaning up resources")
err := tc.Artifacts.Delete(context.TODO(), artifactName, metav1.DeleteOptions{})
Expect(err).ToNot(HaveOccurred())
Eventually(func(g Gomega) int {
res, err := tc.Artifacts.List(context.TODO(), metav1.ListOptions{})
g.Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := tc.Pods.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
g.Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := tc.PVCs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
g.Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
Eventually(func(g Gomega) int {
res, err := tc.Jobs.List(context.TODO(), metav1.ListOptions{LabelSelector: artifactLabelSelector.String()})
g.Expect(err).ToNot(HaveOccurred())
return len(res.Items)
}).WithTimeout(time.Minute).Should(Equal(0))
}

View File

@@ -1,108 +0,0 @@
# https://quay.io/repository/kairos/packages?tab=tags&tag=latest
ARG LEAP_VERSION=15.5
ARG LUET_VERSION=0.35.5
ARG ENKI_VERSION=0.2.0
FROM quay.io/luet/base:$LUET_VERSION AS luet
FROM quay.io/kairos/enki:v${ENKI_VERSION} as enki
FROM opensuse/leap:$LEAP_VERSION as default
RUN zypper ref && zypper dup -y
## ISO+ Arm image + Netboot + cloud images Build depedencies
RUN zypper ref && zypper in -y bc qemu-tools jq cdrtools docker git curl gptfdisk kpartx sudo xfsprogs parted binutils \
util-linux-systemd e2fsprogs curl util-linux udev rsync grub2 dosfstools grub2-x86_64-efi squashfs mtools xorriso lvm2 zstd
COPY --from=luet /usr/bin/luet /usr/bin/luet
ENV LUET_NOLOCK=true
ENV TMPDIR=/tmp
ARG TARGETARCH
# copy both arches
COPY luet-arm64.yaml /tmp/luet-arm64.yaml
COPY luet-amd64.yaml /tmp/luet-amd64.yaml
# Set the default luet config to the current build arch
RUN mkdir -p /etc/luet/
RUN cp /tmp/luet-${TARGETARCH}.yaml /etc/luet/luet.yaml
## Uki artifacts, will be set under the /usr/kairos directory
RUN luet install -y system/systemd-boot
## Live CD artifacts
RUN luet install -y livecd/grub2 --system-target /grub2
RUN luet install -y livecd/grub2-efi-image --system-target /efi
## RPI64
RUN luet install -y firmware/u-boot-rpi64 firmware/raspberrypi-firmware firmware/raspberrypi-firmware-config firmware/raspberrypi-firmware-dt --system-target /rpi/
## PineBook64 Pro
RUN luet install -y arm-vendor-blob/u-boot-rockchip --system-target /pinebookpro/u-boot
## Odroid fw
RUN luet install -y firmware/odroid-c2 --system-target /firmware/odroid-c2
## RAW images for current arch
RUN luet install -y static/grub-efi --system-target /raw/grub
RUN luet install -y static/grub-config --system-target /raw/grubconfig
RUN luet install -y static/grub-artifacts --system-target /raw/grubartifacts
## RAW images for arm64
# Luet will install this artifacts from the current arch repo, so in x86 it will
# get them from the x86 repo and we want it to do it from the arm64 repo, even on x86
# so we use the arm64 luet config and use that to install those on x86
# This is being used by the prepare_arm_images.sh and build-arch-image.sh scripts
RUN luet install --config /tmp/luet-arm64.yaml -y static/grub-efi --system-target /arm/raw/grubefi
RUN luet install --config /tmp/luet-arm64.yaml -y static/grub-config --system-target /arm/raw/grubconfig
RUN luet install --config /tmp/luet-arm64.yaml -y static/grub-artifacts --system-target /arm/raw/grubartifacts
# kairos-agent so we can use the pull-image
RUN luet install -y system/kairos-agent
# remove luet tmp files. Side effect of setting the system-target is that it treats it as a root fs
# so temporal files are stored in each dir
RUN rm -Rf /grub2/var/tmp
RUN rm -Rf /grub2/var/cache
RUN rm -Rf /efi/var/tmp
RUN rm -Rf /efi/var/cache
RUN rm -Rf /rpi/var/tmp
RUN rm -Rf /rpi/var/cache
RUN rm -Rf /pinebookpro/u-boot/var/tmp
RUN rm -Rf /pinebookpro/u-boot/var/cache
RUN rm -Rf /firmware/odroid-c2/var/tmp
RUN rm -Rf /firmware/odroid-c2/var/cache
RUN rm -Rf /raw/grub/var/tmp
RUN rm -Rf /raw/grub/var/cache
RUN rm -Rf /raw/grubconfig/var/tmp
RUN rm -Rf /raw/grubconfig/var/cache
RUN rm -Rf /raw/grubartifacts/var/tmp
RUN rm -Rf /raw/grubartifacts/var/cache
RUN rm -Rf /arm/raw/grubefi/var/tmp
RUN rm -Rf /arm/raw/grubefi/var/cache
RUN rm -Rf /arm/raw/grubconfig/var/tmp
RUN rm -Rf /arm/raw/grubconfig/var/cache
RUN rm -Rf /arm/raw/grubartifacts/var/tmp
RUN rm -Rf /arm/raw/grubartifacts/var/cache
RUN mkdir /config
# ISO build config
COPY ./config.yaml /config/manifest.yaml
COPY ./entrypoint.sh /entrypoint.sh
COPY ./add-cloud-init.sh /add-cloud-init.sh
COPY ./kairos-release.tmpl /kairos-release.tmpl
COPY ./ipxe.tmpl /ipxe.tmpl
COPY ./update-os-release.sh /update-os-release.sh
# ARM helpers
COPY ./build-arm-image.sh /build-arm-image.sh
COPY ./arm /arm
COPY ./prepare_arm_images.sh /prepare_arm_images.sh
# RAW images helpers
COPY ./gce.sh /gce.sh
COPY ./raw-images.sh /raw-images.sh
COPY ./azure.sh /azure.sh
COPY ./netboot.sh /netboot.sh
COPY defaults.yaml /defaults.yaml
COPY --from=enki /enki /usr/bin/enki
ENTRYPOINT [ "/entrypoint.sh" ]

View File

@@ -1,18 +0,0 @@
#!/bin/bash
# docker run --entrypoint /add-cloud-init.sh -v $PWD:/work -ti --rm test https://github.com/kairos-io/kairos/releases/download/v1.1.2/kairos-alpine-v1.1.2.iso /work/test.iso /work/config.yaml
set -ex
ISO=$1
OUT=$2
CONFIG=$3
case ${ISO} in
http*)
curl -L "${ISO}" -o in.iso
ISO=in.iso
;;
esac
# Needs xorriso >=1.5.4
xorriso -indev $ISO -outdev $OUT -map $CONFIG /config.yaml -boot_image any replay

View File

@@ -1,13 +0,0 @@
#!/bin/bash
image=$1
if [ -z "$image" ]; then
echo "No image specified"
exit 1
fi
# conv=notrunc ?
dd if=/firmware/odroid-c2/bl1.bin.hardkernel of=$image conv=fsync bs=1 count=442
dd if=/firmware/odroid-c2/bl1.bin.hardkernel of=$image conv=fsync bs=512 skip=1 seek=1
dd if=/firmware/odroid-c2/u-boot.odroidc2 of=$image conv=fsync bs=512 seek=97

View File

@@ -1,19 +0,0 @@
#!/bin/bash
image=$1
if [ -z "$image" ]; then
echo "No image specified"
exit 1
fi
LOADER_OFFSET=${LOADER_OFFSET:-"64"}
LOADER_IMAGE=${LOADER_IMAGE:-"idbloader.img"}
UBOOT_IMAGE=${UBOOT_IMAGE:-"u-boot.itb"}
UBOOT_OFFSET=${UBOOT_OFFSET:-"16384"}
echo "Writing idbloader"
dd conv=notrunc if=/pinebookpro/u-boot/usr/lib/u-boot/pinebook-pro-rk3399/${LOADER_IMAGE} of="$image" conv=fsync seek=${LOADER_OFFSET}
echo "Writing u-boot image"
dd conv=notrunc if=/pinebookpro/u-boot/usr/lib/u-boot/pinebook-pro-rk3399/${UBOOT_IMAGE} of="$image" conv=fsync seek=${UBOOT_OFFSET}
sync $image

View File

@@ -1,22 +0,0 @@
#!/bin/bash
partprobe
kpartx -va $DRIVE
image=$1
if [ -z "$image" ]; then
echo "No image specified"
exit 1
fi
set -ax
TEMPDIR="$(mktemp -d)"
echo $TEMPDIR
mount "${device}p1" "${TEMPDIR}"
# Copy all rpi files
cp -rfv /rpi/* $TEMPDIR
umount "${TEMPDIR}"

View File

@@ -1,20 +0,0 @@
#!/bin/bash
partprobe
image=$1
if [ -z "$image" ]; then
echo "No image specified"
exit 1
fi
set -ax
TEMPDIR="$(mktemp -d)"
echo $TEMPDIR
mount "${device}p1" "${TEMPDIR}"
# Copy all rpi files
cp -rfv /rpi/* $TEMPDIR
umount "${TEMPDIR}"

View File

@@ -1,17 +0,0 @@
#!/bin/bash
# Transform a raw image disk to azure vhd
RAWIMAGE="$1"
VHDDISK="${2:-disk.vhd}"
cp -rf $RAWIMAGE $VHDDISK.work
MB=$((1024*1024))
size=$(qemu-img info -f raw --output json "$RAWIMAGE" | gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')
# shellcheck disable=SC2004
ROUNDED_SIZE=$(((($size+$MB-1)/$MB)*$MB))
echo "Resizing raw image to $ROUNDED_SIZE"
qemu-img resize -f raw "$VHDDISK.work" $ROUNDED_SIZE
echo "Converting $RAWIMAGE to $VHDDISK"
qemu-img convert -f raw -o subformat=fixed,force_size -O vpc "$VHDDISK.work" "$VHDDISK"
echo "Done"
rm -rf "$VHDDISK.work"

View File

@@ -1,27 +0,0 @@
#!/bin/bash
# Generates raw bootable images with qemu
set -ex
CLOUD_INIT=${1:-cloud_init.yaml}
QEMU=${QEMU:-qemu-system-x86_64}
ISO=${2:-iso.iso}
mkdir -p build
pushd build
touch meta-data
cp -rfv $CLOUD_INIT user-data
mkisofs -output ci.iso -volid cidata -joliet -rock user-data meta-data
truncate -s "+$((20000*1024*1024))" disk.raw
${QEMU} -m 8096 -smp cores=2 \
-nographic -cpu host \
-serial mon:stdio \
-rtc base=utc,clock=rt \
-chardev socket,path=qga.sock,server,nowait,id=qga0 \
-device virtio-serial \
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 \
-drive if=virtio,media=disk,file=disk.raw \
-drive format=raw,media=cdrom,readonly=on,file=$ISO \
-drive format=raw,media=cdrom,readonly=on,file=ci.iso \
-boot d \
-enable-kvm

View File

@@ -1,507 +0,0 @@
#!/bin/bash
## This is a re-adaptation of https://github.com/rancher/elemental-toolkit/blob/main/images/arm-img-builder.sh
set -ex
load_vars() {
model=${MODEL:-odroid_c2}
directory=${DIRECTORY:-}
output_image="${OUTPUT_IMAGE:-arm.img}"
# Img creation options. Size is in MB for all of the vars below
size="${SIZE:-7608}"
state_size="${STATE_SIZE:-4992}"
oem_size="${OEM_SIZE:-64}"
recovery_size="${RECOVERY_SIZE:-2192}"
default_active_size="${DEFAULT_ACTIVE_SIZE:-2400}"
menu_entry="${DEFAULT_MENU_ENTRY:-Kairos}"
## Repositories
final_repo="${FINAL_REPO:-quay.io/costoolkit/releases-teal-arm64}"
repo_type="${REPO_TYPE:-docker}"
# Warning: these default values must be aligned with the values provided
# in 'packages/cos-config/cos-config', provide an environment file using the
# --cos-config flag if different values are needed.
: "${OEM_LABEL:=COS_OEM}"
: "${RECOVERY_LABEL:=COS_RECOVERY}"
: "${ACTIVE_LABEL:=COS_ACTIVE}"
: "${PASSIVE_LABEL:=COS_PASSIVE}"
: "${PERSISTENT_LABEL:=COS_PERSISTENT}"
: "${SYSTEM_LABEL:=COS_SYSTEM}"
: "${STATE_LABEL:=COS_STATE}"
}
cleanup() {
sync
sync
sleep 5
sync
if [ -n "$EFI" ]; then
rm -rf $EFI
fi
if [ -n "$RECOVERY" ]; then
rm -rf $RECOVERY
fi
if [ -n "$STATEDIR" ]; then
rm -rf $STATEDIR
fi
if [ -n "$TARGET" ]; then
umount $TARGET || true
umount $LOOP || true
rm -rf $TARGET
fi
if [ -n "$WORKDIR" ]; then
rm -rf $WORKDIR
fi
if [ -n "$DRIVE" ]; then
umount $DRIVE || true
fi
if [ -n "$recovery" ]; then
umount $recovery || true
fi
if [ -n "$state" ]; then
umount $state || true
fi
if [ -n "$efi" ]; then
umount $efi || true
fi
if [ -n "$oem" ]; then
umount $oem || true
fi
losetup -D "${LOOP}" || true;
losetup -D "${DRIVE}" || true;
if [ "$model" == "rpi3" ]; then
dmsetup remove KairosVG-oem || true;
dmsetup remove KairosVG-recovery || true;
fi
}
ensure_dir_structure() {
local target=$1
for mnt in /sys /proc /dev /tmp /boot /usr/local /oem
do
if [ ! -d "${target}${mnt}" ]; then
mkdir -p ${target}${mnt}
fi
done
}
usage()
{
echo "Usage: $0 [options] image.img"
echo ""
echo "Example: $0 --cos-config cos-config --model odroid-c2 --docker-image <image> output.img"
echo ""
echo "Flags:"
echo " --cos-config: (optional) Specifies a cos-config file for required environment variables"
echo " --config: (optional) Specify a cloud-init config file to embed into the final image"
echo " --manifest: (optional) Specify a manifest file to customize efi/grub packages installed into the image"
echo " --size: (optional) Image size (MB)"
echo " --state-partition-size: (optional) Size of the state partition (MB)"
echo " --recovery-partition-size: (optional) Size of the recovery partition (MB)"
echo " --images-size: (optional) Size of the active/passive/recovery images (MB)"
echo " --docker-image: (optional) A container image which will be used for active/passive/recovery system"
echo " --directory: (optional) A directory which will be used for active/passive/recovery system"
echo " --model: (optional) The board model"
echo " --efi-dir: (optional) A directory with files which will be added to the efi partition"
exit 1
}
get_url()
{
FROM=$1
TO=$2
case $FROM in
ftp*|http*|tftp*)
n=0
attempts=5
until [ "$n" -ge "$attempts" ]
do
curl -o $TO -fL ${FROM} && break
n=$((n+1))
echo "Failed to download, retry attempt ${n} out of ${attempts}"
sleep 2
done
;;
*)
cp -f $FROM $TO
;;
esac
}
trap "cleanup" 1 2 3 6 14 15 EXIT
load_vars
while [ "$#" -gt 0 ]; do
case $1 in
--cos-config)
shift 1
cos_config=$1
;;
--config)
shift 1
config=$1
;;
--manifest)
shift 1
manifest=$1
;;
--size)
shift 1
size=$1
;;
--state-partition-size)
shift 1
state_size=$1
;;
--recovery-partition-size)
shift 1
recovery_size=$1
;;
--images-size)
shift 1
default_active_size=$1
;;
--docker-image)
shift 1
CONTAINER_IMAGE=$1
;;
--directory)
shift 1
directory=$1
;;
--model)
shift 1
model=$1
;;
--efi-dir)
shift 1
efi_dir=$1
;;
--final-repo)
shift 1
final_repo=$1
;;
--repo-type)
shift 1
repo_type=$1
;;
-h)
usage
;;
--help)
usage
;;
*)
if [ "$#" -gt 2 ]; then
usage
fi
output_image=$1
break
;;
esac
shift 1
done
if [ "$model" == "rpi64" ];then
echo "rpi64 model not supported anymore, please select either rpi3 or rpi4"
exit 1
fi
if [ "$model" == "rpi3" ] || [ "$model" == "rpi4" ]; then
container_image=${CONTAINER_IMAGE:-quay.io/costoolkit/examples:rpi-latest}
else
# Odroid C2 image contains kernel-default-extra, might have broader support
container_image=${CONTAINER_IMAGE:-quay.io/costoolkit/examples:odroid-c2-latest}
fi
if [ -n "$cos_config" ] && [ -e "$cos_config" ]; then
# shellcheck source=/dev/null
source "$cos_config"
fi
if [ -z "$output_image" ]; then
echo "No image file specified"
exit 1
fi
if [ -n "$manifest" ]; then
YQ_PACKAGES_COMMAND=(yq e -o=json "$manifest")
final_repo=${final_repo:-$(yq e ".raw_disk.$model.repo" "${manifest}")}
fi
echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
echo "Image Size: $size MB."
echo "Recovery Partition: $recovery_size."
echo "State Partition: $state_size MB."
echo "Images size (active/passive/recovery.img): $default_active_size MB."
echo "Model: $model"
if [ -n "$container_image" ] && [ -z "$directory" ]; then
echo "Container image: $container_image"
fi
if [ -n "$directory" ]; then
echo "Root from directory: $directory"
fi
echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
# Temp dir used during build
WORKDIR=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
#ROOT_DIR=$(git rev-parse --show-toplevel)
TARGET=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
STATEDIR=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
export WORKDIR
# Prepare active.img
echo ">> Preparing active.img"
mkdir -p ${STATEDIR}/cOS
dd if=/dev/zero of=${STATEDIR}/cOS/active.img bs=1M count=$default_active_size
mkfs.ext2 ${STATEDIR}/cOS/active.img -L ${ACTIVE_LABEL}
sync
LOOP=$(losetup --show -f ${STATEDIR}/cOS/active.img)
if [ -z "$LOOP" ]; then
echo "No device"
exit 1
fi
mount -t ext2 $LOOP $TARGET
ensure_dir_structure $TARGET
# Download the container image
if [ -z "$directory" ]; then
echo ">>> Downloading container image"
kairos-agent pull-image $container_image $TARGET
else
echo ">>> Copying files from $directory"
rsync -axq --exclude='host' --exclude='mnt' --exclude='proc' --exclude='sys' --exclude='dev' --exclude='tmp' ${directory}/ $TARGET
fi
# We copy the grubmenu.cfg to a temporary location to be copied later in the state partition
# https://github.com/kairos-io/kairos/blob/62c67e3e61d49435c362014522e5c6696335376f/overlay/files/system/oem/08_grub.yaml#L105
# This is a hack and we need a better way: https://github.com/kairos-io/kairos/issues/1427
tmpgrubconfig=$(mktemp /tmp/grubmeny.cfg.XXXXXX)
cp -rfv $TARGET/etc/kairos/branding/grubmenu.cfg "${tmpgrubconfig}"
umount $TARGET
sync
if [ -n "$LOOP" ]; then
losetup -d $LOOP
fi
echo ">> Preparing passive.img"
cp -rfv ${STATEDIR}/cOS/active.img ${STATEDIR}/cOS/passive.img
tune2fs -L ${PASSIVE_LABEL} ${STATEDIR}/cOS/passive.img
# Preparing recovery
echo ">> Preparing recovery.img"
RECOVERY=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
if [ -z "$RECOVERY" ]; then
echo "No recovery directory"
exit 1
fi
mkdir -p ${RECOVERY}/cOS
cp -rfv ${STATEDIR}/cOS/active.img ${RECOVERY}/cOS/recovery.img
tune2fs -L ${SYSTEM_LABEL} ${RECOVERY}/cOS/recovery.img
# Install real grub config to recovery
cp -rfv /arm/raw/grubconfig/* $RECOVERY
mkdir -p $RECOVERY/grub2/fonts
cp -rfv /arm/raw/grubartifacts/* $RECOVERY/grub2
mv $RECOVERY/grub2/*pf2 $RECOVERY/grub2/fonts
sync
# Prepare efi files
echo ">> Preparing EFI partition"
EFI=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
if [ -z "$EFI" ]; then
echo "No EFI directory"
exit 1
fi
cp -rfv /arm/raw/grubefi/* $EFI
if [ -n "$EFI" ] && [ -n "$efi_dir" ]; then
echo "Copy $efi_dir to EFI directory"
cp -rfv $efi_dir/* $EFI
fi
partprobe
echo ">> Writing image and partition table"
dd if=/dev/zero of="${output_image}" bs=1024000 count="${size}" || exit 1
# Image partitions
# only rpi4 supports gpt
if [ "$model" == "rpi3" ]; then
sgdisk -n 1:8192:+96M -c 1:EFI -t 1:0c00 ${output_image}
sgdisk -n 2:0:+${state_size}M -c 2:state -t 2:8300 ${output_image}
sgdisk -n 3:0:+$(( recovery_size + oem_size ))M -c 3:lvm -t 3:8e00 ${output_image}
sgdisk -n 4:0:+64M -c 4:persistent -t 4:8300 ${output_image}
sgdisk -m 1:2:3:4 ${output_image}
sfdisk --part-type ${output_image} 1 c
elif [ "$model" == "rpi4" ]; then
sgdisk -n 1:0:+96M -c 1:EFI -t 1:ef00 ${output_image}
partprobe
sgdisk -n 2:0:+${state_size}M -c 2:state -t 2:8300 ${output_image}
partprobe
sgdisk -n 3:0:+${recovery_size}M -c 3:recovery -t 3:8300 ${output_image}
partprobe
sgdisk -n 4:0:+${oem_size}M -c 4:oem -t 4:8300 ${output_image}
partprobe
sgdisk -n 5:0:+64M -c 5:persistent -t 5:8300 ${output_image}
partprobe
# Move backup header to end of disk
sgdisk -e ${output_image}
sgdisk -v ${output_image}
else
sgdisk -n 1:8192:+16M -c 1:EFI -t 1:0700 ${output_image}
sgdisk -n 2:0:+${state_size}M -c 2:state -t 2:8300 ${output_image}
sgdisk -n 3:0:+$(( recovery_size + oem_size ))M -c 3:lvm -t 3:8e00 ${output_image}
sgdisk -n 4:0:+64M -c 4:persistent -t 4:8300 ${output_image}
sgdisk -m 1:2:3:4 ${output_image}
fi
# Prepare the image and copy over the files
DRIVE=$(losetup -f "${output_image}" --show)
export DRIVE
if [ -z "${DRIVE}" ]; then
echo "Cannot execute losetup for $output_image"
exit 1
fi
device=${DRIVE/\/dev\//}
if [ -z "$device" ]; then
echo "No device"
exit 1
fi
export device="/dev/mapper/${device}"
partprobe
if [ "$model" == 'rpi4' ]; then
kpartx -vag $DRIVE
else
kpartx -va $DRIVE
fi
echo ">> Populating partitions"
efi=${device}p1
state=${device}p2
recovery=${device}p3
if [ "$model" == 'rpi4' ]; then
oem=${device}p4
persistent=${device}p5
else
persistent=${device}p4
oem_lv=/dev/mapper/KairosVG-oem
recovery_lv=/dev/mapper/KairosVG-recovery
fi
# Create partitions (RECOVERY, STATE, COS_PERSISTENT)
mkfs.vfat -F 32 ${efi}
fatlabel ${efi} COS_GRUB
mkfs.ext3 -F -L ${STATE_LABEL} $state
mkfs.ext3 -F -L ${PERSISTENT_LABEL} $persistent
if [ "$model" == 'rpi4' ]; then
mkfs.ext3 -F -L ${RECOVERY_LABEL} $recovery
mkfs.ext3 -F -L ${OEM_LABEL} $oem
else
pvcreate $recovery
vgcreate KairosVG $recovery
lvcreate -Z n -n oem -L ${oem_size} KairosVG
lvcreate -Z n -n recovery -l 100%FREE KairosVG
vgchange -ay
vgmknodes
mkfs.ext4 -F -L ${OEM_LABEL} $oem_lv
mkfs.ext4 -F -L ${RECOVERY_LABEL} $recovery_lv
fi
mkdir $WORKDIR/state
mkdir $WORKDIR/recovery
mkdir $WORKDIR/efi
mkdir $WORKDIR/oem
mount $state $WORKDIR/state
mount $efi $WORKDIR/efi
if [ "$model" == 'rpi4' ]; then
mount $recovery $WORKDIR/recovery
mount $oem $WORKDIR/oem
else
mount $recovery_lv $WORKDIR/recovery
mount $oem_lv $WORKDIR/oem
fi
cp -rfv /defaults.yaml $WORKDIR/oem/01_defaults.yaml
# Set a OEM config file if specified
if [ -n "$config" ]; then
echo ">> Copying $config OEM config file"
get_url $config $WORKDIR/oem/99_custom.yaml
fi
grub2-editenv $WORKDIR/state/grub_oem_env set "default_menu_entry=$menu_entry"
# We copy the file we saved earier to the STATE partition
cp -rfv "${tmpgrubconfig}" $WORKDIR/state/grubmenu
# Copy over content
cp -arf $EFI/* $WORKDIR/efi
cp -arf $RECOVERY/* $WORKDIR/recovery
cp -arf $STATEDIR/* $WORKDIR/state
umount $WORKDIR/recovery
umount $WORKDIR/state
umount $WORKDIR/efi
umount $WORKDIR/oem
if [ "$model" != 'rpi4' ]; then
vgchange -an
fi
sync
# Flash uboot and vendor-specific bits
echo ">> Performing $model specific bits.."
/arm/boards/$model.sh ${DRIVE}
sync
sleep 5
sync
if [ "$model" == 'rpi4' ]; then
kpartx -dvg $DRIVE
else
kpartx -dv $DRIVE || true
fi
umount $DRIVE || true
echo ">> Done writing $output_image"
echo ">> Creating SHA256 sum"
sha256sum $output_image > $output_image.sha256
cleanup

View File

@@ -1,3 +0,0 @@
iso:
image:
- dir:/grub2

View File

@@ -1,8 +0,0 @@
#cloud-config
name: "Default user"
stages:
initramfs:
- name: "Set default user/pass"
users:
kairos:
passwd: "kairos"

View File

@@ -1,5 +0,0 @@
#!/bin/bash
set -ex
enki --config-dir /config "$@"

View File

@@ -1,15 +0,0 @@
#!/bin/bash
# Transform a raw image disk to gce compatible
RAWIMAGE="$1"
OUT="${2:-$RAWIMAGE.gce.raw}"
cp -rf $RAWIMAGE $OUT
GB=$((1024*1024*1024))
size=$(qemu-img info -f raw --output json "$OUT" | gawk 'match($0, /"virtual-size": ([0-9]+),/, val) {print val[1]}')
# shellcheck disable=SC2004
ROUNDED_SIZE=$(echo "$size/$GB+1"|bc)
echo "Resizing raw image from \"$size\"MB to \"$ROUNDED_SIZE\"GB"
qemu-img resize -f raw "$OUT" "$ROUNDED_SIZE"G
echo "Compressing raw image $OUT to $OUT.tar.gz"
tar -c -z --format=oldgnu -f "$OUT".tar.gz $OUT

View File

@@ -1,7 +0,0 @@
#!ipxe
set dns 8.8.8.8
ifconf
kernel ${RELEASE_URL}/${VERSION}/${ISO_NAME}-kernel root=live:${RELEASE_URL}/${VERSION}/${ISO_NAME}.squashfs initrd=${ISO_NAME}-initrd rd.neednet=1 ip=dhcp rd.cos.disable netboot install-mode config_url=${config} console=tty1 console=ttyS0 rd.live.overlay.overlayfs ${cmdline}
initrd ${RELEASE_URL}/${VERSION}/${ISO_NAME}-initrd
boot

View File

@@ -1,13 +0,0 @@
KAIROS_NAME="${OS_NAME}"
KAIROS_VERSION="${OS_VERSION}"
KAIROS_ID="${OS_ID}"
KAIROS_ID_LIKE="${OS_NAME}"
KAIROS_VERSION_ID="${OS_VERSION}"
KAIROS_PRETTY_NAME="${OS_NAME} ${OS_VERSION}"
KAIROS_BUG_REPORT_URL="${BUG_REPORT_URL}"
KAIROS_HOME_URL="${HOME_URL}"
KAIROS_IMAGE_REPO="${OS_REPO}"
KAIROS_IMAGE_LABEL="${OS_LABEL}"
KAIROS_GITHUB_REPO="${GITHUB_REPO}"
KAIROS_VARIANT="${VARIANT}"
KAIROS_FLAVOR="${FLAVOR}"

View File

@@ -1,16 +0,0 @@
general:
debug: false
spinner_charset: 9
logging:
enable_emoji: false
repositories:
- name: "kairos"
description: "kairos repository"
type: "docker"
cached: true
enable: true
priority: 2
urls:
- "quay.io/kairos/packages"
# renovate: datasource=docker depName=quay.io/kairos/packages
reference: 202410070953-git9232f7ee-repository.yaml

View File

@@ -1,16 +0,0 @@
general:
debug: false
spinner_charset: 9
logging:
enable_emoji: false
repositories:
- name: "kairos-arm64"
description: "kairos repository arm64"
type: "docker"
cached: true
enable: true
priority: 2
urls:
- "quay.io/kairos/packages-arm64"
# renovate: datasource=docker depName=quay.io/kairos/packages-arm64
reference: 202410071128-git9232f7ee-repository.yaml

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# Extracts squashfs, kernel, initrd and generates a ipxe template script
ISO=$1
OUTPUT_NAME=$2
ARTIFACT_NAME=$(basename $OUTPUT_NAME)
isoinfo -x /rootfs.squashfs -R -i $ISO > $OUTPUT_NAME.squashfs
isoinfo -x /boot/kernel -R -i $ISO > $OUTPUT_NAME-kernel
isoinfo -x /boot/initrd -R -i $ISO > $OUTPUT_NAME-initrd
URL=${URL:-https://github.com/kairos-io/kairos/releases/download}
cat > $OUTPUT_NAME.ipxe << EOF
#!ipxe
set url ${URL}/
set kernel $ARTIFACT_NAME-kernel
set initrd $ARTIFACT_NAME-initrd
set rootfs $ARTIFACT_NAME.squashfs
# set config https://example.com/machine-config
# set cmdline extra.values=1
kernel \${url}/\${kernel} initrd=\${initrd} ip=dhcp rd.cos.disable root=live:\${url}/\${rootfs} netboot install-mode config_url=\${config} console=tty1 console=ttyS0 \${cmdline}
initrd \${url}/\${initrd}
boot
EOF

View File

@@ -1,140 +0,0 @@
#!/bin/bash
# This script prepares Kairos state, recovery, oem and pesistent partitions as img files.
set -e
# Temp dir used during build
WORKDIR=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
TARGET=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
STATEDIR=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
: "${OEM_LABEL:=COS_OEM}"
: "${RECOVERY_LABEL:=COS_RECOVERY}"
: "${ACTIVE_LABEL:=COS_ACTIVE}"
: "${PASSIVE_LABEL:=COS_PASSIVE}"
: "${PERSISTENT_LABEL:=COS_PERSISTENT}"
: "${SYSTEM_LABEL:=COS_SYSTEM}"
: "${STATE_LABEL:=COS_STATE}"
size="${SIZE:-7544}"
state_size="${STATE_SIZE:-4992}"
recovery_size="${RECOVERY_SIZE:-2192}"
default_active_size="${DEFAULT_ACTIVE_SIZE:-2400}"
menu_entry="${DEFAULT_MENU_ENTRY:-Kairos}"
container_image="${container_image:-quay.io/kairos/kairos-opensuse-leap-arm-rpi:v1.5.1-k3sv1.25.6-k3s1}"
ensure_dir_structure() {
local target=$1
for mnt in /sys /proc /dev /tmp /boot /usr/local /oem
do
if [ ! -d "${target}${mnt}" ]; then
mkdir -p ${target}${mnt}
fi
done
}
mkdir -p $WORKDIR/tmpefi
# Create the EFI partition FAT16 and include the EFI image and a basic grub.cfg
truncate -s $((20*1024*1024)) bootloader/efi.img
cp -rfv /arm/raw/grubefi/* $WORKDIR/tmpefi
mkfs.fat -F16 -n COS_GRUB bootloader/efi.img
mcopy -s -i bootloader/efi.img $WORKDIR/tmpefi/EFI ::EFI
mkdir -p ${STATEDIR}/cOS
dd if=/dev/zero of=${STATEDIR}/cOS/active.img bs=1M count=$default_active_size
mkfs.ext2 ${STATEDIR}/cOS/active.img -L ${ACTIVE_LABEL}
LOOP=$(losetup --show -f ${STATEDIR}/cOS/active.img)
if [ -z "$LOOP" ]; then
echo "No device"
exit 1
fi
mount -t ext2 $LOOP $TARGET
ensure_dir_structure $TARGET
# Download the container image
if [ -z "$directory" ]; then
echo ">>> Downloading container image"
luet util unpack $container_image $TARGET
else
echo ">>> Copying files from $directory"
rsync -axq --exclude='host' --exclude='mnt' --exclude='proc' --exclude='sys' --exclude='dev' --exclude='tmp' ${directory}/ $TARGET
fi
# We copy the grubmenu.cfg to a temporary location to be copied later in the state partition
# https://github.com/kairos-io/kairos/blob/62c67e3e61d49435c362014522e5c6696335376f/overlay/files/system/oem/08_grub.yaml#L105
# This is a hack and we need a better way: https://github.com/kairos-io/kairos/issues/1427
tmpgrubconfig=$(mktemp /tmp/grubmeny.cfg.XXXXXX)
cp -rfv $TARGET/etc/kairos/branding/grubmenu.cfg "${tmpgrubconfig}"
umount $TARGET
sync
losetup -d $LOOP
echo ">> Preparing passive.img"
cp -rfv ${STATEDIR}/cOS/active.img ${STATEDIR}/cOS/passive.img
tune2fs -L ${PASSIVE_LABEL} ${STATEDIR}/cOS/passive.img
# Preparing recovery
echo ">> Preparing recovery.img"
RECOVERY=$(mktemp -d --tmpdir arm-builder.XXXXXXXXXX)
mkdir -p ${RECOVERY}/cOS
cp -rfv ${STATEDIR}/cOS/active.img ${RECOVERY}/cOS/recovery.img
tune2fs -L ${SYSTEM_LABEL} ${RECOVERY}/cOS/recovery.img
# Install real grub config to recovery
cp -rfv /arm/raw/grubconfig/* $RECOVERY
mkdir -p $RECOVERY/grub2/fonts
cp -rfv /arm/raw/grubartifacts/* $RECOVERY/grub2
mv $RECOVERY/grub2/*pf2 $RECOVERY/grub2/fonts
dd if=/dev/zero of=recovery_partition.img bs=1M count=$recovery_size
dd if=/dev/zero of=state_partition.img bs=1M count=$state_size
mkfs.ext4 -F -L ${RECOVERY_LABEL} recovery_partition.img
LOOP=$(losetup --show -f recovery_partition.img)
mkdir -p $WORKDIR/recovery
mount $LOOP $WORKDIR/recovery
cp -arf $RECOVERY/* $WORKDIR/recovery
umount $WORKDIR/recovery
losetup -d $LOOP
mkfs.ext4 -F -L ${STATE_LABEL} state_partition.img
LOOP=$(losetup --show -f state_partition.img)
mkdir -p $WORKDIR/state
mount $LOOP $WORKDIR/state
cp -arf $STATEDIR/* $WORKDIR/state
grub2-editenv $WORKDIR/state/grub_oem_env set "default_menu_entry=$menu_entry"
# We copy the file we saved earier to the STATE partition
cp -rfv "${tmpgrubconfig}" $WORKDIR/state/grubmenu
umount $WORKDIR/state
losetup -d $LOOP
cp -rfv state_partition.img bootloader/
cp -rfv recovery_partition.img bootloader/
## Optional, prepare COS_OEM and COS_PERSISTENT
# Create the grubenv forcing first boot to be on recovery system
mkdir -p $WORKDIR/oem
cp -rfv /defaults.yaml $WORKDIR/oem/01_defaults.yaml
# Create a 64MB filesystem for OEM volume
truncate -s $((64*1024*1024)) bootloader/oem.img
mkfs.ext2 -L "${OEM_LABEL}" -d $WORKDIR/oem bootloader/oem.img
# Create a 2GB filesystem for COS_PERSISTENT volume
truncate -s $((2048*1024*1024)) bootloader/persistent.img
mkfs.ext2 -L "${PERSISTENT_LABEL}" bootloader/persistent.img

View File

@@ -1,81 +0,0 @@
#!/bin/bash
# Generates EFI bootable images (statically)
# This is a re-adaptation of https://github.com/rancher/elemental-toolkit/blob/v0.8.10-1/images/img-builder.sh, which was dropped
# How to use:
# First extract the image which you want to create an image from:
### luet util unpack <image> rootfs
# Then convert it to a raw disk (EFI only):
### docker run -v $PWD:/output --entrypoint /raw-images.sh -ti --rm test-image /output/rootfs /output/foo.raw cloud-init.yaml
: "${OEM_LABEL:=COS_OEM}"
: "${RECOVERY_LABEL:=COS_RECOVERY}"
: "${EXTEND:=}"
: "${RECOVERY_SIZE:=2048}"
DIRECTORY=$1
OUT=${2:-disk.raw}
CONFIG=$3
echo "Output: $OUT"
set -e
mkdir -p /build/root/grub2
mkdir /build/root/cOS
mkdir /build/efi
cp -rf /raw/grub/* /build/efi
cp -rf /raw/grubconfig/* /build/root
cp -rf /raw/grubartifacts/* /build/root/grub2
echo "Generating squashfs from $DIRECTORY"
mksquashfs $DIRECTORY recovery.squashfs -b 1024k -comp xz -Xbcj x86
mv recovery.squashfs /build/root/cOS/recovery.squashfs
grub2-editenv /build/root/grub_oem_env set "default_menu_entry=Kairos"
# Create a 2GB filesystem for RECOVERY including the contents for root (grub config and squasfs container)
# shellcheck disable=SC2004
truncate -s $(($RECOVERY_SIZE*1024*1024)) rootfs.part
mkfs.ext2 -L "${RECOVERY_LABEL}" -d /build/root rootfs.part
# Create the EFI partition FAT16 and include the EFI image and a basic grub.cfg
truncate -s $((20*1024*1024)) efi.part
mkfs.fat -F16 -n COS_GRUB efi.part
mcopy -s -i efi.part /build/efi/EFI ::EFI
# Create the grubenv forcing first boot to be on recovery system
mkdir -p /build/oem
cp /build/root/etc/cos/grubenv_firstboot /build/oem/grubenv
if [ -n "$CONFIG" ]; then
echo "Copying config file ($CONFIG)"
cp $CONFIG /build/oem
fi
# Create a 64MB filesystem for OEM volume
truncate -s $((64*1024*1024)) oem.part
mkfs.ext2 -L "${OEM_LABEL}" -d /build/oem oem.part
echo "Generating image $OUT"
# Create disk image, add 3MB of initial free space to disk, 1MB is for proper alignement, 2MB are for the hybrid legacy boot.
truncate -s $((3*1024*1024)) $OUT
{
cat efi.part
cat oem.part
cat rootfs.part
} >> $OUT
# Add an extra MB at the end of the disk for the gpt headers, in fact 34 sectors would be enough, but adding some more does not hurt.
truncate -s "+$((1024*1024))" $OUT
if [ -n "$EXTEND" ]; then
echo "Extending image of $EXTEND MB"
truncate -s "+$((EXTEND*1024*1024))" $OUT
fi
# Create the partition table in $OUT (assumes sectors of 512 bytes)
sgdisk -n 1:2048:+2M -c 1:legacy -t 1:EF02 $OUT
sgdisk -n 2:0:+20M -c 2:UEFI -t 2:EF00 $OUT
sgdisk -n 3:0:+64M -c 3:oem -t 3:8300 $OUT
sgdisk -n 4:0:+${RECOVERY_SIZE}M -c 4:root -t 4:8300 $OUT

View File

@@ -1,25 +0,0 @@
#!/bin/bash
# usage:
# docker run --rm -ti --entrypoint /update-os-release.sh \
# -v /etc:/workspace \ # mount the directory where your os-release is, this is by default in /etc but you can mount a different dir for testing
# -e OS_NAME=kairos-core-opensuse-leap \
# -e OS_VERSION=v2.2.0 \
# -e OS_ID="kairos" \
# -e OS_NAME=kairos-core-opensuse-leap \
# -e BUG_REPORT_URL="https://github.com/kairos-io/kairos/issues" \
# -e HOME_URL="https://github.com/kairos-io/kairos" \
# -e OS_REPO="quay.io/kairos/core-opensuse-leap" \
# -e OS_LABEL="latest" \
# -e GITHUB_REPO="kairos-io/kairos" \
# -e VARIANT="core" \
# -e FLAVOR="opensuse-leap"
# quay.io/kairos/osbuilder-tools:latest
set -ex
[ -f "/workspace/kairos-release" ] && sed -i -n '/KAIROS_/!p' /workspace/kairos-release
# Clean up old os-release just in case so we dont have stuff lying around
sed -i -n '/KAIROS_/!p' /workspace/os-release
envsubst >>/workspace/kairos-release < /kairos-release.tmpl
cat /workspace/kairos-release