Compare commits

..

30 Commits
v1 ... v2

Author SHA1 Message Date
CrazyMax
f02d9f4f9b Update CHANGELOG 2021-05-08 01:20:23 +02:00
CrazyMax
400bd87b5a Major version zero doc (#74)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-05-08 00:01:13 +02:00
dependabot[bot]
e4fc7e9e11 Bump hosted-git-info from 2.8.8 to 2.8.9 (#73)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2021-05-07 21:59:27 +02:00
dependabot[bot]
9583a0f404 Bump lodash from 4.17.20 to 4.17.21 (#72)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 21:55:33 +02:00
CrazyMax
5e6d5157fb Handle global expressions (#71)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-05-07 21:53:30 +02:00
CrazyMax
e09df4df3c Update CHANGELOG 2021-04-30 00:54:35 +02:00
CrazyMax
72e5d60481 Add bake-target input (#69)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-30 00:51:48 +02:00
CrazyMax
ae431178c1 Fix setOutput (#67)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-24 00:44:38 +02:00
Teppei Yano
34ebfd6e6b Fix upgrade notes (#66) 2021-04-22 12:06:16 +02:00
dependabot[bot]
94641ff1bb Bump csv-parse from 4.15.3 to 4.15.4 (#65)
* Bump csv-parse from 4.15.3 to 4.15.4

Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.15.3 to 4.15.4.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.15.3...v4.15.4)

Signed-off-by: dependabot[bot] <support@github.com>

* Update generated content

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2021-04-16 10:49:42 +02:00
dependabot[bot]
40bfc8b527 Bump @actions/core from 1.2.6 to 1.2.7 (#64)
* Bump @actions/core from 1.2.6 to 1.2.7

Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.2.6 to 1.2.7.
- [Release notes](https://github.com/actions/toolkit/releases)
- [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md)
- [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core)

Signed-off-by: dependabot[bot] <support@github.com>

* Update generated content

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-16 10:32:55 +02:00
CrazyMax
2e1a5c7fa4 Update CHANGELOG 2021-04-07 21:00:00 +02:00
CrazyMax
1a678de43d Allow to override flavor (#63)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-07 20:54:35 +02:00
CrazyMax
84b9e75d44 Prefix/suffix not taken into account (#62)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-07 20:31:35 +02:00
CrazyMax
f39f06a624 Update CHANGELOG 2021-04-05 21:30:44 +02:00
CrazyMax
36ae18e02c Skip and display warning if tag does not match (#59)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-05 21:19:05 +02:00
CrazyMax
e87dd9466c Fix workflow 2021-04-03 18:44:12 +02:00
CrazyMax
9da1e66de9 Update CHANGELOG 2021-04-03 18:21:52 +02:00
CrazyMax
521a3c5ada Fix README (#56) 2021-04-03 18:18:32 +02:00
CrazyMax
7433b42479 Improve logging (#58)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-04-03 18:15:27 +02:00
CrazyMax
1a8a264b95 Update README 2021-03-31 10:01:20 +02:00
CrazyMax
e04b4d01a9 Cleanup 2021-03-30 13:57:33 +02:00
CrazyMax
ac90ddf06e Update CHANGELOG 2021-03-30 13:23:09 +02:00
dependabot[bot]
972129059a Bump y18n from 4.0.0 to 4.0.1 (#54)
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-30 13:15:08 +02:00
Jakub Bacic
b909bd34ef Fix 'enable' tag attribute (#53) 2021-03-30 13:11:51 +02:00
CrazyMax
72654174f7 Update CHANGELOG 2021-03-29 13:26:34 +02:00
dependabot[bot]
4545671ec8 Bump semver from 7.3.4 to 7.3.5 (#49)
* Bump semver from 7.3.4 to 7.3.5

Bumps [semver](https://github.com/npm/node-semver) from 7.3.4 to 7.3.5.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/master/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v7.3.4...v7.3.5)

Signed-off-by: dependabot[bot] <support@github.com>

* Update generated content

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2021-03-29 13:25:11 +02:00
CrazyMax
5c38e5df03 Enhance workflow (#51)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-29 13:18:19 +02:00
CrazyMax
2cbde9dffa Update CHANGELOG 2021-03-29 13:06:09 +02:00
CrazyMax
2f83320d17 v2 (#50)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-29 13:04:53 +02:00
29 changed files with 3707 additions and 672 deletions

View File

@@ -1,5 +1,2 @@
/.dev
/coverage /coverage
/dist
/lib
/node_modules /node_modules

View File

@@ -2,7 +2,7 @@
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) Contributions to this project are [released](https://docs.github.com/en/github/site-policy/github-terms-of-service#6-contributions-under-repository-license)
to the public under the [project's open source license](LICENSE). to the public under the [project's open source license](LICENSE).
## Submitting a pull request ## Submitting a pull request
@@ -28,5 +28,5 @@ Here are a few things you can do that will increase the likelihood of your pull
## Resources ## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) - [Using Pull Requests](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests)
- [GitHub Help](https://help.github.com) - [GitHub Help](https://docs.github.com/en)

View File

@@ -30,4 +30,5 @@ about: Create a report to help us improve
### Logs ### Logs
> Download the [log file of your build](https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#downloading-logs) and [attach it](https://help.github.com/en/github/managing-your-work-on-github/file-attachments-on-issues-and-pull-requests) to this issue. > Download the [log file of your build](https://docs.github.com/en/actions/managing-workflow-runs/using-workflow-run-logs#downloading-logs)
> and [attach it](https://docs.github.com/en/github/managing-your-work-on-github/file-attachments-on-issues-and-pull-requests) to this issue.

View File

@@ -5,10 +5,14 @@ on:
- cron: '0 */4 * * *' # every 4 hours - cron: '0 */4 * * *' # every 4 hours
push: push:
branches: branches:
- '**' - 'master'
- 'releases/v*'
tags: tags:
- 'v*.*.*' - 'v*.*.*'
pull_request: pull_request:
branches:
- 'master'
- 'releases/v*'
env: env:
DOCKER_IMAGE: localhost:5000/name/app DOCKER_IMAGE: localhost:5000/name/app
@@ -27,7 +31,12 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
tag-sha: true tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=sha
tag-schedule: tag-schedule:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -50,8 +59,12 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
tag-sha: true tags: |
tag-schedule: ${{ matrix.tag-schedule }} type=schedule,pattern=${{ matrix.tag-schedule }}
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=sha
tag-match: tag-match:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -59,9 +72,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- tag-match: '\d{1,3}.\d{1,3}.\d{1,3}' - tag-match: '\d.\d.\d'
tag-match-group: '0' tag-match-group: '0'
- tag-match: '\d{1,3}.\d{1,3}' - tag-match: '\d.\d'
tag-match-group: '0' tag-match-group: '0'
- tag-match: 'v(.*)' - tag-match: 'v(.*)'
tag-match-group: '1' tag-match-group: '1'
@@ -76,18 +89,23 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
tag-sha: true tags: |
tag-match: ${{ matrix.tag-match }} type=schedule
tag-match-group: ${{ matrix.tag-match-group }} type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=match,"pattern=${{ matrix.tag-match }}",group=${{ matrix.tag-match-group }}
type=sha
tag-semver: tag-semver:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
tag-latest: flavor-latest:
- 'true' - "auto"
- 'false' - "true"
- "false"
steps: steps:
- -
name: Checkout name: Checkout
@@ -99,13 +117,19 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
tag-semver: | tags: |
{{raw}} type=schedule
{{version}} type=ref,event=branch
{{major}}.{{minor}}.{{patch}} type=ref,event=tag
tag-latest: ${{ matrix.tag-latest }} type=ref,event=pr
type=semver,pattern={{raw}}
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}.{{patch}}
type=sha
flavor: |
latest=${{ matrix.flavor-latest }}
label-custom: flavor:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@@ -118,7 +142,24 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
label-custom: | flavor: |
prefix=foo-
suffix=-bar
labels:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
uses: ./
with:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
labels: |
maintainer=CrazyMax maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description org.opencontainers.image.description=Another description
@@ -141,11 +182,15 @@ jobs:
uses: ./ uses: ./
with: with:
images: ${{ env.DOCKER_IMAGE }} images: ${{ env.DOCKER_IMAGE }}
tag-sha: true tags: |
tag-semver: | type=schedule
v{{version}} type=ref,event=branch
v{{major}}.{{minor}} type=ref,event=tag
v{{major}} type=ref,event=pr
type=semver,pattern=v{{version}}
type=semver,pattern=v{{major}}.{{minor}}
type=semver,pattern=v{{major}}
type=sha
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
@@ -192,11 +237,15 @@ jobs:
images: | images: |
${{ env.DOCKER_IMAGE }} ${{ env.DOCKER_IMAGE }}
ghcr.io/name/app ghcr.io/name/app
tag-sha: true tags: |
tag-semver: | type=schedule
{{version}} type=ref,event=branch
{{major}}.{{minor}} type=ref,event=tag
{{major}} type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1

View File

@@ -1,9 +1,6 @@
name: label-sponsor name: label-sponsor
on: on:
pull_request:
types:
- 'opened'
issues: issues:
types: types:
- 'opened' - 'opened'

View File

@@ -10,6 +10,7 @@ on:
pull_request: pull_request:
branches: branches:
- 'master' - 'master'
- 'releases/v*'
paths-ignore: paths-ignore:
- '**.md' - '**.md'
@@ -20,12 +21,18 @@ jobs:
- -
name: Checkout name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
-
name: Validate
uses: docker/bake-action@v1
with:
targets: validate
- -
name: Test name: Test
run: docker buildx bake test uses: docker/bake-action@v1
with:
targets: test
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
with: with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/clover.xml file: ./coverage/clover.xml

View File

@@ -1,25 +0,0 @@
name: validate
on:
push:
branches:
- 'master'
- 'releases/v*'
paths-ignore:
- '**.md'
pull_request:
branches:
- 'master'
paths-ignore:
- '**.md'
jobs:
validate:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Validate
run: docker buildx bake validate

View File

@@ -1,5 +1,58 @@
# Changelog # Changelog
## 2.5.0 (2021/05/08)
* Major version zero doc (#74)
* Bump hosted-git-info from 2.8.8 to 2.8.9 (#73)
* Bump lodash from 4.17.20 to 4.17.21 (#72)
* Handle global expressions (#71)
## 2.4.0 (2021/04/30)
* Add `bake-target` input (#69)
* Fix setOutput (#67)
* Bump csv-parse from 4.15.3 to 4.15.4 (#65)
* Bump @actions/core from 1.2.6 to 1.2.7 (#64)
## 2.3.0 (2021/04/07)
* Allow overriding flavor (#63)
* Prefix/suffix not taken into account for `match`, `semver` and `schedule` types (#62)
## 2.2.1 (2021/04/05)
* Skip and display warning if tag does not match (#59)
## 2.2.0 (2021/04/03)
* Improve logging (#58)
* Fix README (#56)
## 2.1.1 (2021/03/30)
* Fix `enable` tag attribute (#53)
* Bump y18n from 4.0.0 to 4.0.1 (#54)
## 2.1.0 (2021/03/29)
* Bump semver from 7.3.4 to 7.3.5 (#49)
* Enhance workflow (#51)
## 2.0.0 (2021/03/29)
This release includes significant changes (#50). Please read the [upgrade notes](UPGRADE.md) for a smooth migration.
`v1` is still available through [`releases/v1` branch](https://github.com/crazy-max/ghaction-docker-meta/tree/releases/v1).
* Add `tags` input
* Inputs `tag-sha`, `tag-edge`, `tag-edge-branch`, `tag-semver`, `tag-match`, `tag-match-group`, `tag-schedule`, `tag-custom`, `tag-custom-only` have been removed and are now handled through the new `tags` input
* Input `label-custom` renamed `labels`
* Add `flavor` input to handle a global prefix, suffix and latest tag behavior (#13 #15 #41)
* Input `tag-latest` removed (use `flavor` input instead)
* Manage tag sorting through `priority` attribute in `tags` input (#27)
* Explicit control over the conditions of each tag through `enable` attribute in `tags` input (#30)
* Allow `semver` and `match` parsing for custom values (#25 #30)
* Display warning message if not tag generated
## 1.12.0 (2021/03/19) ## 1.12.0 (2021/03/19)
* Ignore commas for `label-custom` input (#48) * Ignore commas for `label-custom` input (#48)

497
README.md
View File

@@ -5,6 +5,10 @@
[![Become a sponsor](https://img.shields.io/badge/sponsor-crazy--max-181717.svg?logo=github&style=flat-square)](https://github.com/sponsors/crazy-max) [![Become a sponsor](https://img.shields.io/badge/sponsor-crazy--max-181717.svg?logo=github&style=flat-square)](https://github.com/sponsors/crazy-max)
[![Paypal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://www.paypal.me/crazyws) [![Paypal Donate](https://img.shields.io/badge/donate-paypal-00457c.svg?logo=paypal&style=flat-square)](https://www.paypal.me/crazyws)
## Upgrade from v1
`v2` of this action includes significant changes. Please read the [upgrade notes](UPGRADE.md) for a smooth migration.
## About ## About
GitHub Action to extract metadata (tags, labels) for Docker. This action is particularly useful if used with GitHub Action to extract metadata (tags, labels) for Docker. This action is particularly useful if used with
@@ -23,11 +27,19 @@ ___
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
* [`flavor` input](#flavor-input)
* [`tags` input](#tags-input)
* [`type=schedule`](#typeschedule)
* [`type=semver`](#typesemver)
* [`type=match`](#typematch)
* [`type=edge`](#typeedge)
* [`type=ref`](#typeref)
* [`type=raw`](#typeraw)
* [`type=sha`](#typesha)
* [Notes](#notes) * [Notes](#notes)
* [Latest tag](#latest-tag) * [Latest tag](#latest-tag)
* [Handle semver tag](#handle-semver-tag) * [Global expressions](#global-expressions)
* [`tag-match` examples](#tag-match-examples) * [Major version zero](#major-version-zero)
* [Schedule tag](#schedule-tag)
* [Overwrite labels](#overwrite-labels) * [Overwrite labels](#overwrite-labels)
* [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
* [Contributing](#contributing) * [Contributing](#contributing)
@@ -37,24 +49,18 @@ ___
### Basic ### Basic
| Event | Ref | Commit SHA | Docker Tags |
|-----------------|-------------------------------|------------|-------------------------------------|
| `pull_request` | `refs/pull/2/merge` | `a123b57` | `pr-2` |
| `push` | `refs/heads/master` | `cf20257` | `master` |
| `push` | `refs/heads/my/branch` | `a5df687` | `my-branch` |
| `push tag` | `refs/tags/v1.2.3` | `ad132f5` | `v1.2.3`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `fc89efd` | `v2.0.8-beta.67`, `latest` |
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: branches:
- '**' - 'master'
tags: tags:
- 'v*' - 'v*'
pull_request: pull_request:
branches:
- 'master'
jobs: jobs:
docker: docker:
@@ -65,16 +71,10 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- -
name: Docker meta name: Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: crazy-max/ghaction-docker-meta@v2
with: with:
images: name/app images: name/app
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- -
name: Login to DockerHub name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
@@ -87,33 +87,33 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
``` ```
| Event | Ref | Docker Tags |
|-----------------|-------------------------------|-------------------------------------|
| `pull_request` | `refs/pull/2/merge` | `pr-2` |
| `push` | `refs/heads/master` | `master` |
| `push` | `refs/heads/releases/v1` | `releases-v1` |
| `push tag` | `refs/tags/v1.2.3` | `v1.2.3`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `v2.0.8-beta.67`, `latest` |
### Semver ### Semver
| Event | Ref | Commit SHA | Docker Tags |
|-----------------|-------------------------------|------------|-------------------------------------|
| `pull_request` | `refs/pull/2/merge` | `a123b57` | `pr-2` |
| `push` | `refs/heads/master` | `cf20257` | `master` |
| `push` | `refs/heads/my/branch` | `a5df687` | `my-branch` |
| `push tag` | `refs/tags/v1.2.3` | `ad132f5` | `1.2.3`, `1.2`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `fc89efd` | `2.0.8-beta.67` |
```yaml ```yaml
name: ci name: ci
on: on:
push: push:
branches: branches:
- '**' - 'master'
tags: tags:
- 'v*' - 'v*'
pull_request: pull_request:
branches:
- 'master'
jobs: jobs:
docker: docker:
@@ -124,19 +124,15 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- -
name: Docker meta name: Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: crazy-max/ghaction-docker-meta@v2
with: with:
images: name/app images: name/app
tag-semver: | tags: |
{{version}} type=ref,event=branch
{{major}}.{{minor}} type=ref,event=pr
- type=semver,pattern={{version}}
name: Set up QEMU type=semver,pattern={{major}}.{{minor}}
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- -
name: Login to DockerHub name: Login to DockerHub
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
@@ -149,13 +145,19 @@ jobs:
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
``` ```
| Event | Ref | Docker Tags |
|-----------------|-------------------------------|-------------------------------------|
| `pull_request` | `refs/pull/2/merge` | `pr-2` |
| `push` | `refs/heads/master` | `master` |
| `push` | `refs/heads/releases/v1` | `releases-v1` |
| `push tag` | `refs/tags/v1.2.3` | `1.2.3`, `1.2`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `2.0.8-beta.67` |
### Bake definition ### Bake definition
This action also handles a bake definition file that can be used with the This action also handles a bake definition file that can be used with the
@@ -164,7 +166,6 @@ This action also handles a bake definition file that can be used with the
```hcl ```hcl
// docker-bake.hcl // docker-bake.hcl
target "ghaction-docker-meta" {} target "ghaction-docker-meta" {}
target "build" { target "build" {
@@ -181,10 +182,9 @@ name: ci
on: on:
push: push:
branches: branches:
- '**' - 'master'
tags: tags:
- 'v*' - 'v*'
pull_request:
jobs: jobs:
docker: docker:
@@ -195,40 +195,36 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- -
name: Docker meta name: Docker meta
id: docker_meta id: meta
uses: crazy-max/ghaction-docker-meta@v1 uses: crazy-max/ghaction-docker-meta@v2
with: with:
images: name/app images: name/app
tag-sha: true tags: |
tag-semver: | type=ref,event=branch
{{version}} type=ref,event=pr
{{major}}.{{minor}} type=semver,pattern={{version}}
- type=semver,pattern={{major}}.{{minor}}
name: Set up QEMU type=sha
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- -
name: Build name: Build
uses: docker/bake-action@v1 uses: docker/bake-action@v1
with: with:
files: | files: |
./docker-bake.hcl ./docker-bake.hcl
${{ steps.docker_meta.outputs.bake-file }} ${{ steps.meta.outputs.bake-file }}
targets: | targets: build
build
``` ```
Content of `${{ steps.docker_meta.outputs.bake-file }}` file will look like this: Content of `${{ steps.meta.outputs.bake-file }}` file will look like this with `refs/tags/v1.2.3` ref:
```json ```json
{ {
"target": { "target": {
"ghaction-docker-meta": { "ghaction-docker-meta": {
"tags": [ "tags": [
"name/app:1.1.1", "name/app:1.2.3",
"name/app:1.1", "name/app:1.2",
"name/app:sha-90dd603",
"name/app:latest" "name/app:latest"
], ],
"labels": { "labels": {
@@ -236,14 +232,14 @@ Content of `${{ steps.docker_meta.outputs.bake-file }}` file will look like this
"org.opencontainers.image.description": "This your first repo!", "org.opencontainers.image.description": "This your first repo!",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World", "org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World", "org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.version": "1.1.1", "org.opencontainers.image.version": "1.2.3",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT" "org.opencontainers.image.licenses": "MIT"
}, },
"args": { "args": {
"DOCKER_META_IMAGES": "name/app", "DOCKER_META_IMAGES": "name/app",
"DOCKER_META_VERSION": "1.1.1" "DOCKER_META_VERSION": "1.2.3"
} }
} }
} }
@@ -258,7 +254,7 @@ Following inputs can be used as `step.with` keys
> `List` type is a newline-delimited string > `List` type is a newline-delimited string
> ```yaml > ```yaml
> label-custom: | > labels: |
> org.opencontainers.image.title=MyCustomTitle > org.opencontainers.image.title=MyCustomTitle
> org.opencontainers.image.description=Another description > org.opencontainers.image.description=Another description
> org.opencontainers.image.vendor=MyCompany > org.opencontainers.image.vendor=MyCompany
@@ -272,21 +268,12 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description | | Name | Type | Description |
|---------------------|----------|------------------------------------| |---------------------|----------|------------------------------------|
| `images` | List/CSV | List of Docker images to use as base name for tags | | `images` | List/CSV | List of Docker images to use as base name for tags |
| `tag-sha` | Bool | Add git short commit as Docker tag (default `false`) | | `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
| `tag-edge` | Bool | Enable edge branch tagging (default `false`) | | `flavor` | List | [Flavor](#flavor-input) to apply |
| `tag-edge-branch` | String | Branch that will be tagged as edge (default `repo.default_branch`) | | `labels` | List | List of custom labels |
| `tag-semver` | List/CSV | Handle Git tag as semver [template](#handle-semver-tag) if possible |
| `tag-match` | String | RegExp to match against a Git tag and use first match as Docker tag |
| `tag-match-group` | Number | Group to get if `tag-match` matches (default `0`) |
| `tag-latest` | Bool | Set `latest` Docker tag if `tag-semver`, `tag-match` or Git tag event occurs (default `true`) |
| `tag-schedule` | String | [Template](#schedule-tag) to apply to schedule tag (default `nightly`) |
| `tag-custom` | List/CSV | List of custom tags |
| `tag-custom-only` | Bool | Only use `tag-custom` as Docker tags |
| `label-custom` | List | List of custom labels |
| `sep-tags` | String | Separator to use for tags output (default `\n`) | | `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) | | `sep-labels` | String | Separator to use for labels output (default `\n`) |
| `bake-target` | String | Bake target name (default `ghaction-docker-meta`) |
> `tag-semver` and `tag-match` are mutually exclusive
### outputs ### outputs
@@ -299,59 +286,313 @@ Following outputs are available
| `labels` | String | Docker labels | | `labels` | String | Docker labels |
| `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path | | `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path |
## `flavor` input
`flavor` defines a global behavior for [`tags`](#tags-input):
```yaml
flavor: |
latest=auto
prefix=
suffix=
```
* `latest=<auto|true|false>`: Handle [latest tag](#latest-tag) (default `auto`)
* `prefix=<string>`: A global prefix for each generated tag
* `suffix=<string>`: A global suffix for each generated tag
## `tags` input
`tags` is the core input of this action as everything related to it will reflect the output metadata. This one is in
the form of a key-value pair list in CSV format to remove limitations intrinsically linked to GitHub Actions
(only string format is handled in the input fields). Here is an example:
```yaml
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
```
Each entry is defined by a `type`, which are:
* [`type=schedule`](#typeschedule)
* [`type=semver`](#typesemver)
* [`type=match`](#typematch)
* [`type=edge`](#typeedge)
* [`type=ref`](#typeref)
* [`type=raw`](#typeraw)
* [`type=sha`](#typesha)
And global attributes:
* `enable=<true|false>` enable this entry (default `true`)
* `priority=<number>` priority to manage the order of tags
* `prefix=<string>` add prefix
* `suffix=<string>` add suffix
Default entries if `tags` input is empty:
```yaml
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
```
### `type=schedule`
```yaml
tags: |
# minimal
type=schedule
# default
type=schedule,pattern=nightly
# handlebars
type=schedule,pattern={{date 'YYYYMMDD'}}
```
Will be used on [schedule event](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#schedule).
`pattern` is a specially crafted attribute to support [Handlebars template](https://handlebarsjs.com/guide/) with
the following expressions:
* `date 'format'` ; render date by its [moment format](https://momentjs.com/docs/#/displaying/format/)
| Pattern | Output |
|--------------------------|----------------------|
| `nightly` | `nightly` |
| `{{date 'YYYYMMDD'}}` | `20210326` |
Extended attributes and default values:
```yaml
tags: |
type=schedule,enable=true,priority=1000,prefix=,suffix=,pattern=nightly
```
### `type=semver`
```yaml
tags: |
# minimal
type=semver,pattern={{version}}
# use custom value instead of git tag
type=semver,pattern={{version}},value=v1.0.0
```
Will be used on a [push tag event](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push)
and requires a valid Git tag [semver](https://semver.org/) but you can also use a custom value through `value`
attribute.
`pattern` attribute supports [Handlebars template](https://handlebarsjs.com/guide/) with the following expressions:
* `raw` ; the actual semver
* `version` ; shorthand for `{{major}}.{{minor}}.{{patch}}` (can include pre-release)
* `major` ; major version identifier
* `minor` ; minor version identifier
* `patch` ; patch version identifier
| Git tag | Pattern | Output |
|--------------------|----------------------------------------------------------|----------------------|
| `v1.2.3` | `{{raw}}` | `v1.2.3` |
| `v1.2.3` | `{{version}}` | `1.2.3` |
| `v1.2.3` | `{{major}}.{{minor}}` | `1.2` |
| `v1.2.3` | `v{{major}}` | `v1` |
| `v1.2.3` | `{{minor}}` | `2` |
| `v1.2.3` | `{{patch}}` | `3` |
| `v2.0.8-beta.67` | `{{raw}}` | `2.0.8-beta.67`* |
| `v2.0.8-beta.67` | `{{version}}` | `2.0.8-beta.67` |
| `v2.0.8-beta.67` | `{{major}}.{{minor}}` | `2.0.8-beta.67`* |
> *Pre-release (rc, beta, alpha) will only extend `{{version}}` as tag because they are updated frequently,
> and contain many breaking changes that are (by the author's design) not yet fit for public consumption.
Extended attributes and default values:
```yaml
tags: |
type=semver,enable=true,priority=900,prefix=,suffix=,pattern=,value=
```
### `type=match`
```yaml
tags: |
# minimal
type=match,pattern=\d.\d.\d
# define match group
type=match,pattern=v(.*),group=1
# use custom value instead of git tag
type=match,pattern=v(.*),group=1,value=v1.0.0
```
Can create a regular expression for matching Git tag with a pattern and capturing group. Will be used on a
[push tag event](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push) but you can also use
a custom value through `value` attribute.
| Git tag | Pattern | Group | Output |
|-------------------------|-------------------------------|---------|------------------------|
| `v1.2.3` | `\d.\d.\d` | `0` | `1.2.3` |
| `v2.0.8-beta.67` | `v(.*)` | `1` | `2.0.8-beta.67` |
| `v2.0.8-beta.67` | `v(\d.\d)` | `1` | `2.0` |
| `20200110-RC2` | `\d+` | `0` | `20200110` |
| `p1/v1.2.3` | `p1-v(\d.\d.\d)` | `1` | `1.2.3` |
Extended attributes and default values:
```yaml
tags: |
type=match,enable=true,priority=800,prefix=,suffix=,pattern=,group=0,value=
```
### `type=edge`
```yaml
tags: |
# minimal
type=edge
# define default branch
type=edge,branch=main
```
An `edge` tag reflects the last commit of the active branch on your Git repository. I usually prefer to use `edge`
as a Docker tag for a better distinction or common pattern. This is also used by official images
like [Alpine](https://hub.docker.com/_/alpine).
Extended attributes and default values:
```yaml
tags: |
type=edge,enable=true,priority=700,prefix=,suffix=,branch=$repo.default_branch
```
### `type=ref`
```yaml
tags: |
# branch event
type=ref,event=branch
# tag event
type=ref,event=tag
# pull request event
type=ref,event=pr
```
This type handles Git ref (or reference) for the following events:
* `branch` ; eg. `refs/heads/master`
* `tag` ; eg. `refs/tags/v1.0.0`
* `pr` ; eg. `refs/pull/318/merge`
| Event | Ref | Output |
|-----------------|-------------------------------|-------------------------------|
| `pull_request` | `refs/pull/2/merge` | `pr-2` |
| `push` | `refs/heads/master` | `master` |
| `push` | `refs/heads/my/branch` | `my-branch` |
| `push tag` | `refs/tags/v1.2.3` | `v1.2.3` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `v2.0.8-beta.67` |
Extended attributes and default values:
```yaml
tags: |
# branch event
type=ref,enable=true,priority=600,prefix=,suffix=,event=
# tag event
type=ref,enable=true,priority=600,prefix=,suffix=,event=
# pull request event
type=ref,enable=true,priority=600,prefix=pr-,suffix=,event=
```
### `type=raw`
```yaml
tags: |
type=raw,value=foo
type=raw,value=bar
# or
type=raw,foo
type=raw,bar
# or
foo
bar
```
Output custom tags according to your needs.
Extended attributes and default values:
```yaml
tags: |
type=raw,enable=true,priority=200,prefix=,suffix=,value=
```
### `type=sha`
```yaml
tags: |
# minimal
type=sha
```
Output Git short commit as Docker tag like `sha-ad132f5`.
Extended attributes and default values:
```yaml
tags: |
type=sha,enable=true,priority=100,prefix=sha-,suffix=
```
## Notes ## Notes
### Latest tag ### Latest tag
Latest Docker tag will be generated by default on `push tag` event. If for example you push the `v1.2.3` Git tag, `latest` tag is handled through the [`flavor` input](#flavor-input). It will be generated by default (`auto` mode) for:
you will have at the output of this action the Docker tags `v1.2.3` and `latest`. But you can allow the latest tag to be * [`type=ref,event=tag`](#typeref)
generated only if `tag-semver` is a valid [semver](https://semver.org/) or if Git tag matches a regular expression * [`type=semver,pattern=...`](#typesemver)
with the [`tag-match` input](#tag-match-examples). Can be disabled if `tag-latest` is `false`. * [`type=match,pattern=...`](#typematch)
### Handle semver tag ### Global expressions
If Git tag is a valid [semver](https://semver.org/) you can handle it to output multi Docker tags at once. The following [Handlebars template](https://handlebarsjs.com/guide/) expressions for `prefix`, `suffix` and `value`
`tag-semver` supports multi-line [Handlebars template](https://handlebarsjs.com/guide/) with the following inputs: attributes are available:
| Git tag | `tag-semver` | Valid | Output tags | Output version | | Expression | Output |
|--------------------|----------------------------------------------------------|--------------------|----------------------------|------------------------------| |--------------------------|----------------------|
| `v1.2.3` | `{{raw}}` | :white_check_mark: | `v1.2.3`, `latest` | `v1.2.3` | | `{{branch}}` | `master` |
| `v1.2.3` | `{{version}}` | :white_check_mark: | `1.2.3`, `latest` | `1.2.3` | | `{{tag}}` | `v1.2.3` |
| `v1.2.3` | `{{major}}.{{minor}}` | :white_check_mark: | `1.2`, `latest` | `1.2` | | `{{sha}}` | `90dd603` |
| `v1.2.3` | `v{{major}}` | :white_check_mark: | `v1`, `latest` | `v1` |
| `v1.2.3` | `{{minor}}` | :white_check_mark: | `2`, `latest` | `2` |
| `v1.2.3` | `{{patch}}` | :white_check_mark: | `3`, `latest` | `3` |
| `v1.2.3` | `{{major}}.{{minor}}`<br>`{{major}}.{{minor}}.{{patch}}` | :white_check_mark: | `1.2`, `1.2.3`, `latest` | `1.2`* |
| `v2.0.8-beta.67` | `{{raw}}` | :white_check_mark: | `2.0.8-beta.67`** | `2.0.8-beta.67` |
| `v2.0.8-beta.67` | `{{version}}` | :white_check_mark: | `2.0.8-beta.67` | `2.0.8-beta.67` |
| `v2.0.8-beta.67` | `{{major}}.{{minor}}` | :white_check_mark: | `2.0.8-beta.67`** | `2.0.8-beta.67` |
| `release1` | `{{raw}}` | :x: | `release1` | `release1` |
> *First occurrence of `tag-semver` will be taken as `output.version` ```yaml
tags: |
# dynamically set the branch name as a prefix
type=sha,prefix={{branch}}-
# dynamically set the branch name and sha as a custom tag
type=raw,value=mytag-{{branch}}-{{sha}}
```
> **Pre-release (rc, beta, alpha) will only extend `{{version}}` as tag because they are updated frequently, ### Major version zero
> and contain many breaking changes that are (by the author's design) not yet fit for public consumption.
### `tag-match` examples Major version zero (`0.y.z`) is for initial development and **may** change at any time. This means the public API
[**should not** be considered stable](https://semver.org/#spec-item-4).
| Git tag | `tag-match` | `tag-match-group` | Match | Output tags | Output version | In this case, Docker tag `0` **should not** be generated if you're using [`type=semver`](#typesemver) with `{{major}}`
|-------------------------|------------------------------------|-------------------|----------------------|---------------------------|------------------------------| pattern. You can manage this behavior like this:
| `v1.2.3` | `\d{1,3}.\d{1,3}.\d{1,3}` | `0` | :white_check_mark: | `1.2.3`, `latest` | `1.2.3` |
| `v2.0.8-beta.67` | `v(.*)` | `1` | :white_check_mark: | `2.0.8-beta.67`, `latest` | `2.0.8-beta.67` |
| `v2.0.8-beta.67` | `v(\d.\d)` | `1` | :white_check_mark: | `2.0`, `latest` | `2.0` |
| `release1` | `\d{1,3}.\d{1,3}` | `0` | :x: | `release1` | `release1` |
| `20200110-RC2` | `\d+` | `0` | :white_check_mark: | `20200110`, `latest` | `20200110` |
### Schedule tag ```yaml
# refs/tags/v0.1.2
`tag-schedule` is specially crafted input to support [Handlebars template](https://handlebarsjs.com/guide/) with tags: |
the following expressions: # output 0.1.2
type=semver,pattern={{version}}
| Expression | Example | Description | # output 0.1
|-------------------------|-------------------------------------------|------------------------------------------| type=semver,pattern={{major}}.{{minor}}
| `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/) # disabled if major zero
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
You can find more examples in the [CI workflow](.github/workflows/ci.yml). ```
### Overwrite labels ### Overwrite labels
@@ -362,10 +603,10 @@ labels generated are not suitable, you can overwrite them like this:
- -
name: Docker meta name: Docker meta
id: docker_meta id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1 uses: crazy-max/ghaction-docker-meta@v2
with: with:
images: name/app images: name/app
label-custom: | labels: |
maintainer=CrazyMax maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description org.opencontainers.image.description=Another description

294
UPGRADE.md Normal file
View File

@@ -0,0 +1,294 @@
# Upgrade notes
## v1 to v2
* [inputs](#inputs)
* [`tag-sha`](#tag-sha)
* [`tag-edge` / `tag-edge-branch`](#tag-edge--tag-edge-branch)
* [`tag-semver`](#tag-semver)
* [`tag-match` / `tag-match-group`](#tag-match--tag-match-group)
* [`tag-latest`](#tag-latest)
* [`tag-schedule`](#tag-schedule)
* [`tag-custom` / `tag-custom-only`](#tag-custom--tag-custom-only)
* [`label-custom`](#label-custom)
* [Basic workflow](#basic-workflow)
* [Semver workflow](#semver-workflow)
### inputs
| New | Unchanged | Removed |
|------------|-----------------|--------------------|
| `tags` | `images` | `tag-sha` |
| `flavor` | `sep-tags` | `tag-edge` |
| `labels` | `sep-labels` | `tag-edge-branch` |
| | | `tag-semver` |
| | | `tag-match` |
| | | `tag-match-group` |
| | | `tag-latest` |
| | | `tag-schedule` |
| | | `tag-custom` |
| | | `tag-custom-only` |
| | | `label-custom` |
#### `tag-sha`
```yaml
tags: |
type=sha
```
#### `tag-edge` / `tag-edge-branch`
```yaml
tags: |
# default branch
type=edge
# specify branch
type=edge,branch=main
```
#### `tag-semver`
```yaml
tags: |
type=semver,pattern={{version}}
```
#### `tag-match` / `tag-match-group`
```yaml
tags: |
type=match,pattern=v(.*),group=1
```
#### `tag-latest`
`tag-latest` is now handled through the [`flavor` input](README.md#flavor-input):
```yaml
flavor: |
latest=auto
```
See also the notes about ["latest tag" behavior](README.md#latest-tag)
#### `tag-schedule`
```yaml
tags: |
# default tag (nightly)
type=schedule
# specific pattern
type=schedule,pattern={{date 'YYYYMMDD'}}
```
#### `tag-custom` / `tag-custom-only`
```yaml
tags: |
type=raw,value=foo
type=raw,value=bar
# or
type=raw,foo
type=raw,bar
# or
foo
bar
```
#### `label-custom`
Same behavior for `labels`:
```yaml
labels: |
maintainer=CrazyMax
```
### Basic workflow
```yaml
# v1
name: ci
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: name/app
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
```
```yaml
# v2
name: ci
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: name/app
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
```
### Semver workflow
```yaml
# v1
name: ci
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: name/app
tag-semver: |
{{version}}
{{major}}.{{minor}}
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
```
```yaml
# v2
name: ci
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: name/app
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
```

View File

@@ -1,4 +1,5 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as context from '../src/context'; import * as context from '../src/context';
@@ -171,6 +172,27 @@ describe('asyncForEach', () => {
}); });
}); });
describe('setOutput', () => {
beforeEach(() => {
process.stdout.write = jest.fn();
});
it('setOutput produces the correct command', () => {
context.setOutput('some output', 'some value');
assertWriteCalls([`::set-output name=some output::some value${os.EOL}`]);
});
it('setOutput handles bools', () => {
context.setOutput('some output', false);
assertWriteCalls([`::set-output name=some output::false${os.EOL}`]);
});
it('setOutput handles numbers', () => {
context.setOutput('some output', 1.01);
assertWriteCalls([`::set-output name=some output::1.01${os.EOL}`]);
});
});
// See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67 // See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67
function getInputName(name: string): string { function getInputName(name: string): string {
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;
@@ -179,3 +201,11 @@ function getInputName(name: string): string {
function setInput(name: string, value: string): void { function setInput(name: string, value: string): void {
process.env[getInputName(name)] = value; process.env[getInputName(name)] = value;
} }
// Assert that process.stdout.write calls called only with the given arguments.
function assertWriteCalls(calls: string[]): void {
expect(process.stdout.write).toHaveBeenCalledTimes(calls.length);
for (let i = 0; i < calls.length; i++) {
expect(process.stdout.write).toHaveBeenNthCalledWith(i + 1, calls[i]);
}
}

View File

@@ -0,0 +1,23 @@
GITHUB_ACTION=crazy-maxghaction-dump-context
GITHUB_ACTIONS=true
GITHUB_ACTION_PATH=/home/runner/work/_actions/crazy-max/ghaction-dump-context/v1
GITHUB_ACTOR=crazy-max
GITHUB_API_URL=https://api.github.com
GITHUB_BASE_REF=
GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_6ee180c2-b331-434a-a867-89534cbefd83
GITHUB_EVENT_NAME=push
#GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json
GITHUB_GRAPHQL_URL=https://api.github.com/graphql
GITHUB_HEAD_REF=
GITHUB_JOB=event
GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_6ee180c2-b331-434a-a867-89534cbefd83
GITHUB_REF=refs/tags/p1/v1.0.0
GITHUB_REPOSITORY=crazy-max/test-docker-action
GITHUB_REPOSITORY_OWNER=crazy-max
GITHUB_RETENTION_DAYS=90
GITHUB_RUN_ID=325968230
GITHUB_RUN_NUMBER=4
GITHUB_SERVER_URL=https://github.com
GITHUB_SHA=90dd6032fac8bda1b6c4436a2e65de27961ed071
GITHUB_WORKFLOW=event
GITHUB_WORKSPACE=/home/runner/work/test-docker-action/test-docker-action

115
__tests__/flavor.test.ts Normal file
View File

@@ -0,0 +1,115 @@
import {Flavor, Transform} from '../src/flavor';
describe('transform', () => {
// prettier-ignore
test.each([
[
[
`randomstr`,
`latest=auto`
],
{} as Flavor,
true
],
[
[
`unknwown=foo`
],
{} as Flavor,
true
],
[
[
`latest`,
],
{} as Flavor,
true
],
[
[
`latest=true`
],
{
latest: "true",
prefix: "",
suffix: ""
} as Flavor,
false
],
[
[
`latest=false`
],
{
latest: "false",
prefix: "",
suffix: ""
} as Flavor,
false
],
[
[
`latest=auto`
],
{
latest: "auto",
prefix: "",
suffix: ""
} as Flavor,
false
],
[
[
`latest=foo`
],
{} as Flavor,
true
],
[
[
`prefix=sha-`
],
{
latest: "auto",
prefix: "sha-",
suffix: ""
} as Flavor,
false
],
[
[
`suffix=-alpine`
],
{
latest: "auto",
prefix: "",
suffix: "-alpine"
} as Flavor,
false
],
[
[
`latest=false`,
`prefix=dev-`,
`suffix=-alpine`
],
{
latest: "false",
prefix: "dev-",
suffix: "-alpine"
} as Flavor,
false
],
])('given %p attributes ', async (inputs: string[], expected: Flavor, invalid: boolean) => {
try {
const flavor = Transform(inputs);
console.log(flavor);
expect(flavor).toEqual(expected);
} catch (err) {
if (!invalid) {
console.error(err);
}
expect(true).toBe(invalid);
}
});
});

File diff suppressed because it is too large Load Diff

419
__tests__/tag.test.ts Normal file
View File

@@ -0,0 +1,419 @@
import {Transform, Parse, Tag, Type, RefEvent, DefaultPriorities} from '../src/tag';
describe('transform', () => {
// prettier-ignore
test.each([
[
[
`type=ref,event=branch`,
`type=ref,event=tag`,
`type=ref,event=pr`,
`type=schedule`,
`type=sha`,
`type=raw,foo`,
`type=edge`,
`type=semver,pattern={{version}}`,
`type=match,"pattern=\\d.\\d.\\d",group=0`
],
[
{
type: Type.Schedule,
attrs: {
"priority": DefaultPriorities[Type.Schedule],
"enable": "true",
"pattern": "nightly"
}
},
{
type: Type.Semver,
attrs: {
"priority": DefaultPriorities[Type.Semver],
"enable": "true",
"pattern": "{{version}}",
"value": ""
}
},
{
type: Type.Match,
attrs: {
"priority": DefaultPriorities[Type.Match],
"enable": "true",
"pattern": "\\d.\\d.\\d",
"group": "0",
"value": ""
}
},
{
type: Type.Edge,
attrs: {
"priority": DefaultPriorities[Type.Edge],
"enable": "true",
"branch": ""
}
},
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"event": RefEvent.Branch
}
},
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"event": RefEvent.Tag
}
},
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"prefix": "pr-",
"event": RefEvent.PR
}
},
{
type: Type.Raw,
attrs: {
"priority": DefaultPriorities[Type.Raw],
"enable": "true",
"value": "foo"
}
},
{
type: Type.Sha,
attrs: {
"priority": DefaultPriorities[Type.Sha],
"enable": "true",
"prefix": "sha-"
}
}
] as Tag[],
false
]
])('given %p', async (l: string[], expected: Tag[], invalid: boolean) => {
try {
const tags = Transform(l);
console.log(tags);
expect(tags).toEqual(expected);
} catch (err) {
if (!invalid) {
console.error(err);
}
expect(true).toBe(invalid);
}
});
});
describe('parse', () => {
// prettier-ignore
test.each([
[
`type=schedule,enable=true,pattern={{date 'YYYYMMDD'}}`,
{
type: Type.Schedule,
attrs: {
"priority": DefaultPriorities[Type.Schedule],
"enable": "true",
"pattern": "{{date 'YYYYMMDD'}}"
}
} as Tag,
false
],
[
`type=semver,enable=true,pattern={{version}}`,
{
type: Type.Semver,
attrs: {
"priority": DefaultPriorities[Type.Semver],
"enable": "true",
"pattern": "{{version}}",
"value": ""
}
} as Tag,
false
],
[
`type=semver,priority=1,enable=true,pattern={{version}}`,
{
type: Type.Semver,
attrs: {
"priority": "1",
"enable": "true",
"pattern": "{{version}}",
"value": ""
}
} as Tag,
false
],
[
`type=semver,priority=1,enable=true,pattern={{version}},value=v1.0.0`,
{
type: Type.Semver,
attrs: {
"priority": "1",
"enable": "true",
"pattern": "{{version}}",
"value": "v1.0.0"
}
} as Tag,
false
],
[
`type=match,enable=true,pattern=v(.*),group=1`,
{
type: Type.Match,
attrs: {
"priority": DefaultPriorities[Type.Match],
"enable": "true",
"pattern": "v(.*)",
"group": "1",
"value": ""
}
} as Tag,
false
],
[
`type=match,enable=true,"pattern=^v(\\d.\\d.\\d)$",group=1`,
{
type: Type.Match,
attrs: {
"priority": DefaultPriorities[Type.Match],
"enable": "true",
"pattern": "^v(\\d.\\d.\\d)$",
"group": "1",
"value": ""
}
} as Tag,
false
],
[
`type=match,priority=700,enable=true,pattern=v(.*),group=1`,
{
type: Type.Match,
attrs: {
"priority": "700",
"enable": "true",
"pattern": "v(.*)",
"group": "1",
"value": ""
}
} as Tag,
false
],
[
`type=match,enable=true,pattern=v(.*),group=1,value=v1.2.3`,
{
type: Type.Match,
attrs: {
"priority": DefaultPriorities[Type.Match],
"enable": "true",
"pattern": "v(.*)",
"group": "1",
"value": "v1.2.3"
}
} as Tag,
false
],
[
`type=match,enable=true,pattern=v(.*),group=foo`,
{} as Tag,
true
],
[
`type=edge`,
{
type: Type.Edge,
attrs: {
"priority": DefaultPriorities[Type.Edge],
"enable": "true",
"branch": ""
}
} as Tag,
false
],
[
`type=edge,enable=true,branch=master`,
{
type: Type.Edge,
attrs: {
"priority": DefaultPriorities[Type.Edge],
"enable": "true",
"branch": "master"
}
} as Tag,
false
],
[
`type=ref,event=tag`,
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"event": RefEvent.Tag
}
} as Tag,
false
],
[
`type=ref,event=branch`,
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"event": RefEvent.Branch
}
} as Tag,
false
],
[
`type=ref,event=pr`,
{
type: Type.Ref,
attrs: {
"priority": DefaultPriorities[Type.Ref],
"enable": "true",
"prefix": "pr-",
"event": RefEvent.PR
}
} as Tag,
false
],
[
`type=ref,event=foo`,
{} as Tag,
true
],
[
`type=ref`,
{} as Tag,
true
],
[
`acustomtag`,
{
type: Type.Raw,
attrs: {
"priority": DefaultPriorities[Type.Raw],
"enable": "true",
"value": "acustomtag"
}
} as Tag,
false
],
[
`type=raw`,
{} as Tag,
true
],
[
`type=raw,value=acustomtag2`,
{
type: Type.Raw,
attrs: {
"priority": DefaultPriorities[Type.Raw],
"enable": "true",
"value": "acustomtag2"
}
} as Tag,
false
],
[
`type=raw,enable=true,value=acustomtag4`,
{
type: Type.Raw,
attrs: {
"priority": DefaultPriorities[Type.Raw],
"enable": "true",
"value": "acustomtag4"
}
} as Tag,
false
],
[
`type=raw,enable=false,value=acustomtag5`,
{
type: Type.Raw,
attrs: {
"priority": DefaultPriorities[Type.Raw],
"enable": "false",
"value": "acustomtag5"
}
} as Tag,
false
],
[
`type=sha`,
{
type: Type.Sha,
attrs: {
"priority": DefaultPriorities[Type.Sha],
"enable": "true",
"prefix": "sha-"
}
} as Tag,
false
],
[
`type=sha,prefix=`,
{
type: Type.Sha,
attrs: {
"priority": DefaultPriorities[Type.Sha],
"enable": "true",
"prefix": ""
}
} as Tag,
false
],
[
`type=sha,enable=false`,
{
type: Type.Sha,
attrs: {
"priority": DefaultPriorities[Type.Sha],
"enable": "false",
"prefix": "sha-"
}
} as Tag,
false
],
[
`type=semver`,
{} as Tag,
true
],
[
`type=match`,
{} as Tag,
true
],
[
`type=foo`,
{} as Tag,
true
],
[
`type=sha,enable=foo`,
{} as Tag,
true
]
])('given %p event ', async (s: string, expected: Tag, invalid: boolean) => {
try {
const tag = Parse(s);
console.log(tag);
expect(tag).toEqual(expected);
} catch (err) {
if (!invalid) {
console.error(err);
}
expect(true).toBe(invalid);
}
});
});

View File

@@ -10,47 +10,13 @@ inputs:
images: images:
description: 'List of Docker images to use as base name for tags' description: 'List of Docker images to use as base name for tags'
required: true required: true
tag-sha: tags:
description: 'Add git short SHA as Docker tag' description: 'List of tags as key-value pair attributes'
default: 'false'
required: false required: false
tag-edge: flavor:
description: 'Enable edge branch tagging' description: 'Flavors to apply'
default: 'false'
required: false required: false
tag-edge-branch: labels:
description: 'Branch that will be tagged as edge (default repo.default_branch)'
required: false
tag-semver:
description: 'Handle Git tag as semver template if possible'
required: false
tag-match:
description: 'RegExp to match against a Git tag and use match group as Docker tag'
required: false
tag-match-group:
description: 'Group to get if tag-match matches (default 0)'
default: '0'
required: false
tag-latest:
description: 'Set latest Docker tag if tag-semver, tag-match or Git tag event occurs'
default: 'true'
required: false
tag-match-latest:
deprecationMessage: 'tag-match-latest is deprecated. Use tag-latest instead'
description: '(DEPRECATED) Set latest Docker tag if tag-match matches or on Git tag event'
default: 'true'
required: false
tag-schedule:
description: 'Template to apply to schedule tag'
default: 'nightly'
required: false
tag-custom:
description: 'List of custom tags'
required: false
tag-custom-only:
description: 'Only use tag-custom as Docker tags'
required: false
label-custom:
description: 'List of custom labels' description: 'List of custom labels'
required: false required: false
sep-tags: sep-tags:
@@ -59,6 +25,9 @@ inputs:
sep-labels: sep-labels:
description: 'Separator to use for labels output (default \n)' description: 'Separator to use for labels output (default \n)'
required: false required: false
bake-target:
description: 'Bake target name (default ghaction-docker-meta)'
required: false
github-token: github-token:
description: 'GitHub Token as provided by secrets' description: 'GitHub Token as provided by secrets'
default: ${{ github.token }} default: ${{ github.token }}

View File

@@ -1,51 +0,0 @@
#syntax=docker/dockerfile:1.2
FROM node:12 AS deps
WORKDIR /src
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/src/node_modules \
yarn install
FROM scratch AS update-yarn
COPY --from=deps /src/yarn.lock /
FROM deps AS validate-yarn
COPY .git .git
RUN status=$(git status --porcelain -- yarn.lock); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM deps AS base
COPY . .
FROM base AS build
RUN --mount=type=cache,target=/src/node_modules \
yarn build
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
COPY . .
RUN --mount=type=cache,target=/src/node_modules \
yarn run test
FROM scratch AS test-coverage
COPY --from=test /src/coverage /coverage/
FROM base AS run-format
RUN --mount=type=cache,target=/src/node_modules \
yarn run format
FROM scratch AS format
COPY --from=run-format /src/src/*.ts /src/
FROM base AS validate-format
RUN --mount=type=cache,target=/src/node_modules \
yarn run format-check
FROM scratch AS dist
COPY --from=build /src/dist/ /dist/
FROM build AS validate-build
RUN status=$(git status --porcelain -- dist); if [ -n "$status" ]; then echo $status; exit 1; fi
FROM base AS dev
ENTRYPOINT ["bash"]

715
dist/index.js generated vendored
View File

@@ -39,9 +39,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.asyncForEach = exports.getInputList = exports.getInputs = exports.tmpDir = void 0; exports.setOutput = exports.asyncForEach = exports.getInputList = exports.getInputs = exports.tmpDir = void 0;
const sync_1 = __importDefault(__webpack_require__(8750)); const sync_1 = __importDefault(__webpack_require__(8750));
const core = __importStar(__webpack_require__(2186)); const core = __importStar(__webpack_require__(2186));
const command_1 = __webpack_require__(7351);
const fs = __importStar(__webpack_require__(5747)); const fs = __importStar(__webpack_require__(5747));
const os = __importStar(__webpack_require__(2087)); const os = __importStar(__webpack_require__(2087));
const path = __importStar(__webpack_require__(5622)); const path = __importStar(__webpack_require__(5622));
@@ -56,19 +57,12 @@ exports.tmpDir = tmpDir;
function getInputs() { function getInputs() {
return { return {
images: getInputList('images'), images: getInputList('images'),
tagSha: /true/i.test(core.getInput('tag-sha') || 'false'), tags: getInputList('tags', true),
tagEdge: /true/i.test(core.getInput('tag-edge') || 'false'), flavor: getInputList('flavor', true),
tagEdgeBranch: core.getInput('tag-edge-branch'), labels: getInputList('labels', true),
tagSemver: getInputList('tag-semver'),
tagMatch: core.getInput('tag-match'),
tagMatchGroup: Number(core.getInput('tag-match-group')) || 0,
tagLatest: /true/i.test(core.getInput('tag-latest') || core.getInput('tag-match-latest') || 'true'),
tagSchedule: core.getInput('tag-schedule') || 'nightly',
tagCustom: getInputList('tag-custom'),
tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'),
labelCustom: getInputList('label-custom', true),
sepTags: core.getInput('sep-tags') || `\n`, sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`,
bakeTarget: core.getInput('bake-target') || `ghaction-docker-meta`,
githubToken: core.getInput('github-token') githubToken: core.getInput('github-token')
}; };
} }
@@ -102,10 +96,86 @@ exports.asyncForEach = (array, callback) => __awaiter(void 0, void 0, void 0, fu
yield callback(array[index], index, array); yield callback(array[index], index, array);
} }
}); });
// FIXME: Temp fix https://github.com/actions/toolkit/issues/777
function setOutput(name, value) {
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
//# sourceMappingURL=context.js.map //# sourceMappingURL=context.js.map
/***/ }), /***/ }),
/***/ 3716:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Transform = void 0;
const core = __importStar(__webpack_require__(2186));
function Transform(inputs) {
const flavor = {
latest: 'auto',
prefix: '',
suffix: ''
};
for (const input of inputs) {
const parts = input.split('=', 2);
if (parts.length == 1) {
throw new Error(`Invalid entry: ${input}`);
}
switch (parts[0]) {
case 'latest': {
flavor.latest = parts[1];
if (!['auto', 'true', 'false'].includes(flavor.latest)) {
throw new Error(`Invalid latest flavor entry: ${input}`);
}
break;
}
case 'prefix': {
flavor.prefix = parts[1];
break;
}
case 'suffix': {
flavor.suffix = parts[1];
break;
}
default: {
throw new Error(`Unknown entry: ${input}`);
}
}
}
core.startGroup(`Processing flavor input`);
core.info(`latest=${flavor.latest}`);
core.info(`prefix=${flavor.prefix}`);
core.info(`suffix=${flavor.suffix}`);
core.endGroup();
return flavor;
}
exports.Transform = Transform;
//# sourceMappingURL=flavor.js.map
/***/ }),
/***/ 5928: /***/ 5928:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) { /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
@@ -221,32 +291,42 @@ function run() {
core.endGroup(); core.endGroup();
const meta = new meta_1.Meta(inputs, context, repo); const meta = new meta_1.Meta(inputs, context, repo);
const version = meta.version; const version = meta.version;
core.startGroup(`Docker image version`); if (meta.version.main == undefined || meta.version.main.length == 0) {
core.info(version.main || ''); core.warning(`No Docker image version has been generated. Check tags input.`);
core.endGroup();
core.setOutput('version', version.main || '');
// Docker tags
const tags = meta.tags();
core.startGroup(`Docker tags`);
for (let tag of tags) {
core.info(tag);
} }
core.endGroup(); else {
core.setOutput('tags', tags.join(inputs.sepTags)); core.startGroup(`Docker image version`);
core.info(version.main || '');
core.endGroup();
}
context_1.setOutput('version', version.main || '');
// Docker tags
const tags = meta.getTags();
if (tags.length == 0) {
core.warning('No Docker tag has been generated. Check tags input.');
}
else {
core.startGroup(`Docker tags`);
for (let tag of tags) {
core.info(tag);
}
core.endGroup();
}
context_1.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels // Docker labels
const labels = meta.labels(); const labels = meta.getLabels();
core.startGroup(`Docker labels`); core.startGroup(`Docker labels`);
for (let label of labels) { for (let label of labels) {
core.info(label); core.info(label);
} }
core.endGroup(); core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels)); context_1.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file // Bake definition file
const bakeFile = meta.bakeFile(); const bakeFile = meta.getBakeFile();
core.startGroup(`Bake definition file`); core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8')); core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup(); core.endGroup();
core.setOutput('bake-file', bakeFile); context_1.setOutput('bake-file', bakeFile);
} }
catch (error) { catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
@@ -293,99 +373,242 @@ const path = __importStar(__webpack_require__(5622));
const moment_1 = __importDefault(__webpack_require__(9623)); const moment_1 = __importDefault(__webpack_require__(9623));
const semver = __importStar(__webpack_require__(1383)); const semver = __importStar(__webpack_require__(1383));
const context_1 = __webpack_require__(3842); const context_1 = __webpack_require__(3842);
const tcl = __importStar(__webpack_require__(2829));
const fcl = __importStar(__webpack_require__(3716));
const core = __importStar(__webpack_require__(2186)); const core = __importStar(__webpack_require__(2186));
class Meta { class Meta {
constructor(inputs, context, repo) { constructor(inputs, context, repo) {
this.inputs = inputs; this.inputs = inputs;
if (!this.inputs.tagEdgeBranch) {
this.inputs.tagEdgeBranch = repo.default_branch;
}
this.context = context; this.context = context;
this.repo = repo; this.repo = repo;
this.tags = tcl.Transform(inputs.tags);
this.flavor = fcl.Transform(inputs.flavor);
this.date = new Date(); this.date = new Date();
this.version = this.getVersion(); this.version = this.getVersion();
} }
getVersion() { getVersion() {
const currentDate = this.date;
let version = { let version = {
main: undefined, main: undefined,
partial: [], partial: [],
latest: false latest: undefined
}; };
if (/schedule/.test(this.context.eventName)) { for (const tag of this.tags) {
version.main = handlebars.compile(this.inputs.tagSchedule)({ if (!/true/i.test(tag.attrs['enable'])) {
date: function (format) { continue;
return moment_1.default(currentDate).utc().format(format);
}
});
}
else if (/^refs\/tags\//.test(this.context.ref)) {
version.main = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
if (this.inputs.tagSemver.length > 0 && !semver.valid(version.main)) {
core.warning(`${version.main} is not a valid semver. More info: https://semver.org/`);
} }
if (this.inputs.tagSemver.length > 0 && semver.valid(version.main)) { switch (tag.type) {
const sver = semver.parse(version.main, { case tcl.Type.Schedule: {
includePrerelease: true version = this.procSchedule(version, tag);
}); break;
if (semver.prerelease(version.main)) {
version.main = handlebars.compile('{{version}}')(sver);
} }
else { case tcl.Type.Semver: {
version.latest = this.inputs.tagLatest; version = this.procSemver(version, tag);
version.main = handlebars.compile(this.inputs.tagSemver[0])(sver); break;
for (const semverTpl of this.inputs.tagSemver) { }
const partial = handlebars.compile(semverTpl)(sver); case tcl.Type.Match: {
if (partial == version.main) { version = this.procMatch(version, tag);
continue; break;
} }
version.partial.push(partial); case tcl.Type.Ref: {
if (tag.attrs['event'] == tcl.RefEvent.Branch) {
version = this.procRefBranch(version, tag);
} }
else if (tag.attrs['event'] == tcl.RefEvent.Tag) {
version = this.procRefTag(version, tag);
}
else if (tag.attrs['event'] == tcl.RefEvent.PR) {
version = this.procRefPr(version, tag);
}
break;
} }
} case tcl.Type.Edge: {
else if (this.inputs.tagMatch) { version = this.procEdge(version, tag);
let tagMatch; break;
const isRegEx = this.inputs.tagMatch.match(/^\/(.+)\/(.*)$/);
if (isRegEx) {
tagMatch = version.main.match(new RegExp(isRegEx[1], isRegEx[2]));
} }
else { case tcl.Type.Raw: {
tagMatch = version.main.match(this.inputs.tagMatch); version = this.procRaw(version, tag);
break;
} }
if (tagMatch) { case tcl.Type.Sha: {
version.main = tagMatch[this.inputs.tagMatchGroup]; version = this.procSha(version, tag);
version.latest = this.inputs.tagLatest; break;
} }
} }
else {
version.latest = this.inputs.tagLatest;
}
}
else if (/^refs\/heads\//.test(this.context.ref)) {
version.main = this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
if (this.inputs.tagEdge && this.inputs.tagEdgeBranch === version.main) {
version.main = 'edge';
}
}
else if (/^refs\/pull\//.test(this.context.ref)) {
version.main = `pr-${this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, '')}`;
}
if (this.inputs.tagCustom.length > 0) {
if (this.inputs.tagCustomOnly) {
version = {
main: this.inputs.tagCustom.shift(),
partial: this.inputs.tagCustom,
latest: false
};
}
else {
version.partial.push(...this.inputs.tagCustom);
}
} }
version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index); version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
if (version.latest == undefined) {
version.latest = false;
}
return version; return version;
} }
tags() { procSchedule(version, tag) {
if (!/schedule/.test(this.context.eventName)) {
return version;
}
const currentDate = this.date;
const vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])({
date: function (format) {
return moment_1.default(currentDate).utc().format(format);
}
}), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
procSemver(version, tag) {
if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
return version;
}
let vraw;
if (tag.attrs['value'].length > 0) {
vraw = this.setGlobalExp(tag.attrs['value']);
}
else {
vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
}
if (!semver.valid(vraw)) {
core.warning(`${vraw} is not a valid semver. More info: https://semver.org/`);
return version;
}
let latest = false;
const sver = semver.parse(vraw, {
includePrerelease: true
});
if (semver.prerelease(vraw)) {
vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag);
}
else {
vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
latest = true;
}
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true');
}
procMatch(version, tag) {
if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
return version;
}
let vraw;
if (tag.attrs['value'].length > 0) {
vraw = this.setGlobalExp(tag.attrs['value']);
}
else {
vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
}
let latest = false;
let tmatch;
const isRegEx = tag.attrs['pattern'].match(/^\/(.+)\/(.*)$/);
if (isRegEx) {
tmatch = vraw.match(new RegExp(isRegEx[1], isRegEx[2]));
}
else {
tmatch = vraw.match(tag.attrs['pattern']);
}
if (!tmatch) {
core.warning(`${tag.attrs['pattern']} does not match ${vraw}.`);
return version;
}
if (typeof tmatch[tag.attrs['group']] === 'undefined') {
core.warning(`Group ${tag.attrs['group']} does not exist for ${tag.attrs['pattern']} pattern.`);
return version;
}
vraw = this.setValue(tmatch[tag.attrs['group']], tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true');
}
procRefBranch(version, tag) {
if (!/^refs\/heads\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
procRefTag(version, tag) {
if (!/^refs\/tags\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true');
}
procRefPr(version, tag) {
if (!/^refs\/pull\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
procEdge(version, tag) {
if (!/^refs\/heads\//.test(this.context.ref)) {
return version;
}
let val = this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
if (tag.attrs['branch'].length == 0) {
tag.attrs['branch'] = this.repo.default_branch;
}
if (tag.attrs['branch'] === val) {
val = 'edge';
}
const vraw = this.setValue(val, tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
procRaw(version, tag) {
const vraw = this.setValue(this.setGlobalExp(tag.attrs['value']), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
procSha(version, tag) {
if (!this.context.sha) {
return version;
}
const vraw = this.setValue(this.context.sha.substr(0, 7), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
static setVersion(version, val, latest) {
if (val.length == 0) {
return version;
}
if (version.main == undefined) {
version.main = val;
}
else if (val !== version.main) {
version.partial.push(val);
}
if (version.latest == undefined) {
version.latest = latest;
}
return version;
}
setValue(val, tag) {
if (tag.attrs.hasOwnProperty('prefix')) {
val = `${this.setGlobalExp(tag.attrs['prefix'])}${val}`;
}
else if (this.flavor.prefix.length > 0) {
val = `${this.setGlobalExp(this.flavor.prefix)}${val}`;
}
if (tag.attrs.hasOwnProperty('suffix')) {
val = `${val}${this.setGlobalExp(tag.attrs['suffix'])}`;
}
else if (this.flavor.suffix.length > 0) {
val = `${val}${this.setGlobalExp(this.flavor.suffix)}`;
}
return val;
}
setGlobalExp(val) {
const ctx = this.context;
return handlebars.compile(val)({
branch: function () {
if (!/^refs\/heads\//.test(ctx.ref)) {
return '';
}
return ctx.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
},
tag: function () {
if (!/^refs\/tags\//.test(ctx.ref)) {
return '';
}
return ctx.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
},
sha: function () {
return ctx.sha.substr(0, 7);
}
});
}
getTags() {
if (!this.version.main) { if (!this.version.main) {
return []; return [];
} }
@@ -399,13 +622,10 @@ class Meta {
if (this.version.latest) { if (this.version.latest) {
tags.push(`${imageLc}:latest`); tags.push(`${imageLc}:latest`);
} }
if (this.context.sha && this.inputs.tagSha) {
tags.push(`${imageLc}:sha-${this.context.sha.substr(0, 7)}`);
}
} }
return tags; return tags;
} }
labels() { getLabels() {
var _a; var _a;
let labels = [ let labels = [
`org.opencontainers.image.title=${this.repo.name || ''}`, `org.opencontainers.image.title=${this.repo.name || ''}`,
@@ -417,12 +637,12 @@ class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`, `org.opencontainers.image.revision=${this.context.sha || ''}`,
`org.opencontainers.image.licenses=${((_a = this.repo.license) === null || _a === void 0 ? void 0 : _a.spdx_id) || ''}` `org.opencontainers.image.licenses=${((_a = this.repo.license) === null || _a === void 0 ? void 0 : _a.spdx_id) || ''}`
]; ];
labels.push(...this.inputs.labelCustom); labels.push(...this.inputs.labels);
return labels; return labels;
} }
bakeFile() { getBakeFile() {
let jsonLabels = {}; let jsonLabels = {};
for (let label of this.labels()) { for (let label of this.getLabels()) {
const matches = label.match(/([^=]*)=(.*)/); const matches = label.match(/([^=]*)=(.*)/);
if (!matches) { if (!matches) {
continue; continue;
@@ -432,8 +652,8 @@ class Meta {
const bakeFile = path.join(context_1.tmpDir(), 'ghaction-docker-meta-bake.json').split(path.sep).join(path.posix.sep); const bakeFile = path.join(context_1.tmpDir(), 'ghaction-docker-meta-bake.json').split(path.sep).join(path.posix.sep);
fs.writeFileSync(bakeFile, JSON.stringify({ fs.writeFileSync(bakeFile, JSON.stringify({
target: { target: {
'ghaction-docker-meta': { [this.inputs.bakeTarget]: {
tags: this.tags(), tags: this.getTags(),
labels: jsonLabels, labels: jsonLabels,
args: { args: {
DOCKER_META_IMAGES: this.inputs.images.join(','), DOCKER_META_IMAGES: this.inputs.images.join(','),
@@ -450,6 +670,218 @@ exports.Meta = Meta;
/***/ }), /***/ }),
/***/ 2829:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Parse = exports.Transform = exports.DefaultPriorities = exports.Tag = exports.RefEvent = exports.Type = void 0;
const sync_1 = __importDefault(__webpack_require__(8750));
const core = __importStar(__webpack_require__(2186));
var Type;
(function (Type) {
Type["Schedule"] = "schedule";
Type["Semver"] = "semver";
Type["Match"] = "match";
Type["Edge"] = "edge";
Type["Ref"] = "ref";
Type["Raw"] = "raw";
Type["Sha"] = "sha";
})(Type = exports.Type || (exports.Type = {}));
var RefEvent;
(function (RefEvent) {
RefEvent["Branch"] = "branch";
RefEvent["Tag"] = "tag";
RefEvent["PR"] = "pr";
})(RefEvent = exports.RefEvent || (exports.RefEvent = {}));
class Tag {
constructor() {
this.attrs = {};
}
toString() {
const out = [`type=${this.type}`];
for (let attr in this.attrs) {
out.push(`${attr}=${this.attrs[attr]}`);
}
return out.join(',');
}
}
exports.Tag = Tag;
exports.DefaultPriorities = {
[Type.Schedule]: '1000',
[Type.Semver]: '900',
[Type.Match]: '800',
[Type.Edge]: '700',
[Type.Ref]: '600',
[Type.Raw]: '200',
[Type.Sha]: '100'
};
function Transform(inputs) {
const tags = [];
if (inputs.length == 0) {
// prettier-ignore
inputs = [
`type=schedule`,
`type=ref,event=${RefEvent.Branch}`,
`type=ref,event=${RefEvent.Tag}`,
`type=ref,event=${RefEvent.PR}`
];
}
for (const input of inputs) {
tags.push(Parse(input));
}
const sorted = tags.sort((tag1, tag2) => {
if (Number(tag1.attrs['priority']) < Number(tag2.attrs['priority'])) {
return 1;
}
if (Number(tag1.attrs['priority']) > Number(tag2.attrs['priority'])) {
return -1;
}
return 0;
});
core.startGroup(`Processing tags input`);
for (const tag of sorted) {
core.info(tag.toString());
}
core.endGroup();
return sorted;
}
exports.Transform = Transform;
function Parse(s) {
const fields = sync_1.default(s, {
relaxColumnCount: true,
skipLinesWithEmptyValues: true
})[0];
const tag = new Tag();
for (const field of fields) {
const parts = field.toString().split('=', 2);
if (parts.length == 1) {
tag.attrs['value'] = parts[0].trim();
}
else {
const key = parts[0].trim().toLowerCase();
const value = parts[1].trim();
switch (key) {
case 'type': {
if (!Object.values(Type).includes(value)) {
throw new Error(`Unknown type attribute: ${value}`);
}
tag.type = value;
break;
}
default: {
tag.attrs[key] = value;
break;
}
}
}
}
if (tag.type == undefined) {
tag.type = Type.Raw;
}
switch (tag.type) {
case Type.Schedule: {
if (!tag.attrs.hasOwnProperty('pattern')) {
tag.attrs['pattern'] = 'nightly';
}
break;
}
case Type.Semver: {
if (!tag.attrs.hasOwnProperty('pattern')) {
throw new Error(`Missing pattern attribute for ${s}`);
}
if (!tag.attrs.hasOwnProperty('value')) {
tag.attrs['value'] = '';
}
break;
}
case Type.Match: {
if (!tag.attrs.hasOwnProperty('pattern')) {
throw new Error(`Missing pattern attribute for ${s}`);
}
if (!tag.attrs.hasOwnProperty('group')) {
tag.attrs['group'] = '0';
}
if (isNaN(+tag.attrs['group'])) {
throw new Error(`Invalid match group for ${s}`);
}
if (!tag.attrs.hasOwnProperty('value')) {
tag.attrs['value'] = '';
}
break;
}
case Type.Edge: {
if (!tag.attrs.hasOwnProperty('branch')) {
tag.attrs['branch'] = '';
}
break;
}
case Type.Ref: {
if (!tag.attrs.hasOwnProperty('event')) {
throw new Error(`Missing event attribute for ${s}`);
}
if (!Object.keys(RefEvent)
.map(k => RefEvent[k])
.includes(tag.attrs['event'])) {
throw new Error(`Invalid event for ${s}`);
}
if (tag.attrs['event'] == RefEvent.PR && !tag.attrs.hasOwnProperty('prefix')) {
tag.attrs['prefix'] = 'pr-';
}
break;
}
case Type.Raw: {
if (!tag.attrs.hasOwnProperty('value')) {
throw new Error(`Missing value attribute for ${s}`);
}
break;
}
case Type.Sha: {
if (!tag.attrs.hasOwnProperty('prefix')) {
tag.attrs['prefix'] = 'sha-';
}
break;
}
}
if (!tag.attrs.hasOwnProperty('enable')) {
tag.attrs['enable'] = 'true';
}
if (!tag.attrs.hasOwnProperty('priority')) {
tag.attrs['priority'] = exports.DefaultPriorities[tag.type];
}
if (!['true', 'false'].includes(tag.attrs['enable'])) {
throw new Error(`Invalid value for enable attribute: ${tag.attrs['enable']}`);
}
return tag;
}
exports.Parse = Parse;
//# sourceMappingURL=tag.js.map
/***/ }),
/***/ 7351: /***/ 7351:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) { /***/ (function(__unused_webpack_module, exports, __webpack_require__) {
@@ -646,6 +1078,7 @@ exports.getInput = getInput;
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) { function setOutput(name, value) {
process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, value); command_1.issueCommand('set-output', { name }, value);
} }
exports.setOutput = setOutput; exports.setOutput = setOutput;
@@ -5047,7 +5480,7 @@ class Parser extends Transform {
for(let i = 0, l = record.length; i < l; i++){ for(let i = 0, l = record.length; i < l; i++){
if(columns[i] === undefined || columns[i].disabled) continue if(columns[i] === undefined || columns[i].disabled) continue
// Turn duplicate columns into an array // Turn duplicate columns into an array
if (columns_duplicates_to_array === true && obj[columns[i].name]) { if (columns_duplicates_to_array === true && obj[columns[i].name] !== undefined) {
if (Array.isArray(obj[columns[i].name])) { if (Array.isArray(obj[columns[i].name])) {
obj[columns[i].name] = obj[columns[i].name].concat(record[i]) obj[columns[i].name] = obj[columns[i].name].concat(record[i])
} else { } else {
@@ -20458,22 +20891,30 @@ module.exports = (versions, range, options) => {
/***/ ((module, __unused_webpack_exports, __webpack_require__) => { /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
const Range = __webpack_require__(9828) const Range = __webpack_require__(9828)
const { ANY } = __webpack_require__(1532) const Comparator = __webpack_require__(1532)
const { ANY } = Comparator
const satisfies = __webpack_require__(6055) const satisfies = __webpack_require__(6055)
const compare = __webpack_require__(4309) const compare = __webpack_require__(4309)
// Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff: // Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff:
// - Every simple range `r1, r2, ...` is a subset of some `R1, R2, ...` // - Every simple range `r1, r2, ...` is a null set, OR
// - Every simple range `r1, r2, ...` which is not a null set is a subset of
// some `R1, R2, ...`
// //
// Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff: // Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff:
// - If c is only the ANY comparator // - If c is only the ANY comparator
// - If C is only the ANY comparator, return true // - If C is only the ANY comparator, return true
// - Else return false // - Else if in prerelease mode, return false
// - else replace c with `[>=0.0.0]`
// - If C is only the ANY comparator
// - if in prerelease mode, return true
// - else replace C with `[>=0.0.0]`
// - Let EQ be the set of = comparators in c // - Let EQ be the set of = comparators in c
// - If EQ is more than one, return true (null set) // - If EQ is more than one, return true (null set)
// - Let GT be the highest > or >= comparator in c // - Let GT be the highest > or >= comparator in c
// - Let LT be the lowest < or <= comparator in c // - Let LT be the lowest < or <= comparator in c
// - If GT and LT, and GT.semver > LT.semver, return true (null set) // - If GT and LT, and GT.semver > LT.semver, return true (null set)
// - If any C is a = range, and GT or LT are set, return false
// - If EQ // - If EQ
// - If GT, and EQ does not satisfy GT, return true (null set) // - If GT, and EQ does not satisfy GT, return true (null set)
// - If LT, and EQ does not satisfy LT, return true (null set) // - If LT, and EQ does not satisfy LT, return true (null set)
@@ -20482,13 +20923,16 @@ const compare = __webpack_require__(4309)
// - If GT // - If GT
// - If GT.semver is lower than any > or >= comp in C, return false // - If GT.semver is lower than any > or >= comp in C, return false
// - If GT is >=, and GT.semver does not satisfy every C, return false // - If GT is >=, and GT.semver does not satisfy every C, return false
// - If GT.semver has a prerelease, and not in prerelease mode
// - If no C has a prerelease and the GT.semver tuple, return false
// - If LT // - If LT
// - If LT.semver is greater than any < or <= comp in C, return false // - If LT.semver is greater than any < or <= comp in C, return false
// - If LT is <=, and LT.semver does not satisfy every C, return false // - If LT is <=, and LT.semver does not satisfy every C, return false
// - If any C is a = range, and GT or LT are set, return false // - If GT.semver has a prerelease, and not in prerelease mode
// - If no C has a prerelease and the LT.semver tuple, return false
// - Else return true // - Else return true
const subset = (sub, dom, options) => { const subset = (sub, dom, options = {}) => {
if (sub === dom) if (sub === dom)
return true return true
@@ -20517,8 +20961,21 @@ const simpleSubset = (sub, dom, options) => {
if (sub === dom) if (sub === dom)
return true return true
if (sub.length === 1 && sub[0].semver === ANY) if (sub.length === 1 && sub[0].semver === ANY) {
return dom.length === 1 && dom[0].semver === ANY if (dom.length === 1 && dom[0].semver === ANY)
return true
else if (options.includePrerelease)
sub = [ new Comparator('>=0.0.0-0') ]
else
sub = [ new Comparator('>=0.0.0') ]
}
if (dom.length === 1 && dom[0].semver === ANY) {
if (options.includePrerelease)
return true
else
dom = [ new Comparator('>=0.0.0') ]
}
const eqSet = new Set() const eqSet = new Set()
let gt, lt let gt, lt
@@ -20561,10 +21018,32 @@ const simpleSubset = (sub, dom, options) => {
let higher, lower let higher, lower
let hasDomLT, hasDomGT let hasDomLT, hasDomGT
// if the subset has a prerelease, we need a comparator in the superset
// with the same tuple and a prerelease, or it's not a subset
let needDomLTPre = lt &&
!options.includePrerelease &&
lt.semver.prerelease.length ? lt.semver : false
let needDomGTPre = gt &&
!options.includePrerelease &&
gt.semver.prerelease.length ? gt.semver : false
// exception: <1.2.3-0 is the same as <1.2.3
if (needDomLTPre && needDomLTPre.prerelease.length === 1 &&
lt.operator === '<' && needDomLTPre.prerelease[0] === 0) {
needDomLTPre = false
}
for (const c of dom) { for (const c of dom) {
hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=' hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>='
hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=' hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<='
if (gt) { if (gt) {
if (needDomGTPre) {
if (c.semver.prerelease && c.semver.prerelease.length &&
c.semver.major === needDomGTPre.major &&
c.semver.minor === needDomGTPre.minor &&
c.semver.patch === needDomGTPre.patch) {
needDomGTPre = false
}
}
if (c.operator === '>' || c.operator === '>=') { if (c.operator === '>' || c.operator === '>=') {
higher = higherGT(gt, c, options) higher = higherGT(gt, c, options)
if (higher === c && higher !== gt) if (higher === c && higher !== gt)
@@ -20573,6 +21052,14 @@ const simpleSubset = (sub, dom, options) => {
return false return false
} }
if (lt) { if (lt) {
if (needDomLTPre) {
if (c.semver.prerelease && c.semver.prerelease.length &&
c.semver.major === needDomLTPre.major &&
c.semver.minor === needDomLTPre.minor &&
c.semver.patch === needDomLTPre.patch) {
needDomLTPre = false
}
}
if (c.operator === '<' || c.operator === '<=') { if (c.operator === '<' || c.operator === '<=') {
lower = lowerLT(lt, c, options) lower = lowerLT(lt, c, options)
if (lower === c && lower !== lt) if (lower === c && lower !== lt)
@@ -20593,6 +21080,12 @@ const simpleSubset = (sub, dom, options) => {
if (lt && hasDomGT && !gt && gtltComp !== 0) if (lt && hasDomGT && !gt && gtltComp !== 0)
return false return false
// we needed a prerelease range in a specific tuple, but didn't get one
// then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0,
// because it includes prereleases in the 1.2.3 tuple
if (needDomGTPre || needDomLTPre)
return false
return true return true
} }

View File

@@ -1,54 +1,67 @@
variable "NODE_VERSION" {
default = "12"
}
target "node-version" {
args = {
NODE_VERSION = NODE_VERSION
}
}
group "default" { group "default" {
targets = ["build"] targets = ["build"]
} }
group "pre-checkin" { group "pre-checkin" {
targets = ["update-yarn", "format", "build"] targets = ["vendor-update", "format", "build"]
} }
group "validate" { group "validate" {
targets = ["validate-format", "validate-build", "validate-yarn"] targets = ["format-validate", "build-validate", "vendor-validate"]
}
target "dockerfile" {
dockerfile = "dev.Dockerfile"
}
target "update-yarn" {
inherits = ["dockerfile"]
target = "update-yarn"
output = ["."]
} }
target "build" { target "build" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "dist" dockerfile = "./hack/build.Dockerfile"
target = "build-update"
output = ["."] output = ["."]
} }
target "test" { target "build-validate" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "test-coverage" dockerfile = "./hack/build.Dockerfile"
output = ["."] target = "build-validate"
} }
target "format" { target "format" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "format" dockerfile = "./hack/build.Dockerfile"
target = "format-update"
output = ["."] output = ["."]
} }
target "validate-format" { target "format-validate" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "validate-format" dockerfile = "./hack/build.Dockerfile"
target = "format-validate"
} }
target "validate-build" { target "vendor-update" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "validate-build" dockerfile = "./hack/vendor.Dockerfile"
target = "update"
output = ["."]
} }
target "validate-yarn" { target "vendor-validate" {
inherits = ["dockerfile"] inherits = ["node-version"]
target = "validate-yarn" dockerfile = "./hack/vendor.Dockerfile"
target = "validate"
}
target "test" {
inherits = ["node-version"]
dockerfile = "./hack/test.Dockerfile"
target = "test-coverage"
output = ["./coverage"]
} }

42
hack/build.Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run build && mkdir /out && cp -Rf dist /out/
FROM scratch AS build-update
COPY --from=build /out /
FROM build AS build-validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- dist)" ]; then \
echo >&2 'ERROR: Build result differs. Please build first with "docker buildx bake build"'; \
git status --porcelain -- dist; \
exit 1; \
fi
FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' | cpio -pdm /out
FROM scratch AS format-update
COPY --from=format /out /
FROM deps AS format-validate
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run format-check \

21
hack/test.Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git
WORKDIR /src
FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install
FROM deps AS test
ENV RUNNER_TEMP=/tmp/github_runner
ENV RUNNER_TOOL_CACHE=/tmp/github_tool_cache
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn run test --coverageDirectory=/tmp/coverage
FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /

23
hack/vendor.Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# syntax=docker/dockerfile:1.2
ARG NODE_VERSION
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache git
WORKDIR /src
FROM base AS vendored
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/node_modules \
yarn install && mkdir /out && cp yarn.lock /out
FROM scratch AS update
COPY --from=vendored /out /
FROM vendored AS validate
RUN --mount=type=bind,target=.,rw \
git add -A && cp -rf /out/* .; \
if [ -n "$(git status --porcelain -- yarn.lock)" ]; then \
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor-update"'; \
git status --porcelain -- yarn.lock; \
exit 1; \
fi

View File

@@ -23,12 +23,12 @@
"author": "CrazyMax", "author": "CrazyMax",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.7",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.15.3", "csv-parse": "^4.15.4",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"moment": "^2.29.1", "moment": "^2.29.1",
"semver": "^7.3.4" "semver": "^7.3.5"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.0", "@types/jest": "^26.0.0",

View File

@@ -1,5 +1,6 @@
import csvparse from 'csv-parse/lib/sync'; import csvparse from 'csv-parse/lib/sync';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {issueCommand} from '@actions/core/lib/command';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
@@ -8,19 +9,12 @@ let _tmpDir: string;
export interface Inputs { export interface Inputs {
images: string[]; images: string[];
tagSha: boolean; tags: string[];
tagEdge: boolean; flavor: string[];
tagEdgeBranch: string; labels: string[];
tagSemver: string[];
tagMatch: string;
tagMatchGroup: number;
tagLatest: boolean;
tagSchedule: string;
tagCustom: string[];
tagCustomOnly: boolean;
labelCustom: string[];
sepTags: string; sepTags: string;
sepLabels: string; sepLabels: string;
bakeTarget: string;
githubToken: string; githubToken: string;
} }
@@ -34,19 +28,12 @@ export function tmpDir(): string {
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
images: getInputList('images'), images: getInputList('images'),
tagSha: /true/i.test(core.getInput('tag-sha') || 'false'), tags: getInputList('tags', true),
tagEdge: /true/i.test(core.getInput('tag-edge') || 'false'), flavor: getInputList('flavor', true),
tagEdgeBranch: core.getInput('tag-edge-branch'), labels: getInputList('labels', true),
tagSemver: getInputList('tag-semver'),
tagMatch: core.getInput('tag-match'),
tagMatchGroup: Number(core.getInput('tag-match-group')) || 0,
tagLatest: /true/i.test(core.getInput('tag-latest') || core.getInput('tag-match-latest') || 'true'),
tagSchedule: core.getInput('tag-schedule') || 'nightly',
tagCustom: getInputList('tag-custom'),
tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'),
labelCustom: getInputList('label-custom', true),
sepTags: core.getInput('sep-tags') || `\n`, sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`,
bakeTarget: core.getInput('bake-target') || `ghaction-docker-meta`,
githubToken: core.getInput('github-token') githubToken: core.getInput('github-token')
}; };
} }
@@ -82,3 +69,8 @@ export const asyncForEach = async (array, callback) => {
await callback(array[index], index, array); await callback(array[index], index, array);
} }
}; };
// FIXME: Temp fix https://github.com/actions/toolkit/issues/777
export function setOutput(name: string, value: any): void {
issueCommand('set-output', {name}, value);
}

50
src/flavor.ts Normal file
View File

@@ -0,0 +1,50 @@
import * as core from '@actions/core';
export interface Flavor {
latest: string;
prefix: string;
suffix: string;
}
export function Transform(inputs: string[]): Flavor {
const flavor: Flavor = {
latest: 'auto',
prefix: '',
suffix: ''
};
for (const input of inputs) {
const parts = input.split('=', 2);
if (parts.length == 1) {
throw new Error(`Invalid entry: ${input}`);
}
switch (parts[0]) {
case 'latest': {
flavor.latest = parts[1];
if (!['auto', 'true', 'false'].includes(flavor.latest)) {
throw new Error(`Invalid latest flavor entry: ${input}`);
}
break;
}
case 'prefix': {
flavor.prefix = parts[1];
break;
}
case 'suffix': {
flavor.suffix = parts[1];
break;
}
default: {
throw new Error(`Unknown entry: ${input}`);
}
}
}
core.startGroup(`Processing flavor input`);
core.info(`latest=${flavor.latest}`);
core.info(`prefix=${flavor.prefix}`);
core.info(`suffix=${flavor.suffix}`);
core.endGroup();
return flavor;
}

View File

@@ -1,5 +1,5 @@
import * as fs from 'fs'; import * as fs from 'fs';
import {getInputs, Inputs} from './context'; import {getInputs, Inputs, setOutput} from './context';
import * as github from './github'; import * as github from './github';
import {Meta, Version} from './meta'; import {Meta, Version} from './meta';
import * as core from '@actions/core'; import * as core from '@actions/core';
@@ -29,35 +29,43 @@ async function run() {
const meta: Meta = new Meta(inputs, context, repo); const meta: Meta = new Meta(inputs, context, repo);
const version: Version = meta.version; const version: Version = meta.version;
core.startGroup(`Docker image version`); if (meta.version.main == undefined || meta.version.main.length == 0) {
core.info(version.main || ''); core.warning(`No Docker image version has been generated. Check tags input.`);
core.endGroup(); } else {
core.setOutput('version', version.main || ''); core.startGroup(`Docker image version`);
core.info(version.main || '');
core.endGroup();
}
setOutput('version', version.main || '');
// Docker tags // Docker tags
const tags: Array<string> = meta.tags(); const tags: Array<string> = meta.getTags();
core.startGroup(`Docker tags`); if (tags.length == 0) {
for (let tag of tags) { core.warning('No Docker tag has been generated. Check tags input.');
core.info(tag); } else {
core.startGroup(`Docker tags`);
for (let tag of tags) {
core.info(tag);
}
core.endGroup();
} }
core.endGroup(); setOutput('tags', tags.join(inputs.sepTags));
core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels // Docker labels
const labels: Array<string> = meta.labels(); const labels: Array<string> = meta.getLabels();
core.startGroup(`Docker labels`); core.startGroup(`Docker labels`);
for (let label of labels) { for (let label of labels) {
core.info(label); core.info(label);
} }
core.endGroup(); core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels)); setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file // Bake definition file
const bakeFile: string = meta.bakeFile(); const bakeFile: string = meta.getBakeFile();
core.startGroup(`Bake definition file`); core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8')); core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup(); core.endGroup();
core.setOutput('bake-file', bakeFile); setOutput('bake-file', bakeFile);
} catch (error) { } catch (error) {
core.setFailed(error.message); core.setFailed(error.message);
} }

View File

@@ -4,6 +4,8 @@ import * as path from 'path';
import moment from 'moment'; import moment from 'moment';
import * as semver from 'semver'; import * as semver from 'semver';
import {Inputs, tmpDir} from './context'; import {Inputs, tmpDir} from './context';
import * as tcl from './tag';
import * as fcl from './flavor';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {Context} from '@actions/github/lib/context'; import {Context} from '@actions/github/lib/context';
import {ReposGetResponseData} from '@octokit/types'; import {ReposGetResponseData} from '@octokit/types';
@@ -11,7 +13,7 @@ import {ReposGetResponseData} from '@octokit/types';
export interface Version { export interface Version {
main: string | undefined; main: string | undefined;
partial: string[]; partial: string[];
latest: boolean; latest: boolean | undefined;
} }
export class Meta { export class Meta {
@@ -20,96 +22,263 @@ export class Meta {
private readonly inputs: Inputs; private readonly inputs: Inputs;
private readonly context: Context; private readonly context: Context;
private readonly repo: ReposGetResponseData; private readonly repo: ReposGetResponseData;
private readonly tags: tcl.Tag[];
private readonly flavor: fcl.Flavor;
private readonly date: Date; private readonly date: Date;
constructor(inputs: Inputs, context: Context, repo: ReposGetResponseData) { constructor(inputs: Inputs, context: Context, repo: ReposGetResponseData) {
this.inputs = inputs; this.inputs = inputs;
if (!this.inputs.tagEdgeBranch) {
this.inputs.tagEdgeBranch = repo.default_branch;
}
this.context = context; this.context = context;
this.repo = repo; this.repo = repo;
this.tags = tcl.Transform(inputs.tags);
this.flavor = fcl.Transform(inputs.flavor);
this.date = new Date(); this.date = new Date();
this.version = this.getVersion(); this.version = this.getVersion();
} }
private getVersion(): Version { private getVersion(): Version {
const currentDate = this.date;
let version: Version = { let version: Version = {
main: undefined, main: undefined,
partial: [], partial: [],
latest: false latest: undefined
}; };
if (/schedule/.test(this.context.eventName)) { for (const tag of this.tags) {
version.main = handlebars.compile(this.inputs.tagSchedule)({ if (!/true/i.test(tag.attrs['enable'])) {
date: function (format) { continue;
return moment(currentDate).utc().format(format);
}
});
} else if (/^refs\/tags\//.test(this.context.ref)) {
version.main = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
if (this.inputs.tagSemver.length > 0 && !semver.valid(version.main)) {
core.warning(`${version.main} is not a valid semver. More info: https://semver.org/`);
} }
if (this.inputs.tagSemver.length > 0 && semver.valid(version.main)) { switch (tag.type) {
const sver = semver.parse(version.main, { case tcl.Type.Schedule: {
includePrerelease: true version = this.procSchedule(version, tag);
}); break;
if (semver.prerelease(version.main)) { }
version.main = handlebars.compile('{{version}}')(sver); case tcl.Type.Semver: {
} else { version = this.procSemver(version, tag);
version.latest = this.inputs.tagLatest; break;
version.main = handlebars.compile(this.inputs.tagSemver[0])(sver); }
for (const semverTpl of this.inputs.tagSemver) { case tcl.Type.Match: {
const partial = handlebars.compile(semverTpl)(sver); version = this.procMatch(version, tag);
if (partial == version.main) { break;
continue; }
} case tcl.Type.Ref: {
version.partial.push(partial); if (tag.attrs['event'] == tcl.RefEvent.Branch) {
version = this.procRefBranch(version, tag);
} else if (tag.attrs['event'] == tcl.RefEvent.Tag) {
version = this.procRefTag(version, tag);
} else if (tag.attrs['event'] == tcl.RefEvent.PR) {
version = this.procRefPr(version, tag);
} }
break;
} }
} else if (this.inputs.tagMatch) { case tcl.Type.Edge: {
let tagMatch; version = this.procEdge(version, tag);
const isRegEx = this.inputs.tagMatch.match(/^\/(.+)\/(.*)$/); break;
if (isRegEx) {
tagMatch = version.main.match(new RegExp(isRegEx[1], isRegEx[2]));
} else {
tagMatch = version.main.match(this.inputs.tagMatch);
} }
if (tagMatch) { case tcl.Type.Raw: {
version.main = tagMatch[this.inputs.tagMatchGroup]; version = this.procRaw(version, tag);
version.latest = this.inputs.tagLatest; break;
}
case tcl.Type.Sha: {
version = this.procSha(version, tag);
break;
} }
} else {
version.latest = this.inputs.tagLatest;
}
} else if (/^refs\/heads\//.test(this.context.ref)) {
version.main = this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
if (this.inputs.tagEdge && this.inputs.tagEdgeBranch === version.main) {
version.main = 'edge';
}
} else if (/^refs\/pull\//.test(this.context.ref)) {
version.main = `pr-${this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, '')}`;
}
if (this.inputs.tagCustom.length > 0) {
if (this.inputs.tagCustomOnly) {
version = {
main: this.inputs.tagCustom.shift(),
partial: this.inputs.tagCustom,
latest: false
};
} else {
version.partial.push(...this.inputs.tagCustom);
} }
} }
version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index); version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
if (version.latest == undefined) {
version.latest = false;
}
return version; return version;
} }
public tags(): Array<string> { private procSchedule(version: Version, tag: tcl.Tag): Version {
if (!/schedule/.test(this.context.eventName)) {
return version;
}
const currentDate = this.date;
const vraw = this.setValue(
handlebars.compile(tag.attrs['pattern'])({
date: function (format) {
return moment(currentDate).utc().format(format);
}
}),
tag
);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private procSemver(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
return version;
}
let vraw: string;
if (tag.attrs['value'].length > 0) {
vraw = this.setGlobalExp(tag.attrs['value']);
} else {
vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
}
if (!semver.valid(vraw)) {
core.warning(`${vraw} is not a valid semver. More info: https://semver.org/`);
return version;
}
let latest: boolean = false;
const sver = semver.parse(vraw, {
includePrerelease: true
});
if (semver.prerelease(vraw)) {
vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag);
} else {
vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag);
latest = true;
}
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true');
}
private procMatch(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) {
return version;
}
let vraw: string;
if (tag.attrs['value'].length > 0) {
vraw = this.setGlobalExp(tag.attrs['value']);
} else {
vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
}
let latest: boolean = false;
let tmatch;
const isRegEx = tag.attrs['pattern'].match(/^\/(.+)\/(.*)$/);
if (isRegEx) {
tmatch = vraw.match(new RegExp(isRegEx[1], isRegEx[2]));
} else {
tmatch = vraw.match(tag.attrs['pattern']);
}
if (!tmatch) {
core.warning(`${tag.attrs['pattern']} does not match ${vraw}.`);
return version;
}
if (typeof tmatch[tag.attrs['group']] === 'undefined') {
core.warning(`Group ${tag.attrs['group']} does not exist for ${tag.attrs['pattern']} pattern.`);
return version;
}
vraw = this.setValue(tmatch[tag.attrs['group']], tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true');
}
private procRefBranch(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/heads\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private procRefTag(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/tags\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true');
}
private procRefPr(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/pull\//.test(this.context.ref)) {
return version;
}
const vraw = this.setValue(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private procEdge(version: Version, tag: tcl.Tag): Version {
if (!/^refs\/heads\//.test(this.context.ref)) {
return version;
}
let val = this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
if (tag.attrs['branch'].length == 0) {
tag.attrs['branch'] = this.repo.default_branch;
}
if (tag.attrs['branch'] === val) {
val = 'edge';
}
const vraw = this.setValue(val, tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private procRaw(version: Version, tag: tcl.Tag): Version {
const vraw = this.setValue(this.setGlobalExp(tag.attrs['value']), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private procSha(version: Version, tag: tcl.Tag): Version {
if (!this.context.sha) {
return version;
}
const vraw = this.setValue(this.context.sha.substr(0, 7), tag);
return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true');
}
private static setVersion(version: Version, val: string, latest: boolean): Version {
if (val.length == 0) {
return version;
}
if (version.main == undefined) {
version.main = val;
} else if (val !== version.main) {
version.partial.push(val);
}
if (version.latest == undefined) {
version.latest = latest;
}
return version;
}
private setValue(val: string, tag: tcl.Tag): string {
if (tag.attrs.hasOwnProperty('prefix')) {
val = `${this.setGlobalExp(tag.attrs['prefix'])}${val}`;
} else if (this.flavor.prefix.length > 0) {
val = `${this.setGlobalExp(this.flavor.prefix)}${val}`;
}
if (tag.attrs.hasOwnProperty('suffix')) {
val = `${val}${this.setGlobalExp(tag.attrs['suffix'])}`;
} else if (this.flavor.suffix.length > 0) {
val = `${val}${this.setGlobalExp(this.flavor.suffix)}`;
}
return val;
}
private setGlobalExp(val): string {
const ctx = this.context;
return handlebars.compile(val)({
branch: function () {
if (!/^refs\/heads\//.test(ctx.ref)) {
return '';
}
return ctx.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-');
},
tag: function () {
if (!/^refs\/tags\//.test(ctx.ref)) {
return '';
}
return ctx.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-');
},
sha: function () {
return ctx.sha.substr(0, 7);
}
});
}
public getTags(): Array<string> {
if (!this.version.main) { if (!this.version.main) {
return []; return [];
} }
@@ -124,14 +293,11 @@ export class Meta {
if (this.version.latest) { if (this.version.latest) {
tags.push(`${imageLc}:latest`); tags.push(`${imageLc}:latest`);
} }
if (this.context.sha && this.inputs.tagSha) {
tags.push(`${imageLc}:sha-${this.context.sha.substr(0, 7)}`);
}
} }
return tags; return tags;
} }
public labels(): Array<string> { public getLabels(): Array<string> {
let labels: Array<string> = [ let labels: Array<string> = [
`org.opencontainers.image.title=${this.repo.name || ''}`, `org.opencontainers.image.title=${this.repo.name || ''}`,
`org.opencontainers.image.description=${this.repo.description || ''}`, `org.opencontainers.image.description=${this.repo.description || ''}`,
@@ -142,13 +308,13 @@ export class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`, `org.opencontainers.image.revision=${this.context.sha || ''}`,
`org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}` `org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}`
]; ];
labels.push(...this.inputs.labelCustom); labels.push(...this.inputs.labels);
return labels; return labels;
} }
public bakeFile(): string { public getBakeFile(): string {
let jsonLabels = {}; let jsonLabels = {};
for (let label of this.labels()) { for (let label of this.getLabels()) {
const matches = label.match(/([^=]*)=(.*)/); const matches = label.match(/([^=]*)=(.*)/);
if (!matches) { if (!matches) {
continue; continue;
@@ -162,8 +328,8 @@ export class Meta {
JSON.stringify( JSON.stringify(
{ {
target: { target: {
'ghaction-docker-meta': { [this.inputs.bakeTarget]: {
tags: this.tags(), tags: this.getTags(),
labels: jsonLabels, labels: jsonLabels,
args: { args: {
DOCKER_META_IMAGES: this.inputs.images.join(','), DOCKER_META_IMAGES: this.inputs.images.join(','),

193
src/tag.ts Normal file
View File

@@ -0,0 +1,193 @@
import csvparse from 'csv-parse/lib/sync';
import * as core from '@actions/core';
export enum Type {
Schedule = 'schedule',
Semver = 'semver',
Match = 'match',
Edge = 'edge',
Ref = 'ref',
Raw = 'raw',
Sha = 'sha'
}
export enum RefEvent {
Branch = 'branch',
Tag = 'tag',
PR = 'pr'
}
export class Tag {
public type?: Type;
public attrs: Record<string, string>;
constructor() {
this.attrs = {};
}
public toString(): string {
const out: string[] = [`type=${this.type}`];
for (let attr in this.attrs) {
out.push(`${attr}=${this.attrs[attr]}`);
}
return out.join(',');
}
}
export const DefaultPriorities: Record<Type, string> = {
[Type.Schedule]: '1000',
[Type.Semver]: '900',
[Type.Match]: '800',
[Type.Edge]: '700',
[Type.Ref]: '600',
[Type.Raw]: '200',
[Type.Sha]: '100'
};
export function Transform(inputs: string[]): Tag[] {
const tags: Tag[] = [];
if (inputs.length == 0) {
// prettier-ignore
inputs = [
`type=schedule`,
`type=ref,event=${RefEvent.Branch}`,
`type=ref,event=${RefEvent.Tag}`,
`type=ref,event=${RefEvent.PR}`
];
}
for (const input of inputs) {
tags.push(Parse(input));
}
const sorted = tags.sort((tag1, tag2) => {
if (Number(tag1.attrs['priority']) < Number(tag2.attrs['priority'])) {
return 1;
}
if (Number(tag1.attrs['priority']) > Number(tag2.attrs['priority'])) {
return -1;
}
return 0;
});
core.startGroup(`Processing tags input`);
for (const tag of sorted) {
core.info(tag.toString());
}
core.endGroup();
return sorted;
}
export function Parse(s: string): Tag {
const fields = csvparse(s, {
relaxColumnCount: true,
skipLinesWithEmptyValues: true
})[0];
const tag = new Tag();
for (const field of fields) {
const parts = field.toString().split('=', 2);
if (parts.length == 1) {
tag.attrs['value'] = parts[0].trim();
} else {
const key = parts[0].trim().toLowerCase();
const value = parts[1].trim();
switch (key) {
case 'type': {
if (!Object.values(Type).includes(value)) {
throw new Error(`Unknown type attribute: ${value}`);
}
tag.type = value;
break;
}
default: {
tag.attrs[key] = value;
break;
}
}
}
}
if (tag.type == undefined) {
tag.type = Type.Raw;
}
switch (tag.type) {
case Type.Schedule: {
if (!tag.attrs.hasOwnProperty('pattern')) {
tag.attrs['pattern'] = 'nightly';
}
break;
}
case Type.Semver: {
if (!tag.attrs.hasOwnProperty('pattern')) {
throw new Error(`Missing pattern attribute for ${s}`);
}
if (!tag.attrs.hasOwnProperty('value')) {
tag.attrs['value'] = '';
}
break;
}
case Type.Match: {
if (!tag.attrs.hasOwnProperty('pattern')) {
throw new Error(`Missing pattern attribute for ${s}`);
}
if (!tag.attrs.hasOwnProperty('group')) {
tag.attrs['group'] = '0';
}
if (isNaN(+tag.attrs['group'])) {
throw new Error(`Invalid match group for ${s}`);
}
if (!tag.attrs.hasOwnProperty('value')) {
tag.attrs['value'] = '';
}
break;
}
case Type.Edge: {
if (!tag.attrs.hasOwnProperty('branch')) {
tag.attrs['branch'] = '';
}
break;
}
case Type.Ref: {
if (!tag.attrs.hasOwnProperty('event')) {
throw new Error(`Missing event attribute for ${s}`);
}
if (
!Object.keys(RefEvent)
.map(k => RefEvent[k])
.includes(tag.attrs['event'])
) {
throw new Error(`Invalid event for ${s}`);
}
if (tag.attrs['event'] == RefEvent.PR && !tag.attrs.hasOwnProperty('prefix')) {
tag.attrs['prefix'] = 'pr-';
}
break;
}
case Type.Raw: {
if (!tag.attrs.hasOwnProperty('value')) {
throw new Error(`Missing value attribute for ${s}`);
}
break;
}
case Type.Sha: {
if (!tag.attrs.hasOwnProperty('prefix')) {
tag.attrs['prefix'] = 'sha-';
}
break;
}
}
if (!tag.attrs.hasOwnProperty('enable')) {
tag.attrs['enable'] = 'true';
}
if (!tag.attrs.hasOwnProperty('priority')) {
tag.attrs['priority'] = DefaultPriorities[tag.type];
}
if (!['true', 'false'].includes(tag.attrs['enable'])) {
throw new Error(`Invalid value for enable attribute: ${tag.attrs['enable']}`);
}
return tag;
}

View File

@@ -2,10 +2,10 @@
# yarn lockfile v1 # yarn lockfile v1
"@actions/core@^1.2.6": "@actions/core@^1.2.7":
version "1.2.6" version "1.2.7"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09" resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.7.tgz#594f8c45b213f0146e4be7eda8ae5cf4e198e5ab"
integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA== integrity sha512-kzLFD5BgEvq6ubcxdgPbRKGD2Qrgya/5j+wh4LZzqT915I0V3rED+MvjH6NXghbvk1MXknpNNQ3uKjXSEN00Ig==
"@actions/github@^4.0.0": "@actions/github@^4.0.0":
version "4.0.0" version "4.0.0"
@@ -1176,10 +1176,10 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@^4.15.3: csv-parse@^4.15.4:
version "4.15.3" version "4.15.4"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.15.3.tgz#8a62759617a920c328cb31c351b05053b8f92b10" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.15.4.tgz#ad1ec62aaf71a642982dfcb81f1848184d691db5"
integrity sha512-jlTqDvLdHnYMSr08ynNfk4IAUSJgJjTKy2U5CQBSu4cN9vQOJonLVZP4Qo4gKKrIgIQ5dr07UwOJdi+lRqT12w== integrity sha512-OdBbFc0yZhOm17lSxqkirrHlFFVpKRT0wp4DAGoJelsP3LbGzV9LNr7XmM/lrr0uGkCtaqac9UhP8PDHXOAbMg==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
@@ -1701,9 +1701,9 @@ has@^1.0.3:
function-bind "^1.1.1" function-bind "^1.1.1"
hosted-git-info@^2.1.4: hosted-git-info@^2.1.4:
version "2.8.8" version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
html-encoding-sniffer@^2.0.1: html-encoding-sniffer@^2.0.1:
version "2.0.1" version "2.0.1"
@@ -2546,9 +2546,9 @@ lodash.sortby@^4.7.0:
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
lodash@^4.17.19: lodash@^4.17.19:
version "4.17.20" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lru-cache@^6.0.0: lru-cache@^6.0.0:
version "6.0.0" version "6.0.0"
@@ -3175,10 +3175,10 @@ saxes@^5.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@7.x, semver@^7.3.2, semver@^7.3.4: semver@7.x, semver@^7.3.2, semver@^7.3.5:
version "7.3.4" version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies: dependencies:
lru-cache "^6.0.0" lru-cache "^6.0.0"
@@ -3818,9 +3818,9 @@ xmlchars@^2.2.0:
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
y18n@^4.0.0: y18n@^4.0.0:
version "4.0.0" version "4.0.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
yallist@^4.0.0: yallist@^4.0.0:
version "4.0.0" version "4.0.0"