Skip to content
Merged
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
122 changes: 122 additions & 0 deletions .azure-pipelines/publish-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Trigger: any `v*` release tag (e.g. v1.40.0).
# Can also be queued manually from the ADO UI.
trigger:
tags:
include:
- v*
exclude:
- v*-*

pr: none

parameters:
- name: releaseChannel
displayName: "IMPORTANT: set this to 'canary' when triggering manually"
type: string
default: stable
values:
- stable
- canary

resources:
repositories:
- repository: 1esPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
parameters:
pool:
name: DevDivPlaywrightAzurePipelinesUbuntu2204
os: linux
sdl:
sourceAnalysisPool:
name: DevDivPlaywrightAzurePipelinesWindows2022
# The image must be windows-based due to restrictions of the SDL tools. See: https://aka.ms/AAo6v8e
os: windows
suppression:
suppressionFile: $(Build.SourcesDirectory)\.azure-pipelines\guardian\SDL\.gdnsuppress
stages:
- stage: Publish
jobs:
- job: PublishDocker
displayName: "Publish Docker images to ACR"
# arm64 images are cross-built under QEMU emulation, which is slow.
timeoutInMinutes: 360
steps:
- checkout: self
displayName: "Checkout code"

- task: UseNode@1
inputs:
version: '26.x'
displayName: "Install Node.js"

# Relocate the Docker data-root to the large /mnt volume: this job builds
# 6 images (jammy/noble/resolute x amd64/arm64) and `docker system prune`s
# between them, so the default disk fills up.
- task: Bash@3
displayName: "Setup docker"
inputs:
targetType: "inline"
script: |
set -x
sudo service docker stop
sudo mkdir -p /etc/docker
echo '{ "data-root": "/mnt/docker" }' | sudo tee /etc/docker/daemon.json
sudo service docker start

# Used by scripts/download_driver.sh on the host. The repository root is the
# Docker build context, so .dockerignore keeps this file out of the images.
- task: Bash@3
displayName: "setup .npmrc"
inputs:
targetType: "inline"
script: |
echo "registry=https://devdiv.pkgs.visualstudio.com/DevDiv/_packaging/DevDiv_PublicPackages/npm/registry/" >> .npmrc

- task: npmAuthenticate@0
displayName: "authenticate the private npm registry"
inputs:
workingFile: .npmrc

# Must run before MavenAuthenticate@0 so the credentials it injects into
# ~/.m2/settings.xml are not overwritten by the copy.
- bash: |
mkdir -p ~/.m2
cp .azure-pipelines/settings.xml ~/.m2/settings.xml
displayName: 'Copy settings.xml (mirror Maven Central to DevDiv_PublicPackages)'

- task: MavenAuthenticate@0
displayName: 'Authenticate to DevDiv_PublicPackages feed'
inputs:
artifactsFeeds: DevDiv_PublicPackages

- task: AzureCLI@2
displayName: "Login to ACR via OIDC"
inputs:
azureSubscription: "Playwright-CDN"
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: "az acr login --name playwright"

- task: Bash@3
displayName: "Register QEMU (binfmt) for arm64 cross-build"
inputs:
targetType: "inline"
script: "docker run --rm --privileged ${ACR_CACHE_PREFIX}tonistiigi/binfmt --install arm64"
env:
ACR_CACHE_PREFIX: "playwright.azurecr.io/cached/"

- task: Bash@3
displayName: "Build & publish Docker images"
inputs:
targetType: "inline"
# The authenticated settings.xml is passed to `docker build` as a secret so
# that Maven inside the image also resolves from DevDiv_PublicPackages.
script: MAVEN_SETTINGS_SECRET="$HOME/.m2/settings.xml" ./utils/docker/publish_docker.sh ${{ parameters.releaseChannel }}
env:
ACR_CACHE_PREFIX: "playwright.azurecr.io/cached/"
UBUNTU_MIRROR_PREFIX: "azure."
3 changes: 2 additions & 1 deletion .azure-pipelines/settings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Maven settings used only by the Azure Pipelines release build (publish.yml).
Maven settings used only by the Azure Pipelines release builds (publish.yml and
publish-docker.yml).

Routes all remote artifact and plugin resolution through the DevDiv_PublicPackages
Azure Artifacts feed instead of Maven Central, as required by SFI-ES4.2.4.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/playwright-java-release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,4 @@ gh pr create --repo microsoft/playwright-java --head <user>:mark-v-1.X.0 --base

## 4. Publish

The user publishes the draft release manually once the `mark-v-1.X.0` PR is merged. After publishing, CI pushes the artifacts to Maven Central and runs the Docker workflow automatically: https://github.com/microsoft/playwright-java/actions.
The user publishes the draft release manually once the `mark-v-1.X.0` PR is merged. After publishing, the `v1.X.0` tag triggers the Azure DevOps pipelines that push the artifacts to Maven Central (`.azure-pipelines/publish.yml`) and the Docker images to ACR (`.azure-pipelines/publish-docker.yml`).
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The repository root is the Docker build context (see utils/docker/build.sh).
# Keep registry credentials written by the release pipeline out of the images.
**/.npmrc

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

should this include maven secrets as well?

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.

No need — MavenAuthenticate injects credentials into ~/.m2/settings.xml on the agent, which is outside the build context (repo root), so COPY . can't pick it up. It reaches the build only via --secret and isn't persisted in any layer. .npmrc needs the entry only because the pipeline writes it into the repo root.

# Downloaded by publish_docker.sh after the first push.
utils/docker/oras
30 changes: 0 additions & 30 deletions .github/workflows/publish_docker.yml

This file was deleted.

