Skip to content
Closed
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
56 changes: 56 additions & 0 deletions .github/actions/apt-install/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: 'Install APT packages'
description: 'Fast apt-get install with slow repos removed'
inputs:
packages:
description: 'Space-separated list of packages to install'
required: true
update:
description: 'Run apt-get update before installing'
required: false
default: 'true'
runs:
using: composite
steps:
- name: Configure apt for CI
shell: bash
run: |
# These are idempotent and fast — safe to run every time
sudo rm -f /var/lib/man-db/auto-update

# the Microsoft repo's kubelet does not provide /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
# [Service]
# EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# ExecStart=/usr/bin/kubelet $KUBELET_KUBEADM_ARGS
# and otherwise fetching from microsoft-prod wastes time
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list
sudo rm -f /etc/apt/sources.list.d/azure-cli.list

cat <<'EOF' | sudo tee /etc/dpkg/dpkg.cfg.d/01_nodoc > /dev/null
path-exclude /usr/share/doc/*
path-exclude /usr/share/man/*
path-exclude /usr/share/locale/*
path-include /usr/share/locale/en*
EOF

cat <<'EOF' | sudo tee /etc/apt/apt.conf.d/99-ci-optimizations > /dev/null
Acquire::Queue-Mode "access";
Acquire::Retries "3";
APT::Install-Recommends "false";
APT::Install-Suggests "false";
APT::Get::Assume-Yes "true";
APT::Get::Quiet "true";
APT::Get::Show-Upgraded "false";
APT::AutoRemove::SuggestsImportant "false";
DPkg::Options:: "--force-unsafe-io";
EOF

- name: Update package index
if: ${{ inputs.update == 'true' }}
shell: bash
run: |
sudo apt-get update

- name: Install packages
shell: bash
run: sudo apt-get install ${{ inputs.packages }}
27 changes: 27 additions & 0 deletions .github/actions/free-up-disk-space/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
name: 'Free up disk space'
description: 'Removes unnecessary packages and files to free up disk space on GitHub runners'
runs:
using: "composite"
steps:
- name: Free up disk space
shell: bash
run: |
set -x
df -h
sudo docker system prune -af || true
sudo apt-get clean
sudo rm -rf /tmp/*
sudo apt-get update
sudo apt-get purge -y '^dotnet-.*' '^llvm-.*' 'php.*' '^mongodb-.*'
sudo apt-get autoremove -y --purge
sudo apt-get clean
sudo rm -rf /usr/local/.ghcup &
sudo rm -rf /usr/local/lib/android &
sudo rm -rf /usr/local/share/boost &
sudo rm -rf /usr/local/lib/node_modules &
sudo rm -rf /usr/share/dotnet &
sudo rm -rf /opt/ghc &
sudo rm -rf /opt/hostedtoolcache/CodeQL &
wait
df -h
120 changes: 120 additions & 0 deletions .github/actions/install-podman-action/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
name: 'Install Podman'
description: 'Installs Podman from Homebrew'
inputs:
platform:
description: 'Target platform for Podman installation (linux/amd64, linux/s390x, linux/ppc64le, linux/arm64)'
required: true
runs:
using: "composite"
steps:
# https://github.com/containers/buildah/issues/2521#issuecomment-884779112
- name: Workaround https://github.com/containers/podman/issues/22152#issuecomment-2027705598
shell: bash
run: sudo apt-get -qq remove podman crun

# Ensure parent dir exists and is runner-owned so cache restore can set file modes (avoids "Cannot change mode: Operation not permitted").
- name: Prepare .linuxbrew parent for cache
shell: bash
run: |
sudo mkdir -p /home/linuxbrew
sudo chown -R "$(whoami):$(whoami)" /home/linuxbrew

- uses: actions/cache@v5
# https://docs.github.com/en/actions/reference/variables-reference#default-environment-variables
# https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables
id: cached-linuxbrew
with:
path: /home/linuxbrew/.linuxbrew
key: linuxbrew-${{ runner.os }}-${{ runner.arch }}

# Restored cache can have wrong ownership or immutable flags; fix so brew/podman and mode changes work.
- name: Fix .linuxbrew ownership and permissions after cache restore
if: steps.cached-linuxbrew.outputs.cache-hit == 'true'
shell: bash
run: |
if [[ -d /home/linuxbrew/.linuxbrew ]]; then
sudo chattr -R -i /home/linuxbrew/.linuxbrew 2>/dev/null || true
sudo chown -R "$(whoami):$(whoami)" /home/linuxbrew/.linuxbrew
sudo chmod -R u+rwX,go=rX /home/linuxbrew/.linuxbrew
fi

- name: Install podman (linux/amd64, or qemu-user emulation)
if: contains(fromJSON('["linux/amd64", "linux/s390x", "linux/ppc64le"]'), inputs.platform) && steps.cached-linuxbrew.outputs.cache-hit != 'true'
shell: bash
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
/home/linuxbrew/.linuxbrew/bin/brew install podman

# Warning: Your CPU architecture (arm64) is not supported. We only support
# x86_64 CPU architectures. You will be unable to use binary packages (bottles).
#
# This is a Tier 2 configuration:
# https://docs.brew.sh/Support-Tiers#tier-2
# Do not report any issues to Homebrew/* repositories!
# Read the above document instead before opening any issues or PRs.
- name: Install podman (linux/arm64)
if: inputs.platform == 'linux/arm64' && steps.cached-linuxbrew.outputs.cache-hit != 'true'
# Error: podman: no bottle available!
# If you're feeling brave, you can try to install from source with:
shell: bash
run: |
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
/home/linuxbrew/.linuxbrew/bin/brew install --build-from-source podman

- name: Add linuxbrew to PATH
shell: bash
run: echo "/home/linuxbrew/.linuxbrew/bin/" >> $GITHUB_PATH

- name: Configure Podman
shell: bash
run: |
set -Eeuxo pipefail

# podman running as service ignores the TMPDIR env var here, let's give it a bind-mount to /var/tmp
mkdir -p $TMPDIR
sudo mount --bind -o rw,noexec,nosuid,nodev,bind $TMPDIR /var/tmp

# podman from brew has its own /etc (was giving me Failed to obtain podman configuration: runroot must be set)
# the (default) config location is also where cri-o gets its storage defaults (that can be overriden in crio.conf)
sudo cp ci/cached-builds/containers.conf /etc/containers.conf
sudo cp ci/cached-builds/containers.conf /home/linuxbrew/.linuxbrew/opt/podman/etc/containers.conf
sudo cp ci/cached-builds/storage.conf /etc/containers/storage.conf
sudo cp ci/cached-builds/storage.conf /home/linuxbrew/.linuxbrew/opt/podman/etc/containers/storage.conf
sudo cp ci/cached-builds/registries.conf /etc/containers/registries.conf
sudo cp ci/cached-builds/registries.conf /home/linuxbrew/.linuxbrew/opt/podman/etc/containers/registries.conf

# should reset storage when changing storage.conf
mkdir -p $HOME/.local/share/containers/storage/tmp
# remote (CONTAINER_HOST) podman does not do reset (and refuses --force option)
sudo /home/linuxbrew/.linuxbrew/opt/podman/bin/podman system reset --force

# https://github.com/containers/podman/pull/25504
# podman 5.5.0: The podman system reset command no longer removes the user's podman.sock API socket
sudo rm -rf /var/run/podman

# https://github.com/containers/podman/blob/main/docs/tutorials/socket_activation.md
# since `brew services start podman` is buggy, let's do our own brew-compatible service
# Regarding directory paths, see https://unix.stackexchange.com/questions/224992/where-do-i-put-my-systemd-unit-file
sudo mkdir -p /usr/local/lib/systemd/system/
sudo cp ci/cached-builds/podman.service /usr/local/lib/systemd/system/podman.service
sudo cp ci/cached-builds/podman.socket /usr/local/lib/systemd/system/podman.socket
sudo systemctl daemon-reload
sudo systemctl unmask --now podman.service podman.socket
sudo systemctl start podman.socket

# needed (much) later for trivy
echo "PODMAN_SOCK=/var/run/podman/podman.sock" >> $GITHUB_ENV

# quick check podman works
podman ps

- name: Show error logs (on failure)
if: ${{ failure() }}
shell: bash
run: |
set -Eeuxo pipefail

journalctl -xe
ls -AlF /var/run/podman/podman.sock || echo "Socket /var/run/podman/podman.sock not found"
sudo ss -xlpn | grep 'podman.sock' || echo "No active listener found for podman.sock via ss"
126 changes: 126 additions & 0 deletions .github/actions/playwright-test/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
name: 'Playwright Browser Tests'
description: |
Run Playwright browser tests for workbench images.

Features:
- Runs Playwright tests inside the official Microsoft Playwright container
- Uses Podman to run the test container with proper isolation
- Supports testcontainers for container orchestration
- Uploads test reports as artifacts on pull requests

Requirements:
- Podman must be installed and running
- The workbench image must be available in the local Podman storage
- tests/browser directory must contain the Playwright test configuration

inputs:
test-target:
description: 'The workbench image to test (full image reference).'
required: true
podman-socket:
description: 'Path to Podman socket'
required: false
default: '/var/run/podman/podman.sock'
playwright-version:
description: 'Version of the Playwright container to use (e.g., v1.58.1-noble). If not provided, extracted from package.json5'
required: false
default: ''
working-directory:
description: 'Directory containing the Playwright tests'
required: false
default: 'tests/browser'
upload-report:
description: 'Whether to upload the Playwright report as an artifact'
required: false
default: 'false'
artifact-name:
description: 'Name for the uploaded artifact (required if upload-report is true)'
required: false
default: 'playwright-report'
artifact-retention-days:
description: 'Number of days to retain the artifact'
required: false
default: '30'
grep-pattern:
description: 'Grep pattern passed to --grep to filter tests (e.g. "@codeserver", "@smoke"). Empty string runs all tests.'
required: false
default: ''

outputs:
outcome:
description: 'The outcome of the Playwright tests (success or failure)'
value: ${{ steps.playwright.outcome }}

runs:
using: 'composite'
steps:
- name: Determine Playwright version
id: playwright-version
shell: bash
run: |
if [[ -n "${INPUT_PLAYWRIGHT_VERSION}" ]]; then
echo "version=${INPUT_PLAYWRIGHT_VERSION}" >> "$GITHUB_OUTPUT"
else
# Extract version from package.json5 (single source of truth)
# package.json5 has: "@playwright/test": "=1.58.1"
# Container tag format is: v1.58.1-noble
PKG_VERSION=$(grep -oP '"@playwright/test":\s*"=?\K[0-9.]+' "${WORKING_DIRECTORY}/package.json5")
if [[ -z "$PKG_VERSION" ]]; then
echo "::error::Failed to extract Playwright version from package.json5"
exit 1
fi
echo "version=v${PKG_VERSION}-noble" >> "$GITHUB_OUTPUT"
echo "Extracted Playwright version: v${PKG_VERSION}-noble"
fi
env:
INPUT_PLAYWRIGHT_VERSION: ${{ inputs.playwright-version }}
WORKING_DIRECTORY: ${{ inputs.working-directory }}

- name: Run Playwright tests
id: playwright
shell: bash
# --ipc=host because Microsoft says so in Playwright docs
# --net=host because testcontainers connects to the Reaper container's exposed port
# we need to pass through the relevant environment variables
# DEBUG configures Node.js debuggers, sets different verbosity as needed
# CI=true is set on every CI nowadays
# PODMAN_SOCK should be mounted to /var/run/docker.sock, other likely mounting locations may not exist (mkdir -p)
# TEST_TARGET is the workbench image the test will run
# --volume(s) let us access docker socket and not clobber host's node_modules
run: |
podman run \
--interactive --rm \
--ipc=host \
--net=host \
--env "CI=true" \
--env "NPM_CONFIG_fund=false" \
--env "DEBUG=testcontainers:*" \
--env "PODMAN_SOCK=/var/run/docker.sock" \
--env "TEST_TARGET" \
--volume "${PODMAN_SOCKET}":/var/run/docker.sock \
--volume "${PWD}":/mnt \
--volume /mnt/node_modules \
"mcr.microsoft.com/playwright:${PLAYWRIGHT_VERSION}" \
/bin/bash <<EOF
set -Eeuxo pipefail
cd /mnt
npm install -g pnpm && pnpm install
pnpm exec playwright test ${GREP_PATTERN:+--grep "$GREP_PATTERN"}
exit 0
EOF
working-directory: ${{ inputs.working-directory }}
env:
# Only set TEST_TARGET if provided, otherwise playwright.config.ts uses its default
TEST_TARGET: ${{ inputs.test-target || '' }}
PODMAN_SOCKET: ${{ inputs.podman-socket }}
PLAYWRIGHT_VERSION: ${{ steps.playwright-version.outputs.version }}
GREP_PATTERN: ${{ inputs.grep-pattern }}

- name: Upload Playwright report
if: ${{ !cancelled() && inputs.upload-report == 'true' }}
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact-name }}
path: ${{ inputs.working-directory }}/playwright-report/
retention-days: ${{ inputs.artifact-retention-days }}
Loading
Loading