Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,44 @@ e2e: ## Run e2e tests (requires: make deploy-bink). V=1 for verbose. RUN=<regex>
# each package, even though we just have one here--but I really like streaming
# output...).
rm -rf $(ARTIFACTS)
cd test/e2e && KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) BINK_CLUSTER_NAME=$(BINK_CLUSTER_NAME) \
$(if $(BINK_NODE_IMAGE),BINK_NODE_IMAGE=$(BINK_NODE_IMAGE)) \
cd test/e2e && KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) \
E2E_PROVIDER=bink \
BINK_CLUSTER_NAME=$(BINK_CLUSTER_NAME) \
BINK_NODE_DISK_IMAGE=$(BINK_NODE_DISK_IMAGE) \
BINK_LOCAL_REGISTRY_NODE_IMAGE=$(BINK_LOCAL_REGISTRY_NODE_IMAGE) \
E2E_NODE_IMAGE_REGISTRY=$(BINK_LOCAL_REGISTRY_NODE_IMAGE) \
ARTIFACTS=$(ARTIFACTS) \
BINK_NODE_IMAGE_DIGEST=$$(skopeo inspect --tls-verify=false --format '{{.Digest}}' docker://localhost:5000/node:latest) \
BINK_NODE_IMAGE_UPDATE_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update | jq -r '.Digest') \
BINK_NODE_IMAGE_UPDATE2_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update2 | jq -r '.Digest') \
E2E_NODE_IMAGE_DIGEST=$$(skopeo inspect --tls-verify=false --format '{{.Digest}}' docker://localhost:5000/node:latest) \
E2E_NODE_IMAGE_UPDATE_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update | jq -r '.Digest') \
E2E_NODE_IMAGE_UPDATE2_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update2 | jq -r '.Digest') \
E2E_REGISTRY_USER=$(E2E_REGISTRY_USER) E2E_REGISTRY_PASSWORD=$(E2E_REGISTRY_PASSWORD) \
go test -timeout 30m -count=1 $(if $(V),-v) $(if $(RUN),-run $(RUN)) .

# EKS e2e settings
EKS_CLUSTER_NAME ?=
EKS_NODE_GROUP ?=
AWS_REGION ?=
EKS_NODE_IMAGE_REF ?=
EKS_NODE_IMAGE_UPDATE_REF ?=
EKS_NODE_IMAGE_UPDATE2_REF ?=
EKS_REGISTRY_USER ?=
EKS_REGISTRY_PASSWORD ?=

.PHONY: e2e-eks
e2e-eks: ## Run e2e tests against EKS. V=1 for verbose. RUN=<regex> to filter.
rm -rf $(ARTIFACTS)
cd test/e2e && KUBECONFIG="$(KUBECONFIG)" \
E2E_PROVIDER=eks \
EKS_CLUSTER_NAME="$(EKS_CLUSTER_NAME)" \
EKS_NODE_GROUP="$(EKS_NODE_GROUP)" \
AWS_REGION="$(AWS_REGION)" \
E2E_NODE_IMAGE_REF="$(EKS_NODE_IMAGE_REF)" \
E2E_NODE_IMAGE_UPDATE_REF="$(EKS_NODE_IMAGE_UPDATE_REF)" \
E2E_NODE_IMAGE_UPDATE2_REF="$(EKS_NODE_IMAGE_UPDATE2_REF)" \
E2E_REGISTRY_USER="$(EKS_REGISTRY_USER)" \
E2E_REGISTRY_PASSWORD="$(EKS_REGISTRY_PASSWORD)" \
ARTIFACTS="$(ARTIFACTS)" \
go test -timeout 30m -count=1 $(if $(V),-v) $(if $(RUN),-run $(RUN)) .

##@ Build

.PHONY: build
Expand Down Expand Up @@ -202,7 +229,7 @@ deploy-bink: start-bink build-update-image kustomize ## Deploy to a bink cluster

.PHONY: gather-bink
gather-bink: ## Gather diagnostic logs from the bink cluster.
KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) BINK_CLUSTER_NAME=$(BINK_CLUSTER_NAME) \
KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) \
hack/gather-logs.sh $(ARTIFACTS)/gather-bink controller

