Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
16 changes: 16 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Build context for python/Containerfile is the repo root.
rust/target/
**/target/
python/.venv/
**/.venv/
**/__pycache__/
**/.pytest_cache/
**/.mypy_cache/
**/.ruff_cache/
e2e/testdata/
.github/
docs/_build/
controller/bin/
*.qcow2
*.img
*.raw
14 changes: 14 additions & 0 deletions .github/actions/load-e2e-artifacts/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ runs:
name: exporterset-controller-image-${{ inputs.arch }}
path: /tmp/artifacts

- name: Download exporter image
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: exporter-image-${{ inputs.arch }}
path: /tmp/artifacts

- name: Download qemu-runtime image
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: qemu-runtime-image-${{ inputs.arch }}
path: /tmp/artifacts

- name: Download python wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
Expand All @@ -39,5 +51,7 @@ runs:
docker load < /tmp/artifacts/controller-image.tar
docker load < /tmp/artifacts/operator-image.tar
docker load < /tmp/artifacts/exporterset-controller-image.tar
docker load < /tmp/artifacts/exporter-image.tar
docker load < /tmp/artifacts/qemu-runtime-image.tar
mkdir -p controller/deploy/operator/dist
cp /tmp/artifacts/operator-install.yaml controller/deploy/operator/dist/install.yaml
82 changes: 79 additions & 3 deletions .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,77 @@ jobs:
path: /tmp/exporterset-controller-image.tar
retention-days: 1

build-exporter-image:
needs: changes
if: needs.changes.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch'
strategy:
matrix:
include: ${{ fromJson(needs.changes.outputs.e2e-matrix) }}
runs-on: ${{ matrix.os }}
timeout-minutes: 45
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0

- name: Cache exporter image
id: cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: /tmp/exporter-image.tar
# Bust when exporter image inputs change: Containerfile, Python sources,
# lockfiles, or embedded jumpstarter-exec (rust/).
key: exporter-image-${{ matrix.arch }}-${{ hashFiles('python/Containerfile', 'python/packages/**/*.py', 'python/**/pyproject.toml', 'python/uv.lock', 'rust/jumpstarter-exec/**', 'controller/Makefile') }}

- name: Build exporter image
if: steps.cache.outputs.cache-hit != 'true'
run: |
make -C controller docker-build-exporter
docker save quay.io/jumpstarter-dev/jumpstarter:latest -o /tmp/exporter-image.tar
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Upload exporter image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: exporter-image-${{ matrix.arch }}
path: /tmp/exporter-image.tar
retention-days: 1

