diff --git a/.github/workflows/push-to-gar-docker-multiarch.yaml b/.github/workflows/push-to-gar-docker-multiarch.yaml new file mode 100644 index 000000000..552897f42 --- /dev/null +++ b/.github/workflows/push-to-gar-docker-multiarch.yaml @@ -0,0 +1,131 @@ +name: Multi-platform Docker image push to Google Artifact Registry + +on: + workflow_call: + inputs: + platforms: + default: "['amd64']" + 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 + - name: Build multi-arch + id: build-multiarch + uses: ./shared-workflows/actions/build-and-push-image-digest + 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 }} diff --git a/.gitignore b/.gitignore index 9f11b755a..66f8fb502 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea/ +.vscode/ diff --git a/actions/build-and-push-image-digests/README.md b/actions/build-and-push-image-digests/README.md new file mode 100644 index 000000000..0129d7371 --- /dev/null +++ b/actions/build-and-push-image-digests/README.md @@ -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. + +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: "" # e.g. us-docker.pkg.dev, optional + tags: "" + 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 diff --git a/actions/build-and-push-image-digests/action.yaml b/actions/build-and-push-image-digests/action.yaml new file mode 100644 index 000000000..30bbab6e7 --- /dev/null +++ b/actions/build-and-push-image-digests/action.yaml @@ -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: + 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: + 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 }} + - name: Prepare + 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} + - 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} + - 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 diff --git a/actions/create-and-push-image-manifests/README.md b/actions/create-and-push-image-manifests/README.md new file mode 100644 index 000000000..c3df71983 --- /dev/null +++ b/actions/create-and-push-image-manifests/README.md @@ -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: + tag: + 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). | diff --git a/actions/create-and-push-image-manifests/action.yaml b/actions/create-and-push-image-manifests/action.yaml new file mode 100644 index 000000000..4ee550bb0 --- /dev/null +++ b/actions/create-and-push-image-manifests/action.yaml @@ -0,0 +1,53 @@ +--- +description: Pulls image digests from GitHub and pushes manifests to GAR +inputs: + full-image-name: + description: Image name to push the manifest for + required: true + tag: + description: Tag to create and push the manifest for + required: true + environment: + description: Environment to suffix the image digest + required: true +name: Create and push image manifest + +runs: + using: composite + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - 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: Download digests + uses: actions/download-artifact@v4 + with: + merge-multiple: true + path: /tmp/digests + pattern: digests-*-${{ inputs.tag }}-${{ inputs.environment }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - id: meta + name: Docker meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.full-image-name }} + tags: ${{ inputs.tag }} + - id: login-to-gar + uses: grafana/shared-workflows/actions/login-to-gar@main + with: + environment: ${{ inputs.environment }} + - name: Create manifest list and push + working-directory: /tmp/digests + shell: bash + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ inputs.full-image-name }}@sha256:%s ' *) + - name: Inspect image + shell: bash + run: | + docker buildx imagetools inspect ${{ inputs.full-image-name }}:${{ steps.meta.outputs.version }}