diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ed4643bda14..92e53231665 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -342,6 +342,9 @@ jobs: TEST_LOG_PATH: ${{ github.workspace }}/artifacts/log/test-logs TestsRunningOutsideOfRepo: true TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX: 'netaspireci.azurecr.io' + # CLI E2E Dockerfiles consume this as a build arg to avoid overloaded default Ubuntu package servers. + # azure.archive.ubuntu.com does not serve HTTPS; HTTP is intentional and matches the GitHub-hosted Ubuntu runner fallback mirror. + ASPIRE_E2E_UBUNTU_APT_MIRROR: ${{ runner.os == 'Linux' && (runner.arch == 'ARM64' && 'https://azure.ports.ubuntu.com/ubuntu-ports/' || 'http://azure.archive.ubuntu.com/ubuntu/') || '' }} # PR metadata for CLI E2E tests that download artifacts from a PR GITHUB_PR_NUMBER: ${{ fromJson(inputs.properties).requiresCliArchive == true && github.event.pull_request.number || '' }} GITHUB_PR_HEAD_SHA: ${{ fromJson(inputs.properties).requiresCliArchive == true && github.event.pull_request.head.sha || '' }} @@ -428,6 +431,9 @@ jobs: NUGET_PACKAGES: ${{ github.workspace }}/.packages PLAYWRIGHT_INSTALLED: ${{ fromJson(inputs.properties).enablePlaywrightInstall != true && 'false' || 'true' }} TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX: 'netaspireci.azurecr.io' + # CLI E2E Dockerfiles consume this as a build arg to avoid overloaded default Ubuntu package servers. + # azure.archive.ubuntu.com does not serve HTTPS; HTTP is intentional and matches the GitHub-hosted Ubuntu runner fallback mirror. + ASPIRE_E2E_UBUNTU_APT_MIRROR: ${{ runner.os == 'Linux' && (runner.arch == 'ARM64' && 'https://azure.ports.ubuntu.com/ubuntu-ports/' || 'http://azure.archive.ubuntu.com/ubuntu/') || '' }} # PR metadata for CLI E2E tests that download artifacts from a PR GITHUB_PR_NUMBER: ${{ fromJson(inputs.properties).requiresCliArchive == true && github.event.pull_request.number || '' }} GITHUB_PR_HEAD_SHA: ${{ fromJson(inputs.properties).requiresCliArchive == true && github.event.pull_request.head.sha || '' }} diff --git a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2ETestHelpers.cs b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2ETestHelpers.cs index 764d208871d..b92a2b4d373 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2ETestHelpers.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2ETestHelpers.cs @@ -311,6 +311,7 @@ private static void EnsurePolyglotBaseImage(string repoRoot, ITestOutputHelper o startInfo.ArgumentList.Add("--quiet"); startInfo.ArgumentList.Add("--build-arg"); startInfo.ArgumentList.Add("SKIP_SOURCE_BUILD=true"); + AddUbuntuAptMirrorBuildArg(startInfo); startInfo.ArgumentList.Add("-f"); startInfo.ArgumentList.Add(dockerfilePath); startInfo.ArgumentList.Add("-t"); @@ -360,6 +361,7 @@ private static void EnsurePodmanBaseImage(string repoRoot, ITestOutputHelper out startInfo.ArgumentList.Add("build"); startInfo.ArgumentList.Add("--quiet"); + AddUbuntuAptMirrorBuildArg(startInfo); startInfo.ArgumentList.Add("-f"); startInfo.ArgumentList.Add(dockerfilePath); startInfo.ArgumentList.Add("-t"); @@ -446,6 +448,18 @@ private static string GenerateDockerContainerName() return $"hex1b-test-{Guid.NewGuid():N}".Substring(0, 32); } + private static void AddUbuntuAptMirrorBuildArg(ProcessStartInfo startInfo) + { + var buildArgs = new Dictionary(); + CliInstallStrategy.ConfigureUbuntuAptMirrorBuildArg(buildArgs); + + foreach (var (name, value) in buildArgs) + { + startInfo.ArgumentList.Add("--build-arg"); + startInfo.ArgumentList.Add($"{name}={value}"); + } + } + /// /// Walks up from the test assembly directory to find the repo root (contains Aspire.slnx). /// diff --git a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliInstallStrategyTests.cs b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliInstallStrategyTests.cs index f242b036c54..6c79e77c563 100644 --- a/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliInstallStrategyTests.cs +++ b/tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliInstallStrategyTests.cs @@ -158,6 +158,34 @@ public void ConfigureContainer_AddsPrMetadataForPullRequest() Assert.Equal("52669a7cac3d4f10c6269909fc38e77124ed177c", options.Environment["GITHUB_PR_HEAD_SHA"]); } + [Fact] + public void ConfigureContainer_AddsUbuntuAptMirrorBuildArgWhenEnvironmentVariableIsSet() + { + using var environment = new EnvironmentVariableScope( + (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, "http://azure.archive.ubuntu.com/ubuntu/")); + + var strategy = CliInstallStrategy.LatestGa(); + var options = new DockerContainerOptions(); + + strategy.ConfigureContainer(options); + + Assert.Equal("http://azure.archive.ubuntu.com/ubuntu/", options.BuildArgs[CliInstallStrategy.UbuntuAptMirrorBuildArgName]); + } + + [Fact] + public void ConfigureContainer_DoesNotAddUbuntuAptMirrorBuildArgWhenEnvironmentVariableIsEmpty() + { + using var environment = new EnvironmentVariableScope( + (CliInstallStrategy.UbuntuAptMirrorEnvironmentVariableName, null)); + + var strategy = CliInstallStrategy.LatestGa(); + var options = new DockerContainerOptions(); + + strategy.ConfigureContainer(options); + + Assert.DoesNotContain(CliInstallStrategy.UbuntuAptMirrorBuildArgName, options.BuildArgs.Keys); + } + [Fact] public void Detect_DotnetTool_WhenEnvironmentVariableIsSet() { diff --git a/tests/Shared/CliInstallStrategy.cs b/tests/Shared/CliInstallStrategy.cs index 2803ab3530e..17a30ce1942 100644 --- a/tests/Shared/CliInstallStrategy.cs +++ b/tests/Shared/CliInstallStrategy.cs @@ -209,6 +209,8 @@ internal enum CliInstallQuality internal sealed class CliInstallStrategy { internal const string CliArchiveDirEnvironmentVariableName = "ASPIRE_E2E_CLI_ARCHIVE_DIR"; + internal const string UbuntuAptMirrorBuildArgName = "UBUNTU_APT_MIRROR"; + internal const string UbuntuAptMirrorEnvironmentVariableName = "ASPIRE_E2E_UBUNTU_APT_MIRROR"; private const string PreinstalledEnvironmentVariableName = "ASPIRE_E2E_PREINSTALLED"; /// @@ -513,6 +515,7 @@ public static CliInstallStrategy Detect(Action? log = null) public void ConfigureContainer(DockerContainerOptions config) { config.BuildArgs["SKIP_SOURCE_BUILD"] = "true"; + ConfigureUbuntuAptMirrorBuildArg(config.BuildArgs); switch (Mode) { @@ -559,6 +562,15 @@ public void ConfigureContainer(DockerContainerOptions config) } } + internal static void ConfigureUbuntuAptMirrorBuildArg(IDictionary buildArgs) + { + var ubuntuAptMirror = Environment.GetEnvironmentVariable(UbuntuAptMirrorEnvironmentVariableName); + if (!string.IsNullOrWhiteSpace(ubuntuAptMirror)) + { + buildArgs[UbuntuAptMirrorBuildArgName] = ubuntuAptMirror; + } + } + /// /// Returns a description of this strategy for test output logging. /// diff --git a/tests/Shared/Docker/Dockerfile.e2e b/tests/Shared/Docker/Dockerfile.e2e index 9adb1fd7558..ee66407869e 100644 --- a/tests/Shared/Docker/Dockerfile.e2e +++ b/tests/Shared/Docker/Dockerfile.e2e @@ -20,8 +20,11 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG SKIP_SOURCE_BUILD=false +ARG UBUNTU_APT_MIRROR= # Install native AOT build toolchain. +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN if [ "$SKIP_SOURCE_BUILD" != "true" ]; then \ apt-get update -qq && \ apt-get install -y --no-install-recommends clang zlib1g-dev && \ @@ -47,10 +50,14 @@ RUN if [ "$SKIP_SOURCE_BUILD" != "true" ]; then \ # ============================================================ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS runtime +ARG UBUNTU_APT_MIRROR= + # --- Common tooling (shared between dotnet and polyglot variants) --- # Install gh CLI (needed by get-aspire-cli-pr.sh to download PR artifacts). # Install Docker CLI plus the buildx/compose plugins used by publish and deploy flows. +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN apt-get update -qq && \ apt-get install -y --no-install-recommends gpg docker.io docker-buildx docker-compose-v2 unzip && \ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ diff --git a/tests/Shared/Docker/Dockerfile.e2e-podman b/tests/Shared/Docker/Dockerfile.e2e-podman index 44b458ffb31..72e619f1dcd 100644 --- a/tests/Shared/Docker/Dockerfile.e2e-podman +++ b/tests/Shared/Docker/Dockerfile.e2e-podman @@ -4,6 +4,10 @@ # deploy flows can be tested without depending on host Podman state. FROM mcr.microsoft.com/dotnet/sdk:10.0 +ARG UBUNTU_APT_MIRROR= + +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN apt-get update -qq && \ apt-get install -y --no-install-recommends \ curl \ diff --git a/tests/Shared/Docker/Dockerfile.e2e-polyglot-base b/tests/Shared/Docker/Dockerfile.e2e-polyglot-base index 33f22ece629..a4a6e095aa3 100644 --- a/tests/Shared/Docker/Dockerfile.e2e-polyglot-base +++ b/tests/Shared/Docker/Dockerfile.e2e-polyglot-base @@ -21,8 +21,11 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build ARG SKIP_SOURCE_BUILD=false +ARG UBUNTU_APT_MIRROR= # Install native AOT build toolchain. +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN if [ "$SKIP_SOURCE_BUILD" != "true" ]; then \ apt-get update -qq && \ apt-get install -y --no-install-recommends clang zlib1g-dev && \ @@ -48,7 +51,11 @@ RUN if [ "$SKIP_SOURCE_BUILD" != "true" ]; then \ # ============================================================ FROM ubuntu:24.04 AS runtime +ARG UBUNTU_APT_MIRROR= + # Install base tools and Docker CLI. +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN apt-get update -qq && \ apt-get install -y --no-install-recommends \ ca-certificates curl gpg docker.io git libicu-dev unzip && \ diff --git a/tests/Shared/Docker/Dockerfile.e2e-polyglot-java b/tests/Shared/Docker/Dockerfile.e2e-polyglot-java index b8daa3dbddd..eeb3edd7dc1 100644 --- a/tests/Shared/Docker/Dockerfile.e2e-polyglot-java +++ b/tests/Shared/Docker/Dockerfile.e2e-polyglot-java @@ -3,6 +3,10 @@ FROM aspire-e2e-polyglot-base +ARG UBUNTU_APT_MIRROR= + +COPY tests/Shared/Docker/configure-ubuntu-apt-mirror.sh /usr/local/bin/ +RUN sh /usr/local/bin/configure-ubuntu-apt-mirror.sh "$UBUNTU_APT_MIRROR" RUN apt-get update -qq && \ apt-get install -y --no-install-recommends openjdk-25-jdk && \ rm -rf /var/lib/apt/lists/* diff --git a/tests/Shared/Docker/configure-ubuntu-apt-mirror.sh b/tests/Shared/Docker/configure-ubuntu-apt-mirror.sh new file mode 100644 index 00000000000..72f3943bc51 --- /dev/null +++ b/tests/Shared/Docker/configure-ubuntu-apt-mirror.sh @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu + +ubuntu_apt_mirror="${1:-}" + +if [ -z "$ubuntu_apt_mirror" ]; then + exit 0 +fi + +if [ ! -f /etc/os-release ]; then + echo "Skipping UBUNTU_APT_MIRROR because /etc/os-release was not found." >&2 + exit 0 +fi + +. /etc/os-release + +if [ "${ID:-}" != "ubuntu" ]; then + echo "Skipping UBUNTU_APT_MIRROR because current image ID is '${ID:-unknown}', not 'ubuntu'." >&2 + exit 0 +fi + +case "$ubuntu_apt_mirror" in + *://*) ;; + *) + echo "UBUNTU_APT_MIRROR must be an absolute URI, got '$ubuntu_apt_mirror'." >&2 + exit 1 + ;; +esac + +if [ -z "${VERSION_CODENAME:-}" ]; then + echo "Could not determine the Ubuntu version codename from /etc/os-release." >&2 + exit 1 +fi + +ubuntu_apt_mirror="${ubuntu_apt_mirror%/}" +ubuntu_apt_components="${UBUNTU_APT_COMPONENTS:-main restricted universe multiverse}" +ubuntu_sources_file="/etc/apt/sources.list.d/ubuntu.sources" + +mkdir -p "$(dirname "$ubuntu_sources_file")" + +cat > "$ubuntu_sources_file" < /etc/apt/sources.list +fi