build-qemu-runtime-image:
needs: changes
if: needs.changes.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch'
strategy:
matrix:
include: ${{ fromJson(needs.changes.outputs.e2e-matrix) }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0

- name: Cache qemu-runtime image
id: cache
uses: actions/cache@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: /tmp/qemu-runtime-image.tar
# Separate from exporter cache: only QEMU runtime image build inputs.
key: qemu-runtime-image-${{ matrix.arch }}-${{ hashFiles('controller/Makefile', 'controller/Containerfile.qemu-runtime') }}

- name: Build qemu-runtime image
if: steps.cache.outputs.cache-hit != 'true'
run: |
make -C controller docker-build-qemu-runtime
docker save quay.io/jumpstarter-dev/virtual/qemu-runtime:latest -o /tmp/qemu-runtime-image.tar

- name: Upload qemu-runtime image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: qemu-runtime-image-${{ matrix.arch }}
path: /tmp/qemu-runtime-image.tar
retention-days: 1

build-python-wheels:
needs: changes
if: needs.changes.outputs.should_run == 'true' || github.event_name == 'workflow_dispatch'
Expand Down Expand Up @@ -215,12 +286,14 @@ jobs:
# ===========================================================================

e2e-tests:
needs: [changes, build-controller-image, build-operator-image, build-exporterset-controller-image, build-python-wheels]
needs: [changes, build-controller-image, build-operator-image, build-exporterset-controller-image, build-exporter-image, build-qemu-runtime-image, build-python-wheels]
strategy:
matrix:
include: ${{ fromJson(needs.changes.outputs.e2e-matrix) }}
runs-on: ${{ matrix.os }}
timeout-minutes: 60
# Includes ExporterSet QEMU coverage (TCG); flash/boot still skipped until #924.
# Job > Ginkgo suite timeout (60m in e2e/lib/common.sh) for setup/log upload.
timeout-minutes: 70
steps:
- name: Checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand Down Expand Up @@ -254,6 +327,9 @@ jobs:
sudo modprobe "$mod" 2>/dev/null || true
done

- name: Prefetch Alpine guest image for ExporterSet QEMU
run: bash e2e/scripts/ensure-qemu-guest-image.sh

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this must run from the setup-e2e make target, not be part of github ci yaml.


- name: Setup e2e test environment
run: make e2e-setup
env:
Expand Down Expand Up @@ -320,7 +396,7 @@ jobs:
artifact-name: e2e-logs-compat-old-controller

e2e-compat-old-client:
needs: [changes, build-controller-image, build-operator-image, build-exporterset-controller-image, build-python-wheels]
needs: [changes, build-controller-image, build-operator-image, build-exporterset-controller-image, build-exporter-image, build-qemu-runtime-image, build-python-wheels]
# Skip on PRs — compat tests run in merge queue and workflow_dispatch.
if: >-
github.event_name != 'pull_request'
Expand Down
17 changes: 12 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ help:
@echo " make docs-test - Run documentation tests"
@echo ""
@echo "End-to-end testing:"
@echo " make e2e-setup - Setup e2e test environment (one-time)"
@echo " make e2e-run - Run e2e tests (requires e2e-setup first)"
@echo " make e2e - Same as e2e-run"
@echo " make e2e-full - Full setup + run (for CI or first time)"
@echo " make e2e-clean - Clean up e2e test environment (delete cluster, certs, etc.)"
@echo " make e2e-setup - Setup e2e test environment (one-time)"
@echo " make e2e-run - Run full e2e suite (includes ExporterSet QEMU)"
@echo " make e2e - Same as e2e-run"
@echo " make e2e-exporterset-qemu - Run ExporterSet QEMU e2e only"
@echo " make e2e-full - Full setup + run (for CI or first time)"
@echo " make e2e-clean - Clean up e2e test environment (delete cluster, certs, etc.)"
@echo ""
@echo "Per-project targets:"
@echo " make build-<project> - Build specific project"
Expand Down Expand Up @@ -198,6 +199,12 @@ e2e-run:
@echo "Running e2e tests..."
@bash e2e/run-e2e.sh

# Focused local run of ExporterSet QEMU e2e (also covered by make e2e-run / CI).
.PHONY: e2e-exporterset-qemu
e2e-exporterset-qemu:
@echo "Running ExporterSet QEMU e2e tests..."
@GINKGO_LABEL_FILTER=exporterset-qemu bash e2e/run-e2e.sh

# Convenience alias for running e2e tests
.PHONY: e2e
e2e: e2e-run
Expand Down
3 changes: 3 additions & 0 deletions controller/Containerfile.qemu-runtime
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ RUN dnf install --setopt=install_weak_deps=False -y \
virtiofsd && \
dnf clean all

# Default image USER is non-root; ExporterSet QEMU pods override target-runtime
# to runAsUser 0 via Pod securityContext so QEMU can access devices and shared
# volume paths owned by the exporter container.
USER 65532:65532

ENTRYPOINT ["/shared/jumpstarter-exec"]
Expand Down
4 changes: 4 additions & 0 deletions controller/hack/deploy_vars
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ BASEDOMAIN=${BASEDOMAIN:-"jumpstarter.${IP}.nip.io"}
IMG=${IMG:-quay.io/jumpstarter-dev/jumpstarter-controller:latest}
OPERATOR_IMG=${OPERATOR_IMG:-$(make -C deploy/operator --no-print-directory -s print-img 2>/dev/null || echo "quay.io/jumpstarter-dev/jumpstarter-operator:latest")}
EXPORTER_SET_CONTROLLER_IMG=${EXPORTER_SET_CONTROLLER_IMG:-quay.io/jumpstarter-dev/jumpstarter-exporterset-controller:latest}
EXPORTER_IMG=${EXPORTER_IMG:-quay.io/jumpstarter-dev/jumpstarter:latest}
QEMU_RUNTIME_IMG=${QEMU_RUNTIME_IMG:-quay.io/jumpstarter-dev/virtual/qemu-runtime:latest}

# Determine endpoints based on NETWORKING_MODE and CLUSTER_TYPE
if [ "${NETWORKING_MODE}" == "ingress" ]; then
Expand Down Expand Up @@ -46,4 +48,6 @@ export IMAGE_TAG
export IMG
export OPERATOR_IMG
export EXPORTER_SET_CONTROLLER_IMG
export EXPORTER_IMG
export QEMU_RUNTIME_IMG

12 changes: 12 additions & 0 deletions controller/hack/deploy_with_operator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ fi
load_image "${IMG}"
load_image "${OPERATOR_IMG}"
load_image "${EXPORTER_SET_CONTROLLER_IMG}"
# Exporter + QEMU runtime images are required for ExporterSet QEMU e2e / samples.
# Missing images are skipped so plain controller deploys still work.
if container_image_exists "${EXPORTER_IMG}"; then
load_image "${EXPORTER_IMG}"
else
echo -e "${YELLOW}Skipping load of exporter image (not present locally): ${EXPORTER_IMG}${NC}"
fi
if container_image_exists "${QEMU_RUNTIME_IMG}"; then
load_image "${QEMU_RUNTIME_IMG}"
else
echo -e "${YELLOW}Skipping load of qemu-runtime image (not present locally): ${QEMU_RUNTIME_IMG}${NC}"
fi

# Deploy the operator
echo -e "${GREEN}Deploying Jumpstarter operator ...${NC}"
Expand Down
1 change: 1 addition & 0 deletions controller/hack/sample-x86_64-kind.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ spec:
arch: x86_64
smp: 1
mem: 1G
disk_size: 2G
15 changes: 15 additions & 0 deletions controller/hack/utils
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export K3S_KUBECONFIG=${K3S_KUBECONFIG:-/etc/rancher/k3s/k3s.yaml}

# Color codes for terminal output
export GREEN='\033[0;32m'
export YELLOW='\033[1;33m'
export NC='\033[0m' # No Color

# Get external IP address
Expand Down Expand Up @@ -76,6 +77,20 @@ load_image() {
esac
}

# Return 0 if the image exists in the local container tool store.
container_image_exists() {
local image=$1
local tool=${CONTAINER_TOOL:-}
if [ -z "${tool}" ]; then
if command -v podman >/dev/null 2>&1; then
tool=podman
else
tool=docker
fi
fi
${tool} image inspect "${image}" >/dev/null 2>&1
}

create_cluster() {
_require_valid_cluster_type
case "${CLUSTER_TYPE}" in
Expand Down
10 changes: 7 additions & 3 deletions controller/internal/exporterset/exporterconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ const (
// configVolumeName is the volume name for the ExporterConfig Secret.
configVolumeName = "exporter-config"

// configMountPath is where the ExporterConfig Secret is mounted.
configMountPath = "/etc/jumpstarter/exporters"
// ExporterConfigMountPath is where the ExporterConfig Secret is mounted
// inside the exporter container (QEMU provisioner and injectConfigVolume).
ExporterConfigMountPath = "/etc/jumpstarter/exporters"

// exporterContainerName is the init-container name in the sidecar Pod.
// configMountPath is the unexported alias used within this package.
configMountPath = ExporterConfigMountPath

// exporterContainerName is the main container that runs jmp run.
exporterContainerName = "exporter"
)

Expand Down
26 changes: 22 additions & 4 deletions controller/internal/exporterset/provisioners/qemu/enrich_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,29 @@ func TestEnrichExporterExport_defaultsFromMergedParameters(t *testing.T) {
if got := config["smp"]; got != float64(4) {
t.Errorf("smp = %v, want 4", got)
}
if got := config["mem"]; got != "4Gi" {
t.Errorf("mem = %v, want 4Gi", got)
if got := config["mem"]; got != "4G" {
t.Errorf("mem = %v, want 4G", got)
}
if got := config["disk_size"]; got != "40Gi" {
t.Errorf("disk_size = %v, want 40Gi", got)
if got := config["disk_size"]; got != "40G" {
t.Errorf("disk_size = %v, want 40G", got)
}
}

func TestNormalizeQemuSize(t *testing.T) {
cases := []struct {
in, want string
}{
{"10Gi", "10G"},
{"512Mi", "512M"},
{"1Ti", "1T"},
{"2G", "2G"},
{"128M", "128M"},
}
for _, tc := range cases {
got := normalizeQemuSize(tc.in)
if got != tc.want {
t.Errorf("normalizeQemuSize(%q) = %v, want %q", tc.in, got, tc.want)
}
}
}

Expand Down
Loading
Loading