.PHONY: teardown-bink
Expand Down
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ module github.com/bootc-dev/bootc-operator
go 1.26.0

require (
github.com/aws/aws-sdk-go-v2 v1.47.0
github.com/aws/aws-sdk-go-v2/config v1.33.5
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.78.0
github.com/aws/aws-sdk-go-v2/service/ec2 v1.332.0
github.com/aws/aws-sdk-go-v2/service/eks v1.99.0
github.com/distribution/reference v0.6.0
github.com/fsnotify/fsnotify v1.10.1
github.com/go-logr/logr v1.4.4
Expand All @@ -18,6 +23,18 @@ require (
require (
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.20.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 // indirect
github.com/aws/smithy-go v1.28.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
Expand Down
34 changes: 34 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/aws/aws-sdk-go-v2 v1.47.0 h1:0jsHallhJCeaU0Ko48c/3FK1ctOQ7NpzggxriJOQ8MQ=
github.com/aws/aws-sdk-go-v2 v1.47.0/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU=
github.com/aws/aws-sdk-go-v2/config v1.33.5 h1:UA1dmokBFOLFoOyVBhO6HjM6edy0MIk5AZSkJVcksQw=
github.com/aws/aws-sdk-go-v2/config v1.33.5/go.mod h1:Dop8axzz0xx38GExIYWXdeyc8QQ7Cr+nPsxpD/LYy4U=
github.com/aws/aws-sdk-go-v2/credentials v1.20.5 h1:wklUVvHMc9xTQ3rcp49/ISpiMnhbCicJcA6n6S8m7J8=
github.com/aws/aws-sdk-go-v2/credentials v1.20.5/go.mod h1:fyEdrn6ccLFOkoK84j5bQyGTxp9zPt5l2XMhxf4DVZs=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0 h1:AM4hHjww+PSFtt6E+UrBrPlZkWsePCLEt9AjkfQX+yM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.0/go.mod h1:3x/yXezeQjpOvBb4jEMxrS8SXvpdvJ5abv6l5c1gWM8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3 h1:Hp/VgjP0BysR3OgLlR057Vz2LcbbVnoWeJ+3qWiS/fY=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.3/go.mod h1:nwGV5qw7F1IZPgxCvA/ph8N2TAuz+BkRG/bXn808qMA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3 h1:MUaM4f+kj1ZIBPZfUS8cxP1GKXXZtHJjAthy93AN7SM=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.3/go.mod h1:6YmVmEVRI5ZZzRjCSsb9SryKH0hAlMRdgA7kG9aDvBU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3 h1:fuSCw4Z2qfRCztMPO3GXJNSiEp6Wee+WOLwrHHUMy9c=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.3/go.mod h1:6SxcHheD1pPR5+kWm1wGvjlL/YqUsh267sAfEmN4K7A=
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.78.0 h1:CN7ZkNEZb5Ob0DtntBQLE7cdpT13gzS1Gn+QMoZOjHA=
github.com/aws/aws-sdk-go-v2/service/autoscaling v1.78.0/go.mod h1:nkWNnRTHDlkZZrZzhmcPrOkoF+werzJzCOHGpbIpcfA=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.332.0 h1:T9rFYUxhZEBmnIgrzKZjd/0xMsWRF+brY/Gpgf5rc40=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.332.0/go.mod h1:2o5yJcnWuaBOsnNqlO1reYs1OQffFkqf2xJ2mmMWNH4=
github.com/aws/aws-sdk-go-v2/service/eks v1.99.0 h1:DJhHBuKLyXU5krDg94uUwAbU4828vmurQd0O/VBanVk=
github.com/aws/aws-sdk-go-v2/service/eks v1.99.0/go.mod h1:flOCIr3poFqMcmCXl5ZZIhtDm0/n8tofP/Tio422uO0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3 h1:bON1rJf67TSTDCKg816AAIE4xSTtoo9tl0XRkO72R+I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.3/go.mod h1:c5BBpjJcQXpfeq9iASyVKA3T6vX6B6LEXY4mL/gklDY=
github.com/aws/aws-sdk-go-v2/service/signin v1.10.0 h1:ZD5qFpWcaOKdTuhBi431pIDkCgrMkMlMT6jlpSPoIRI=
github.com/aws/aws-sdk-go-v2/service/signin v1.10.0/go.mod h1:8Nuuf+tR346PjJ3MvZPh9pekbLiLQFWJhzMXfwy7alA=
github.com/aws/aws-sdk-go-v2/service/sso v1.38.0 h1:JGeeBcMlhg1xtOXYpeCaTQBZObtXMPQCUqBcmr65NRA=
github.com/aws/aws-sdk-go-v2/service/sso v1.38.0/go.mod h1:XwteswG9EOMRFm73UT0t+MbTwyLxMrEXkU6e+v92Lzo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0 h1:obhahQXDEdVEv8y5bTKXR30LVaxYe1kyYM0L7l2Iq+k=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.43.0/go.mod h1:6twZZ/aXHNy1vXUO8koUbp++MYzMASkOgEBdkbJYmO0=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.0 h1:Zpnqa6XtrNzXZnwbdCqHOXpXhMsa01ql/pcRQ1sb4hk=
github.com/aws/aws-sdk-go-v2/service/sts v1.51.0/go.mod h1:/8JRcdTt//hG0Q4BTmGbuOplT7ABe+5rdtqUHqXvYIM=
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
Expand Down
22 changes: 13 additions & 9 deletions hack/gather-logs.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/bin/bash
# Gather diagnostic logs from a bink cluster.
# Gather diagnostic logs from a cluster.
#
# Usage: hack/gather-logs.sh <output-dir> [node-names...]
#
# Expects KUBECONFIG and BINK_CLUSTER_NAME from environment.
# Expects KUBECONFIG from environment.
# Each command's output is written to a separate file in <output-dir>.
# Individual command failures are non-fatal.

Expand All @@ -15,7 +15,6 @@ if [[ $# -lt 1 ]]; then
fi

: "${KUBECONFIG:?must be set}"
: "${BINK_CLUSTER_NAME:?must be set}"

output_dir="$1"
shift
Expand All @@ -32,10 +31,6 @@ run() {
"$@" > "${output_dir}/${filename}" 2>&1 || true
}

# Host diagnostics
run "host-journal.txt" journalctl --no-pager
run "host-dmesg.txt" dmesg

# Cluster-wide commands
run "k-get-pods.txt" kubectl get pods -n bootc-operator -o wide
run "k-describe-pods.txt" kubectl describe pods -n bootc-operator
Expand All @@ -50,10 +45,19 @@ for pod in $(kubectl get pods -n bootc-operator -o jsonpath='{.items[*].metadata
run "k-logs-${pod}-previous.log" kubectl logs -n bootc-operator "${pod}" --all-containers --previous
done

# Per-node commands
# Per-node commands via daemon pod exec
for node in "${nodes[@]}"; do
run "k-describe-node-${node}.txt" kubectl describe node "${node}"
run "journal-${node}.txt" bink node ssh "${node}" --cluster-name "${BINK_CLUSTER_NAME}" -- journalctl --no-pager

daemon_pod=$(kubectl get pods -n bootc-operator \
-l app.kubernetes.io/name=bootc-operator,app.kubernetes.io/component=daemon \
--field-selector "spec.nodeName=${node}" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)

if [[ -n "${daemon_pod}" ]]; then
run "journal-${node}.txt" kubectl exec -n bootc-operator "${daemon_pod}" -- \
journalctl --no-pager
fi
done

echo "Done."
29 changes: 19 additions & 10 deletions test/e2e/bootcnode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const (
// BootcNodePool selecting it, and verifies that a BootcNode is created
// and the node is labeled bootc.dev/managed.
func TestControllerMembership(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -110,6 +111,7 @@ func TestControllerMembership(t *testing.T) {
// original image, then updates the pool to a new image and verifies the
// full update lifecycle: staging, reboot, and idle with the new image.
func TestUpdateReboot(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -304,6 +306,7 @@ func TestUpdateReboot(t *testing.T) {
// the controller resolves the tag to a digest, then retags the image
// and verifies re-resolution triggers a rollout.
func TestTagResolution(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -411,6 +414,7 @@ func TestTagResolution(t *testing.T) {
// image and that the non-rebooting node does not wastefully reboot into the
// first update image.
func TestMidRolloutImageChange(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -608,6 +612,7 @@ func getBootCount(t *testing.T, env *e2eutil.Env, ctx context.Context, nodeName
// pool paused, verifies the node stages but does not reboot, then resumes
// and verifies the update completes.
func TestPauseResume(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -732,6 +737,7 @@ func TestPauseResume(t *testing.T) {
// original image, then updates to a non-existing image and verifies the
// node enters degraded state and the update does not proceed.
func TestNonExistingImage(t *testing.T) {
e2eutil.Providers(t, "bink")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand Down Expand Up @@ -818,6 +824,7 @@ func TestNonExistingImage(t *testing.T) {
// registry shares storage with the unauthenticated one (port 5000),
// so the update image is already available at both endpoints.
func TestPullSecretAuth(t *testing.T) {
e2eutil.Providers(t, "bink", "eks")
g := NewWithT(t)
g.SetDefaultEventuallyTimeout(pollTimeout)
g.SetDefaultEventuallyPollingInterval(pollInterval)
Expand All @@ -830,19 +837,20 @@ func TestPullSecretAuth(t *testing.T) {
ctx := context.Background()
nodeName := env.AddNode(t)

// The auth registry shares storage with the unauthenticated
// registry, so the update image pushed to localhost:5000 is
// already visible at auth-registry.cluster.local:5001.
// For bink, the auth registry (port 5001) shares storage with the
// unauthenticated registry (port 5000), so we rewrite the image ref
// to go through the auth endpoint. For EKS, the update image already
// requires authentication.
authImageRef := env.AuthImageRef()
digest := env.NodeImageUpdateDigest()
registryHost := env.RegistryHost()

// Create a dockerconfigjson Secret with credentials for the
// in-cluster auth registry hostname.
authStr := base64.StdEncoding.EncodeToString(
[]byte(env.RegistryUser() + ":" + env.RegistryPassword()),
)
dockerCfg := fmt.Sprintf(
`{"auths":{"auth-registry.cluster.local:5001":{"auth":"%s"}}}`,
authStr,
`{"auths":{%q:{"auth":"%s"}}}`,
registryHost, authStr,
)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -857,8 +865,6 @@ func TestPullSecretAuth(t *testing.T) {
g.Expect(env.Client.Create(ctx, secret)).To(Succeed())
t.Cleanup(func() { _ = env.Client.Delete(ctx, secret) })

// Create a pool targeting the auth registry with the pull secret.
authImageRef := "auth-registry.cluster.local:5001/node@" + digest
pool := env.NewPool("pullsecret", authImageRef,
testutil.WithPullSecret(secret.Name, secret.Namespace),
)
Expand All @@ -876,14 +882,17 @@ func TestPullSecretAuth(t *testing.T) {
t.Logf("BootcNode %q has pullSecretRef set", nodeName)

// Wait for the node to stage and reboot into the update image.
// Check Booted.Image (the full ref with manifest digest) rather
// than Booted.ImageDigest (the content digest) because they can
// differ with remote registries.
g.Eventually(func() (bootcv1alpha1.BootcNodeStatus, error) {
var bn bootcv1alpha1.BootcNode
err := env.Client.Get(ctx, client.ObjectKey{Name: nodeName}, &bn)
return bn.Status, err
}).WithTimeout(5 * time.Minute).Should(And(
HaveField("Booted", And(
Not(BeNil()),
HaveField("ImageDigest", Equal(digest)),
HaveField("Image", Equal(authImageRef)),
)),
HaveField("Conditions", ContainElement(And(
HaveField("Type", bootcv1alpha1.NodeIdle),
Expand Down
1 change: 1 addition & 0 deletions test/e2e/crd_smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// e2e-worthy flows once we have more of the controller and daemon implemented.
// Note more comprehensive CRD round-trip tests exist in the unit tests.
func TestCRDSmoke(t *testing.T) {
e2eutil.Providers(t, "bink")
env := e2eutil.New(t)

ctx := context.Background()
Expand Down
Loading