From 6e536d5400715f5a79336c324002513aca83176d Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 09:26:21 -0700 Subject: [PATCH 1/9] Adjust tagging schema for branches Pushes to the `main` branch will update the `dev` tag. Pushes to all other branches will update `branch-` tags. --- action.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/action.yml b/action.yml index 6815c48..598e3ba 100644 --- a/action.yml +++ b/action.yml @@ -43,8 +43,9 @@ runs: latest=false images: ${{ inputs.registry }}/${{ steps.yaml-data.outputs.result }} tags: | - type=raw,enable=${{github.event_name == 'push'}},value=dev,event=branch - type=match,pattern=v(.*),group=1 + type=raw,enable=${{ github.ref == 'refs/heads/main' }},value=dev + type=ref,enable=${{ github.ref != 'refs/heads/main' }},prefix=branch-,event=branch + type=semver,pattern={{version}} ${{ inputs.custom-tags }} - name: Log in to the Container registry From 6f8182c201591f2612a07f48f011cef9fafb3df1 Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 09:28:51 -0700 Subject: [PATCH 2/9] Post build comment with URL of new image --- action.yml | 11 +++++++++++ post-url.js | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 post-url.js diff --git a/action.yml b/action.yml index 598e3ba..6e0470f 100644 --- a/action.yml +++ b/action.yml @@ -56,8 +56,19 @@ runs: password: ${{ inputs.github-token }} - name: Build and push Docker image + id: buildpush uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} push: true tags: ${{ steps.meta.outputs.tags }} + + - name: Log comment with image URL + uses: actions/github-script@v7 + env: + IMAGE_NAME: ${{ steps.yaml-data.outputs.result }} + IMAGE_DIGEST: ${{ steps.buildpush.outputs.digest }} + with: + script: | + const script = require(`${process.env['GITHUB_ACTION_PATH']}/post-url.js`) + await script({ github, context, core }) diff --git a/post-url.js b/post-url.js new file mode 100644 index 0000000..5957794 --- /dev/null +++ b/post-url.js @@ -0,0 +1,19 @@ +module.exports = async ({ github, context, core }) => { + const { IMAGE_NAME, IMAGE_DIGEST } = process.env + + for await (const response of github.paginate.iterator( + github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg, { + package_type: 'container', + package_name: IMAGE_NAME, + org: context.payload.organization.login + })) { + for (const version of response.data) { + if (version.name === IMAGE_DIGEST) { + core.notice(`Uploaded new image ${version.html_url}`) + return + } + } + } + + core.error('Could not find URL for new image!') +} From 8d39f1c899220a99b106c360196c79975c8fc6fb Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 09:29:58 -0700 Subject: [PATCH 3/9] Delete docker tags on git branch/tag deletion --- action.yml | 18 +++++++++++++++++- delete-tags.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 delete-tags.js diff --git a/action.yml b/action.yml index 6e0470f..e9028de 100644 --- a/action.yml +++ b/action.yml @@ -35,7 +35,20 @@ runs: with: cmd: yq '${{ inputs.image-name-key-path }}' '${{ inputs.metadata-file }}' + # Take this path if the event is a branch deletion + - if: github.event_name == 'delete' + name: Delete matching docker tags + uses: actions/github-script@v7 + env: + IMAGE_NAME: ${{ steps.yaml-data.outputs.result }} + with: + script: | + const script = require(`${process.env['GITHUB_ACTION_PATH']}/delete-tags.js`) + await script({ github, context, core }) + + # Take this path if the event is not a deletion - name: Create tags + if: github.event_name != 'delete' id: meta uses: docker/metadata-action@v5 with: @@ -49,6 +62,7 @@ runs: ${{ inputs.custom-tags }} - name: Log in to the Container registry + if: github.event_name != 'delete' uses: docker/login-action@v3 with: registry: ${{ inputs.registry }} @@ -57,13 +71,15 @@ runs: - name: Build and push Docker image id: buildpush + if: github.event_name != 'delete' uses: docker/build-push-action@v5 with: context: ${{ inputs.context }} push: true tags: ${{ steps.meta.outputs.tags }} - - name: Log comment with image URL + - if: github.event_name != 'delete' + name: Log comment with image URL uses: actions/github-script@v7 env: IMAGE_NAME: ${{ steps.yaml-data.outputs.result }} diff --git a/delete-tags.js b/delete-tags.js new file mode 100644 index 0000000..b369d0f --- /dev/null +++ b/delete-tags.js @@ -0,0 +1,49 @@ +module.exports = async ({ github, context, core }) => { + const { IMAGE_NAME } = process.env + + let tagName + + if (context.payload.ref_type === 'branch') { + tagName = `branch-${context.payload.ref}` + } else { + tagName = context.payload.ref.match(/^v(.*)$/)[1] + } + + let didDelete = false + + for await (const response of github.paginate.iterator( + github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg, { + package_type: 'container', + package_name: IMAGE_NAME, + org: context.payload.organization.login + })) { + for (const version of response.data) { + const tags = version.metadata?.container?.tags + if (tags?.includes(tagName)) { + core.notice(`Package version ${version.html_url} matches tag ${tagName} and will be deleted`) + + const otherTags = tags.filter((tag) => tag !== tagName) + if (otherTags.length) { + core.warning(`Image version has other tags that will be lost: ${otherTags}`) + } + + await github.rest.packages.deletePackageVersionForOrg({ + package_type: 'container', + package_name: IMAGE_NAME, + org: context.payload.organization.login, + package_version_id: version.id + }) + + didDelete = true + break + } + } + if (didDelete) { + break + } + } + + if (!didDelete) { + core.warning(`Did not find version tagged ${tagName}`) + } +} From 9d9cdacef6af60dec70f0bf602526989d2c66cb5 Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 11:00:02 -0700 Subject: [PATCH 4/9] Update the README --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 55dbcb5..76463ad 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,81 @@ -# Project/Repo Title +# Docker Build Action -Template Repository for the Boutros Lab general project repos. Describe a simple overview of use/purpose here. +An Action to automatically build and push images to the [GitHub Container registry](https://github.com/features/packages). ## Description -An in-depth paragraph about your project and overview of use. +This action will build and push images of the form `ghcr.io//:`. The `` field is controlled by the following logic: -## License +| Pushed Ref | Type | Resulting Tag | +| ---------- | -------------- | ----------------- | +| `main` | default branch | `dev` | +| `mybranch` | branch | `branch-mybranch` | +| `v1.2.3` | tag | `1.2.3` | +When a git branch or tag is deleted, the corresponding docker will be deleted as well. + +## Usage + +```yaml +--- +name: Update image in GHCR + +run-name: > + ${{ + github.event_name == 'delete' && format( + 'Delete `{0}{1}`', + github.event.ref_type == 'branch' && 'branch-' || '', + github.event.ref + ) + || github.ref == 'refs/heads/main' && 'Update `dev`' + || format( + 'Update `{0}{1}`', + !startsWith(github.ref, 'refs/tags') && 'branch-' || '', + github.ref_name + ) + }} docker tag + +on: + push: + branches-ignore: ['gh-pages'] + tags: ['v*'] + delete: + +jobs: + push-or-delete-image: + runs-on: ubuntu-latest + name: Update GitHub Container Registry + permissions: + contents: read + packages: write + steps: + - uses: uclahs-cds/tool-Docker-action@v2 +``` -Author: Name1(username1@mednet.ucla.edu), Name2(username2@mednet.ucla.edu) +The complicated `run-name` logic above controls the workflow run names listed on the Actions page: + +| Ref Name | Ref Type | `push` Run Name | `delete` Run Name | +| -------------------- | -------- | ----------------------------------- | ----------------------------------- | +| Push to `main` | branch | Update `dev` docker tag | Delete `dev` docker tag | +| Push to `mybranch` | branch | Update `branch-mybranch` docker tag | Delete `branch-mybranch` docker tag | +| Push to `v1.2.3` tag | tag | Update `v1.2.3` docker tag | Delete `v1.2.3` docker tag | + +### Inputs + +| Name | Default | Description | +| `organization` | -- | The GitHub organizational host of the image. Defaults to the organization of the calling repository. | +| `metadata-file` | `metadata.yaml` | Metadata file storing the image name. | +| `image-name-key-path` | `.image_name` | [`yq`](https://github.com/mikefarah/yq) query for the image name within the metadata file. | +| `github-token` | `github.token` | Token used for authentication. Requires `contents: read` for the calling repository and `packages:write` for the host organization. | +| `custom-tags` | -- | Additional lines to add to the [docker/metadata-action `tags` argument](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input). | +| `context` | `.` | The docker build context. Only required if the `Dockerfile` is not in the repository root. | + +## License -[This project] is licensed under the GNU General Public License version 2. See the file LICENSE.md for the terms of the GNU GPL license. +Author: Nicholas Wiltsie (nwiltsie@mednet.ucla.edu), Yash Patel (yashpatel@mednet.ucla.edu) - +tool-docker-action is licensed under the GNU General Public License version 2. See the file LICENSE.md for the terms of the GNU GPL license. -Copyright (C) 2021 University of California Los Angeles ("Boutros Lab") All rights reserved. +Copyright (C) 2024 University of California Los Angeles ("Boutros Lab") All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. From a582d3785949e734a3d297769187633c8ac1bb94 Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 11:19:51 -0700 Subject: [PATCH 5/9] Use 'organization' input --- action.yml | 21 +++++++++++++++------ delete-tags.js | 6 +++--- post-url.js | 4 ++-- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/action.yml b/action.yml index e9028de..4deea4e 100644 --- a/action.yml +++ b/action.yml @@ -1,10 +1,9 @@ --- name: 'Docker-build-release' -description: 'Build Docker image and push to repository' +description: 'Build Docker image and push to GHCR' inputs: - registry: - description: 'Registry to which image will be pushed' - default: ghcr.io/uclahs-cds + organization: + description: 'Organizational host for the image. Defaults to the organization of the calling repository.' metadata-file: description: 'Metadata YAML file containing information' default: metadata.yaml @@ -35,11 +34,20 @@ runs: with: cmd: yq '${{ inputs.image-name-key-path }}' '${{ inputs.metadata-file }}' + - name: Parse organization + id: parse-org + shell: bash + env: + CALLING_ORGANIZATION: ${{ github.event.organization.login }} + INPUT_ORGANIZATION: ${{ inputs.organization }} + run: echo "org=${INPUT_ORGANIZATION:-$CALLING_ORGANIZATION}" >> "$GITHUB_OUTPUT" + # Take this path if the event is a branch deletion - if: github.event_name == 'delete' name: Delete matching docker tags uses: actions/github-script@v7 env: + ORGANIZATION: ${{ steps.parse-org.outputs.org }} IMAGE_NAME: ${{ steps.yaml-data.outputs.result }} with: script: | @@ -54,7 +62,7 @@ runs: with: flavor: | latest=false - images: ${{ inputs.registry }}/${{ steps.yaml-data.outputs.result }} + images: ghcr.io/${{ steps.parse-org.outputs.org }}/${{ steps.yaml-data.outputs.result }} tags: | type=raw,enable=${{ github.ref == 'refs/heads/main' }},value=dev type=ref,enable=${{ github.ref != 'refs/heads/main' }},prefix=branch-,event=branch @@ -65,7 +73,7 @@ runs: if: github.event_name != 'delete' uses: docker/login-action@v3 with: - registry: ${{ inputs.registry }} + registry: ghcr.io/${{ steps.parse-org.outputs.org }} username: ${{ github.actor }} password: ${{ inputs.github-token }} @@ -82,6 +90,7 @@ runs: name: Log comment with image URL uses: actions/github-script@v7 env: + ORGANIZATION: ${{ steps.parse-org.outputs.org }} IMAGE_NAME: ${{ steps.yaml-data.outputs.result }} IMAGE_DIGEST: ${{ steps.buildpush.outputs.digest }} with: diff --git a/delete-tags.js b/delete-tags.js index b369d0f..faed84c 100644 --- a/delete-tags.js +++ b/delete-tags.js @@ -1,5 +1,5 @@ module.exports = async ({ github, context, core }) => { - const { IMAGE_NAME } = process.env + const { IMAGE_NAME, ORGANIZATION } = process.env let tagName @@ -15,7 +15,7 @@ module.exports = async ({ github, context, core }) => { github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg, { package_type: 'container', package_name: IMAGE_NAME, - org: context.payload.organization.login + org: ORGANIZATION })) { for (const version of response.data) { const tags = version.metadata?.container?.tags @@ -30,7 +30,7 @@ module.exports = async ({ github, context, core }) => { await github.rest.packages.deletePackageVersionForOrg({ package_type: 'container', package_name: IMAGE_NAME, - org: context.payload.organization.login, + org: ORGANIZATION, package_version_id: version.id }) diff --git a/post-url.js b/post-url.js index 5957794..ab2952e 100644 --- a/post-url.js +++ b/post-url.js @@ -1,11 +1,11 @@ module.exports = async ({ github, context, core }) => { - const { IMAGE_NAME, IMAGE_DIGEST } = process.env + const { ORGANIZATION, IMAGE_NAME, IMAGE_DIGEST } = process.env for await (const response of github.paginate.iterator( github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg, { package_type: 'container', package_name: IMAGE_NAME, - org: context.payload.organization.login + org: ORGANIZATION })) { for (const version of response.data) { if (version.name === IMAGE_DIGEST) { From c2a778530e26b900db2fbeb785d8821eb572c466 Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 11:45:19 -0700 Subject: [PATCH 6/9] Update metadata.yaml --- metadata.yaml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/metadata.yaml b/metadata.yaml index eb9a6dd..35e4f15 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -1,8 +1,13 @@ --- -Category: '' # shoule be one of docker/pipeline/project/template/tool/training/users -Description: '' # Description of why the repository exists -Maintainers: ['someone@mednet.ucla.edu', 'someoneelse@mednet.ucla.edu'] # email address of maintainers -Contributors: 'Xavier Hernandez' # Full names of contributors -Languages: ['R', 'perl', 'nextflow'] # programming languages used -Dependencies: 'BPG' # packages, tools that repo needs to run -References: '' # is the tool/dependencies published, is there a confluence page +Category: tool +Description: GitHub Action to build and deploy docker images +Maintainers: + - nwiltsie@mednet.ucla.edu +Contributors: + - Nicholas Wiltsie + - Yash Patel +Languages: + - bash + - javascript +Dependencies: +References: From 9bedb783cd197bafb525f0dbe6d22bc2f60bee51 Mon Sep 17 00:00:00 2001 From: Nicholas Wiltsie Date: Mon, 24 Jun 2024 11:47:53 -0700 Subject: [PATCH 7/9] Update CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597ef27..4f32a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Action for automatic Docker image build and push - Custom tag option - Add `context` argument to allow for Dockerfiles in subfolders +- Delete docker versions when git branches/tags are deleted ### Changed - Unpack `build-release` folder - Replace `jbutcher5/read-yaml` with `mikefarah/yq` for YAML parsing - Use `${github.token}` as default value for `github-token`. +- Require usage of , change `registry` input to `organization` +- Build on pushes to all branches +- Tag non-`main` branches as `branch-` From 598589aeeba11fed55366d8a4cd9bb7d7e01107f Mon Sep 17 00:00:00 2001 From: Nick Wiltsie Date: Mon, 8 Jul 2024 12:10:30 -0700 Subject: [PATCH 8/9] Clean up table formatting in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 76463ad..fb79fcf 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This action will build and push images of the form `ghcr.io// Date: Tue, 9 Jul 2024 11:48:23 -0700 Subject: [PATCH 9/9] Version-pin yq Action, enable dependabot for GitHub Actions --- .github/dependabot.yml | 8 ++++++++ action.yml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..c7face8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +--- +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "weekly" diff --git a/action.yml b/action.yml index 4deea4e..29712c5 100644 --- a/action.yml +++ b/action.yml @@ -30,7 +30,7 @@ runs: - name: Read YAML id: yaml-data - uses: mikefarah/yq@v4 + uses: mikefarah/yq@v4.44.2 with: cmd: yq '${{ inputs.image-name-key-path }}' '${{ inputs.metadata-file }}'