mirror of
https://github.com/kairos-io/osbuilder.git
synced 2025-12-24 20:34:11 +00:00
Compare commits
173 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ce271c3a | ||
|
|
19b3a57878 | ||
|
|
f3ee5c3dc1 | ||
|
|
4aa7211f96 | ||
|
|
3aa1fa1104 | ||
|
|
8813eb4809 | ||
|
|
95509370f6 | ||
|
|
42fde4a1fb | ||
|
|
169ba969f3 | ||
|
|
6b7aac0e4b | ||
|
|
405eda716a | ||
|
|
c77f6c564a | ||
|
|
ac3bc8bcc6 | ||
|
|
cf125b7371 | ||
|
|
ff80eae58f | ||
|
|
8b243a00fa | ||
|
|
745b2a18f1 | ||
|
|
28ff9cf07b | ||
|
|
19d96a575a | ||
|
|
902fd4f3eb | ||
|
|
69396c7e73 | ||
|
|
6daeff735e | ||
|
|
87716e187f | ||
|
|
f70e8d5cde | ||
|
|
bc34ec93a9 | ||
|
|
95ea70f0c3 | ||
|
|
f9a79f337d | ||
|
|
e43cc63dec | ||
|
|
308e1cf479 | ||
|
|
5c512fa5d4 | ||
|
|
bdcfec72d0 | ||
|
|
551e6018b4 | ||
|
|
45ca6ce3e4 | ||
|
|
2b922cedd7 | ||
|
|
601d02d4f7 | ||
|
|
823c3f5d6e | ||
|
|
f090954bac | ||
|
|
c3f4026908 | ||
|
|
fedf3911a0 | ||
|
|
2f7cd450cd | ||
|
|
ce1cd74d17 | ||
|
|
6b9a74b772 | ||
|
|
240d812cca | ||
|
|
fa781ea885 | ||
|
|
d951f97d84 | ||
|
|
6a04794629 | ||
|
|
b6aafd6e23 | ||
|
|
9b9a4b248b | ||
|
|
925f177f73 | ||
|
|
ffd6afdfbd | ||
|
|
37d0d025d1 | ||
|
|
b16b67488a | ||
|
|
c49b394e6c | ||
|
|
bf4cb26932 | ||
|
|
94b36a31f8 | ||
|
|
18122a9656 | ||
|
|
9b9a0383ce | ||
|
|
a06f226c7b | ||
|
|
9cd36189cd | ||
|
|
0eb05808fc | ||
|
|
a29158475f | ||
|
|
36ea48fa23 | ||
|
|
3daf5c5421 | ||
|
|
29f9840d90 | ||
|
|
22133b515c | ||
|
|
d9148667f2 | ||
|
|
24b7fe2a6d | ||
|
|
93bd16dd0f | ||
|
|
0dca7d9ea1 | ||
|
|
570b6a244f | ||
|
|
f4842fba7e | ||
|
|
95739cf7e6 | ||
|
|
9392be9bd8 | ||
|
|
8b2fac5bbb | ||
|
|
2806239183 | ||
|
|
66c7aa5fb3 | ||
|
|
a6d03cffdf | ||
|
|
b21a5725a8 | ||
|
|
46d1e8547b | ||
|
|
29b4eb5964 | ||
|
|
0340dbf5bf | ||
|
|
2a55c3e080 | ||
|
|
beabe36c36 | ||
|
|
9af9939367 | ||
|
|
0fe28a034f | ||
|
|
db9c52985b | ||
|
|
fbe3874152 | ||
|
|
77756071c6 | ||
|
|
ee5df3f0ea | ||
|
|
cbc77a5493 | ||
|
|
7813c2eec4 | ||
|
|
32cd7c76cb | ||
|
|
82c9a08353 | ||
|
|
dcd3b4be97 | ||
|
|
cedc294836 | ||
|
|
c0fc19b345 | ||
|
|
40477e3ceb | ||
|
|
7df2f481f8 | ||
|
|
907b32d5e5 | ||
|
|
446807680b | ||
|
|
e118e769fe | ||
|
|
97b58723a9 | ||
|
|
8570a9ed85 | ||
|
|
aa04578df9 | ||
|
|
39cfe7af30 | ||
|
|
10d5f627fc | ||
|
|
89dec58dd9 | ||
|
|
54ae9607ee | ||
|
|
ddb88c0e2e | ||
|
|
2c18ce3fbd | ||
|
|
7c417e773f | ||
|
|
ba9290acb7 | ||
|
|
627d7e87ed | ||
|
|
f00b095ec1 | ||
|
|
10b1baff0d | ||
|
|
5c13bd2bdc | ||
|
|
c86be82d66 | ||
|
|
104eb89548 | ||
|
|
815a9e2898 | ||
|
|
52475cf069 | ||
|
|
f30e9a6a6a | ||
|
|
7005550b32 | ||
|
|
18959ee26a | ||
|
|
cbc6ca3fc0 | ||
|
|
44fb9dc165 | ||
|
|
902c311c17 | ||
|
|
f8a2ff5531 | ||
|
|
ca2d1fc9cc | ||
|
|
c2748aecad | ||
|
|
b834974606 | ||
|
|
30f8b9cb1c | ||
|
|
2b601c1420 | ||
|
|
e5cded4980 | ||
|
|
a482917a27 | ||
|
|
48c41866ed | ||
|
|
89f39c7ada | ||
|
|
5158b5b7f3 | ||
|
|
d73c9528b1 | ||
|
|
b54cac66b7 | ||
|
|
754ac31929 | ||
|
|
87c309421d | ||
|
|
939857b201 | ||
|
|
b82332d9bf | ||
|
|
f0cec49b08 | ||
|
|
f32d1c4baa | ||
|
|
1a34dc98f2 | ||
|
|
b7af88b618 | ||
|
|
275bbf8871 | ||
|
|
866dc42c48 | ||
|
|
e4482ddc08 | ||
|
|
a946063eac | ||
|
|
73afbbf332 | ||
|
|
17ef38753d | ||
|
|
bcda489c96 | ||
|
|
e731abc99b | ||
|
|
1354314fd4 | ||
|
|
98bb28c046 | ||
|
|
9f456b0119 | ||
|
|
1dc94d57c8 | ||
|
|
08db23aca8 | ||
|
|
89b014b2b2 | ||
|
|
9a6003118e | ||
|
|
f67f1939b9 | ||
|
|
d744abf9ab | ||
|
|
320dd7a070 | ||
|
|
66e9e7a940 | ||
|
|
b9f6893e28 | ||
|
|
e933ba05d5 | ||
|
|
6e0146ef4d | ||
|
|
be735dccad | ||
|
|
196c84dee0 | ||
|
|
862d937209 | ||
|
|
8fc4c182ba |
2
.github/workflows/bump_repos.yml
vendored
2
.github/workflows/bump_repos.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install earthly
|
||||
uses: Luet-lab/luet-install-action@v1
|
||||
with:
|
||||
|
||||
24
.github/workflows/enki.yml
vendored
24
.github/workflows/enki.yml
vendored
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: 'run enki unit tests'
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: enki-${{ github.ref || github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: earthly/actions-setup@v1.0.7
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
run: cd tools-image/enki && earthly -P +test
|
||||
6
.github/workflows/image.yml
vendored
6
.github/workflows/image.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Prepare
|
||||
id: prep
|
||||
@@ -50,14 +50,14 @@ jobs:
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
|
||||
4
.github/workflows/lint.yaml
vendored
4
.github/workflows/lint.yaml
vendored
@@ -15,7 +15,7 @@ env:
|
||||
FORCE_COLOR: 1
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: kairos-io/linting-composite-action/.github/workflows/reusable-linting.yaml@v0.0.8
|
||||
uses: kairos-io/linting-composite-action/.github/workflows/reusable-linting.yaml@v0.0.10
|
||||
with:
|
||||
yamldirs: ".github/workflows/ config/ tools-image/"
|
||||
yamldirs: ".github/workflows/ config/"
|
||||
is-go: true
|
||||
|
||||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Test
|
||||
run: |
|
||||
make kind-e2e-tests
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Test
|
||||
run: |
|
||||
make controller-tests
|
||||
|
||||
66
.github/workflows/tool-image.yml
vendored
66
.github/workflows/tool-image.yml
vendored
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: 'build tools container images'
|
||||
|
||||
on:
|
||||
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@v3
|
||||
|
||||
- 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@v2
|
||||
with:
|
||||
registry: quay.io
|
||||
username: ${{ secrets.QUAY_USERNAME }}
|
||||
password: ${{ secrets.QUAY_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v4
|
||||
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 }}
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build the manager binary
|
||||
FROM golang:1.20 as builder
|
||||
FROM golang:1.23 AS builder
|
||||
|
||||
WORKDIR /workspace
|
||||
# Copy the Go Modules manifests
|
||||
|
||||
25
Earthfile
25
Earthfile
@@ -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
|
||||
|
||||
6
Makefile
6
Makefile
@@ -177,13 +177,15 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest
|
||||
|
||||
## Tool Versions
|
||||
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"
|
||||
.PHONY: kustomize
|
||||
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
|
||||
$(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
|
||||
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
|
||||
|
||||
@@ -66,7 +66,7 @@ To install, use helm:
|
||||
# Adds the kairos repo to helm
|
||||
$ helm repo add kairos https://kairos-io.github.io/helm-charts
|
||||
"kairos" has been added to your repositories
|
||||
$ helm repo update
|
||||
$ helm repo update
|
||||
Hang tight while we grab the latest from your chart repositories...
|
||||
...Successfully got an update from the "kairos" chart repository
|
||||
Update Complete. ⎈Happy Helming!⎈
|
||||
|
||||
@@ -49,8 +49,9 @@ type OSArtifactSpec struct {
|
||||
CloudConfigRef *SecretKeySelector `json:"cloudConfigRef,omitempty"`
|
||||
GRUBConfig string `json:"grubConfig,omitempty"`
|
||||
|
||||
Bundles []string `json:"bundles,omitempty"`
|
||||
OSRelease string `json:"osRelease,omitempty"`
|
||||
Bundles []string `json:"bundles,omitempty"`
|
||||
OSRelease string `json:"osRelease,omitempty"`
|
||||
KairosRelease string `json:"kairosRelease,omitempty"`
|
||||
|
||||
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||
Exporters []batchv1.JobSpec `json:"exporters,omitempty"`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: manager-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
@@ -16,15 +15,6 @@ rules:
|
||||
- ""
|
||||
resources:
|
||||
- persistentvolumeclaims
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- create
|
||||
|
||||
@@ -18,6 +18,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -107,6 +108,29 @@ func osReleaseContainer(containerImage string) corev1.Container {
|
||||
}
|
||||
}
|
||||
|
||||
func kairosReleaseContainer(containerImage string) corev1.Container {
|
||||
return corev1.Container{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
Name: "kairos-release",
|
||||
Image: containerImage,
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
"cp -rfv /etc/kairos-release /rootfs/etc/kairos-release",
|
||||
},
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "config",
|
||||
MountPath: "/etc/kairos-release",
|
||||
SubPath: "kairos-release",
|
||||
},
|
||||
{
|
||||
Name: "rootfs",
|
||||
MountPath: "/rootfs",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OSArtifactReconciler) newArtifactPVC(artifact *osbuilder.OSArtifact) *corev1.PersistentVolumeClaim {
|
||||
if artifact.Spec.Volume == nil {
|
||||
artifact.Spec.Volume = &corev1.PersistentVolumeClaimSpec{
|
||||
@@ -133,10 +157,10 @@ func (r *OSArtifactReconciler) newArtifactPVC(artifact *osbuilder.OSArtifact) *c
|
||||
}
|
||||
|
||||
func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder.OSArtifact) *corev1.Pod {
|
||||
cmd := fmt.Sprintf(
|
||||
"/entrypoint.sh --debug --name %s build-iso --date=false --output /artifacts dir:/rootfs",
|
||||
artifact.Name,
|
||||
)
|
||||
var cmd strings.Builder
|
||||
cmd.WriteString("auroraboot --debug build-iso")
|
||||
cmd.WriteString(fmt.Sprintf(" --override-name %s", artifact.Name))
|
||||
cmd.WriteString(" --date=false")
|
||||
|
||||
volumeMounts := []corev1.VolumeMount{
|
||||
{
|
||||
@@ -157,27 +181,29 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
})
|
||||
}
|
||||
|
||||
cloudImgCmd := fmt.Sprintf(
|
||||
"/raw-images.sh /rootfs /artifacts/%s.raw",
|
||||
artifact.Name,
|
||||
)
|
||||
var cloudImgCmd strings.Builder
|
||||
cloudImgCmd.WriteString("auroraboot --debug")
|
||||
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 {
|
||||
volumeMounts = append(volumeMounts, corev1.VolumeMount{
|
||||
Name: "cloudconfig",
|
||||
MountPath: "/iso/iso-overlay/cloud_config.yaml",
|
||||
MountPath: "/cloud-config.yaml",
|
||||
SubPath: artifact.Spec.CloudConfigRef.Key,
|
||||
})
|
||||
|
||||
cloudImgCmd += " /iso/iso-overlay/cloud_config.yaml"
|
||||
cloudImgCmd.WriteString(" --cloud-config /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 != "" {
|
||||
cmd = fmt.Sprintf(
|
||||
"/entrypoint.sh --debug --name %s build-iso --date=false --overlay-iso /iso/iso-overlay --output /artifacts dir:/rootfs",
|
||||
artifact.Name,
|
||||
)
|
||||
cmd.WriteString(" --cloud-config /cloud-config.yaml")
|
||||
}
|
||||
cmd.WriteString(" --output /artifacts dir:/rootfs")
|
||||
|
||||
buildIsoContainer := corev1.Container{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
@@ -186,7 +212,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
Image: r.ToolImage,
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
cmd,
|
||||
cmd.String(),
|
||||
},
|
||||
VolumeMounts: volumeMounts,
|
||||
}
|
||||
@@ -199,7 +225,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
cloudImgCmd,
|
||||
cloudImgCmd.String(),
|
||||
},
|
||||
VolumeMounts: volumeMounts,
|
||||
}
|
||||
@@ -211,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{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||
@@ -222,15 +254,24 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
Value: artifact.Spec.NetbootURL,
|
||||
}},
|
||||
Args: []string{
|
||||
fmt.Sprintf(
|
||||
"/netboot.sh /artifacts/%s.iso /artifacts/%s",
|
||||
artifact.Name,
|
||||
artifact.Name,
|
||||
),
|
||||
netbootCmd.String(),
|
||||
},
|
||||
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{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||
@@ -238,15 +279,24 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
Image: r.ToolImage,
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
fmt.Sprintf(
|
||||
"/azure.sh /artifacts/%s.raw /artifacts/%s.vhd",
|
||||
artifact.Name,
|
||||
artifact.Name,
|
||||
),
|
||||
azureCmd.String(),
|
||||
},
|
||||
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{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||
@@ -254,11 +304,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
Image: r.ToolImage,
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
fmt.Sprintf(
|
||||
"/gce.sh /artifacts/%s.raw /artifacts/%s.gce.raw",
|
||||
artifact.Name,
|
||||
artifact.Name,
|
||||
),
|
||||
gceCmd.String(),
|
||||
},
|
||||
VolumeMounts: volumeMounts,
|
||||
}
|
||||
@@ -358,9 +404,15 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
if artifact.Spec.OSRelease != "" {
|
||||
podSpec.InitContainers = append(podSpec.InitContainers, osReleaseContainer(r.ToolImage))
|
||||
}
|
||||
if artifact.Spec.KairosRelease != "" {
|
||||
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 {
|
||||
podSpec.Containers = append(podSpec.Containers, buildIsoContainer)
|
||||
podSpec.InitContainers = append(podSpec.InitContainers, buildIsoContainer)
|
||||
}
|
||||
|
||||
if artifact.Spec.Netboot {
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
|
||||
const (
|
||||
FinalizerName = "build.kairos.io/osbuilder-finalizer"
|
||||
CompatibleAurorabootVersion = "v0.14.0"
|
||||
artifactLabel = "build.kairos.io/artifact"
|
||||
artifactExporterIndexAnnotation = "build.kairos.io/export-index"
|
||||
)
|
||||
@@ -127,6 +128,14 @@ func (r *OSArtifactReconciler) createPVC(ctx context.Context, artifact *osbuilde
|
||||
return pvc, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/phayes/freeport"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -20,6 +19,7 @@ import (
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var _ = Describe("OSArtifactReconciler", func() {
|
||||
@@ -51,20 +51,15 @@ var _ = Describe("OSArtifactReconciler", func() {
|
||||
utilruntime.Must(clientgoscheme.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{
|
||||
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())
|
||||
})
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,10 +21,12 @@ import (
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
@@ -73,7 +75,6 @@ var _ = BeforeSuite(func() {
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
@@ -100,10 +101,27 @@ func createRandomNamespace(clientset *kubernetes.Clientset) string {
|
||||
}, metav1.CreateOptions{})
|
||||
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
|
||||
}
|
||||
|
||||
func deleteNamepace(clientset *kubernetes.Clientset, name string) {
|
||||
err := clientset.CoreV1().Namespaces().Delete(context.Background(), name, metav1.DeleteOptions{})
|
||||
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")
|
||||
}
|
||||
|
||||
32
go.mod
32
go.mod
@@ -1,10 +1,10 @@
|
||||
module github.com/kairos-io/osbuilder
|
||||
|
||||
go 1.18
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/onsi/ginkgo/v2 v2.11.0
|
||||
github.com/onsi/gomega v1.27.10
|
||||
github.com/onsi/ginkgo/v2 v2.20.2
|
||||
github.com/onsi/gomega v1.34.2
|
||||
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
|
||||
k8s.io/api v0.24.0
|
||||
k8s.io/apimachinery v0.24.0
|
||||
@@ -25,28 +25,28 @@ require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/emicklei/go-restful v2.16.0+incompatible // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/zapr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/gnostic v0.5.7-v3refs // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
@@ -60,17 +60,17 @@ require (
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/term v0.27.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
79
go.sum
79
go.sum
@@ -127,8 +127,9 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.16.0+incompatible h1:rgqiKNjTnFQA6kkhFe16D8epTksy9HQ1MyrbDXSdYhM=
|
||||
github.com/emicklei/go-restful v2.16.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -164,8 +165,8 @@ 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.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.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
|
||||
github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@@ -178,8 +179,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
@@ -242,8 +243,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
@@ -265,8 +266,9 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -341,8 +343,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
@@ -377,23 +380,21 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||
github.com/onsi/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.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
|
||||
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
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/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
@@ -473,8 +474,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
@@ -522,6 +524,7 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
@@ -540,10 +543,9 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/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-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
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-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -580,7 +582,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -630,10 +631,8 @@ 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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -729,16 +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-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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -748,10 +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -819,10 +812,8 @@ 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.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.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
|
||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -957,8 +948,8 @@ 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/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
3
main.go
3
main.go
@@ -18,6 +18,7 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
// 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.")
|
||||
|
||||
// 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.BoolVar(&enableLeaderElection, "leader-elect", false,
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": [
|
||||
"after 11pm every weekday",
|
||||
"before 7am every weekday",
|
||||
"every weekend"
|
||||
],
|
||||
"timezone": "Europe/Brussels"
|
||||
}
|
||||
26
renovate.json5
Normal file
26
renovate.json5
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": [
|
||||
"after 11pm every weekday",
|
||||
"before 7am every weekday",
|
||||
"every weekend"
|
||||
],
|
||||
"timezone": "Europe/Brussels",
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "repositories",
|
||||
"matchPackagePatterns": ["^quay.io/kairos/packages*"]
|
||||
},
|
||||
{
|
||||
"matchPackagePatterns": ["^quay.io/kairos/packages*"],
|
||||
"versioning": "regex:^(?<major>\\d{12})-git(?<patch>[a-f0-9]{8})-repository\\.yaml$"
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"automerge": true
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TODO: Bump to some recent kubernetes version
|
||||
KUBE_VERSION=${KUBE_VERSION:-v1.22.7}
|
||||
CLUSTER_NAME="${CLUSTER_NAME:-kairos-osbuilder-e2e}"
|
||||
|
||||
@@ -30,4 +31,4 @@ EXTERNAL_IP=$(kubectl get nodes -o jsonpath='{.items[].status.addresses[?(@.type
|
||||
export EXTERNAL_IP
|
||||
export BRIDGE_IP="172.18.0.1"
|
||||
kubectl get nodes -o wide
|
||||
cd $ROOT_DIR/tests && $GINKGO -r -v ./e2e
|
||||
cd $ROOT_DIR/tests && $GINKGO -r -v ./e2e
|
||||
|
||||
356
tests/e2e/e2e_formats_test.go
Normal file
356
tests/e2e/e2e_formats_test.go
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,41 +1,21 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "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"
|
||||
)
|
||||
|
||||
var _ = Describe("ISO build test", func() {
|
||||
var artifactName string
|
||||
var artifacts, pods, pvcs, jobs dynamic.ResourceInterface
|
||||
var scheme *runtime.Scheme
|
||||
var artifactLabelSelector labels.Selector
|
||||
var tc *TestClients
|
||||
|
||||
BeforeEach(func() {
|
||||
k8s := dynamic.NewForConfigOrDie(ctrl.GetConfigOrDie())
|
||||
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")
|
||||
tc = SetupTestClients()
|
||||
|
||||
artifact := &osbuilder.OSArtifact{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -46,7 +26,7 @@ var _ = Describe("ISO build test", func() {
|
||||
GenerateName: "simple-",
|
||||
},
|
||||
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,
|
||||
DiskSize: "",
|
||||
Exporters: []batchv1.JobSpec{
|
||||
@@ -76,87 +56,12 @@ var _ = Describe("ISO build test", func() {
|
||||
},
|
||||
}
|
||||
|
||||
uArtifact := unstructured.Unstructured{}
|
||||
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)
|
||||
artifactName, artifactLabelSelector = tc.CreateArtifact(artifact)
|
||||
})
|
||||
|
||||
It("works", func() {
|
||||
By("starting the build")
|
||||
Eventually(func(g Gomega) {
|
||||
w, err := pods.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("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))
|
||||
tc.WaitForBuildCompletion(artifactName, artifactLabelSelector)
|
||||
tc.WaitForExportCompletion(artifactLabelSelector)
|
||||
tc.Cleanup(artifactName, artifactLabelSelector)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,13 +1,160 @@
|
||||
package e2e_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
osbuilder "github.com/kairos-io/osbuilder/api/v1alpha2"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "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) {
|
||||
RegisterFailHandler(Fail)
|
||||
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))
|
||||
}
|
||||
|
||||
2
tests/fixtures/simple.yaml
vendored
2
tests/fixtures/simple.yaml
vendored
@@ -31,7 +31,7 @@ spec:
|
||||
fi
|
||||
menuentry "install" --class os --unrestricted {
|
||||
echo Loading kernel...
|
||||
$linux ($root)/boot/kernel.xz cdroot root=live:CDLABEL=COS_LIVE rd.live.dir=/ rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 rd.cos.disable vga=795 nomodeset nodepair.enable
|
||||
$linux ($root)/boot/kernel.xz cdroot root=live:CDLABEL=COS_LIVE rd.live.dir=/ rd.live.squashimg=rootfs.squashfs console=tty1 console=ttyS0 rd.cos.disable vga=795 nomodeset install-mode
|
||||
echo Loading initrd...
|
||||
$initrd ($root)/boot/rootfs.xz
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
# https://quay.io/repository/kairos/packages?tab=tags&tag=latest
|
||||
ARG LEAP_VERSION=15.5
|
||||
ARG LUET_VERSION=0.34.0
|
||||
FROM quay.io/luet/base:$LUET_VERSION AS luet
|
||||
|
||||
FROM golang:1.20 as enki
|
||||
ENV CGO_ENABLED=0
|
||||
COPY ./enki /src/enki
|
||||
WORKDIR /src/enki
|
||||
RUN go mod download
|
||||
# Set arg/env after go mod download, otherwise we invalidate the cached layers due to the commit changing easily
|
||||
ARG ENKI_VERSION=0.0.1
|
||||
ARG ENKI_COMMIT=""
|
||||
ENV ENKI_VERSION=${ENKI_VERSION}
|
||||
ENV ENKI_COMMIT=${ENKI_COMMIT}
|
||||
RUN go build \
|
||||
-ldflags "-w -s \
|
||||
-X github.com/kairos-io/enki/internal/version.version=$ENKI_VERSION \
|
||||
-X github.com/kairos-io/enki/internal/version.gitCommit=$ENKI_COMMIT" \
|
||||
-o /usr/bin/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 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
|
||||
|
||||
## 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 ./os-release.tmpl /os-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 /usr/bin/enki /usr/bin/enki
|
||||
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
@@ -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}"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -1,511 +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}
|
||||
disable_lvm=${DISABLE_LVM:-false}
|
||||
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;
|
||||
dmsetup remove KairosVG-oem || true;
|
||||
dmsetup remove KairosVG-recovery || true;
|
||||
}
|
||||
|
||||
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 " --local: (optional) Use local repository when building"
|
||||
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"
|
||||
echo " --disable-lvm: (optional- no arguments) LVM for the recovery and oem partitions will be disabled"
|
||||
echo " --use-lvm: (deprecated and ignored. Kept for backwards compatibility)"
|
||||
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
|
||||
;;
|
||||
--local)
|
||||
local_build=true
|
||||
;;
|
||||
--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
|
||||
;;
|
||||
--disable-lvm)
|
||||
disable_lvm=true
|
||||
;;
|
||||
--use-lvm)
|
||||
disable_lvm=false
|
||||
;;
|
||||
-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
|
||||
echo "label: gpt" | sfdisk "${output_image}"
|
||||
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}M -c 3:recovery -t 3:8300 ${output_image}
|
||||
sgdisk -n 4:0:+${oem_size}M -c 4:oem -t 4:8300 ${output_image}
|
||||
sgdisk -n 5:0:+64M -c 5:persistent -t 5:8300 ${output_image}
|
||||
sgdisk -g ${output_image}
|
||||
sgdisk -m 1:2:3:4:5 ${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.ext4 -F -L ${STATE_LABEL} $state
|
||||
mkfs.ext4 -F -L ${PERSISTENT_LABEL} $persistent
|
||||
|
||||
if [ "$model" == 'rpi4' ]; then
|
||||
mkfs.ext4 -F -L ${RECOVERY_LABEL} $recovery
|
||||
mkfs.ext4 -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
|
||||
@@ -1,6 +0,0 @@
|
||||
iso:
|
||||
uefi:
|
||||
- dir:/efi
|
||||
image:
|
||||
- dir:/efi
|
||||
- dir:/grub2
|
||||
@@ -1,8 +0,0 @@
|
||||
#cloud-config
|
||||
name: "Default user"
|
||||
stages:
|
||||
initramfs:
|
||||
- name: "Set default user/pass"
|
||||
users:
|
||||
kairos:
|
||||
passwd: "kairos"
|
||||
@@ -1,19 +0,0 @@
|
||||
VERSION 0.7
|
||||
|
||||
# renovate: datasource=docker depName=golang
|
||||
ARG --global GO_VERSION=1.20-alpine3.18
|
||||
|
||||
test:
|
||||
FROM golang:$GO_VERSION
|
||||
RUN apk add rsync gcc musl-dev docker jq
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN go mod download
|
||||
ARG TEST_PATHS=./...
|
||||
ARG LABEL_FILTER=
|
||||
ENV CGO_ENABLED=1
|
||||
# Some test require the docker sock exposed
|
||||
WITH DOCKER
|
||||
RUN go run github.com/onsi/ginkgo/v2/ginkgo run --label-filter "$LABEL_FILTER" -v --fail-fast --race --covermode=atomic --coverprofile=coverage.out --coverpkg=github.com/kairos-io/enki/... -p -r $TEST_PATHS
|
||||
END
|
||||
SAVE ARTIFACT coverage.out AS LOCAL coverage.out
|
||||
@@ -1,122 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/kairos-io/enki/pkg/action"
|
||||
"github.com/kairos-io/enki/pkg/config"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/mount-utils"
|
||||
)
|
||||
|
||||
// NewBuildISOCmd returns a new instance of the build-iso subcommand and appends it to
|
||||
// the root command.
|
||||
func NewBuildISOCmd() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "build-iso SOURCE",
|
||||
Short: "Build bootable installation media ISOs",
|
||||
Long: "Build bootable installation media ISOs\n\n" +
|
||||
"SOURCE - should be provided as uri in following format <sourceType>:<sourceName>\n" +
|
||||
" * <sourceType> - might be [\"dir\", \"file\", \"oci\", \"docker\"], as default is \"docker\"\n" +
|
||||
" * <sourceName> - is path to file or directory, image name with tag version",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
return CheckRoot()
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
path, err := exec.LookPath("mount")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mounter := mount.New(path)
|
||||
|
||||
cfg, err := config.ReadConfigBuild(viper.GetString("config-dir"), cmd.Flags(), mounter)
|
||||
if err != nil {
|
||||
cfg.Logger.Errorf("Error reading config: %s\n", err)
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
// Set this after parsing of the flags, so it fails on parsing and prints usage properly
|
||||
cmd.SilenceUsage = true
|
||||
cmd.SilenceErrors = true // Do not propagate errors down the line, we control them
|
||||
spec, err := config.ReadBuildISO(cfg, flags)
|
||||
if err != nil {
|
||||
cfg.Logger.Errorf("invalid install command setup %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
imgSource, err := v1.NewSrcFromURI(args[0])
|
||||
if err != nil {
|
||||
cfg.Logger.Errorf("not a valid rootfs source image argument: %s", args[0])
|
||||
return err
|
||||
}
|
||||
spec.RootFS = []*v1.ImageSource{imgSource}
|
||||
} else if len(spec.RootFS) == 0 {
|
||||
errmsg := "rootfs source image for building ISO was not provided"
|
||||
cfg.Logger.Errorf(errmsg)
|
||||
return fmt.Errorf(errmsg)
|
||||
}
|
||||
|
||||
// Repos and overlays can't be unmarshaled directly as they require
|
||||
// to be merged on top and flags do not match any config value key
|
||||
oRootfs, _ := flags.GetString("overlay-rootfs")
|
||||
oUEFI, _ := flags.GetString("overlay-uefi")
|
||||
oISO, _ := flags.GetString("overlay-iso")
|
||||
|
||||
if oRootfs != "" {
|
||||
if ok, err := utils.Exists(cfg.Fs, oRootfs); ok {
|
||||
spec.RootFS = append(spec.RootFS, v1.NewDirSrc(oRootfs))
|
||||
} else {
|
||||
cfg.Logger.Errorf("Invalid value for overlay-rootfs")
|
||||
return fmt.Errorf("Invalid path '%s': %v", oRootfs, err)
|
||||
}
|
||||
}
|
||||
if oUEFI != "" {
|
||||
if ok, err := utils.Exists(cfg.Fs, oUEFI); ok {
|
||||
spec.UEFI = append(spec.UEFI, v1.NewDirSrc(oUEFI))
|
||||
} else {
|
||||
cfg.Logger.Errorf("Invalid value for overlay-uefi")
|
||||
return fmt.Errorf("Invalid path '%s': %v", oUEFI, err)
|
||||
}
|
||||
}
|
||||
if oISO != "" {
|
||||
if ok, err := utils.Exists(cfg.Fs, oISO); ok {
|
||||
spec.Image = append(spec.Image, v1.NewDirSrc(oISO))
|
||||
} else {
|
||||
cfg.Logger.Errorf("Invalid value for overlay-iso")
|
||||
return fmt.Errorf("Invalid path '%s': %v", oISO, err)
|
||||
}
|
||||
}
|
||||
|
||||
buildISO := action.NewBuildISOAction(cfg, spec)
|
||||
err = buildISO.ISORun()
|
||||
if err != nil {
|
||||
cfg.Logger.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
c.Flags().StringP("name", "n", "", "Basename of the generated ISO file")
|
||||
c.Flags().StringP("output", "o", "", "Output directory (defaults to current directory)")
|
||||
c.Flags().Bool("date", false, "Adds a date suffix into the generated ISO file")
|
||||
c.Flags().String("overlay-rootfs", "", "Path of the overlayed rootfs data")
|
||||
c.Flags().String("overlay-uefi", "", "Path of the overlayed uefi data")
|
||||
c.Flags().String("overlay-iso", "", "Path of the overlayed iso data")
|
||||
c.Flags().String("label", "", "Label of the ISO volume")
|
||||
archType := newEnumFlag([]string{"x86_64", "arm64"}, "x86_64")
|
||||
c.Flags().Bool("squash-no-compression", true, "Disable squashfs compression.")
|
||||
c.Flags().VarP(archType, "arch", "a", "Arch to build the image for")
|
||||
return c
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(NewBuildISOCmd())
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright © 2022 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var _ = Describe("BuildISO", Label("iso", "cmd"), func() {
|
||||
var buf *bytes.Buffer
|
||||
BeforeEach(func() {
|
||||
buf = new(bytes.Buffer)
|
||||
rootCmd.SetOut(buf)
|
||||
rootCmd.SetErr(buf)
|
||||
})
|
||||
AfterEach(func() {
|
||||
viper.Reset()
|
||||
})
|
||||
It("Errors out if no rootfs sources are defined", Label("flags"), func() {
|
||||
_, _, err := executeCommandC(rootCmd, "build-iso")
|
||||
fmt.Println(buf)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("rootfs source image for building ISO was not provided"))
|
||||
})
|
||||
It("Errors out if rootfs is a non valid argument", Label("flags"), func() {
|
||||
_, _, err := executeCommandC(rootCmd, "build-iso", "/no/image/reference")
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("invalid image reference"))
|
||||
})
|
||||
It("Errors out if overlay roofs path does not exist", Label("flags"), func() {
|
||||
_, _, err := executeCommandC(
|
||||
rootCmd, "build-iso", "system/cos", "--overlay-rootfs", "/nonexistingpath",
|
||||
)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("Invalid path"))
|
||||
})
|
||||
It("Errors out if overlay uefi path does not exist", Label("flags"), func() {
|
||||
_, _, err := executeCommandC(
|
||||
rootCmd, "build-iso", "someimage:latest", "--overlay-uefi", "/nonexistingpath",
|
||||
)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("Invalid path"))
|
||||
})
|
||||
It("Errors out if overlay iso path does not exist", Label("flags"), func() {
|
||||
_, _, err := executeCommandC(
|
||||
rootCmd, "build-iso", "some/image:latest", "--overlay-iso", "/nonexistingpath",
|
||||
)
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("Invalid path"))
|
||||
})
|
||||
})
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
Copyright © 2021 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestWhitebox(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "CLI whitebox test suite")
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
Copyright © 2021 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func executeCommandC(cmd *cobra.Command, args ...string) (c *cobra.Command, output string, err error) {
|
||||
// Set args to command
|
||||
cmd.SetArgs(args)
|
||||
// store old stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
// Change stdout to our pipe
|
||||
os.Stdout = w
|
||||
// run the command
|
||||
c, err = cmd.ExecuteC()
|
||||
if err != nil {
|
||||
// Remember to restore stdout!
|
||||
os.Stdout = oldStdout
|
||||
return nil, "", err
|
||||
}
|
||||
err = w.Close()
|
||||
if err != nil {
|
||||
// Remember to restore stdout!
|
||||
os.Stdout = oldStdout
|
||||
return nil, "", err
|
||||
}
|
||||
// Read output from our pipe
|
||||
out, _ := ioutil.ReadAll(r)
|
||||
// restore stdout
|
||||
os.Stdout = oldStdout
|
||||
|
||||
return c, string(out), nil
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func NewRootCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "enki",
|
||||
Short: "enki",
|
||||
}
|
||||
cmd.PersistentFlags().Bool("debug", false, "Enable debug output")
|
||||
cmd.PersistentFlags().String("config-dir", "/etc/elemental", "Set config dir (default is /etc/elemental)")
|
||||
cmd.PersistentFlags().String("logfile", "", "Set logfile")
|
||||
cmd.PersistentFlags().Bool("quiet", false, "Do not output to stdout")
|
||||
_ = viper.BindPFlag("debug", cmd.PersistentFlags().Lookup("debug"))
|
||||
_ = viper.BindPFlag("config-dir", cmd.PersistentFlags().Lookup("config-dir"))
|
||||
_ = viper.BindPFlag("logfile", cmd.PersistentFlags().Lookup("logfile"))
|
||||
_ = viper.BindPFlag("quiet", cmd.PersistentFlags().Lookup("quiet"))
|
||||
|
||||
if viper.GetBool("debug") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = NewRootCmd()
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
|
||||
err := rootCmd.Execute()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// CheckRoot is a helper to return on PreRunE, so we can add it to commands that require root
|
||||
func CheckRoot() error {
|
||||
if os.Geteuid() != 0 {
|
||||
return errors.New("this command requires root privileges")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type enum struct {
|
||||
Allowed []string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (a enum) String() string {
|
||||
return a.Value
|
||||
}
|
||||
|
||||
func (a *enum) Set(p string) error {
|
||||
isIncluded := func(opts []string, val string) bool {
|
||||
for _, opt := range opts {
|
||||
if val == opt {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if !isIncluded(a.Allowed, p) {
|
||||
return fmt.Errorf("%s is not included in %s", p, strings.Join(a.Allowed, ","))
|
||||
}
|
||||
a.Value = p
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *enum) Type() string {
|
||||
return "string"
|
||||
}
|
||||
|
||||
// newEnum give a list of allowed flag parameters, where the second argument is the default
|
||||
func newEnumFlag(allowed []string, d string) *enum {
|
||||
return &enum{
|
||||
Allowed: allowed,
|
||||
Value: d,
|
||||
}
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
module github.com/kairos-io/enki
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/kairos-io/kairos-agent/v2 v2.1.11-0.20230713071318-9a16b94e2af6
|
||||
github.com/kairos-io/kairos-sdk v0.0.9-0.20230719194412-fe26d1de9166
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/onsi/ginkgo/v2 v2.11.0
|
||||
github.com/onsi/gomega v1.27.10
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/twpayne/go-vfs v1.7.2
|
||||
k8s.io/mount-utils v0.27.3
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
atomicgo.dev/cursor v0.1.3 // indirect
|
||||
atomicgo.dev/keyboard v0.2.9 // indirect
|
||||
atomicgo.dev/schedule v0.0.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.10.0-rc.8 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230117203413-a47887b8f098 // indirect
|
||||
github.com/StackExchange/wmi v1.2.1 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59 // indirect
|
||||
github.com/cavaliergopher/grab v2.0.0+incompatible // indirect
|
||||
github.com/cavaliergopher/grab/v3 v3.0.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.1 // indirect
|
||||
github.com/containerd/cgroups v1.1.0 // indirect
|
||||
github.com/containerd/console v1.0.3 // indirect
|
||||
github.com/containerd/containerd v1.7.1 // indirect
|
||||
github.com/containerd/continuity v0.3.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/denisbrodbeck/machineid v1.0.1 // indirect
|
||||
github.com/diskfs/go-diskfs v1.3.0 // indirect
|
||||
github.com/distribution/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v23.0.6+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/go-git/go-git/v5 v5.4.2 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-containerregistry v0.15.2 // indirect
|
||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/gookit/color v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.15 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/itchyny/gojq v0.12.12 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.5 // indirect
|
||||
github.com/jaypipes/ghw v0.10.0 // indirect
|
||||
github.com/jaypipes/pcidb v1.0.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/kendru/darwin/go/depgraph v0.0.0-20221105232959-877d6a81060c // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.5 // indirect
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/moby v23.0.4+incompatible // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/mudler/entities v0.0.0-20220905203055-68348bae0f49 // indirect
|
||||
github.com/mudler/yip v1.3.1-0.20230704124832-e5812d0f5890 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc3 // indirect
|
||||
github.com/packethost/packngo v0.29.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee // indirect
|
||||
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pkg/xattr v0.4.9 // indirect
|
||||
github.com/pterm/pterm v0.12.63 // indirect
|
||||
github.com/qeesung/image2ascii v1.0.1 // indirect
|
||||
github.com/rancher-sandbox/linuxkit v1.0.1-0.20230517173613-432a87ba3e09 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/samber/lo v1.37.0 // indirect
|
||||
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/spectrocloud-labs/herd v0.4.2 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tredoe/osutil/v2 v2.0.0-rc.16 // indirect
|
||||
github.com/ulikunitz/xz v0.5.11 // indirect
|
||||
github.com/vbatts/tar-split v0.11.3 // indirect
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/vmware/vmw-guestinfo v0.0.0-20220317130741-510905f0efa3 // indirect
|
||||
github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect
|
||||
github.com/willdonnelly/passwd v0.0.0-20141013001024-7935dab3074c // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/zcalusic/sysinfo v0.9.5 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/net v0.12.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/djherbis/times.v1 v1.3.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
k8s.io/klog/v2 v2.90.1 // indirect
|
||||
k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect
|
||||
pault.ag/go/modprobe v0.1.2 // indirect
|
||||
pault.ag/go/topsort v0.1.1 // indirect
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,36 +0,0 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "v0.0.1"
|
||||
// gitCommit is the git sha1
|
||||
gitCommit = ""
|
||||
)
|
||||
|
||||
// BuildInfo describes the compile time information.
|
||||
type BuildInfo struct {
|
||||
// Version is the current semver.
|
||||
Version string `json:"version,omitempty"`
|
||||
// GitCommit is the git sha1.
|
||||
GitCommit string `json:"git_commit,omitempty"`
|
||||
// GoVersion is the version of the Go compiler used.
|
||||
GoVersion string `json:"go_version,omitempty"`
|
||||
}
|
||||
|
||||
func GetVersion() string {
|
||||
return version
|
||||
}
|
||||
|
||||
// Get returns build info
|
||||
func Get() BuildInfo {
|
||||
v := BuildInfo{
|
||||
Version: GetVersion(),
|
||||
GitCommit: gitCommit,
|
||||
GoVersion: runtime.Version(),
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/kairos-io/enki/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
Copyright © 2022 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestActionSuite(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Actions test suite")
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
package action
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/elemental"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
sdk "github.com/kairos-io/kairos-sdk/utils"
|
||||
)
|
||||
|
||||
type BuildISOAction struct {
|
||||
cfg *v1.BuildConfig
|
||||
spec *v1.LiveISO
|
||||
e *elemental.Elemental
|
||||
}
|
||||
|
||||
type BuildISOActionOption func(a *BuildISOAction)
|
||||
|
||||
func NewBuildISOAction(cfg *v1.BuildConfig, spec *v1.LiveISO, opts ...BuildISOActionOption) *BuildISOAction {
|
||||
b := &BuildISOAction{
|
||||
cfg: cfg,
|
||||
e: elemental.NewElemental(&cfg.Config),
|
||||
spec: spec,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(b)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ISORun will install the system from a given configuration
|
||||
func (b *BuildISOAction) ISORun() (err error) {
|
||||
cleanup := sdk.NewCleanStack()
|
||||
defer func() { err = cleanup.Cleanup(err) }()
|
||||
|
||||
isoTmpDir, err := utils.TempDir(b.cfg.Fs, "", "enki-iso")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cleanup.Push(func() error { return b.cfg.Fs.RemoveAll(isoTmpDir) })
|
||||
|
||||
rootDir := filepath.Join(isoTmpDir, "rootfs")
|
||||
err = utils.MkdirAll(b.cfg.Fs, rootDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uefiDir := filepath.Join(isoTmpDir, "uefi")
|
||||
err = utils.MkdirAll(b.cfg.Fs, uefiDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isoDir := filepath.Join(isoTmpDir, "iso")
|
||||
err = utils.MkdirAll(b.cfg.Fs, isoDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if b.cfg.OutDir != "" {
|
||||
err = utils.MkdirAll(b.cfg.Fs, b.cfg.OutDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed creating output folder: %s", b.cfg.OutDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b.cfg.Logger.Infof("Preparing squashfs root...")
|
||||
err = b.applySources(rootDir, b.spec.RootFS...)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed installing OS packages: %v", err)
|
||||
return err
|
||||
}
|
||||
err = utils.CreateDirStructure(b.cfg.Fs, rootDir)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed creating root directory structure: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Infof("Preparing EFI image...")
|
||||
err = b.applySources(uefiDir, b.spec.UEFI...)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed installing EFI packages: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Infof("Preparing ISO image root tree...")
|
||||
err = b.applySources(isoDir, b.spec.Image...)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed installing ISO image packages: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.prepareISORoot(isoDir, rootDir, uefiDir)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed preparing ISO's root tree: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Infof("Creating ISO image...")
|
||||
err = b.burnISO(isoDir)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Errorf("Failed preparing ISO's root tree: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b BuildISOAction) prepareISORoot(isoDir string, rootDir string, uefiDir string) error {
|
||||
kernel, initrd, err := b.e.FindKernelInitrd(rootDir)
|
||||
if err != nil {
|
||||
b.cfg.Logger.Error("Could not find kernel and/or initrd")
|
||||
return err
|
||||
}
|
||||
err = utils.MkdirAll(b.cfg.Fs, filepath.Join(isoDir, "boot"), constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//TODO document boot/kernel and boot/initrd expectation in bootloader config
|
||||
b.cfg.Logger.Debugf("Copying Kernel file %s to iso root tree", kernel)
|
||||
err = utils.CopyFile(b.cfg.Fs, kernel, filepath.Join(isoDir, constants.IsoKernelPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Debugf("Copying initrd file %s to iso root tree", initrd)
|
||||
err = utils.CopyFile(b.cfg.Fs, initrd, filepath.Join(isoDir, constants.IsoInitrdPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Info("Creating squashfs...")
|
||||
err = utils.CreateSquashFS(b.cfg.Runner, b.cfg.Logger, rootDir, filepath.Join(isoDir, constants.IsoRootFile), constants.GetDefaultSquashfsOptions())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.cfg.Logger.Info("Creating EFI image...")
|
||||
err = b.createEFI(uefiDir, filepath.Join(isoDir, constants.IsoEFIPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b BuildISOAction) createEFI(root string, img string) error {
|
||||
efiSize, err := utils.DirSize(b.cfg.Fs, root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// align efiSize to the next 4MB slot
|
||||
align := int64(4 * 1024 * 1024)
|
||||
efiSizeMB := (efiSize/align*align + align) / (1024 * 1024)
|
||||
|
||||
err = b.e.CreateFileSystemImage(&v1.Image{
|
||||
File: img,
|
||||
Size: uint(efiSizeMB),
|
||||
FS: constants.EfiFs,
|
||||
Label: constants.EfiLabel,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := b.cfg.Fs.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
_, err = b.cfg.Runner.Run("mcopy", "-s", "-i", img, filepath.Join(root, f.Name()), "::")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b BuildISOAction) burnISO(root string) error {
|
||||
cmd := "xorriso"
|
||||
var outputFile string
|
||||
var isoFileName string
|
||||
|
||||
if b.cfg.Date {
|
||||
currTime := time.Now()
|
||||
isoFileName = fmt.Sprintf("%s.%s.iso", b.cfg.Name, currTime.Format("20060102"))
|
||||
} else {
|
||||
isoFileName = fmt.Sprintf("%s.iso", b.cfg.Name)
|
||||
}
|
||||
|
||||
outputFile = isoFileName
|
||||
if b.cfg.OutDir != "" {
|
||||
outputFile = filepath.Join(b.cfg.OutDir, outputFile)
|
||||
}
|
||||
|
||||
if exists, _ := utils.Exists(b.cfg.Fs, outputFile); exists {
|
||||
b.cfg.Logger.Warnf("Overwriting already existing %s", outputFile)
|
||||
err := b.cfg.Fs.Remove(outputFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-volid", b.spec.Label, "-joliet", "on", "-padding", "0",
|
||||
"-outdev", outputFile, "-map", root, "/", "-chmod", "0755", "--",
|
||||
}
|
||||
args = append(args, constants.GetXorrisoBooloaderArgs(root)...)
|
||||
|
||||
out, err := b.cfg.Runner.Run(cmd, args...)
|
||||
b.cfg.Logger.Debugf("Xorriso: %s", string(out))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checksum, err := utils.CalcFileChecksum(b.cfg.Fs, outputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checksum computation failed: %w", err)
|
||||
}
|
||||
err = b.cfg.Fs.WriteFile(fmt.Sprintf("%s.sha256", outputFile), []byte(fmt.Sprintf("%s %s\n", checksum, isoFileName)), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write checksum file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b BuildISOAction) applySources(target string, sources ...*v1.ImageSource) error {
|
||||
for _, src := range sources {
|
||||
_, err := b.e.DumpSource(target, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *BuildISOAction) PrepareEFI(rootDir, uefiDir string) error {
|
||||
err := utils.MkdirAll(g.cfg.Fs, filepath.Join(uefiDir, constants.EfiBootPath), constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch g.cfg.Arch {
|
||||
case constants.ArchAmd64, constants.Archx86:
|
||||
err = utils.CopyFile(
|
||||
g.cfg.Fs,
|
||||
filepath.Join(rootDir, constants.GrubEfiImagex86),
|
||||
filepath.Join(uefiDir, constants.GrubEfiImagex86Dest),
|
||||
)
|
||||
case constants.ArchArm64:
|
||||
err = utils.CopyFile(
|
||||
g.cfg.Fs,
|
||||
filepath.Join(rootDir, constants.GrubEfiImageArm64),
|
||||
filepath.Join(uefiDir, constants.GrubEfiImageArm64Dest),
|
||||
)
|
||||
default:
|
||||
err = fmt.Errorf("Not supported architecture: %v", g.cfg.Arch)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return g.cfg.Fs.WriteFile(filepath.Join(uefiDir, constants.EfiBootPath, constants.GrubCfg), []byte(constants.GrubEfiCfg), constants.FilePerm)
|
||||
}
|
||||
|
||||
func (g *BuildISOAction) PrepareISO(rootDir, imageDir string) error {
|
||||
|
||||
err := utils.MkdirAll(g.cfg.Fs, filepath.Join(imageDir, constants.GrubPrefixDir), constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch g.cfg.Arch {
|
||||
case constants.ArchAmd64, constants.Archx86:
|
||||
// Create eltorito image
|
||||
eltorito, err := g.BuildEltoritoImg(rootDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Inlude loaders in expected paths
|
||||
loaderDir := filepath.Join(imageDir, constants.IsoLoaderPath)
|
||||
err = utils.MkdirAll(g.cfg.Fs, loaderDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
loaderFiles := []string{eltorito, constants.GrubBootHybridImg}
|
||||
loaderFiles = append(loaderFiles, strings.Split(constants.SyslinuxFiles, " ")...)
|
||||
for _, f := range loaderFiles {
|
||||
err = utils.CopyFile(g.cfg.Fs, filepath.Join(rootDir, f), loaderDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fontsDir := filepath.Join(loaderDir, "/grub2/fonts")
|
||||
err = utils.MkdirAll(g.cfg.Fs, fontsDir, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = utils.CopyFile(g.cfg.Fs, filepath.Join(rootDir, constants.GrubFont), fontsDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case constants.ArchArm64:
|
||||
// TBC
|
||||
default:
|
||||
return fmt.Errorf("Not supported architecture: %v", g.cfg.Arch)
|
||||
}
|
||||
|
||||
// Write grub.cfg file
|
||||
err = g.cfg.Fs.WriteFile(
|
||||
filepath.Join(imageDir, constants.GrubPrefixDir, constants.GrubCfg),
|
||||
[]byte(fmt.Sprintf(constants.GrubCfgTemplate, g.spec.GrubEntry, g.spec.Label)),
|
||||
constants.FilePerm,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Include EFI contents in iso root too
|
||||
return g.PrepareEFI(rootDir, imageDir)
|
||||
}
|
||||
|
||||
func (g *BuildISOAction) BuildEltoritoImg(rootDir string) (string, error) {
|
||||
var args []string
|
||||
args = append(args, "-O", constants.GrubBiosTarget)
|
||||
args = append(args, "-o", constants.GrubBiosImg)
|
||||
args = append(args, "-p", constants.GrubPrefixDir)
|
||||
args = append(args, "-d", constants.GrubI386BinDir)
|
||||
args = append(args, strings.Split(constants.GrubModules, " ")...)
|
||||
|
||||
chRoot := utils.NewChroot(rootDir, &g.cfg.Config)
|
||||
out, err := chRoot.Run("grub2-mkimage", args...)
|
||||
if err != nil {
|
||||
g.cfg.Logger.Errorf("grub2-mkimage failed: %s", string(out))
|
||||
g.cfg.Logger.Errorf("Error: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
concatFiles := func() error {
|
||||
return utils.ConcatFiles(
|
||||
g.cfg.Fs, []string{constants.GrubBiosCDBoot, constants.GrubBiosImg},
|
||||
constants.GrubEltoritoImg,
|
||||
)
|
||||
}
|
||||
err = chRoot.RunCallback(concatFiles)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return constants.GrubEltoritoImg, nil
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
Copyright © 2022 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kairos-io/enki/pkg/action"
|
||||
"github.com/kairos-io/enki/pkg/config"
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"github.com/twpayne/go-vfs/vfst"
|
||||
)
|
||||
|
||||
var _ = Describe("BuildISOAction", func() {
|
||||
var cfg *v1.BuildConfig
|
||||
var runner *v1mock.FakeRunner
|
||||
var fs vfs.FS
|
||||
var logger v1.Logger
|
||||
var mounter *v1mock.ErrorMounter
|
||||
var syscall *v1mock.FakeSyscall
|
||||
var client *v1mock.FakeHTTPClient
|
||||
var cloudInit *v1mock.FakeCloudInitRunner
|
||||
var cleanup func()
|
||||
var memLog *bytes.Buffer
|
||||
var imageExtractor *v1mock.FakeImageExtractor
|
||||
BeforeEach(func() {
|
||||
runner = v1mock.NewFakeRunner()
|
||||
syscall = &v1mock.FakeSyscall{}
|
||||
mounter = v1mock.NewErrorMounter()
|
||||
client = &v1mock.FakeHTTPClient{}
|
||||
memLog = &bytes.Buffer{}
|
||||
logger = v1.NewBufferLogger(memLog)
|
||||
logger.SetLevel(logrus.DebugLevel)
|
||||
cloudInit = &v1mock.FakeCloudInitRunner{}
|
||||
fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{})
|
||||
imageExtractor = v1mock.NewFakeImageExtractor(logger)
|
||||
|
||||
cfg = config.NewBuildConfig(
|
||||
config.WithFs(fs),
|
||||
config.WithRunner(runner),
|
||||
config.WithLogger(logger),
|
||||
config.WithMounter(mounter),
|
||||
config.WithSyscall(syscall),
|
||||
config.WithClient(client),
|
||||
config.WithCloudInitRunner(cloudInit),
|
||||
config.WithImageExtractor(imageExtractor),
|
||||
)
|
||||
})
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
Describe("Build ISO", Label("iso"), func() {
|
||||
var iso *v1.LiveISO
|
||||
BeforeEach(func() {
|
||||
iso = config.NewISO()
|
||||
|
||||
tmpDir, err := utils.TempDir(fs, "", "test")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
cfg.Date = false
|
||||
cfg.OutDir = tmpDir
|
||||
|
||||
runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
|
||||
switch cmd {
|
||||
case "xorriso":
|
||||
err := fs.WriteFile(filepath.Join(tmpDir, "elemental.iso"), []byte("profound thoughts"), constants.FilePerm)
|
||||
return []byte{}, err
|
||||
default:
|
||||
return []byte{}, nil
|
||||
}
|
||||
}
|
||||
})
|
||||
It("Successfully builds an ISO from a Docker image", func() {
|
||||
rootSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.RootFS = []*v1.ImageSource{rootSrc}
|
||||
uefiSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.UEFI = []*v1.ImageSource{uefiSrc}
|
||||
imageSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.Image = []*v1.ImageSource{imageSrc}
|
||||
|
||||
// Create kernel and vmlinuz
|
||||
// Thanks to the testfs stuff in utils.TempDir we know what the temp fs is gonna be as
|
||||
// its predictable
|
||||
bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot")
|
||||
err := utils.MkdirAll(fs, bootDir, constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(bootDir, "vmlinuz"))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(bootDir, "initrd"))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
buildISO := action.NewBuildISOAction(cfg, iso)
|
||||
err = buildISO.ISORun()
|
||||
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("Fails if kernel or initrd is not found in rootfs", func() {
|
||||
rootSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.RootFS = []*v1.ImageSource{rootSrc}
|
||||
uefiSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.UEFI = []*v1.ImageSource{uefiSrc}
|
||||
imageSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.Image = []*v1.ImageSource{imageSrc}
|
||||
|
||||
By("fails without kernel")
|
||||
buildISO := action.NewBuildISOAction(cfg, iso)
|
||||
err := buildISO.ISORun()
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("No file found with prefixes"))
|
||||
Expect(err.Error()).To(ContainSubstring("uImage Image zImage vmlinuz image"))
|
||||
|
||||
bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot")
|
||||
err = utils.MkdirAll(fs, bootDir, constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(bootDir, "vmlinuz"))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
By("fails without initrd")
|
||||
buildISO = action.NewBuildISOAction(cfg, iso)
|
||||
err = buildISO.ISORun()
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("No file found with prefixes"))
|
||||
Expect(err.Error()).To(ContainSubstring("initrd initramfs"))
|
||||
})
|
||||
It("Fails installing image sources", func() {
|
||||
rootSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.RootFS = []*v1.ImageSource{rootSrc}
|
||||
uefiSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.UEFI = []*v1.ImageSource{uefiSrc}
|
||||
imageSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.Image = []*v1.ImageSource{imageSrc}
|
||||
|
||||
imageExtractor.SideEffect = func(imageRef, destination, platformRef string) error {
|
||||
return fmt.Errorf("uh oh")
|
||||
}
|
||||
|
||||
buildISO := action.NewBuildISOAction(cfg, iso)
|
||||
err := buildISO.ISORun()
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("uh oh"))
|
||||
})
|
||||
It("Fails on ISO filesystem creation", func() {
|
||||
rootSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.RootFS = []*v1.ImageSource{rootSrc}
|
||||
uefiSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.UEFI = []*v1.ImageSource{uefiSrc}
|
||||
imageSrc, _ := v1.NewSrcFromURI("oci:image:version")
|
||||
iso.Image = []*v1.ImageSource{imageSrc}
|
||||
|
||||
bootDir := filepath.Join("/tmp/enki-iso/rootfs", "boot")
|
||||
err := utils.MkdirAll(fs, bootDir, constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(bootDir, "vmlinuz"))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create(filepath.Join(bootDir, "initrd"))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
runner.SideEffect = func(command string, args ...string) ([]byte, error) {
|
||||
if command == "xorriso" {
|
||||
return []byte{}, errors.New("Burn ISO error")
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
buildISO := action.NewBuildISOAction(cfg, iso)
|
||||
err = buildISO.ISORun()
|
||||
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("Burn ISO error"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,303 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/kairos-io/enki/internal/version"
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/cloudinit"
|
||||
"github.com/kairos-io/kairos-agent/v2/pkg/http"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/sanity-io/litter"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"io"
|
||||
"io/fs"
|
||||
"k8s.io/mount-utils"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var decodeHook = viper.DecodeHook(
|
||||
mapstructure.ComposeDecodeHookFunc(
|
||||
UnmarshalerHook(),
|
||||
mapstructure.StringToTimeDurationHookFunc(),
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
),
|
||||
)
|
||||
|
||||
func WithFs(fs v1.FS) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Fs = fs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithLogger(logger v1.Logger) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Logger = logger
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithSyscall(syscall v1.SyscallInterface) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Syscall = syscall
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithMounter(mounter mount.Interface) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Mounter = mounter
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithRunner(runner v1.Runner) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Runner = runner
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithClient(client v1.HTTPClient) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Client = client
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCloudInitRunner(ci v1.CloudInitRunner) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.CloudInitRunner = ci
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithArch(arch string) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.Arch = arch
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithImageExtractor(extractor v1.ImageExtractor) func(r *v1.Config) error {
|
||||
return func(r *v1.Config) error {
|
||||
r.ImageExtractor = extractor
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type GenericOptions func(a *v1.Config) error
|
||||
|
||||
func ReadConfigBuild(configDir string, flags *pflag.FlagSet, mounter mount.Interface) (*v1.BuildConfig, error) {
|
||||
logger := v1.NewLogger()
|
||||
if configDir == "" {
|
||||
configDir = "."
|
||||
}
|
||||
|
||||
cfg := NewBuildConfig(
|
||||
WithLogger(logger),
|
||||
WithMounter(mounter),
|
||||
)
|
||||
|
||||
configLogger(cfg.Logger, cfg.Fs)
|
||||
|
||||
viper.AddConfigPath(configDir)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.SetConfigName("manifest.yaml")
|
||||
// If a config file is found, read it in.
|
||||
_ = viper.MergeInConfig()
|
||||
|
||||
// Bind buildconfig flags
|
||||
bindGivenFlags(viper.GetViper(), flags)
|
||||
|
||||
// unmarshal all the vars into the config object
|
||||
err := viper.Unmarshal(cfg, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
cfg.Logger.Warnf("error unmarshalling config: %s", err)
|
||||
}
|
||||
|
||||
err = cfg.Sanitize()
|
||||
cfg.Logger.Debugf("Full config loaded: %s", litter.Sdump(cfg))
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func ReadBuildISO(b *v1.BuildConfig, flags *pflag.FlagSet) (*v1.LiveISO, error) {
|
||||
iso := NewISO()
|
||||
vp := viper.Sub("iso")
|
||||
if vp == nil {
|
||||
vp = viper.New()
|
||||
}
|
||||
// Bind build-iso cmd flags
|
||||
bindGivenFlags(vp, flags)
|
||||
|
||||
err := vp.Unmarshal(iso, setDecoder, decodeHook)
|
||||
if err != nil {
|
||||
b.Logger.Warnf("error unmarshalling LiveISO: %s", err)
|
||||
}
|
||||
err = iso.Sanitize()
|
||||
b.Logger.Debugf("Loaded LiveISO: %s", litter.Sdump(iso))
|
||||
return iso, err
|
||||
}
|
||||
|
||||
func NewISO() *v1.LiveISO {
|
||||
return &v1.LiveISO{
|
||||
Label: constants.ISOLabel,
|
||||
GrubEntry: constants.GrubDefEntry,
|
||||
UEFI: []*v1.ImageSource{},
|
||||
Image: []*v1.ImageSource{},
|
||||
}
|
||||
}
|
||||
|
||||
func NewBuildConfig(opts ...GenericOptions) *v1.BuildConfig {
|
||||
b := &v1.BuildConfig{
|
||||
Config: *NewConfig(opts...),
|
||||
Name: constants.BuildImgName,
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func NewConfig(opts ...GenericOptions) *v1.Config {
|
||||
log := v1.NewLogger()
|
||||
arch, err := utils.GolangArchToArch(runtime.GOARCH)
|
||||
if err != nil {
|
||||
log.Errorf("invalid arch: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
c := &v1.Config{
|
||||
Fs: vfs.OSFS,
|
||||
Logger: log,
|
||||
Syscall: &v1.RealSyscall{},
|
||||
Client: http.NewClient(),
|
||||
Repos: []v1.Repository{},
|
||||
Arch: arch,
|
||||
SquashFsNoCompression: true,
|
||||
}
|
||||
for _, o := range opts {
|
||||
err := o(c)
|
||||
if err != nil {
|
||||
log.Errorf("error applying config option: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// delay runner creation after we have run over the options in case we use WithRunner
|
||||
if c.Runner == nil {
|
||||
c.Runner = &v1.RealRunner{Logger: c.Logger}
|
||||
}
|
||||
|
||||
// Now check if the runner has a logger inside, otherwise point our logger into it
|
||||
// This can happen if we set the WithRunner option as that doesn't set a logger
|
||||
if c.Runner.GetLogger() == nil {
|
||||
c.Runner.SetLogger(c.Logger)
|
||||
}
|
||||
|
||||
// Delay the yip runner creation, so we set the proper logger instead of blindly setting it to the logger we create
|
||||
// at the start of NewRunConfig, as WithLogger can be passed on init, and that would result in 2 different logger
|
||||
// instances, on the config.Logger and the other on config.CloudInitRunner
|
||||
if c.CloudInitRunner == nil {
|
||||
c.CloudInitRunner = cloudinit.NewYipCloudInitRunner(c.Logger, c.Runner, vfs.OSFS)
|
||||
}
|
||||
|
||||
if c.Mounter == nil {
|
||||
c.Mounter = mount.New(constants.MountBinary)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func configLogger(log v1.Logger, vfs v1.FS) {
|
||||
// Set debug level
|
||||
if viper.GetBool("debug") {
|
||||
log.SetLevel(v1.DebugLevel())
|
||||
}
|
||||
|
||||
// Set formatter so both file and stdout format are equal
|
||||
log.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
DisableColors: false,
|
||||
DisableTimestamp: false,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
|
||||
// Logfile
|
||||
logfile := viper.GetString("logfile")
|
||||
if logfile != "" {
|
||||
o, err := vfs.OpenFile(logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, fs.ModePerm)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Could not open %s for logging to file: %s", logfile, err.Error())
|
||||
}
|
||||
|
||||
// else set it to both stdout and the file
|
||||
mw := io.MultiWriter(os.Stdout, o)
|
||||
log.SetOutput(mw)
|
||||
} else { // no logfile
|
||||
if viper.GetBool("quiet") { // quiet is enabled so discard all logging
|
||||
log.SetOutput(io.Discard)
|
||||
} else { // default to stdout
|
||||
log.SetOutput(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Starting enki version %s", version.GetVersion())
|
||||
if log.GetLevel() == logrus.DebugLevel {
|
||||
log.Debugf("%+v\n", version.Get())
|
||||
}
|
||||
}
|
||||
|
||||
// BindGivenFlags binds to viper only passed flags, ignoring any non provided flag
|
||||
func bindGivenFlags(vp *viper.Viper, flagSet *pflag.FlagSet) {
|
||||
if flagSet != nil {
|
||||
flagSet.VisitAll(func(f *pflag.Flag) {
|
||||
if f.Changed {
|
||||
_ = vp.BindPFlag(f.Name, f)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// setDecoder sets ZeroFields mastructure attribute to true
|
||||
func setDecoder(config *mapstructure.DecoderConfig) {
|
||||
// Make sure we zero fields before applying them, this is relevant for slices
|
||||
// so we do not merge with any already present value and directly apply whatever
|
||||
// we got form configs.
|
||||
config.ZeroFields = true
|
||||
}
|
||||
|
||||
type Unmarshaler interface {
|
||||
CustomUnmarshal(interface{}) (bool, error)
|
||||
}
|
||||
|
||||
func UnmarshalerHook() mapstructure.DecodeHookFunc {
|
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) {
|
||||
// get the destination object address if it is not passed by reference
|
||||
if to.CanAddr() {
|
||||
to = to.Addr()
|
||||
}
|
||||
// If the destination implements the unmarshaling interface
|
||||
u, ok := to.Interface().(Unmarshaler)
|
||||
if !ok {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
// If it is nil and a pointer, create and assign the target value first
|
||||
if to.IsNil() && to.Type().Kind() == reflect.Ptr {
|
||||
to.Set(reflect.New(to.Type().Elem()))
|
||||
u = to.Interface().(Unmarshaler)
|
||||
}
|
||||
// Call the custom unmarshaling method
|
||||
cont, err := u.CustomUnmarshal(from.Interface())
|
||||
if cont {
|
||||
// Continue with the decoding stack
|
||||
return from.Interface(), err
|
||||
}
|
||||
// Decoding finalized
|
||||
return to.Interface(), err
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package constants
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
GrubDefEntry = "Kairos"
|
||||
EfiLabel = "COS_GRUB"
|
||||
ISOLabel = "COS_LIVE"
|
||||
MountBinary = "/usr/bin/mount"
|
||||
EfiFs = "vfat"
|
||||
IsoRootFile = "rootfs.squashfs"
|
||||
IsoEFIPath = "/boot/uefi.img"
|
||||
BuildImgName = "elemental"
|
||||
EfiBootPath = "/EFI/BOOT"
|
||||
GrubEfiImagex86 = "/usr/share/grub2/x86_64-efi/grub.efi"
|
||||
GrubEfiImageArm64 = "/usr/share/grub2/arm64-efi/grub.efi"
|
||||
GrubEfiImagex86Dest = EfiBootPath + "/bootx64.efi"
|
||||
GrubEfiImageArm64Dest = EfiBootPath + "/bootaa64.efi"
|
||||
GrubCfg = "grub.cfg"
|
||||
GrubPrefixDir = "/boot/grub2"
|
||||
GrubEfiCfg = "search --no-floppy --file --set=root " + IsoKernelPath +
|
||||
"\nset prefix=($root)" + GrubPrefixDir +
|
||||
"\nconfigfile $prefix/" + GrubCfg
|
||||
|
||||
GrubFont = "/usr/share/grub2/unicode.pf2"
|
||||
GrubBootHybridImg = "/usr/share/grub2/i386-pc/boot_hybrid.img"
|
||||
SyslinuxFiles = "/usr/share/syslinux/isolinux.bin " +
|
||||
"/usr/share/syslinux/menu.c32 " +
|
||||
"/usr/share/syslinux/chain.c32 " +
|
||||
"/usr/share/syslinux/mboot.c32"
|
||||
IsoLoaderPath = "/boot/x86_64/loader"
|
||||
GrubCfgTemplate = `search --no-floppy --file --set=root /boot/kernel
|
||||
set default=0
|
||||
set timeout=10
|
||||
set timeout_style=menu
|
||||
set linux=linux
|
||||
set initrd=initrd
|
||||
if [ "${grub_cpu}" = "x86_64" -o "${grub_cpu}" = "i386" -o "${grub_cpu}" = "arm64" ];then
|
||||
if [ "${grub_platform}" = "efi" ]; then
|
||||
if [ "${grub_cpu}" != "arm64" ]; then
|
||||
set linux=linuxefi
|
||||
set initrd=initrdefi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "${grub_platform}" = "efi" ]; then
|
||||
echo "Please press 't' to show the boot menu on this console"
|
||||
fi
|
||||
set font=($root)/boot/${grub_cpu}/loader/grub2/fonts/unicode.pf2
|
||||
if [ -f ${font} ];then
|
||||
loadfont ${font}
|
||||
fi
|
||||
menuentry "%s" --class os --unrestricted {
|
||||
echo Loading kernel...
|
||||
$linux ($root)/boot/kernel cdroot root=live:CDLABEL=%s rd.live.dir=/ rd.live.squashimg=rootfs.squashfs rd.live.overlay.overlayfs console=tty1 console=ttyS0 rd.cos.disable
|
||||
echo Loading initrd...
|
||||
$initrd ($root)/boot/initrd
|
||||
}
|
||||
|
||||
if [ "${grub_platform}" = "efi" ]; then
|
||||
hiddenentry "Text mode" --hotkey "t" {
|
||||
set textmode=true
|
||||
terminal_output console
|
||||
}
|
||||
fi`
|
||||
GrubBiosTarget = "i386-pc"
|
||||
GrubI386BinDir = "/usr/share/grub2/i386-pc"
|
||||
GrubBiosImg = GrubI386BinDir + "/core.img"
|
||||
GrubBiosCDBoot = GrubI386BinDir + "/cdboot.img"
|
||||
GrubEltoritoImg = GrubI386BinDir + "/eltorito.img"
|
||||
//TODO this list could be optimized
|
||||
GrubModules = "ext2 iso9660 linux echo configfile search_label search_fs_file search search_fs_uuid " +
|
||||
"ls normal gzio png fat gettext font minicmd gfxterm gfxmenu all_video xfs btrfs lvm luks " +
|
||||
"gcry_rijndael gcry_sha256 gcry_sha512 crypto cryptodisk test true loadenv part_gpt " +
|
||||
"part_msdos biosdisk vga vbe chain boot"
|
||||
|
||||
IsoHybridMBR = "/boot/x86_64/loader/boot_hybrid.img"
|
||||
IsoBootCatalog = "/boot/x86_64/boot.catalog"
|
||||
IsoBootFile = "/boot/x86_64/loader/eltorito.img"
|
||||
|
||||
// These paths are arbitrary but coupled to grub.cfg
|
||||
IsoKernelPath = "/boot/kernel"
|
||||
IsoInitrdPath = "/boot/initrd"
|
||||
|
||||
// Default directory and file fileModes
|
||||
DirPerm = os.ModeDir | os.ModePerm
|
||||
FilePerm = 0666
|
||||
NoWriteDirPerm = 0555 | os.ModeDir
|
||||
TempDirPerm = os.ModePerm | os.ModeSticky | os.ModeDir
|
||||
|
||||
ArchAmd64 = "amd64"
|
||||
Archx86 = "x86_64"
|
||||
ArchArm64 = "arm64"
|
||||
)
|
||||
|
||||
// GetDefaultSquashfsOptions returns the default options to use when creating a squashfs
|
||||
func GetDefaultSquashfsOptions() []string {
|
||||
return []string{"-b", "1024k"}
|
||||
}
|
||||
|
||||
func GetXorrisoBooloaderArgs(root string) []string {
|
||||
args := []string{
|
||||
"-boot_image", "grub", fmt.Sprintf("bin_path=%s", IsoBootFile),
|
||||
"-boot_image", "grub", fmt.Sprintf("grub2_mbr=%s/%s", root, IsoHybridMBR),
|
||||
"-boot_image", "grub", "grub2_boot_info=on",
|
||||
"-boot_image", "any", "partition_offset=16",
|
||||
"-boot_image", "any", fmt.Sprintf("cat_path=%s", IsoBootCatalog),
|
||||
"-boot_image", "any", "cat_hidden=on",
|
||||
"-boot_image", "any", "boot_info_table=on",
|
||||
"-boot_image", "any", "platform_id=0x00",
|
||||
"-boot_image", "any", "emul_type=no_emulation",
|
||||
"-boot_image", "any", "load_size=2048",
|
||||
"-append_partition", "2", "0xef", filepath.Join(root, IsoEFIPath),
|
||||
"-boot_image", "any", "next",
|
||||
"-boot_image", "any", "efi_path=--interval:appended_partition_2:all::",
|
||||
"-boot_image", "any", "platform_id=0xef",
|
||||
"-boot_image", "any", "emul_type=no_emulation",
|
||||
}
|
||||
return args
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/*
|
||||
Copyright © 2022 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
)
|
||||
|
||||
// Chroot represents the struct that will allow us to run commands inside a given chroot
|
||||
type Chroot struct {
|
||||
path string
|
||||
defaultMounts []string
|
||||
extraMounts map[string]string
|
||||
activeMounts []string
|
||||
config *v1.Config
|
||||
}
|
||||
|
||||
func NewChroot(path string, config *v1.Config) *Chroot {
|
||||
return &Chroot{
|
||||
path: path,
|
||||
defaultMounts: []string{"/dev", "/dev/pts", "/proc", "/sys"},
|
||||
extraMounts: map[string]string{},
|
||||
activeMounts: []string{},
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// ChrootedCallback runs the given callback in a chroot environment
|
||||
func ChrootedCallback(cfg *v1.Config, path string, bindMounts map[string]string, callback func() error) error {
|
||||
chroot := NewChroot(path, cfg)
|
||||
chroot.SetExtraMounts(bindMounts)
|
||||
return chroot.RunCallback(callback)
|
||||
}
|
||||
|
||||
// Sets additional bind mounts for the chroot enviornment. They are represented
|
||||
// in a map where the key is the path outside the chroot and the value is the
|
||||
// path inside the chroot.
|
||||
func (c *Chroot) SetExtraMounts(extraMounts map[string]string) {
|
||||
c.extraMounts = extraMounts
|
||||
}
|
||||
|
||||
// Prepare will mount the defaultMounts as bind mounts, to be ready when we run chroot
|
||||
func (c *Chroot) Prepare() error {
|
||||
var err error
|
||||
keys := []string{}
|
||||
mountOptions := []string{"bind"}
|
||||
|
||||
if len(c.activeMounts) > 0 {
|
||||
return errors.New("There are already active mountpoints for this instance")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, mnt := range c.defaultMounts {
|
||||
mountPoint := fmt.Sprintf("%s%s", strings.TrimSuffix(c.path, "/"), mnt)
|
||||
err = MkdirAll(c.config.Fs, mountPoint, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.config.Mounter.Mount(mnt, mountPoint, "bind", mountOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.activeMounts = append(c.activeMounts, mountPoint)
|
||||
}
|
||||
|
||||
for k := range c.extraMounts {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
mountPoint := fmt.Sprintf("%s%s", strings.TrimSuffix(c.path, "/"), c.extraMounts[k])
|
||||
err = MkdirAll(c.config.Fs, mountPoint, constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.config.Mounter.Mount(k, mountPoint, "bind", mountOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.activeMounts = append(c.activeMounts, mountPoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close will unmount all active mounts created in Prepare on reverse order
|
||||
func (c *Chroot) Close() error {
|
||||
failures := []string{}
|
||||
for len(c.activeMounts) > 0 {
|
||||
curr := c.activeMounts[len(c.activeMounts)-1]
|
||||
c.config.Logger.Debugf("Unmounting %s from chroot", curr)
|
||||
c.activeMounts = c.activeMounts[:len(c.activeMounts)-1]
|
||||
err := c.config.Mounter.Unmount(curr)
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Error unmounting %s: %s", curr, err)
|
||||
failures = append(failures, curr)
|
||||
}
|
||||
}
|
||||
if len(failures) > 0 {
|
||||
c.activeMounts = failures
|
||||
return fmt.Errorf("failed closing chroot environment. Unmount failures: %v", failures)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCallback runs the given callback in a chroot environment
|
||||
func (c *Chroot) RunCallback(callback func() error) (err error) {
|
||||
var currentPath string
|
||||
var oldRootF *os.File
|
||||
|
||||
// Store current path
|
||||
currentPath, err = os.Getwd()
|
||||
if err != nil {
|
||||
c.config.Logger.Error("Failed to get current path")
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
tmpErr := os.Chdir(currentPath)
|
||||
if err == nil && tmpErr != nil {
|
||||
err = tmpErr
|
||||
}
|
||||
}()
|
||||
|
||||
// Store current root
|
||||
oldRootF, err = c.config.Fs.Open("/")
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Can't open current root")
|
||||
return err
|
||||
}
|
||||
defer oldRootF.Close()
|
||||
|
||||
if len(c.activeMounts) == 0 {
|
||||
err = c.Prepare()
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Can't mount default mounts")
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
tmpErr := c.Close()
|
||||
if err == nil {
|
||||
err = tmpErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Change to new dir before running chroot!
|
||||
err = c.config.Syscall.Chdir(c.path)
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Can't chdir %s: %s", c.path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = c.config.Syscall.Chroot(c.path)
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Can't chroot %s: %s", c.path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore to old root
|
||||
defer func() {
|
||||
tmpErr := oldRootF.Chdir()
|
||||
if tmpErr != nil {
|
||||
c.config.Logger.Errorf("Can't change to old root dir")
|
||||
if err == nil {
|
||||
err = tmpErr
|
||||
}
|
||||
} else {
|
||||
tmpErr = c.config.Syscall.Chroot(".")
|
||||
if tmpErr != nil {
|
||||
c.config.Logger.Errorf("Can't chroot back to old root")
|
||||
if err == nil {
|
||||
err = tmpErr
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return callback()
|
||||
}
|
||||
|
||||
// Run executes a command inside a chroot
|
||||
func (c *Chroot) Run(command string, args ...string) (out []byte, err error) {
|
||||
callback := func() error {
|
||||
out, err = c.config.Runner.Run(command, args...)
|
||||
return err
|
||||
}
|
||||
err = c.RunCallback(callback)
|
||||
if err != nil {
|
||||
c.config.Logger.Errorf("Cant run command %s with args %v on chroot: %s", command, args, err)
|
||||
c.config.Logger.Debugf("Output from command: %s", out)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CreateSquashFS creates a squash file at destination from a source, with options
|
||||
// TODO: Check validity of source maybe?
|
||||
func CreateSquashFS(runner v1.Runner, logger v1.Logger, source string, destination string, options []string) error {
|
||||
// create args
|
||||
args := []string{source, destination}
|
||||
// append options passed to args in order to have the correct order
|
||||
// protect against options passed together in the same string , i.e. "-x add" instead of "-x", "add"
|
||||
var optionsExpanded []string
|
||||
for _, op := range options {
|
||||
optionsExpanded = append(optionsExpanded, strings.Split(op, " ")...)
|
||||
}
|
||||
args = append(args, optionsExpanded...)
|
||||
out, err := runner.Run("mksquashfs", args...)
|
||||
if err != nil {
|
||||
logger.Debugf("Error running squashfs creation, stdout: %s", out)
|
||||
logger.Errorf("Error while creating squashfs from %s to %s: %s", source, destination, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GolangArchToArch(arch string) (string, error) {
|
||||
switch strings.ToLower(arch) {
|
||||
case constants.ArchAmd64:
|
||||
return constants.Archx86, nil
|
||||
case constants.ArchArm64:
|
||||
return constants.ArchArm64, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid arch")
|
||||
}
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"github.com/twpayne/go-vfs/vfst"
|
||||
)
|
||||
|
||||
// MkdirAll directory and all parents if not existing
|
||||
func MkdirAll(fs v1.FS, name string, mode os.FileMode) (err error) {
|
||||
if _, isReadOnly := fs.(*vfs.ReadOnlyFS); isReadOnly {
|
||||
return permError("mkdir", name)
|
||||
}
|
||||
if name, err = fs.RawPath(name); err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
return os.MkdirAll(name, mode)
|
||||
}
|
||||
|
||||
// permError returns an *os.PathError with Err syscall.EPERM.
|
||||
func permError(op, path string) error {
|
||||
return &os.PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: syscall.EPERM,
|
||||
}
|
||||
}
|
||||
|
||||
// Copies source file to target file using Fs interface
|
||||
func CreateDirStructure(fs v1.FS, target string) error {
|
||||
for _, dir := range []string{"/run", "/dev", "/boot", "/usr/local", "/oem"} {
|
||||
err := MkdirAll(fs, filepath.Join(target, dir), constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, dir := range []string{"/proc", "/sys"} {
|
||||
err := MkdirAll(fs, filepath.Join(target, dir), constants.NoWriteDirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := MkdirAll(fs, filepath.Join(target, "/tmp"), constants.DirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Set /tmp permissions regardless the umask setup
|
||||
err = fs.Chmod(filepath.Join(target, "/tmp"), constants.TempDirPerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TempDir creates a temp file in the virtual fs
|
||||
// Took from afero.FS code and adapted
|
||||
func TempDir(fs v1.FS, dir, prefix string) (name string, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
// This skips adding random stuff to the created temp dir so the temp dir created is predictable for testing
|
||||
if _, isTestFs := fs.(*vfst.TestFS); isTestFs {
|
||||
err = MkdirAll(fs, filepath.Join(dir, prefix), 0700)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name = filepath.Join(dir, prefix)
|
||||
return
|
||||
}
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
try := filepath.Join(dir, prefix+nextRandom())
|
||||
err = MkdirAll(fs, try, 0700)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err == nil {
|
||||
name = try
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Random number state.
|
||||
// We generate random temporary file names so that there's a good
|
||||
// chance the file doesn't exist yet - keeps the number of tries in
|
||||
// TempFile to a minimum.
|
||||
var rand uint32
|
||||
var randmu sync.Mutex
|
||||
|
||||
func reseed() uint32 {
|
||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||
}
|
||||
|
||||
func nextRandom() string {
|
||||
randmu.Lock()
|
||||
r := rand
|
||||
if r == 0 {
|
||||
r = reseed()
|
||||
}
|
||||
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||
rand = r
|
||||
randmu.Unlock()
|
||||
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||
}
|
||||
|
||||
// CopyFile Copies source file to target file using Fs interface. If target
|
||||
// is directory source is copied into that directory using source name file.
|
||||
func CopyFile(fs v1.FS, source string, target string) (err error) {
|
||||
return ConcatFiles(fs, []string{source}, target)
|
||||
}
|
||||
|
||||
// IsDir check if the path is a dir
|
||||
func IsDir(fs v1.FS, path string) (bool, error) {
|
||||
fi, err := fs.Stat(path)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return fi.IsDir(), nil
|
||||
}
|
||||
|
||||
// ConcatFiles Copies source files to target file using Fs interface.
|
||||
// Source files are concatenated into target file in the given order.
|
||||
// If target is a directory source is copied into that directory using
|
||||
// 1st source name file.
|
||||
func ConcatFiles(fs v1.FS, sources []string, target string) (err error) {
|
||||
if len(sources) == 0 {
|
||||
return fmt.Errorf("Empty sources list")
|
||||
}
|
||||
if dir, _ := IsDir(fs, target); dir {
|
||||
target = filepath.Join(target, filepath.Base(sources[0]))
|
||||
}
|
||||
|
||||
targetFile, err := fs.Create(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = targetFile.Close()
|
||||
} else {
|
||||
_ = fs.Remove(target)
|
||||
}
|
||||
}()
|
||||
|
||||
var sourceFile *os.File
|
||||
for _, source := range sources {
|
||||
sourceFile, err = fs.Open(source)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
_, err = io.Copy(targetFile, sourceFile)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = sourceFile.Close()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DirSize returns the accumulated size of all files in folder
|
||||
func DirSize(fs v1.FS, path string) (int64, error) {
|
||||
var size int64
|
||||
err := vfs.Walk(fs, path, func(_ string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// Check if a file or directory exists.
|
||||
func Exists(fs v1.FS, path string) (bool, error) {
|
||||
_, err := fs.Stat(path)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// CalcFileChecksum opens the given file and returns the sha256 checksum of it.
|
||||
func CalcFileChecksum(fs v1.FS, fileName string) (string, error) {
|
||||
f, err := fs.Open(fileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
Copyright © 2021 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestWhitebox(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Utils test suite")
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
Copyright © 2021 SUSE LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package utils_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
conf "github.com/kairos-io/enki/pkg/config"
|
||||
"github.com/kairos-io/enki/pkg/constants"
|
||||
"github.com/kairos-io/enki/pkg/utils"
|
||||
v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1"
|
||||
v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/twpayne/go-vfs"
|
||||
"github.com/twpayne/go-vfs/vfst"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = Describe("Utils", Label("utils"), func() {
|
||||
var config *v1.Config
|
||||
var runner *v1mock.FakeRunner
|
||||
var logger v1.Logger
|
||||
var syscall *v1mock.FakeSyscall
|
||||
var client *v1mock.FakeHTTPClient
|
||||
var mounter *v1mock.ErrorMounter
|
||||
var fs vfs.FS
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
runner = v1mock.NewFakeRunner()
|
||||
syscall = &v1mock.FakeSyscall{}
|
||||
mounter = v1mock.NewErrorMounter()
|
||||
client = &v1mock.FakeHTTPClient{}
|
||||
logger = v1.NewNullLogger()
|
||||
// Ensure /tmp exists in the VFS
|
||||
fs, cleanup, _ = vfst.NewTestFS(nil)
|
||||
fs.Mkdir("/tmp", constants.DirPerm)
|
||||
fs.Mkdir("/run", constants.DirPerm)
|
||||
fs.Mkdir("/etc", constants.DirPerm)
|
||||
|
||||
config = conf.NewConfig(
|
||||
conf.WithFs(fs),
|
||||
conf.WithRunner(runner),
|
||||
conf.WithLogger(logger),
|
||||
conf.WithMounter(mounter),
|
||||
conf.WithSyscall(syscall),
|
||||
conf.WithClient(client),
|
||||
)
|
||||
})
|
||||
AfterEach(func() { cleanup() })
|
||||
|
||||
Describe("Chroot", Label("chroot"), func() {
|
||||
var chroot *utils.Chroot
|
||||
BeforeEach(func() {
|
||||
chroot = utils.NewChroot(
|
||||
"/whatever",
|
||||
config,
|
||||
)
|
||||
})
|
||||
Describe("ChrootedCallback method", func() {
|
||||
It("runs a callback in a chroot", func() {
|
||||
err := utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error {
|
||||
return nil
|
||||
})
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = utils.ChrootedCallback(config, "/somepath", map[string]string{}, func() error {
|
||||
return fmt.Errorf("callback error")
|
||||
})
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("callback error"))
|
||||
})
|
||||
})
|
||||
Describe("on success", func() {
|
||||
It("command should be called in the chroot", func() {
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
})
|
||||
It("commands should be called with a customized chroot", func() {
|
||||
chroot.SetExtraMounts(map[string]string{"/real/path": "/in/chroot/path"})
|
||||
Expect(chroot.Prepare()).To(BeNil())
|
||||
defer chroot.Close()
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).To(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
_, err = chroot.Run("chroot-another-command")
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
It("runs a callback in a custom chroot", func() {
|
||||
called := false
|
||||
callback := func() error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
err := chroot.RunCallback(callback)
|
||||
Expect(err).To(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
Expect(called).To(BeTrue())
|
||||
})
|
||||
})
|
||||
Describe("on failure", func() {
|
||||
It("should return error if chroot-command fails", func() {
|
||||
runner.ReturnError = errors.New("run error")
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
})
|
||||
It("should return error if callback fails", func() {
|
||||
called := false
|
||||
callback := func() error {
|
||||
called = true
|
||||
return errors.New("Callback error")
|
||||
}
|
||||
err := chroot.RunCallback(callback)
|
||||
Expect(err).NotTo(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
Expect(called).To(BeTrue())
|
||||
})
|
||||
It("should return error if preparing twice before closing", func() {
|
||||
Expect(chroot.Prepare()).To(BeNil())
|
||||
defer chroot.Close()
|
||||
Expect(chroot.Prepare()).NotTo(BeNil())
|
||||
Expect(chroot.Close()).To(BeNil())
|
||||
Expect(chroot.Prepare()).To(BeNil())
|
||||
})
|
||||
It("should return error if failed to chroot", func() {
|
||||
syscall.ErrorOnChroot = true
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(syscall.WasChrootCalledWith("/whatever")).To(BeTrue())
|
||||
Expect(err.Error()).To(ContainSubstring("chroot error"))
|
||||
})
|
||||
It("should return error if failed to mount on prepare", Label("mount"), func() {
|
||||
mounter.ErrorOnMount = true
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("mount error"))
|
||||
})
|
||||
It("should return error if failed to unmount on close", Label("unmount"), func() {
|
||||
mounter.ErrorOnUnmount = true
|
||||
_, err := chroot.Run("chroot-command")
|
||||
Expect(err).ToNot(BeNil())
|
||||
Expect(err.Error()).To(ContainSubstring("failed closing chroot"))
|
||||
})
|
||||
})
|
||||
})
|
||||
Describe("CopyFile", Label("CopyFile"), func() {
|
||||
It("Copies source file to target file", func() {
|
||||
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create("/some/file")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Stat("/some/otherfile")
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).ShouldNot(HaveOccurred())
|
||||
e, err := utils.Exists(fs, "/some/otherfile")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(e).To(BeTrue())
|
||||
})
|
||||
It("Copies source file to target folder", func() {
|
||||
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = utils.MkdirAll(fs, "/someotherfolder", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Create("/some/file")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
_, err = fs.Stat("/someotherfolder/file")
|
||||
Expect(err).Should(HaveOccurred())
|
||||
Expect(utils.CopyFile(fs, "/some/file", "/someotherfolder")).ShouldNot(HaveOccurred())
|
||||
e, err := utils.Exists(fs, "/someotherfolder/file")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(e).To(BeTrue())
|
||||
})
|
||||
It("Fails to open non existing file", func() {
|
||||
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil())
|
||||
_, err = fs.Stat("/some/otherfile")
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
It("Fails to copy on non writable target", func() {
|
||||
err := utils.MkdirAll(fs, "/some", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
fs.Create("/some/file")
|
||||
_, err = fs.Stat("/some/otherfile")
|
||||
Expect(err).NotTo(BeNil())
|
||||
fs = vfs.NewReadOnlyFS(fs)
|
||||
Expect(utils.CopyFile(fs, "/some/file", "/some/otherfile")).NotTo(BeNil())
|
||||
_, err = fs.Stat("/some/otherfile")
|
||||
Expect(err).NotTo(BeNil())
|
||||
})
|
||||
})
|
||||
Describe("CreateDirStructure", Label("CreateDirStructure"), func() {
|
||||
It("Creates essential directories", func() {
|
||||
dirList := []string{"sys", "proc", "dev", "tmp", "boot", "usr/local", "oem"}
|
||||
for _, dir := range dirList {
|
||||
_, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir))
|
||||
Expect(err).NotTo(BeNil())
|
||||
}
|
||||
Expect(utils.CreateDirStructure(fs, "/my/root")).To(BeNil())
|
||||
for _, dir := range dirList {
|
||||
fi, err := fs.Stat(fmt.Sprintf("/my/root/%s", dir))
|
||||
Expect(err).To(BeNil())
|
||||
if fi.Name() == "tmp" {
|
||||
Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0777"))
|
||||
Expect(fi.Mode() & os.ModeSticky).NotTo(Equal(0))
|
||||
}
|
||||
if fi.Name() == "sys" {
|
||||
Expect(fmt.Sprintf("%04o", fi.Mode().Perm())).To(Equal("0555"))
|
||||
}
|
||||
}
|
||||
})
|
||||
It("Fails on non writable target", func() {
|
||||
fs = vfs.NewReadOnlyFS(fs)
|
||||
Expect(utils.CreateDirStructure(fs, "/my/root")).NotTo(BeNil())
|
||||
})
|
||||
})
|
||||
Describe("DirSize", Label("fs"), func() {
|
||||
BeforeEach(func() {
|
||||
err := utils.MkdirAll(fs, "/folder/subfolder", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
f, err := fs.Create("/folder/file")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = f.Truncate(1024)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
f, err = fs.Create("/folder/subfolder/file")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = f.Truncate(2048)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
})
|
||||
It("Returns the expected size of a test folder", func() {
|
||||
size, err := utils.DirSize(fs, "/folder")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(size).To(Equal(int64(3072)))
|
||||
})
|
||||
})
|
||||
Describe("CalcFileChecksum", Label("checksum"), func() {
|
||||
It("compute correct sha256 checksum", func() {
|
||||
testData := strings.Repeat("abcdefghilmnopqrstuvz\n", 20)
|
||||
testDataSHA256 := "7f182529f6362ae9cfa952ab87342a7180db45d2c57b52b50a68b6130b15a422"
|
||||
|
||||
err := fs.Mkdir("/iso", constants.DirPerm)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
err = fs.WriteFile("/iso/test.iso", []byte(testData), 0644)
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
checksum, err := utils.CalcFileChecksum(fs, "/iso/test.iso")
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(checksum).To(Equal(testDataSHA256))
|
||||
})
|
||||
})
|
||||
Describe("CreateSquashFS", Label("CreateSquashFS"), func() {
|
||||
It("runs with no options if none given", func() {
|
||||
err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{})
|
||||
Expect(runner.IncludesCmds([][]string{
|
||||
{"mksquashfs", "source", "dest"},
|
||||
})).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
It("runs with options if given", func() {
|
||||
err := utils.CreateSquashFS(runner, logger, "source", "dest", constants.GetDefaultSquashfsOptions())
|
||||
cmd := []string{"mksquashfs", "source", "dest"}
|
||||
cmd = append(cmd, constants.GetDefaultSquashfsOptions()...)
|
||||
Expect(runner.IncludesCmds([][]string{
|
||||
cmd,
|
||||
})).To(BeNil())
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
It("returns an error if it fails", func() {
|
||||
runner.ReturnError = errors.New("error")
|
||||
err := utils.CreateSquashFS(runner, logger, "source", "dest", []string{})
|
||||
Expect(runner.IncludesCmds([][]string{
|
||||
{"mksquashfs", "source", "dest"},
|
||||
})).To(BeNil())
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
enki --config-dir /config $@
|
||||
@@ -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
|
||||
@@ -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 nodepair.enable config_url=${config} console=tty1 console=ttyS0 rd.live.overlay.overlayfs ${cmdline}
|
||||
initrd ${RELEASE_URL}/${VERSION}/${ISO_NAME}-initrd
|
||||
boot
|
||||
@@ -1,15 +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"
|
||||
reference: 20230816140633-repository.yaml
|
||||
@@ -1,15 +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"
|
||||
reference: 20230816141243-repository.yaml
|
||||
@@ -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 nodepair.enable config_url=\${config} console=tty1 console=ttyS0 \${cmdline}
|
||||
initrd \${url}/\${initrd}
|
||||
boot
|
||||
EOF
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,23 +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
|
||||
|
||||
sed -i -n '/KAIROS_/!p' /workspace/os-release
|
||||
envsubst >>/workspace/os-release < /os-release.tmpl
|
||||
|
||||
cat /workspace/os-release
|
||||
Reference in New Issue
Block a user