mirror of
https://github.com/kairos-io/osbuilder.git
synced 2025-12-24 12:22:35 +00:00
Compare commits
1 Commits
renovate/p
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ce271c3a |
2
.github/workflows/bump_repos.yml
vendored
2
.github/workflows/bump_repos.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Bump cos 🔧
|
||||
run: earthly +bump-repositories
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT_TOKEN }}
|
||||
push-to-fork: ci-forks/osbuilder
|
||||
|
||||
4
Makefile
4
Makefile
@@ -183,7 +183,9 @@ KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/k
|
||||
.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.
|
||||
|
||||
@@ -18,6 +18,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -156,10 +157,10 @@ func (r *OSArtifactReconciler) newArtifactPVC(artifact *osbuilder.OSArtifact) *c
|
||||
}
|
||||
|
||||
func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder.OSArtifact) *corev1.Pod {
|
||||
cmd := fmt.Sprintf(
|
||||
"auroraboot --debug build-iso --override-name %s --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{
|
||||
{
|
||||
@@ -180,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(
|
||||
"auroraboot --debug build-iso --override-name %s --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,
|
||||
@@ -209,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,
|
||||
}
|
||||
@@ -222,7 +225,7 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
|
||||
Command: []string{"/bin/bash", "-cxe"},
|
||||
Args: []string{
|
||||
cloudImgCmd,
|
||||
cloudImgCmd.String(),
|
||||
},
|
||||
VolumeMounts: volumeMounts,
|
||||
}
|
||||
@@ -234,6 +237,12 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
}}
|
||||
}
|
||||
|
||||
var netbootCmd strings.Builder
|
||||
netbootCmd.WriteString("auroraboot --debug netboot")
|
||||
netbootCmd.WriteString(fmt.Sprintf(" /artifacts/%s.iso", artifact.Name))
|
||||
netbootCmd.WriteString(" /artifacts")
|
||||
netbootCmd.WriteString(fmt.Sprintf(" %s", artifact.Name))
|
||||
|
||||
extractNetboot := corev1.Container{
|
||||
ImagePullPolicy: corev1.PullAlways,
|
||||
SecurityContext: &corev1.SecurityContext{Privileged: ptr(true)},
|
||||
@@ -245,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)},
|
||||
@@ -261,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)},
|
||||
@@ -277,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,
|
||||
}
|
||||
@@ -385,8 +408,11 @@ func (r *OSArtifactReconciler) newBuilderPod(pvcName string, artifact *osbuilder
|
||||
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 {
|
||||
|
||||
@@ -128,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: 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")
|
||||
}
|
||||
|
||||
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{
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user