13 changes: 10 additions & 3 deletions utils/docker/Dockerfile.jammy
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
FROM ubuntu:jammy
ARG ACR_CACHE_PREFIX
FROM ${ACR_CACHE_PREFIX}ubuntu:jammy

ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=America/Los_Angeles
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-jammy"
ARG UBUNTU_MIRROR_PREFIX

ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

# === INSTALL JDK and Maven ===

RUN apt-get update && \
RUN find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) \
-exec sed -i "s|http://archive.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}archive.ubuntu.com|g; s|http://ports.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}ports.ubuntu.com|g" {} + && \
apt-get update && \
apt-get install -y --no-install-recommends openjdk-25-jdk \
# Install utilities required for downloading browsers
wget \
Expand Down Expand Up @@ -47,7 +51,10 @@ RUN mkdir /ms-playwright && \

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
# The optional Maven settings secret routes dependency resolution through a mirror
# (see .azure-pipelines/publish-docker.yml).
RUN --mount=type=secret,id=mavensettings,target=/root/.m2/settings.xml,required=false \
cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
Expand Down
13 changes: 10 additions & 3 deletions utils/docker/Dockerfile.noble
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
FROM ubuntu:noble
ARG ACR_CACHE_PREFIX
FROM ${ACR_CACHE_PREFIX}ubuntu:noble

ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=America/Los_Angeles
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-noble"
ARG UBUNTU_MIRROR_PREFIX

ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

# === INSTALL JDK and Maven ===

RUN apt-get update && \
RUN find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) \
-exec sed -i "s|http://archive.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}archive.ubuntu.com|g; s|http://ports.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}ports.ubuntu.com|g" {} + && \
apt-get update && \
apt-get install -y --no-install-recommends openjdk-25-jdk \
# Install utilities required for downloading browsers
wget \
Expand Down Expand Up @@ -47,7 +51,10 @@ RUN mkdir /ms-playwright && \

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
# The optional Maven settings secret routes dependency resolution through a mirror
# (see .azure-pipelines/publish-docker.yml).
RUN --mount=type=secret,id=mavensettings,target=/root/.m2/settings.xml,required=false \
cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
Expand Down
13 changes: 10 additions & 3 deletions utils/docker/Dockerfile.resolute
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
FROM ubuntu:resolute
ARG ACR_CACHE_PREFIX
FROM ${ACR_CACHE_PREFIX}ubuntu:resolute

ARG DEBIAN_FRONTEND=noninteractive
ARG TZ=America/Los_Angeles
ARG DOCKER_IMAGE_NAME_TEMPLATE="mcr.microsoft.com/playwright/java:v%version%-resolute"
ARG UBUNTU_MIRROR_PREFIX

ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

# === INSTALL JDK and Maven ===

RUN apt-get update && \
RUN find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) \
-exec sed -i "s|http://archive.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}archive.ubuntu.com|g; s|http://ports.ubuntu.com|http://${UBUNTU_MIRROR_PREFIX}ports.ubuntu.com|g" {} + && \
apt-get update && \
apt-get install -y --no-install-recommends openjdk-25-jdk \
# Install utilities required for downloading browsers
wget \
Expand Down Expand Up @@ -47,7 +51,10 @@ RUN mkdir /ms-playwright && \

COPY . /tmp/pw-java

RUN cd /tmp/pw-java && \
# The optional Maven settings secret routes dependency resolution through a mirror
# (see .azure-pipelines/publish-docker.yml).
RUN --mount=type=secret,id=mavensettings,target=/root/.m2/settings.xml,required=false \
cd /tmp/pw-java && \
mvn install -D skipTests --no-transfer-progress && \
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI \
-D exec.args="install-driver" -f playwright/pom.xml --no-transfer-progress && \
Expand Down
34 changes: 33 additions & 1 deletion utils/docker/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,36 @@ PW_TARGET_ARCH=$(echo $1 | cut -c3-)
# it up via `COPY . /tmp/pw-java`.
../../scripts/download_driver.sh

docker build --platform "${PLATFORM}" --build-arg "PW_TARGET_ARCH=${PW_TARGET_ARCH}" -t "$3" -f "Dockerfile.$2" ../../
SECRET_ARGS=()
if [[ -n "${MAVEN_SETTINGS_SECRET:-}" ]]; then
SECRET_ARGS+=(--secret "id=mavensettings,src=${MAVEN_SETTINGS_SECRET}")
fi

# Keep each arch image a plain single-platform manifest without the unknown/unknown platform entry.
export BUILDX_NO_DEFAULT_ATTESTATIONS=1

# arm64 images are cross-built under QEMU user-mode emulation, where Ubuntu 22.04's
# ldconfig segfaults intermittently at startup (tonistiigi/binfmt#298, every binfmt
# build since QEMU 8.1.4). apt's libc-bin trigger runs ldconfig, so a crash fails the
# whole `docker build`. Retry: BuildKit keeps the layers that already succeeded, so a
# retry re-runs only the failed RUN step.
MAX_ATTEMPTS=1
if [[ "${PLATFORM}" == "linux/arm64" ]]; then
MAX_ATTEMPTS=3
fi

for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
if docker build --platform "${PLATFORM}" \
--build-arg "PW_TARGET_ARCH=${PW_TARGET_ARCH}" \
--build-arg ACR_CACHE_PREFIX="${ACR_CACHE_PREFIX}" \
--build-arg UBUNTU_MIRROR_PREFIX="${UBUNTU_MIRROR_PREFIX}" \
"${SECRET_ARGS[@]}" \
-t "$3" -f "Dockerfile.$2" ../../; then
exit 0
fi
if (( attempt < MAX_ATTEMPTS )); then
echo "docker build failed (attempt ${attempt}/${MAX_ATTEMPTS}), retrying..." >&2
fi
done
echo "ERROR: docker build failed after ${MAX_ATTEMPTS} attempt(s)" >&2
exit 1
Loading
Loading