Skip to content
Draft
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
248 changes: 162 additions & 86 deletions .github/workflows/build-cli-e2e-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,14 @@ on:
type: boolean
default: true

env:
CLI_E2E_DOTNET_IMAGE_ARTIFACT: cli-e2e-dotnet-image
CLI_E2E_DOTNET_IMAGE_TAG: aspire-cli-e2e-dotnet:prebuilt
CLI_E2E_DOTNET_IMAGE_CACHE_SCOPE: cli-e2e-dotnet
CLI_E2E_POLYGLOT_IMAGE_ARTIFACT: cli-e2e-polyglot-image
CLI_E2E_POLYGLOT_IMAGE_TAG: aspire-cli-e2e-polyglot:prebuilt
CLI_E2E_POLYGLOT_JAVA_IMAGE_ARTIFACT: cli-e2e-polyglot-java-image
CLI_E2E_POLYGLOT_JAVA_IMAGE_TAG: aspire-cli-e2e-polyglot-java:prebuilt
CLI_E2E_POLYGLOT_BASE_IMAGE_TAG: aspire-e2e-polyglot-base:latest
CLI_E2E_POLYGLOT_BASE_IMAGE_CACHE_SCOPE: cli-e2e-polyglot-base
CLI_E2E_INCLUDE_POLYGLOT_IMAGES: ${{ inputs.includePolyglotImages }}

jobs:
build:
name: Build CLI E2E Docker image
runs-on: ${{ github.repository_owner == 'microsoft' && '8-core-ubuntu-latest' || 'ubuntu-latest' }}
timeout-minutes: 45
timeout-minutes: 60
env:
INCLUDE_POLYGLOT: ${{ inputs.includePolyglotImages }}
UPLOAD_ARTIFACTS: ${{ inputs.uploadImageArtifacts }}

steps:
- name: Checkout code
Expand All @@ -52,114 +43,199 @@ jobs:
}
}

- name: Build CLI E2E Docker image
- name: Build (and optionally save) CLI E2E Docker images
shell: bash
run: |
set -euo pipefail

docker buildx create --name cli-e2e-builder --use
docker buildx inspect --bootstrap

mkdir -p artifacts/cli-e2e-image

# Build a single image through the cli-e2e-builder buildx container
# with GHA layer cache and an apt-mirror fallback. We try the Azure
# Ubuntu mirror first (faster + more reliable from GitHub-hosted
# runners) and fall back to the default sources on failure.
# SKIP_SOURCE_BUILD is only relevant to the .NET-bearing images so
# it is opt-in via the trailing flag. All remaining args are tags
# (-t) — the polyglot base passes two tags so both the
# FROM-referenced base name and the artifact-friendly polyglot name
# resolve locally without a follow-up `docker tag` step.
#
# Only used for the .NET image and the polyglot base; extended
# polyglot images build through the default `docker` driver below
# because the cli-e2e-builder's container image store can't see
# the host daemon's `aspire-e2e-polyglot-base:latest` tag.
build_image() {
local display_name="$1"
local dockerfile="$2"
local tag="$3"
local cache_scope="$4"
local mirror="$5"
local -a build_args=(--build-arg SKIP_SOURCE_BUILD=true)

if [[ -n "$mirror" ]]; then
echo "Building $display_name with Ubuntu apt mirror: $mirror"
build_args+=(--build-arg "UBUNTU_APT_MIRROR=$mirror")
else
echo "Building $display_name with the default Ubuntu apt sources"
local display="$1" dockerfile="$2" cache_scope="$3" skip_source="$4"
shift 4
local -a tag_args=()
local t
for t in "$@"; do
tag_args+=(-t "$t")
done

local -a extra_args=()
if [[ "$skip_source" == "true" ]]; then
extra_args+=(--build-arg SKIP_SOURCE_BUILD=true)
fi

docker buildx build \
--load \
--cache-from "type=gha,scope=$cache_scope" \
--cache-to "type=gha,scope=$cache_scope,mode=max,ignore-error=true" \
"${build_args[@]}" \
-f "$dockerfile" \
-t "$tag" \
.
local mirror="http://azure.archive.ubuntu.com/ubuntu/"
while :; do
local -a mirror_args=()
if [[ -n "$mirror" ]]; then
echo "Building $display with Ubuntu apt mirror: $mirror"
mirror_args+=(--build-arg "UBUNTU_APT_MIRROR=$mirror")
else
echo "Building $display with the default Ubuntu apt sources"
fi

if docker buildx build \
--load \
--cache-from "type=gha,scope=$cache_scope" \
--cache-to "type=gha,scope=$cache_scope,mode=max,ignore-error=true" \
"${extra_args[@]}" \
"${mirror_args[@]}" \
"${tag_args[@]}" \
-f "$dockerfile" \
.; then
return 0
fi

if [[ -z "$mirror" ]]; then
return 1
fi
echo "$display build failed with the Azure Ubuntu apt mirror; retrying with default Ubuntu apt sources"
mirror=""
done
}

