diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index ae944b04..65615c82 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -309,6 +309,8 @@ jobs: fail-fast: false matrix: include: + - os: azurelinux3 + arch: linux/amd64,linux/arm64 - os: bookworm arch: linux/amd64,linux/arm64 - os: bullseye @@ -903,7 +905,7 @@ jobs: # Rate limiting on Azure DevOps SaaS APIs is triggered quickly by integration tests, so we need to limit the number of parallel jobs max-parallel: 3 matrix: - os: [bookworm, bullseye, focal, jammy, noble, ubi8, ubi9] + os: [azurelinux3, bookworm, bullseye, focal, jammy, noble, ubi8, ubi9] steps: - name: Checkout uses: actions/checkout@v4.1.7 diff --git a/docs/content/docs/advanced-topics/bicep-deployment.md b/docs/content/docs/advanced-topics/bicep-deployment.md index e143716c..1e1bc493 100644 --- a/docs/content/docs/advanced-topics/bicep-deployment.md +++ b/docs/content/docs/advanced-topics/bicep-deployment.md @@ -6,22 +6,22 @@ Bicep is a deployment language for Azure, allowing to easily deploy resources on #### Bicep parameters -| Parameter | Description | Default | -| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `autoscalingMaxReplicas` | Maximum number of simultaneous jobs the agent can run | `100` | -| `autoscalingMinReplicas` | Minimum number of replicas the agent should have | `0` | -| `autoscalingPollingInterval` | Minimum number of replicas the agent should have; Warning, a low value will cause rate limiting or throttling, and can cause high load on the Azure DevOps API | `10` | -| `extraEnv` | Extra environment variables to pass to the agent | `[]` | -| `imageFlavor` | Flavor of the container image, represents the Linux distribution. Allowed values: `bookworm`, `bullseye`, `focal`, `jammy`, `noble`, `ubi8`, `ubi9` | `bookworm` | -| `imageName` | Name of the container image | `clemlesne/blue-agent` | -| `imageRegistry` | Registry of the container image. Allowed values: `docker.io`, `ghcr.io` | `ghcr.io` | -| `imageVersion` | Version of the container image, it is recommended to use a specific version like "1.0.0" instead of "latest" | `main` | -| `instance` | Name of the instance, will be used to build the name of the resources | Value from `deployment().name` | -| `location` | Location of resources | `westeurope` | -| `pipelinesCapabilities` | Capabilities of the agent | `['arch_x64']` | -| `pipelinesOrganizationURL` | URL of the Azure DevOps organization | _None_ | -| `pipelinesPersonalAccessToken` | Personal access token allowing the agent to connect to the Azure DevOps organization. This parameter is secure. | _None_ | -| `pipelinesPoolName` | Name of the Azure Pipelines self-hosted pool the agent should be added to | _None_ | -| `pipelinesTimeout` | Timeout in seconds for the agent to run a job before it is automatically terminated | `3600` | -| `resourcesCpu` | Number of CPU cores allocated to the agent | `2` | -| `resourcesMemory` | Amount of memory allocated to the agent | `4Gi` | +| Parameter | Description | Default | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ | +| `autoscalingMaxReplicas` | Maximum number of simultaneous jobs the agent can run | `100` | +| `autoscalingMinReplicas` | Minimum number of replicas the agent should have | `0` | +| `autoscalingPollingInterval` | Minimum number of replicas the agent should have; Warning, a low value will cause rate limiting or throttling, and can cause high load on the Azure DevOps API | `10` | +| `extraEnv` | Extra environment variables to pass to the agent | `[]` | +| `imageFlavor` | Flavor of the container image, represents the Linux distribution. Allowed values: `azurelinux3`, `bookworm`, `bullseye`, `focal`, `jammy`, `noble`, `ubi8`, `ubi9` | `bookworm` | +| `imageName` | Name of the container image | `clemlesne/blue-agent` | +| `imageRegistry` | Registry of the container image. Allowed values: `docker.io`, `ghcr.io` | `ghcr.io` | +| `imageVersion` | Version of the container image, it is recommended to use a specific version like "1.0.0" instead of "latest" | `main` | +| `instance` | Name of the instance, will be used to build the name of the resources | Value from `deployment().name` | +| `location` | Location of resources | `westeurope` | +| `pipelinesCapabilities` | Capabilities of the agent | `['arch_x64']` | +| `pipelinesOrganizationURL` | URL of the Azure DevOps organization | _None_ | +| `pipelinesPersonalAccessToken` | Personal access token allowing the agent to connect to the Azure DevOps organization. This parameter is secure. | _None_ | +| `pipelinesPoolName` | Name of the Azure Pipelines self-hosted pool the agent should be added to | _None_ | +| `pipelinesTimeout` | Timeout in seconds for the agent to run a job before it is automatically terminated | `3600` | +| `resourcesCpu` | Number of CPU cores allocated to the agent | `2` | +| `resourcesMemory` | Amount of memory allocated to the agent | `4Gi` | diff --git a/docs/content/docs/advanced-topics/docker-in-docker.md b/docs/content/docs/advanced-topics/docker-in-docker.md index 056f4c8a..ae658581 100644 --- a/docs/content/docs/advanced-topics/docker-in-docker.md +++ b/docs/content/docs/advanced-topics/docker-in-docker.md @@ -17,6 +17,7 @@ Linux systems are supported, but not Windows: | `Ref` | Container build inside of the agent with BuildKit | | ------------------------------------------------ | ------------------------------------------------- | +| `ghcr.io/clemlesne/blue-agent:azurelinux3-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:bookworm-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:bullseye-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:focal-main` | ✅ | diff --git a/docs/content/docs/getting-started.md b/docs/content/docs/getting-started.md index 63e7cdbd..357a0dd9 100644 --- a/docs/content/docs/getting-started.md +++ b/docs/content/docs/getting-started.md @@ -84,6 +84,7 @@ OS support is generally called "flavor" in this documentation. The following tab | `Ref` | OS | `Size` | `Arch` | Support | | ------------------------------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ghcr.io/clemlesne/blue-agent:azurelinux3-main` | [Azure Linux 3](https://github.com/microsoft/azurelinux) | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/clemlesne/blue-agent/azurelinux3-main?label=) | `amd64`, `arm64/v8` | [See Microsoft Azure documentation.](https://learn.microsoft.com/en-us/azure/aks/support-policies) | | `ghcr.io/clemlesne/blue-agent:bookworm-main` | [Debian Bookworm (12)](https://www.debian.org/releases/bookworm) slim | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/clemlesne/blue-agent/bookworm-main?label=) | `amd64`, `arm64/v8` | [See Debian LTS wiki.](https://wiki.debian.org/LTS) | | `ghcr.io/clemlesne/blue-agent:bullseye-main` | [Debian Bullseye (11)](https://www.debian.org/releases/bullseye) slim | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/clemlesne/blue-agent/bullseye-main?label=) | `amd64`, `arm64/v8` | [See Debian LTS wiki.](https://wiki.debian.org/LTS) | | `ghcr.io/clemlesne/blue-agent:noble-main` | [Ubuntu Noble (24.04)](https://www.releases.ubuntu.com/noble) minimal | ![Docker Image Size (tag)](https://img.shields.io/docker/image-size/clemlesne/blue-agent/noble-main?label=) | `amd64` | [See Ubuntu LTS wiki.](https://wiki.ubuntu.com/Releases) | diff --git a/docs/content/docs/security.md b/docs/content/docs/security.md index e664b7ed..4a293488 100644 --- a/docs/content/docs/security.md +++ b/docs/content/docs/security.md @@ -14,6 +14,7 @@ Scanned systems: | `Ref` | Vulnerability scans with Snyk | | ------------------------------------------------ | ----------------------------- | +| `ghcr.io/clemlesne/blue-agent:azurelinux3-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:bookworm-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:bullseye-main` | ✅ | | `ghcr.io/clemlesne/blue-agent:focal-main` | ✅ | diff --git a/src/bicep/main.bicep b/src/bicep/main.bicep index 8e53fff3..85fd72d9 100644 --- a/src/bicep/main.bicep +++ b/src/bicep/main.bicep @@ -11,6 +11,7 @@ param autoscalingPollingInterval int = 10 param extraEnv array = [] @description('Flavor of the container image, represents the Linux distribution') @allowed([ + 'azurelinux3' 'bookworm' 'bullseye' 'focal' diff --git a/src/docker/Dockerfile-azurelinux3 b/src/docker/Dockerfile-azurelinux3 new file mode 100644 index 00000000..5095d832 --- /dev/null +++ b/src/docker/Dockerfile-azurelinux3 @@ -0,0 +1,269 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0-azurelinux3.0@sha256:658394c35959ef19b0e83bef04dd5fad40899fcf44ef89e6ea2b073cb4eb879a AS base + +# Configure local user +ENV USER=root +ENV HOME=/app-root + +# Allow tdnf to valides TLS connections +ENV GNUPGHOME=/root/.gnupg + +# Avoid Python cache during build +ENV PYTHONDONTWRITEBYTECODE=1 + +# Install: +# - Azure CLI system requirements (C/Rust build tools for libs non pre-built on this platform) +# - Azure Pipelines agent system requirements +# - iptables, for BuildKit +# - gzip, make, tar, unzip, wget, zip, zstd for developer ease-of-life +# - zsh, for inter-operability +RUN --mount=target=/var/cache/tdnf,type=cache,id=yum-${TARGETPLATFORM},sharing=locked \ + tdnf update -y \ + && tdnf install -y \ + build-essential \ + ca-certificates \ + cargo \ + curl \ + findutils \ + gcc \ + gcc-c++ \ + git \ + git-core \ + git-lfs \ + gnupg \ + gzip \ + hostname \ + iptables \ + iputils \ + jq \ + lsb-release \ + make \ + openssl \ + openssl-devel \ + pkg-config \ + shadow-utils \ + sudo \ + tar \ + unzip \ + wget \ + zip \ + zsh \ + zstd \ + && find / -depth -type d -name __pycache__ -exec rm -rf {} \; 2> /dev/null + +# Copy helper script, then verify installation +COPY arch.sh . +RUN chmod +x arch.sh \ + && bash arch.sh + +# Persist Python version +ARG PYTHON_VERSION_MAJOR_MINOR +ARG PYTHON_VERSION_PATCH +ENV PYTHON_VERSION=${PYTHON_VERSION_MAJOR_MINOR}.${PYTHON_VERSION_PATCH} + +FROM base AS rootlesskit + +# Install Go, then verify installation +ARG GO_VERSION +ENV GO_VERSION=${GO_VERSION} +RUN rm -rf /usr/local/go \ + && curl -LsSf --retry 8 --retry-all-errors https://go.dev/dl/go${GO_VERSION}.linux-$(ARCH_X64=amd64 bash arch.sh).tar.gz | tar -xz -C /usr/local +ENV PATH="${PATH}:/usr/local/go/bin" +RUN go version + +# Install RootlessKit, then verify installation +ARG ROOTLESSKIT_VERSION +ENV ROOTLESSKIT_VERSION=${ROOTLESSKIT_VERSION} +RUN --mount=target=/rootlesskit-${ROOTLESSKIT_VERSION},type=cache,id=rootlesskit-${ROOTLESSKIT_VERSION}-${TARGETPLATFORM},sharing=locked \ + git clone --depth 1 --branch v${ROOTLESSKIT_VERSION} https://github.com/rootless-containers/rootlesskit.git rootlesskit \ + # Ugly but that's work + && cp -r rootlesskit/* rootlesskit-${ROOTLESSKIT_VERSION} \ + && rm -rf rootlesskit \ + && cd rootlesskit-${ROOTLESSKIT_VERSION} \ + && make \ + && make install \ + && cd .. \ + && rootlesskit --version \ + && rootlessctl --version + +FROM base AS python + +# Build Python from source, then verify installation +ARG PYTHON_VERSION +ENV PYTHON_VERSION=${PYTHON_VERSION} +RUN --mount=target=/var/cache/tdnf,type=cache,id=yum-${TARGETPLATFORM},sharing=locked --mount=target=/Python-${PYTHON_VERSION},type=cache,id=python-${PYTHON_VERSION}-${TARGETPLATFORM},sharing=locked \ + tdnf update -y \ + && tdnf install -y \ + bzip2 \ + bzip2-devel \ + expat \ + expat-devel \ + gdb \ + gdbm-devel \ + glibc-devel \ + libffi-devel \ + libstdc++-devel \ + libuuid-devel \ + libxml2-devel \ + ncurses-devel \ + rpm-build \ + sqlite \ + sqlite-devel \ + sqlite-libs \ + xz-devel \ + xz-libs \ + zlib-devel \ + && curl -LsSf --retry 8 --retry-all-errors https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz -o python.tgz \ + && tar -xzf python.tgz \ + && rm python.tgz \ + && cd Python-${PYTHON_VERSION} \ + && gnu_arch="$(rpm --eval '%{_target_cpu}')-centos-linux-gnu" \ + && ./configure \ + --build=$gnu_arch \ + --enable-optimizations \ + --with-ensurepip=install \ + --with-lto \ + && make profile-removal \ + && extra_cflags="$(rpm --eval '%{optflags}')" \ + && ldflags="$(rpm --eval '%{__global_ldflags}')" \ + && make -j $(nproc) "EXTRA_CFLAGS=${extra_cflags:-}" "LDFLAGS=${ldflags:-}" \ + && make install \ + && cd .. \ + && python3 --version \ + && python3 -m pip --version \ + && find / -depth -type d -name __pycache__ -exec rm -rf {} \; 2> /dev/null + +FROM base + +# Install Python, then verify installation +COPY --from=python /usr/local/bin/python${PYTHON_VERSION_MAJOR_MINOR} /usr/local/bin/python${PYTHON_VERSION_MAJOR_MINOR} +COPY --from=python /usr/local/lib/python${PYTHON_VERSION_MAJOR_MINOR} /usr/local/lib/python${PYTHON_VERSION_MAJOR_MINOR} +RUN ln -s /usr/local/bin/python${PYTHON_VERSION_MAJOR_MINOR} /usr/local/bin/python3 \ + && ln -s /usr/local/bin/python${PYTHON_VERSION_MAJOR_MINOR} /usr/local/bin/python \ + && python --version \ + && python3 --version \ + && python${PYTHON_VERSION_MAJOR_MINOR} --version \ + && python3 -m pip --version + +# Install Python build tools +RUN --mount=target=/${USER}/.cache/pip,type=cache,id=pip-${PYTHON_VERSION_MAJOR_MINOR}-${TARGETPLATFORM},sharing=locked \ + python3 -m pip \ + --disable-pip-version-check \ + --quiet \ + install \ + setuptools wheel \ + && find / -depth -type d -name __pycache__ -exec rm -rf {} \; 2> /dev/null + +# Install Azure CLI, then verify installation +ARG AZURE_CLI_VERSION +ENV AZURE_CLI_VERSION=${AZURE_CLI_VERSION} +RUN --mount=target=/${USER}/.cache/pip,type=cache,id=pip-${PYTHON_VERSION_MAJOR_MINOR}-${TARGETPLATFORM},sharing=locked \ + python3 -m pip \ + --disable-pip-version-check \ + --quiet \ + install \ + azure-cli==${AZURE_CLI_VERSION} \ + && az version \ + && rm -rf ${HOME}/.azure ${HOME}/.cache/pip \ + && find / -depth -type d -name __pycache__ -exec rm -rf {} \; 2> /dev/null + +# Install AWS CLI, then verify installation +ARG AWS_CLI_VERSION +ENV AWS_CLI_VERSION=${AWS_CLI_VERSION} +RUN curl -LsSf --retry 8 --retry-all-errors https://awscli.amazonaws.com/awscli-exe-linux-$(ARCH_X64=x86_64 ARCH_ARM64=aarch64 bash arch.sh)-${AWS_CLI_VERSION}.zip -o awscli.zip \ + && unzip -q awscli.zip \ + && ./aws/install \ + && rm -rf awscli.zip aws \ + && aws --version + +# Install Google Cloud CLI, then verify installation +ARG GCLOUD_CLI_VERSION +ENV GCLOUD_CLI_VERSION=${GCLOUD_CLI_VERSION} +RUN curl -LsSf --retry 8 --retry-all-errors https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-${GCLOUD_CLI_VERSION}-linux-$(ARCH_X64=x86_64 ARCH_ARM64=arm bash arch.sh).tar.gz | tar -xz -C /usr/local \ + && /usr/local/google-cloud-sdk/install.sh \ + --additional-components beta \ + --quiet \ + && ln -s /usr/local/google-cloud-sdk/bin/gcloud /usr/bin/gcloud \ + && ln -s /usr/local/google-cloud-sdk/bin/gsutil /usr/bin/gsutil \ + && gcloud version \ + && rm -rf /usr/local/google-cloud-sdk/.install ${HOME}/.config/gcloud \ + && find / -depth -type d -name __pycache__ -exec rm -rf {} \; 2> /dev/null + +# Install Powershell, then verify installation +ARG POWERSHELL_VERSION +ENV POWERSHELL_VERSION=${POWERSHELL_VERSION} +RUN mkdir -p /opt/microsoft/powershell \ + && curl -LsSf --retry 8 --retry-all-errors https://github.com/PowerShell/PowerShell/releases/download/v${POWERSHELL_VERSION}/powershell-${POWERSHELL_VERSION}-linux-$(bash arch.sh).tar.gz | tar -xz -C /opt/microsoft/powershell \ + && chmod +x /opt/microsoft/powershell/pwsh \ + && ln -s /opt/microsoft/powershell/pwsh /usr/bin/pwsh \ + && pwsh -Version \ + && rm -rf ${HOME}/.config/powershell ${HOME}/.cache/powershell + +# Install YQ, then verify installation +ARG YQ_VERSION +ENV YQ_VERSION=${YQ_VERSION} +RUN curl -LsSf --retry 8 --retry-all-errors https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_$(ARCH_X64=amd64 bash arch.sh) -o /usr/bin/yq \ + && chmod +x /usr/bin/yq \ + && yq --version + +# Install Tini, then verify installation +ARG TINI_VERSION +ENV TINI_VERSION=${TINI_VERSION} +RUN curl -LsSf --retry 8 --retry-all-errors https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini-$(ARCH_X64=amd64 bash arch.sh) -o /tini \ + && chmod +x /tini \ + && /tini --version +ENTRYPOINT ["/tini", "--"] + +# Install BuildKit, then verify installation +ARG BUILDKIT_VERSION +ENV BUILDKIT_VERSION=${BUILDKIT_VERSION} +RUN mkdir buildkit \ + && curl -LsSf --retry 8 --retry-all-errors https://github.com/moby/buildkit/releases/download/v${BUILDKIT_VERSION}/buildkit-v${BUILDKIT_VERSION}.linux-$(ARCH_X64=amd64 bash arch.sh).tar.gz | tar -xz -C buildkit \ + && mv buildkit/bin/* /usr/local/bin \ + && rm -rf buildkit \ + && buildctl --version \ + && buildkitd --version + +# Install RootlessKit, then verify installation +COPY --from=rootlesskit /usr/local/bin/rootless* /usr/bin/ +RUN rootlesskit --version \ + && rootlessctl --version + +# Install Azure Pipelines Agent sources, then verify installation +ARG AZP_AGENT_VERSION +ENV AZP_AGENT_VERSION=${AZP_AGENT_VERSION} +ENV AZP_HOME=${HOME}/azp-agent +# Disable agent auto-updates +# See: https://github.com/microsoft/azure-pipelines-agent/blob/b5ff4408239f3e938560f8b2e3848df76489a8d0/src/Agent.Listener/Agent.cs#L354C24-L354C24 +ENV agent.disableupdate="1" +RUN mkdir -p ${AZP_HOME} \ + && curl -LsSf --retry 8 --retry-all-errors https://vstsagentpackage.azureedge.net/agent/${AZP_AGENT_VERSION}/pipelines-agent-linux-$(bash arch.sh)-${AZP_AGENT_VERSION}.tar.gz | tar -xz -C ${AZP_HOME} \ + && cd ${AZP_HOME} \ + && chmod +x run-docker.sh config.sh \ + && AGENT_ALLOW_RUNASROOT="1" bash run-docker.sh --version \ + && rm -rf _diag \ + # Allow local user to R/W to agent home + && chmod -R a+w . +ENV AZP_WORK=${HOME}/azp-work +ENV AZP_CUSTOM_CERT_PEM=${HOME}/azp-custom-certs + +# Cleanup helper script +RUN rm arch.sh + +# Reset Python configs to default +ENV PYTHONDONTWRITEBYTECODE= +ENV PIP_BREAK_SYSTEM_PACKAGES= + +# Configure local user +RUN mkdir -p /run/user/0 ${HOME}/.local/tmp ${HOME}/.local/share/buildkit \ + && chown -R ${USER} /run/user/0 ${HOME} \ + && echo ${USER}:100000:65536 | tee /etc/subuid | tee /etc/subgid +USER 0:0 +ENV XDG_RUNTIME_DIR=/run/user/0 +ENV TMPDIR=${HOME}/.local/tmp +ENV BUILDKIT_HOST=unix:///run/user/0/buildkit/buildkitd.sock + +# Install Azure Pipelines Agent startup script +WORKDIR ${AZP_HOME} +COPY start.sh . +# Run as exec form, so that it can receive signals from Tini +CMD ["bash", "start.sh"] diff --git a/test/pipeline/root.yaml b/test/pipeline/root.yaml index a4f7490d..d280a021 100644 --- a/test/pipeline/root.yaml +++ b/test/pipeline/root.yaml @@ -20,10 +20,16 @@ jobs: - bash: | if command -v apt-get &> /dev/null; then + echo "Using apt-get" sudo apt-get update sudo apt-get install -y python3-pip elif command -v microdnf &> /dev/null; then + echo "Using microdnf" sudo microdnf install -y python3.11-pip + elif command -v tdnf &> /dev/null; then + echo "Using tdnf" + sudo tdnf update -y + sudo tdnf install -y python3.11-pip else echo "No suported package manager" exit 1