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
131 changes: 131 additions & 0 deletions .github/workflows/push-to-gar-docker-multiarch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
name: Multi-platform Docker image push to Google Artifact Registry

on:
workflow_call:
inputs:
platforms:
default: "['amd64']"
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it perhaps be better to also use the same format as we have with the docker/build-push-action?

linux/amd64
linux/arm64
...

type: string
image_name:
required: true
type: string
registry:
description: |
Google Artifact Registry to store docker images in.
default: "us-docker.pkg.dev"
type: string
tags:
type: string
description: |
List of Docker images to be pushed.
required: true
context:
description: |
Path to the Docker build context.
default: "."
type: string
environment:
description: |
Environment for pushing artifacts (can be either dev or prod).
default: dev
type: string
build-args:
type: string
description: |
List of arguments necessary for the Docker image to be built.
default: ""
file:
type: string
description: |
The dockerfile to use.
required: false
cache-from:
type: string
description: |
Where cache should be fetched from
required: false
default: "type=gha"
cache-to:
type: string
description: |
Where cache should be stored to
required: false
default: "type=gha,mode=max"
ssh:
type: string
description: |
List of SSH agent socket or keys to expose to the build

permissions:
contents: read
id-token: write

env:
platform_env: ${{ inputs.platforms }}

jobs:
build-multi-arch:
outputs:
full_image_name: ${{ steps.build-multiarch.outputs.full_image_name }}
strategy:
matrix:
os: ${{ fromJson(inputs.platforms ) }}
tag: ${{ fromJson(inputs.tags ) }}
runs-on: ubuntu-${{ matrix.os }}-large
name: build-${{ matrix.os }}-image-for-tag-${{ matrix.tag }}
steps:
- name: Checkout
env:
action_repo: ${{ github.action_repository }}
action_ref: ${{ github.action_ref }}
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ env.action_repo }}
ref: ${{ env.action_ref }}
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: grafana/shared-workflows
ref: multiarch-docker-builds-using-sh-runners
path: shared-workflows
Comment on lines +77 to +90
Copy link
Member

Choose a reason for hiding this comment

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

Aren't these the same? I thought github.action_repository will be shared-workflows when you're running this, ditto action_ref and the selected ref? Or is it different for reusable workflows?

- name: Build multi-arch
id: build-multiarch
uses: ./shared-workflows/actions/build-and-push-image-digest
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
uses: ./shared-workflows/actions/build-and-push-image-digest
uses: ./shared-workflows/actions/build-and-push-image-digests

