Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build all branches, delete image versions when branches/tags are deleted #14

Merged
merged 9 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from 8 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
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(username1@mednet.ucla.edu), Name2(username2@mednet.ucla.edu)
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 (nwiltsie@mednet.ucla.edu), Yash Patel (yashpatel@mednet.ucla.edu)

<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
53 changes: 45 additions & 8 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 Down Expand Up @@ -35,28 +34,66 @@ 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: |
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: ['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:
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!')
}