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
127 changes: 127 additions & 0 deletions .github/workflows/publish-expensidev-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Publish Expensidev Image

on:
workflow_call:
inputs:
image_name:
description: "Full GHCR image name (for example, ghcr.io/expensify/expensidev-php)"
required: true
type: string
dockerfile:
description: "Dockerfile path relative to the Expensidev repository root (for example, ci/docker/Dockerfile.php)"
required: true
type: string
concurrency_group:
description: "Concurrency group name used by the calling workflow"
required: true
type: string
extra_checkouts:
description: "JSON array of extra repository checkouts. Each object supports name, repository, ref, path, and optional submodules (boolean)."
required: false
type: string
default: "[]"

env:
IMAGE_NAME: ${{ inputs.image_name }}
CHECKOUT_TOKEN: ${{ secrets.MELVIN_GH_TOKEN || secrets.CODE_EXPENSIFY_TOKEN || github.token }}

jobs:
build:
name: Build ${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 90
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: blacksmith-32vcpu-ubuntu-2404
platform: linux/amd64
- arch: arm64
runner: blacksmith-32vcpu-ubuntu-2404-arm
platform: linux/arm64
steps:
- name: Checkout Expensidev
uses: useblacksmith/checkout@c9796daa2a4bdebdab5bd16be2c09a70cd4e1121 # v1
with:
repository: Expensify/Expensidev
ref: main
token: ${{ env.CHECKOUT_TOKEN }}
path: Expensidev

- name: Checkout extra repositories
env:
EXTRA_CHECKOUTS: ${{ inputs.extra_checkouts }}
run: |
set -euo pipefail

if [ -z "$EXTRA_CHECKOUTS" ] || [ "$EXTRA_CHECKOUTS" = "[]" ]; then
echo "No extra checkouts requested"
exit 0
fi

while IFS= read -r checkout; do
name=$(echo "$checkout" | jq -r '.name')
repository=$(echo "$checkout" | jq -r '.repository')
ref=$(echo "$checkout" | jq -r '.ref')
path=$(echo "$checkout" | jq -r '.path')
submodules=$(echo "$checkout" | jq -r '.submodules // false')

echo "Checking out ${name} (${repository}@${ref}) to ${path}"
mkdir -p "$(dirname "$path")"

clone_url="https://x-access-token:${CHECKOUT_TOKEN}@github.com/${repository}.git"
if [ "$submodules" = "true" ]; then
git clone --branch "$ref" --recursive "$clone_url" "$path"
else
git clone --branch "$ref" "$clone_url" "$path"
fi
done < <(echo "$EXTRA_CHECKOUTS" | jq -c '.[]')

- name: Setup Docker Builder
uses: useblacksmith/setup-docker-builder@33fed32c1ba8775f20366ec9e8d0cd9fe8fc1dd3 # v1.3.0

- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ env.CHECKOUT_TOKEN }}

- name: Build and push image
uses: useblacksmith/build-push-action@30c71162f16ea2c27c3e21523255d209b8b538c1 # v2
with:
context: Expensidev
file: Expensidev/${{ inputs.dockerfile }}
push: true
provenance: false
platforms: ${{ matrix.platform }}
tags: ${{ env.IMAGE_NAME }}:latest-${{ matrix.arch }}

publish-manifest:
name: Publish multi-arch manifest
runs-on: blacksmith-2vcpu-ubuntu-2404
needs: [build]
permissions:
contents: read
packages: write
steps:
- name: Setup Docker Builder
uses: useblacksmith/setup-docker-builder@33fed32c1ba8775f20366ec9e8d0cd9fe8fc1dd3 # v1.3.0

- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ env.CHECKOUT_TOKEN }}

- name: Publish latest manifest
run: |
docker buildx imagetools create \
--tag "${{ env.IMAGE_NAME }}:latest" \
"${{ env.IMAGE_NAME }}:latest-amd64" \
"${{ env.IMAGE_NAME }}:latest-arm64"
60 changes: 54 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Expensify Shared GitHub Actions workflows 🔄
# Expensify Shared GitHub Actions workflows 🔄

## What is the repository used for?

Expand All @@ -18,7 +18,7 @@ jobs:
with:
# Repository name with owner. For example, Expensify/eslint-config-expensify
# Required, String, default: ${{ github.repository }}
repository: ''
repository: ""

# True if we should run npm run build for the package
# Optional, Boolean, default: false
Expand All @@ -37,13 +37,61 @@ jobs:
secrets: inherit
```

### `publish-expensidev-image.yml`

Builds and publishes multi-arch Expensidev Docker images to GHCR. Consumer repositories keep their own triggers and concurrency settings, then call this reusable workflow with image-specific inputs.

```yml
on:
push:
branches:
- production
workflow_dispatch:

concurrency:
group: publish-expensidev-php-image
cancel-in-progress: false

jobs:
publish:
uses: Expensify/GitHub-Actions/.github/workflows/publish-expensidev-image.yml@main
secrets: inherit
permissions:
contents: read
packages: write
with:
# Full GHCR image name
# Required, String
image_name: ghcr.io/expensify/expensidev-php

# Dockerfile path relative to the Expensidev repository root
# Required, String
dockerfile: ci/docker/Dockerfile.php

# Concurrency group name used by the calling workflow
# Required, String
concurrency_group: publish-expensidev-php-image

# JSON array of extra repository checkouts checked out under Expensidev/
# Optional, String, default: []
# Each object supports: name, repository, ref, path, submodules (optional boolean)
extra_checkouts: >-
[
{"name":"Auth","repository":"Expensify/Auth","ref":"${{ github.sha }}","path":"Expensidev/Auth"},
{"name":"Bedrock","repository":"Expensify/Bedrock","ref":"expensify_prod","path":"Expensidev/Bedrock","submodules":true}
]
```

The workflow always checks out `Expensify/Expensidev@main`, then applies any `extra_checkouts` before building `linux/amd64` and `linux/arm64` images and publishing a combined `:latest` manifest.

## Rulesets

GitHub [org-level rulesets](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-workflows-to-pass-before-merging) can be configured to run a workflow check against pull requests in all repos in the org. This is a very powerful feature, but there are some caveats and best practices to be aware of when enabling a ruleset.

- Supported Event Triggers are documented [here](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#supported-event-triggers). However:
- When a workflow runs in response to a ruleset, some configs such as `branches`, `paths`, `paths-ignore`, that would normally be valid in a workflow are ignored.
- The default activity types for each event will be used. This means that something like `pull_request:comment` will not work - the `pull_request` event will always be triggered for the default activity types listed in the documentation.
- If you need to target or exclude specific branches, that can be configured in the ruleset settings.
- If you need to target or exclude specific paths, that must be implemented manually in the workflow itself.
- When a workflow runs in response to a ruleset, some configs such as `branches`, `paths`, `paths-ignore`, that would normally be valid in a workflow are ignored.
- The default activity types for each event will be used. This means that something like `pull_request:comment` will not work - the `pull_request` event will always be triggered for the default activity types listed in the documentation.
- If you need to target or exclude specific branches, that can be configured in the ruleset settings.
- If you need to target or exclude specific paths, that must be implemented manually in the workflow itself.
- Due to a GitHub :bug:, PRs that are open when the rule is enabled will get stuck with a pending check that will never get picked up. The easiest way to fix that is to close and reopen the PR. Consider writing a script to close and reopen all open PRs across the org after the check is enabled.
- It is less disruptive to [configure the ruleset to `Evaluate` first](https://docs.github.com/en/enterprise-cloud@latest/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#using-evaluate-mode-for-ruleset-workflows), then `Active` once the kinks are worked out.
Loading