-
Notifications
You must be signed in to change notification settings - Fork 9
feat: add OCIVolumes feature gate with ORAS OCI volume support #342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| package tasks | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| ) | ||
|
|
||
| const testOrasImage = "ghcr.io/oras-project/oras:v1.2.0" | ||
|
|
||
| func TestOCIVolumes_MountsOnBuildTask(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
| task := GenerateBuildAutomotiveImageTask("test-ns", cfg, "") | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| for _, step := range task.Spec.Steps { | ||
| assertHasVolumeMount(t, step.VolumeMounts, ociVolumeNameOras, ociMountPathOras) | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_ReturnedByOCIVolumes(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
|
|
||
| vols := OCIVolumes(cfg) | ||
| assertHasImageVolume(t, vols, ociVolumeNameOras, testOrasImage) | ||
| } | ||
|
|
||
| func TestOCIVolumes_AbsentWhenDisabled(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: false, | ||
| } | ||
| task := GenerateBuildAutomotiveImageTask("test-ns", cfg, "") | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| if vols := OCIVolumes(cfg); len(vols) != 0 { | ||
| t.Fatal("OCIVolumes should return nil when disabled") | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_AbsentWhenNilConfig(t *testing.T) { | ||
| task := GenerateBuildAutomotiveImageTask("test-ns", nil, "") | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| if vols := OCIVolumes(nil); len(vols) != 0 { | ||
| t.Fatal("OCIVolumes should return nil with nil config") | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_AbsentWhenEmptyImage(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: "", | ||
| } | ||
| task := GenerateBuildAutomotiveImageTask("test-ns", cfg, "") | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| if vols := OCIVolumes(cfg); len(vols) != 0 { | ||
| t.Fatal("OCIVolumes should return nil when OrasImage is empty") | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_MountsOnPushTask(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
| task := GeneratePushArtifactRegistryTask("test-ns", cfg) | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| for _, step := range task.Spec.Steps { | ||
| assertHasVolumeMount(t, step.VolumeMounts, ociVolumeNameOras, ociMountPathOras) | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_AbsentOnSealedTask(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
| task := GenerateSealedTaskForOperation("test-ns", "prepare", cfg) | ||
|
|
||
| assertTaskHasNoVolume(t, task.Spec.Volumes) | ||
| for _, step := range task.Spec.Steps { | ||
| for _, vm := range step.VolumeMounts { | ||
| if vm.Name == ociVolumeNameOras { | ||
| t.Fatalf("sealed task step %q should not have OCI volume mount", step.Name) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_ReadOnly(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
| task := GenerateBuildAutomotiveImageTask("test-ns", cfg, "") | ||
|
|
||
| for _, step := range task.Spec.Steps { | ||
| for _, vm := range step.VolumeMounts { | ||
| if vm.Name == ociVolumeNameOras && !vm.ReadOnly { | ||
| t.Fatalf("ORAS volume mount on step %q should be read-only", step.Name) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func TestOCIVolumes_PullPolicy(t *testing.T) { | ||
| cfg := &BuildConfig{ | ||
| UseOCIVolumes: true, | ||
| OrasImage: testOrasImage, | ||
| } | ||
|
|
||
| vols := OCIVolumes(cfg) | ||
| for _, vol := range vols { | ||
| if vol.Name == ociVolumeNameOras { | ||
| if vol.Image == nil { | ||
| t.Fatal("ORAS volume should use Image volume source") | ||
| } | ||
| if vol.Image.PullPolicy != corev1.PullIfNotPresent { | ||
| t.Fatalf("ORAS volume pull policy = %v, want IfNotPresent", vol.Image.PullPolicy) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func assertHasImageVolume(t *testing.T, volumes []corev1.Volume, name, reference string) { | ||
| t.Helper() | ||
| for _, vol := range volumes { | ||
| if vol.Name == name { | ||
| if vol.Image == nil { | ||
| t.Fatalf("volume %q exists but is not an Image volume", name) | ||
| } | ||
| if vol.Image.Reference != reference { | ||
| t.Fatalf("volume %q reference = %q, want %q", name, vol.Image.Reference, reference) | ||
| } | ||
| return | ||
| } | ||
| } | ||
| t.Fatalf("image volume %q not found", name) | ||
| } | ||
|
|
||
| func assertTaskHasNoVolume(t *testing.T, volumes []corev1.Volume) { | ||
| t.Helper() | ||
| for _, vol := range volumes { | ||
| if vol.Name == ociVolumeNameOras { | ||
| t.Fatalf("volume %q should not be on Task spec (belongs in podTemplate)", vol.Name) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func assertHasVolumeMount(t *testing.T, mounts []corev1.VolumeMount, name, path string) { | ||
| t.Helper() | ||
| for _, vm := range mounts { | ||
| if vm.Name == name { | ||
| if vm.MountPath != path { | ||
| t.Fatalf("volume mount %q path = %q, want %q", name, vm.MountPath, path) | ||
| } | ||
| return | ||
| } | ||
| } | ||
| t.Fatalf("volume mount %q not found", name) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,109 @@ if [[ -n "${ADO_TRACE_ID:-}" ]]; then | |
| echo "{\"traceID\":\"${ADO_TRACE_ID}\",\"msg\":\"task started\",\"hostname\":\"${HOSTNAME:-unknown}\"}" | ||
| fi | ||
|
|
||
| # Add OCI volume tool paths when mounted (OCIVolumes feature gate) | ||
| OCI_TOOLS_BASE="/oci-tools" | ||
| if [ -d "$OCI_TOOLS_BASE/oras/bin" ]; then | ||
| export PATH="$OCI_TOOLS_BASE/oras/bin:$PATH" | ||
| fi | ||
|
|
||
| # --- ORAS install --- | ||
|
|
||
| # install_oras downloads and checksum-verifies the ORAS CLI binary. | ||
| # Sets ORAS_BIN to the installed path and adds it to PATH. | ||
| # Skips if oras is already available (e.g. via OCI volume mount). | ||
| install_oras() { | ||
| if command -v oras >/dev/null 2>&1; then | ||
| ORAS_BIN="$(command -v oras)" | ||
| echo "ORAS already available at $ORAS_BIN" | ||
| return 0 | ||
| fi | ||
|
Comment on lines
+41
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the oras binary location change, then |
||
|
|
||
| if [ -d "$OCI_TOOLS_BASE/oras" ]; then | ||
| echo "WARN: OCI volume mounted at $OCI_TOOLS_BASE/oras but oras binary not found on PATH, falling back to download" >&2 | ||
| fi | ||
|
|
||
| ORAS_VERSION="1.2.0" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oras version is hardcoded in the fallback. If the operator configure
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can introduce a variable to the task, but the plan is that over time (not too long hopefully) only ImageVolume will be used and the fallback will be removed entirely |
||
| case "$(uname -m)" in | ||
| x86_64) ORAS_ARCH="amd64" ;; | ||
| aarch64|arm64) ORAS_ARCH="arm64" ;; | ||
| *) | ||
| echo "ERROR: Unsupported architecture: $(uname -m)" >&2 | ||
| return 1 | ||
| ;; | ||
| esac | ||
| ORAS_TARBALL="oras_${ORAS_VERSION}_linux_${ORAS_ARCH}.tar.gz" | ||
| ORAS_BASE_URL="https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}" | ||
| ORAS_CHECKSUMS="oras_${ORAS_VERSION}_checksums.txt" | ||
|
|
||
| _cleanup_oras_files() { | ||
| rm -f "$ORAS_TARBALL" "$ORAS_CHECKSUMS" oras | ||
| } | ||
|
|
||
| trap _cleanup_oras_files EXIT | ||
|
|
||
| echo "Downloading ORAS ${ORAS_VERSION} with integrity verification..." | ||
|
|
||
| curl -LO "${ORAS_BASE_URL}/${ORAS_TARBALL}" || { | ||
| echo "ERROR: Failed to download ORAS tarball" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| curl -LO "${ORAS_BASE_URL}/${ORAS_CHECKSUMS}" || { | ||
| echo "ERROR: Failed to download ORAS checksums" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| expected_checksum=$(grep "${ORAS_TARBALL}" "${ORAS_CHECKSUMS}" | cut -d' ' -f1) | ||
| if [ -z "$expected_checksum" ]; then | ||
| echo "ERROR: Could not find checksum for ${ORAS_TARBALL} in checksums file" >&2 | ||
| return 1 | ||
| fi | ||
|
|
||
| if command -v sha256sum >/dev/null; then | ||
| actual_checksum=$(sha256sum "${ORAS_TARBALL}" | cut -d' ' -f1) | ||
| elif command -v shasum >/dev/null; then | ||
| actual_checksum=$(shasum -a 256 "${ORAS_TARBALL}" | cut -d' ' -f1) | ||
| else | ||
| echo "ERROR: Neither sha256sum nor shasum available for checksum verification" >&2 | ||
| return 1 | ||
| fi | ||
|
|
||
| if [ "$expected_checksum" != "$actual_checksum" ]; then | ||
| echo "ERROR: Checksum verification failed for ${ORAS_TARBALL}" >&2 | ||
| echo " Expected: $expected_checksum" >&2 | ||
| echo " Actual: $actual_checksum" >&2 | ||
| return 1 | ||
| fi | ||
|
|
||
| echo "Checksum verification passed: $expected_checksum" | ||
|
|
||
| tar -zxf "$ORAS_TARBALL" oras || { | ||
| echo "ERROR: Failed to extract ORAS from tarball" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| ORAS_INSTALL_DIR="${HOME:-/tmp}/bin" | ||
| if [ "$ORAS_INSTALL_DIR" = "//bin" ] || [ "$ORAS_INSTALL_DIR" = "/bin" ]; then | ||
| ORAS_INSTALL_DIR="/tmp/bin" | ||
| fi | ||
| mkdir -p "$ORAS_INSTALL_DIR" | ||
| mv oras "$ORAS_INSTALL_DIR/" || { | ||
| echo "ERROR: Failed to install ORAS binary" >&2 | ||
| return 1 | ||
| } | ||
|
|
||
| if ! echo "$PATH" | grep -q "$ORAS_INSTALL_DIR"; then | ||
| export PATH="$ORAS_INSTALL_DIR:$PATH" | ||
| fi | ||
|
|
||
| _cleanup_oras_files | ||
| trap - EXIT | ||
|
|
||
| ORAS_BIN="$ORAS_INSTALL_DIR/oras" | ||
| echo "ORAS ${ORAS_VERSION} installed successfully" | ||
| } | ||
|
|
||
| # --- Validation --- | ||
|
|
||
| validate_container_ref() { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.