Skip to content

Commit

Permalink
Merge pull request #14 from uclahs-cds/nwiltsie-tag-lifecycle
Browse files Browse the repository at this point in the history
Build all branches, delete image versions when branches/tags are deleted
  • Loading branch information
nwiltsie authored Jul 9, 2024
2 parents 940af99 + c1962a4 commit 05db1a0
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 24 deletions.
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ghcr.io>, change `registry` input to `organization`
- Build on pushes to all branches
- Tag non-`main` branches as `branch-<branchname>`
79 changes: 71 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,83 @@
# 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/<organization>/<image>:<version>`. The `<version>` 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:

Author: Name1([email protected]), Name2([email protected])
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
```
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 ([email protected]), Yash Patel ([email protected])

<one line to give the project/program's name and a brief idea of what it does.>
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.

Expand Down
55 changes: 46 additions & 9 deletions action.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -31,32 +30,70 @@ 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 }}'

- 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: |
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:
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.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
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 }}

- 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 }}

- if: github.event_name != 'delete'
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:
script: |
const script = require(`${process.env['GITHUB_ACTION_PATH']}/post-url.js`)
await script({ github, context, core })
49 changes: 49 additions & 0 deletions delete-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module.exports = async ({ github, context, core }) => {
const { IMAGE_NAME, ORGANIZATION } = 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: ORGANIZATION
})) {
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: ORGANIZATION,
package_version_id: version.id
})

didDelete = true
break
}
}
if (didDelete) {
break
}
}

if (!didDelete) {
core.warning(`Did not find version tagged ${tagName}`)
}
}
19 changes: 12 additions & 7 deletions metadata.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
---
Category: '' # shoule be one of docker/pipeline/project/template/tool/training/users
Description: '' # Description of why the repository exists
Maintainers: ['[email protected]', '[email protected]'] # 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:
- [email protected]
Contributors:
- Nicholas Wiltsie
- Yash Patel
Languages:
- bash
- javascript
Dependencies:
References:
19 changes: 19 additions & 0 deletions post-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = async ({ github, context, core }) => {
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: ORGANIZATION
})) {
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!')
}

0 comments on commit 05db1a0

Please sign in to comment.