with:
platform: "linux/${{ matrix.os }}"
image_name: ${{ inputs.image_name }}
tags: ${{ matrix.tag }}
context: ${{ inputs.context }}
build-args: ${{ inputs.build-args }}
cache-from: ${{ inputs.cache-from }}
cache-to: ${{ inputs.cache-to }}
file: ${{ inputs.file }}
ssh: ${{ inputs.ssh }}
push-manifest:
strategy:
matrix:
tag: ${{ fromJson(inputs.tags ) }}
needs: [build-multi-arch]
name: push-manifest
runs-on: ubuntu-x64-small
steps:
- name: Checkout
env:
action_repo: ${{ github.action_repository }}
action_ref: ${{ github.action_ref }}
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ env.action_repo }}
ref: ${{ env.action_ref }}
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: grafana/shared-workflows
ref: multiarch-docker-builds-using-sh-runners
path: shared-workflows
- name: Create and push image manifests
uses: ./shared-workflows/actions/create-and-push-image-manifests
with:
full-image-name: ${{ needs.build-multi-arch.outputs.full_image_name }}
tag: ${{ matrix.tag }}
environment: ${{ inputs.environment }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.idea/
.vscode/
56 changes: 56 additions & 0 deletions actions/build-and-push-image-digests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# build-and-push-image-digests

This is a composite GitHub Action, used to build and push image digests to GitHub, so then they can be picked up by the `push-to-gar-docker-multiarch` action, to create a manifest and push the actual image to GAR.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
This is a composite GitHub Action, used to build and push image digests to GitHub, so then they can be picked up by the `push-to-gar-docker-multiarch` action, to create a manifest and push the actual image to GAR.
This is a composite GitHub Action, used to build and push image digests to GitHub, so then they can be picked up by the `push-to-gar-docker-multiarch` workflow, to create a manifest and push the actual image to GAR.


The user is able to see digests appearing on the github action level after every successful run.

This action can be used together with the [create-and-push-image-manifests](../create-and-push-image-manifests/README.md) action so the digests can form a manifest and push it to GAR.

```yaml
name: CI
on:
pull_request:

# These permissions are needed to assume roles from Github's OIDC.
permissions:
contents: read
id-token: write

jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- id: checkout
uses: actions/checkout@v4

- id: build-and-push-image-digests
uses: grafana/shared-workflows/actions/build-and-push-image-digests@main
with:
registry: "<YOUR-GAR>" # e.g. us-docker.pkg.dev, optional
tags: "<IMAGE_TAG>"
context: "<YOUR_CONTEXT>" # e.g. "." - where the Dockerfile is
image_name: "backstage" # name of the image to be published, required
environment: "dev" # can be either dev/prod
```

## Inputs

| Name | Type | Description |
| ---------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `registry` | String | Google Artifact Registry to store docker images in. |
| `tags` | List | Tags that should be used for the image (see the [metadata-action][mda] for details) |
| `context` | List | Path to the Docker build context. |
| `environment` | Bool | Environment for pushing artifacts (can be either dev or prod). |
| `image_name` | String | Name of the image to be pushed to GAR. |
| `build-args` | String | List of arguments necessary for the Docker image to be built. |
| `push` | Boolean | Whether to push the image to the registry. |
| `file` | String | Path and filename of the dockerfile to build from. (Default: `{context}/Dockerfile`) |
| `platforms` | List | List of platforms the image should be built for (e.g. `linux/amd64,linux/arm64`) |
| `cache-from` | String | Where cache should be fetched from ([more about GHA and container caching](https://www.kenmuse.com/blog/implementing-docker-layer-caching-in-github-actions/)) |
| `cache-to` | String | Where cache should be stored to ([more about GHA and container caching](https://www.kenmuse.com/blog/implementing-docker-layer-caching-in-github-actions/)) |
| `ssh` | List | List of SSH agent socket or keys to expose to the build ([more about ssh for docker/build-push-action](https://github.com/docker/build-push-action?tab=readme-ov-file#inputs)) |
| `build-contexts` | List | List of additional [build contexts](https://github.com/docker/build-push-action?tab=readme-ov-file#inputs) (e.g., `name=path`) |
| `docker-buildx-driver` | String | The [driver](https://github.com/docker/setup-buildx-action/tree/v3/?tab=readme-ov-file#customizing) to use for Docker Buildx |

[mda]: https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input
147 changes: 147 additions & 0 deletions actions/build-and-push-image-digests/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
name: Build and push image digests to GitHub
description: Composite action to build and push image digests to GitHub

inputs:
registry:
description: |
Google Artifact Registry to store docker images in.
default: "us-docker.pkg.dev"
tag:
description: |
Docker images tag to be pushed.
required: true
context:
description: |
Path to the Docker build context.
default: "."
environment:
description: |
Environment for pushing artifacts (can be either dev or prod).
default: dev
image_name:
description: |
Name of the image to be pushed to GAR.
required: true
build-args:
description: |
List of arguments necessary for the Docker image to be built.
default: ""
file:
description: |
The dockerfile to use.
required: false
platform:
Copy link
Contributor

Choose a reason for hiding this comment

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

In the README that input is called platforms, which is more fitting IMO 🙂

description: |
Platforms to build the image for
required: true
cache-from:
description: |
Where cache should be fetched from
required: false
default: "type=gha"
cache-to:
description: |
Where cache should be stored to
required: false
default: "type=gha,mode=max"
ssh:
description: |
List of SSH agent socket or keys to expose to the build
build-contexts:
description: |
List of additional build contexts (e.g., name=path)
required: false
docker-buildx-driver:
description: |
The driver to use for Docker Buildx
required: false
default: "docker-container"

outputs:
full_image_name:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dirty but for now re-calculating the whole image name for GAR is difficult, we can get away with it using a simple output.

description: Full image name
value: ${{ steps.export-full-image-name.outputs.full_image_name }}

runs:
using: composite
steps:
- name: try printing env
shell: bash
run: |
echo ${{ env.platform_env }}
Comment on lines +68 to +71
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- name: try printing env
shell: bash
run: |
echo ${{ env.platform_env }}

- name: Prepare
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a comment saying what this is doing, or make the name more descriptive?

shell: bash
run: |
platform=${{ inputs.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: grafana/shared-workflows
ref: multiarch-docker-builds-using-sh-runners
path: shared-workflows
- name: Get repository name
id: get-repository-name
shell: bash
run: |
REPO_NAME=$(echo ${{ github.repository }} | awk -F'/' '{print $2}')
echo "repo_name=${REPO_NAME}" >> ${GITHUB_OUTPUT}
Comment on lines +83 to +88
Copy link
Member

Choose a reason for hiding this comment

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

This could be merged into prepare I think

- name: Resolve GCP project
id: resolve-project
shell: bash
run: |
if [ "${{ inputs.environment }}" == 'dev' ]; then
PROJECT="grafanalabs-dev"
elif [ "${{ inputs.environment }}" == 'prod' ]; then
PROJECT="grafanalabs-global"
else
echo "Invalid environment. Valid environment variable inputs: dev, prod"
exit 1
fi
echo "project=${PROJECT}" >> ${GITHUB_OUTPUT}
Comment on lines +89 to +101
Copy link
Member

Choose a reason for hiding this comment

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

And this probably, or alternatively made into a standalone action since we repeat it all over the place.

- name: Login to GAR
uses: ./shared-workflows/actions/login-to-gar
with:
environment: ${{ inputs.environment }}
- name: Export full image name
id: export-full-image-name
shell: bash
run: |
FULL_IMAGE_NAME="${{ inputs.registry }}/${{ steps.resolve-project.outputs.project }}/docker-${{ steps.get-repository-name.outputs.repo_name }}-${{ inputs.environment }}/${{ inputs.image_name }}"
echo "full_image_name=${FULL_IMAGE_NAME}" >> ${GITHUB_OUTPUT}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${FULL_IMAGE_NAME}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
with:
driver: ${{ inputs.docker-buildx-driver }}
- name: Build the container
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
id: build
with:
context: ${{ inputs.context }}
labels: ${{ steps.meta.outputs.labels }}
outputs: type=image,name=${{ steps.export-full-image-name.outputs.full_image_name }},push-by-digest=true,name-canonical=true,push=true
build-args: ${{ inputs.build-args }}
cache-from: ${{ inputs.cache-from }}
cache-to: ${{ inputs.cache-to }}
file: ${{ inputs.file }}
platforms: ${{ inputs.platform }}
ssh: ${{ inputs.ssh }}
build-contexts: ${{ inputs.build-contexts }}
- name: Export digest
shell: bash
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}-${{ inputs.tag }}-${{ inputs.environment }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
43 changes: 43 additions & 0 deletions actions/create-and-push-image-manifests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# create-and-push-image-manifests

This is a composite GitHub Action, used to create and push image manifests to GAR.

This action can be used together with the [build-and-push-image-digests](../build-and-push-image-digests/README.md) action so the digests can be built and then use the current action to form a manifest.

```yaml
name: CI
on:
pull_request:

# These permissions are needed to assume roles from Github's OIDC.
permissions:
contents: read
id-token: write

jobs:
push-manifest:
runs-on: ubuntu-x64-small
steps:
- name: Checkout
env:
action_repo: ${{ github.action_repository }}
action_ref: ${{ github.action_ref }}
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
repository: ${{ env.action_repo }}
ref: ${{ env.action_ref }}
- name: Create and push image manifests
uses: grafana/shared-workflows/actions/create-and-push-image-manifests@main
with:
full-image-name: <FULL_IMAGE_NAME>
tag: <TAG>
environment: <ENVIRONMENT>
```

## Inputs

| Name | Type | Description |
| ----------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `full-image-name` | String | Full image name for docker image, e.g. `us-docker.pkg.dev/grafanalabs-dev/docker-grafana-enterprise-dev/grafana-enterprise` |
| `tag` | String | Tag for the image you want to push |
| `environment` | String | Environment for pushing artifacts (can be either dev or prod). |
Loading