build_java_image() {
local mirror="$1"
local -a build_args=()

if [[ -n "$mirror" ]]; then
echo "Building Java polyglot image with Ubuntu apt mirror: $mirror"
build_args+=(--build-arg "UBUNTU_APT_MIRROR=$mirror")
else
echo "Building Java polyglot image with the default Ubuntu apt sources"
fi

DOCKER_BUILDKIT=1 docker build \
"${build_args[@]}" \
-f tests/Shared/Docker/Dockerfile.e2e-polyglot-java \
-t "$CLI_E2E_POLYGLOT_JAVA_IMAGE_TAG" \
.
save_image() {
local tag="$1" tarball="$2"
[[ "$UPLOAD_ARTIFACTS" == "true" ]] || return 0
docker save "$tag" | gzip > "artifacts/cli-e2e-image/$tarball"
}

build_with_mirror_retry() {
local display_name="$1"
local dockerfile="$2"
local tag="$3"
local cache_scope="$4"

if ! build_image "$display_name" "$dockerfile" "$tag" "$cache_scope" "http://azure.archive.ubuntu.com/ubuntu/"; then
echo "$display_name build failed with the Azure Ubuntu apt mirror; retrying with default Ubuntu apt sources"
build_image "$display_name" "$dockerfile" "$tag" "$cache_scope" ""
fi
}
# .NET image (uses SKIP_SOURCE_BUILD=true).
build_image ".NET image" tests/Shared/Docker/Dockerfile.e2e cli-e2e-dotnet true \
aspire-cli-e2e-dotnet:prebuilt
save_image aspire-cli-e2e-dotnet:prebuilt aspire-cli-e2e-dotnet.tar.gz

build_with_mirror_retry ".NET image" "tests/Shared/Docker/Dockerfile.e2e" "$CLI_E2E_DOTNET_IMAGE_TAG" "$CLI_E2E_DOTNET_IMAGE_CACHE_SCOPE"

if [[ "$CLI_E2E_INCLUDE_POLYGLOT_IMAGES" == "true" ]]; then
build_with_mirror_retry "polyglot base image" "tests/Shared/Docker/Dockerfile.e2e-polyglot-base" "$CLI_E2E_POLYGLOT_BASE_IMAGE_TAG" "$CLI_E2E_POLYGLOT_BASE_IMAGE_CACHE_SCOPE"
docker tag "$CLI_E2E_POLYGLOT_BASE_IMAGE_TAG" "$CLI_E2E_POLYGLOT_IMAGE_TAG"

if ! build_java_image "http://azure.archive.ubuntu.com/ubuntu/"; then
echo "Java polyglot image build failed with the Azure Ubuntu apt mirror; retrying with default Ubuntu apt sources"
build_java_image ""
fi
if [[ "$INCLUDE_POLYGLOT" != "true" ]]; then
exit 0
fi

- name: Save CLI E2E Docker image tarballs
if: ${{ inputs.uploadImageArtifacts }}
shell: bash
run: |
set -euo pipefail

mkdir -p artifacts/cli-e2e-image
docker save "$CLI_E2E_DOTNET_IMAGE_TAG" | gzip > artifacts/cli-e2e-image/aspire-cli-e2e-dotnet.tar.gz
if [[ "$CLI_E2E_INCLUDE_POLYGLOT_IMAGES" == "true" ]]; then
docker save "$CLI_E2E_POLYGLOT_IMAGE_TAG" | gzip > artifacts/cli-e2e-image/aspire-cli-e2e-polyglot.tar.gz
docker save "$CLI_E2E_POLYGLOT_JAVA_IMAGE_TAG" | gzip > artifacts/cli-e2e-image/aspire-cli-e2e-polyglot-java.tar.gz
fi
# Polyglot base image. Tag with both the FROM-referenced base name
# (used by the extended dockerfiles below) and the artifact-friendly
# polyglot tag (used by downstream loaders in load-cli-e2e-images.sh).
# SKIP_SOURCE_BUILD=true: CI installs the CLI from prebuilt artifacts
# (downloaded per-test in run-tests.yml), not from a source build inside
# the container. The container source-build path is only used for local
# `docker build` dev iteration and is intentionally skipped here so
# arcade does not need RepositoryUrl/Source Link metadata that is
# missing once `.dockerignore` strips `.git/` from the build context.
build_image "polyglot base image" tests/Shared/Docker/Dockerfile.e2e-polyglot-base cli-e2e-polyglot-base true \
aspire-e2e-polyglot-base:latest \
aspire-cli-e2e-polyglot:prebuilt
save_image aspire-cli-e2e-polyglot:prebuilt aspire-cli-e2e-polyglot.tar.gz

# Extended polyglot images. All layer on top of the polyglot base and
# install a language toolchain via apt or a multi-stage COPY from the
# official language image.
#
# These build through the default docker driver (host daemon) instead
# of the cli-e2e-builder buildx container, because:
#
# 1. They `FROM aspire-e2e-polyglot-base`, which was just `--load`-ed
# into the host daemon. The cli-e2e-builder uses the
# docker-container driver, which has an image store separate from
# the host. A naked `FROM aspire-e2e-polyglot-base` from inside
# that container resolves through docker.io/library/... and 401s.
# Earlier attempts to bridge this with
# `--build-context aspire-e2e-polyglot-base=docker-image://...`
# were still resolved as a remote registry reference by buildkit,
# not a local daemon lookup, so they 401'd the same way.
#
# 2. The default driver can't export `type=gha` cache, but these
# images are thin layers on top of the cached base — losing the
# cache for them is cheap. The heavy upstream `golang:1` /
# `rust:1` COPY stages re-pull on every build but those are
# multi-stage source images, not registry pulls of our base,
# and they're rate-limit-free official images.
for lang in java python go rust; do
display="${lang^} polyglot image"
tag="aspire-cli-e2e-polyglot-${lang}:prebuilt"
mirror="http://azure.archive.ubuntu.com/ubuntu/"
while :; do
mirror_arg=()
if [[ -n "$mirror" ]]; then
echo "Building $display with Ubuntu apt mirror: $mirror"
mirror_arg+=(--build-arg "UBUNTU_APT_MIRROR=$mirror")
else
echo "Building $display with the default Ubuntu apt sources"
fi
if DOCKER_BUILDKIT=1 docker build \
"${mirror_arg[@]}" \
-f "tests/Shared/Docker/Dockerfile.e2e-polyglot-${lang}" \
-t "$tag" \
.; then
break
fi
if [[ -z "$mirror" ]]; then
echo "$display build failed with the default Ubuntu apt sources" >&2
exit 1
fi
echo "$display build failed with the Azure Ubuntu apt mirror; retrying with default Ubuntu apt sources"
mirror=""
done
save_image "$tag" "aspire-cli-e2e-polyglot-${lang}.tar.gz"
done

- name: Upload CLI E2E .NET Docker image
if: ${{ inputs.uploadImageArtifacts }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ env.CLI_E2E_DOTNET_IMAGE_ARTIFACT }}
name: cli-e2e-dotnet-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-dotnet.tar.gz
retention-days: 1

- name: Upload CLI E2E polyglot Docker image
if: ${{ inputs.uploadImageArtifacts && inputs.includePolyglotImages }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ env.CLI_E2E_POLYGLOT_IMAGE_ARTIFACT }}
name: cli-e2e-polyglot-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-polyglot.tar.gz
retention-days: 1

- name: Upload CLI E2E Java Docker image
if: ${{ inputs.uploadImageArtifacts && inputs.includePolyglotImages }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: ${{ env.CLI_E2E_POLYGLOT_JAVA_IMAGE_ARTIFACT }}
name: cli-e2e-polyglot-java-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-polyglot-java.tar.gz
retention-days: 1

- name: Upload CLI E2E Python Docker image
if: ${{ inputs.uploadImageArtifacts && inputs.includePolyglotImages }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: cli-e2e-polyglot-python-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-polyglot-python.tar.gz
retention-days: 1

- name: Upload CLI E2E Go Docker image
if: ${{ inputs.uploadImageArtifacts && inputs.includePolyglotImages }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: cli-e2e-polyglot-go-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-polyglot-go.tar.gz
retention-days: 1

- name: Upload CLI E2E Rust Docker image
if: ${{ inputs.uploadImageArtifacts && inputs.includePolyglotImages }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: cli-e2e-polyglot-rust-image
path: artifacts/cli-e2e-image/aspire-cli-e2e-polyglot-rust.tar.gz
retention-days: 1
Loading
Loading