diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 26e5b38..b5a8193 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,10 +5,14 @@ on:
- cron: '0 */4 * * *' # every 4 hours
push:
branches:
- - '**'
+ - 'master'
+ - 'releases/v*'
tags:
- 'v*.*.*'
pull_request:
+ branches:
+ - 'master'
+ - 'releases/v*'
env:
DOCKER_IMAGE: localhost:5000/name/app
@@ -27,7 +31,12 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
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:
runs-on: ubuntu-latest
@@ -50,8 +59,12 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
- tag-sha: true
- tag-schedule: ${{ matrix.tag-schedule }}
+ tags: |
+ type=schedule,pattern=${{ matrix.tag-schedule }}
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=sha
tag-match:
runs-on: ubuntu-latest
@@ -76,18 +89,48 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
- tag-sha: true
- tag-match: ${{ matrix.tag-match }}
- tag-match-group: ${{ matrix.tag-match-group }}
+ tags: |
+ type=schedule
+ 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:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- tag-latest:
- - 'true'
- - 'false'
+ flavor-latest:
+ - "auto"
+ - "true"
+ - "false"
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v2
+ -
+ name: Docker meta
+ uses: ./
+ with:
+ images: |
+ ${{ env.DOCKER_IMAGE }}
+ ghcr.io/name/app
+ tags: |
+ type=schedule
+ type=ref,event=branch
+ type=ref,event=tag
+ 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 }}
+
+ flavor:
+ runs-on: ubuntu-latest
steps:
-
name: Checkout
@@ -99,13 +142,11 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
- tag-semver: |
- {{raw}}
- {{version}}
- {{major}}.{{minor}}.{{patch}}
- tag-latest: ${{ matrix.tag-latest }}
+ flavor: |
+ prefix=foo-
+ suffix=-bar
- label-custom:
+ labels:
runs-on: ubuntu-latest
steps:
-
@@ -118,7 +159,7 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
- label-custom: |
+ labels: |
maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description
@@ -141,11 +182,15 @@ jobs:
uses: ./
with:
images: ${{ env.DOCKER_IMAGE }}
- tag-sha: true
- tag-semver: |
- v{{version}}
- v{{major}}.{{minor}}
- v{{major}}
+ tags: |
+ type=schedule
+ type=ref,event=branch
+ type=ref,event=tag
+ 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
uses: docker/setup-qemu-action@v1
@@ -192,11 +237,15 @@ jobs:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
- tag-sha: true
- tag-semver: |
- {{version}}
- {{major}}.{{minor}}
- {{major}}
+ tags: |
+ type=schedule
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=sha
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 6c8bd39..3bac495 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,6 +10,7 @@ on:
pull_request:
branches:
- 'master'
+ - 'releases/v*'
paths-ignore:
- '**.md'
@@ -20,12 +21,18 @@ jobs:
-
name: Checkout
uses: actions/checkout@v2
+ -
+ name: Validate
+ uses: docker/bake-action@v1
+ with:
+ targets: validate
-
name: Test
- run: docker buildx bake test
+ uses: docker/bake-action@v1
+ with:
+ targets: test
-
name: Upload coverage
uses: codecov/codecov-action@v1
with:
- token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage/clover.xml
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
deleted file mode 100644
index 0134e68..0000000
--- a/.github/workflows/validate.yml
+++ /dev/null
@@ -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
diff --git a/README.md b/README.md
index 2e52e0f..6b86be8 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,10 @@
[](https://github.com/sponsors/crazy-max)
[](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
GitHub Action to extract metadata (tags, labels) for Docker. This action is particularly useful if used with
@@ -23,11 +27,17 @@ ___
* [Customizing](#customizing)
* [inputs](#inputs)
* [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)
* [Latest tag](#latest-tag)
- * [Handle semver tag](#handle-semver-tag)
- * [`tag-match` examples](#tag-match-examples)
- * [Schedule tag](#schedule-tag)
* [Overwrite labels](#overwrite-labels)
* [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
* [Contributing](#contributing)
@@ -37,24 +47,18 @@ ___
### 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
name: ci
on:
push:
branches:
- - '**'
+ - 'master'
tags:
- 'v*'
pull_request:
+ branches:
+ - 'master'
jobs:
docker:
@@ -65,16 +69,10 @@ jobs:
uses: actions/checkout@v2
-
name: Docker meta
- id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v1
+ id: meta
+ uses: crazy-max/ghaction-docker-meta@v2
with:
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
if: github.event_name != 'pull_request'
@@ -87,22 +85,20 @@ jobs:
uses: docker/build-push-action@v2
with:
context: .
- file: ./Dockerfile
- platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.docker_meta.outputs.tags }}
- labels: ${{ steps.docker_meta.outputs.labels }}
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
```
-### Semver
+| 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` |
-| 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` |
+### Semver
```yaml
name: ci
@@ -110,10 +106,12 @@ name: ci
on:
push:
branches:
- - '**'
+ - 'master'
tags:
- 'v*'
pull_request:
+ branches:
+ - 'master'
jobs:
docker:
@@ -124,19 +122,16 @@ jobs:
uses: actions/checkout@v2
-
name: Docker meta
- id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v1
+ id: meta
+ uses: crazy-max/ghaction-docker-meta@v2
with:
images: name/app
- tag-semver: |
- {{version}}
- {{major}}.{{minor}}
- -
- name: Set up QEMU
- uses: docker/setup-qemu-action@v1
- -
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
@@ -149,13 +144,19 @@ jobs:
uses: docker/build-push-action@v2
with:
context: .
- file: ./Dockerfile
- platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
- tags: ${{ steps.docker_meta.outputs.tags }}
- labels: ${{ steps.docker_meta.outputs.labels }}
+ tags: ${{ steps.meta.outputs.tags }}
+ 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
This action also handles a bake definition file that can be used with the
@@ -164,7 +165,6 @@ This action also handles a bake definition file that can be used with the
```hcl
// docker-bake.hcl
-
target "ghaction-docker-meta" {}
target "build" {
@@ -181,10 +181,9 @@ name: ci
on:
push:
branches:
- - '**'
+ - 'master'
tags:
- 'v*'
- pull_request:
jobs:
docker:
@@ -195,40 +194,37 @@ jobs:
uses: actions/checkout@v2
-
name: Docker meta
- id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v1
+ id: meta
+ uses: crazy-max/ghaction-docker-meta@v2
with:
images: name/app
- tag-sha: true
- tag-semver: |
- {{version}}
- {{major}}.{{minor}}
- -
- name: Set up QEMU
- uses: docker/setup-qemu-action@v1
- -
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v1
+ tags: |
+ type=ref,event=branch
+ type=ref,event=tag
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=sha
-
name: Build
uses: docker/bake-action@v1
with:
files: |
./docker-bake.hcl
- ${{ steps.docker_meta.outputs.bake-file }}
- targets: |
- build
+ ${{ steps.meta.outputs.bake-file }}
+ targets: 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
{
"target": {
"ghaction-docker-meta": {
"tags": [
- "name/app:1.1.1",
- "name/app:1.1",
+ "name/app:1.2.3",
+ "name/app:1.2",
+ "name/app:sha-90dd603",
"name/app:latest"
],
"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.url": "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.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
},
"args": {
"DOCKER_META_IMAGES": "name/app",
- "DOCKER_META_VERSION": "1.1.1"
+ "DOCKER_META_VERSION": "1.2.3"
}
}
}
@@ -272,22 +268,12 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description |
|---------------------|----------|------------------------------------|
| `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`) |
-| `tag-edge` | Bool | Enable edge branch tagging (default `false`) |
-| `tag-edge-branch` | String | Branch that will be tagged as edge (default `repo.default_branch`) |
-| `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 |
+| `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
+| `flavor` | List | [Flavor](#flavor-input) to apply |
+| `labels` | List | List of custom labels |
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
-> `tag-semver` and `tag-match` are mutually exclusive
-
### outputs
Following outputs are available
@@ -299,59 +285,277 @@ Following outputs are available
| `labels` | String | Docker labels |
| `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path |
-## Notes
+## `flavor` input
-### Latest tag
+`flavor` defines a global behavior for [`tags`](#tags-input):
-Latest Docker tag will be generated by default on `push tag` event. If for example you push the `v1.2.3` Git tag,
-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
-generated only if `tag-semver` is a valid [semver](https://semver.org/) or if Git tag matches a regular expression
-with the [`tag-match` input](#tag-match-examples). Can be disabled if `tag-latest` is `false`.
-
-### Handle semver tag
-
-If Git tag is a valid [semver](https://semver.org/) you can handle it to output multi Docker tags at once.
-`tag-semver` supports multi-line [Handlebars template](https://handlebarsjs.com/guide/) with the following inputs:
-
-| Git tag | `tag-semver` | Valid | Output tags | Output version |
-|--------------------|----------------------------------------------------------|--------------------|----------------------------|------------------------------|
-| `v1.2.3` | `{{raw}}` | :white_check_mark: | `v1.2.3`, `latest` | `v1.2.3` |
-| `v1.2.3` | `{{version}}` | :white_check_mark: | `1.2.3`, `latest` | `1.2.3` |
-| `v1.2.3` | `{{major}}.{{minor}}` | :white_check_mark: | `1.2`, `latest` | `1.2` |
-| `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}}`
`{{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`
-
-> **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.
+```yaml
+flavor: |
+ latest=auto
+ prefix=
+ suffix=
+```
-### `tag-match` examples
+* `latest=`: Handle [latest tag](#latest-tag) (default `auto`)
+* `prefix=`: A global prefix for each generated tag
+* `suffix=`: A global suffix for each generated tag
-| Git tag | `tag-match` | `tag-match-group` | Match | Output tags | Output version |
-|-------------------------|------------------------------------|-------------------|----------------------|---------------------------|------------------------------|
-| `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` |
+## `tags` input
-### Schedule tag
+`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:
-`tag-schedule` is specially crafted input to support [Handlebars template](https://handlebarsjs.com/guide/) with
+```yaml
+tags: |
+ type=schedule
+ type=ref,event=branch
+ type=ref,event=tag
+ 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=` enable this entry (default `true`)
+* `priority=` priority to manage the order of tags
+* `prefix=` add prefix
+* `suffix=` 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:
-| Expression | Example | Description |
-|-------------------------|-------------------------------------------|------------------------------------------|
-| `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/)
+```yaml
+tags: |
+ type=semver,enable=true,priority=900,prefix=,suffix=,pattern=,value=
+```
+
+### `type=match`
+
+```yaml
+tags: |
+ # minimal
+ type=match,pattern=\d{8}
+ # double quotes if comma in pattern
+ type=match,"pattern=\d{1,3}.\d{1,3}.\d{1,3}"
+ # 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{1,3}.\d{1,3}.\d{1,3}` | `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` |
+
+Extended attributes and default values:
+
+```yaml
+tags: |
+ type=group,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: |
+ # minimal branch event
+ type=ref,event=branch
+ # minimal tag event
+ type=ref,event=tag
+ # minimal 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: |
+ # event branch
+ type=ref,enable=true,priority=600,prefix=,suffix=,event=
+ # event tag
+ type=ref,enable=true,priority=600,prefix=,suffix=,event=
+ # event pr
+ 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
+
+### Latest tag
-You can find more examples in the [CI workflow](.github/workflows/ci.yml).
+`latest` tag is handled through the [`flavor` input](#flavor-input). It will be generated by default (`auto` mode) for:
+* [`type=ref,event=tag`](#typeref)
+* [`type=semver,pattern=...`](#typesemver)
+* [`type=match,pattern=...`](#typematch)
### Overwrite labels
@@ -362,10 +566,10 @@ labels generated are not suitable, you can overwrite them like this:
-
name: Docker meta
id: docker_meta
- uses: crazy-max/ghaction-docker-meta@v1
+ uses: crazy-max/ghaction-docker-meta@v2
with:
images: name/app
- label-custom: |
+ labels: |
maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description
diff --git a/UPGRADE.md b/UPGRADE.md
new file mode 100644
index 0000000..f6d5e52
--- /dev/null
+++ b/UPGRADE.md
@@ -0,0 +1,295 @@
+# 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.docker_meta.outputs.tags }}
+ labels: ${{ steps.docker_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=tag
+ 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 }}
+```
diff --git a/__tests__/flavor.test.ts b/__tests__/flavor.test.ts
new file mode 100644
index 0000000..fcd6642
--- /dev/null
+++ b/__tests__/flavor.test.ts
@@ -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);
+ }
+ });
+});
diff --git a/__tests__/meta.test.ts b/__tests__/meta.test.ts
index 3dab864..071581e 100644
--- a/__tests__/meta.test.ts
+++ b/__tests__/meta.test.ts
@@ -36,7 +36,7 @@ beforeEach(() => {
});
});
-const tagsLabelsTest = async (envFile: string, inputs: Inputs, exVersion: Version, exTags: Array, exLabels: Array) => {
+const tagsLabelsTest = async (name: string, envFile: string, inputs: Inputs, exVersion: Version, exTags: Array, exLabels: Array) => {
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
const context = github.context();
console.log(process.env, context);
@@ -48,11 +48,11 @@ const tagsLabelsTest = async (envFile: string, inputs: Inputs, exVersion: Versio
console.log('version', version);
expect(version).toEqual(exVersion);
- const tags = meta.tags();
+ const tags = meta.getTags();
console.log('tags', tags);
expect(tags).toEqual(exTags);
- const labels = meta.labels();
+ const labels = meta.getLabels();
console.log('labels', labels);
expect(labels).toEqual(exLabels);
};
@@ -61,6 +61,7 @@ describe('null', () => {
// prettier-ignore
test.each([
[
+ 'null01',
'event_null.env',
{
images: ['user/app'],
@@ -83,9 +84,13 @@ describe('null', () => {
]
],
[
+ 'null02',
'event_empty.env',
{
images: ['user/app'],
+ tags: [
+ `type=sha`
+ ]
} as Inputs,
{
main: undefined,
@@ -104,13 +109,14 @@ describe('null', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ ])('given %p with %p event', tagsLabelsTest);
});
describe('push', () => {
// prettier-ignore
test.each([
[
+ 'push01',
'event_push.env',
{
images: ['user/app'],
@@ -135,10 +141,13 @@ describe('push', () => {
]
],
[
+ 'push02',
'event_push_defbranch.env',
{
images: ['user/app'],
- tagEdge: true,
+ tags: [
+ `type=edge`
+ ],
} as Inputs,
{
main: 'edge',
@@ -160,6 +169,7 @@ describe('push', () => {
]
],
[
+ 'push03',
'event_push_defbranch.env',
{
images: ['user/app'],
@@ -184,10 +194,13 @@ describe('push', () => {
]
],
[
+ 'push04',
'event_workflow_dispatch.env',
{
images: ['user/app'],
- tagEdge: true,
+ tags: [
+ `type=edge`
+ ],
} as Inputs,
{
main: 'edge',
@@ -209,6 +222,7 @@ describe('push', () => {
]
],
[
+ 'push05',
'event_push.env',
{
images: ['org/app', 'ghcr.io/user/app'],
@@ -234,10 +248,13 @@ describe('push', () => {
]
],
[
+ 'push06',
'event_push_defbranch.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagEdge: true,
+ tags: [
+ `type=edge`
+ ],
} as Inputs,
{
main: 'edge',
@@ -260,14 +277,18 @@ describe('push', () => {
]
],
[
+ 'push07',
'event_push.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
+ tags: [
+ `type=ref,event=branch`,
+ `type=sha`
+ ],
} as Inputs,
{
main: 'dev',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -288,15 +309,18 @@ describe('push', () => {
]
],
[
+ 'push08',
'event_push_defbranch.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
- tagEdge: true,
+ tags: [
+ `type=edge`,
+ `type=sha`
+ ],
} as Inputs,
{
main: 'edge',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -317,16 +341,18 @@ describe('push', () => {
]
],
[
+ 'push09',
'event_push.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
- tagEdge: true,
- tagEdgeBranch: 'dev'
+ tags: [
+ `type=edge,branch=dev`,
+ `type=sha`
+ ],
} as Inputs,
{
main: 'edge',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -347,16 +373,18 @@ describe('push', () => {
]
],
[
+ 'push10',
'event_push_defbranch.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
- tagEdge: true,
- tagEdgeBranch: 'dev'
+ tags: [
+ `type=edge,branch=dev`,
+ `type=sha`
+ ],
} as Inputs,
{
main: 'master',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -377,15 +405,18 @@ describe('push', () => {
]
],
[
+ 'push11',
'event_push_invalidchars.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
- tagEdge: true,
+ tags: [
+ `type=edge`,
+ `type=sha`
+ ],
} as Inputs,
{
main: 'my-feature-1245',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -405,13 +436,136 @@ describe('push', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ [
+ 'push12',
+ 'event_push_invalidchars.env',
+ {
+ images: ['org/app', 'ghcr.io/user/app'],
+ tags: [
+ `type=semver,pattern={{version}}`,
+ `type=edge`
+ ],
+ } as Inputs,
+ {
+ main: 'my-feature-1245',
+ partial: [],
+ latest: false
+ } as Version,
+ [
+ 'org/app:my-feature-1245',
+ 'ghcr.io/user/app:my-feature-1245'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=my-feature-1245",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'push13',
+ 'event_push_defbranch.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=ref,priority=2000,event=branch`,
+ `type=edge`
+ ],
+ } as Inputs,
+ {
+ main: 'master',
+ partial: ['edge'],
+ latest: false
+ } as Version,
+ [
+ 'user/app:master',
+ 'user/app:edge'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=master",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'push14',
+ 'event_push_defbranch.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=semver,pattern={{version}},value=v1.2.3`,
+ `type=edge`
+ ],
+ } as Inputs,
+ {
+ main: '1.2.3',
+ partial: ['edge'],
+ latest: true
+ } as Version,
+ [
+ 'user/app:1.2.3',
+ 'user/app:edge',
+ 'user/app:latest'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=1.2.3",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'push15',
+ 'event_push_defbranch.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=match,pattern=v(.*),group=1,value=v1.2.3`,
+ `type=edge`
+ ],
+ } as Inputs,
+ {
+ main: '1.2.3',
+ partial: ['edge'],
+ latest: true
+ } as Version,
+ [
+ 'user/app:1.2.3',
+ 'user/app:edge',
+ 'user/app:latest'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=1.2.3",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ]
+ ])('given %p with %p event', tagsLabelsTest);
});
-describe('push tag', () => {
+describe('tag', () => {
// prettier-ignore
test.each([
[
+ 'tag01',
'event_tag_release1.env',
{
images: ['user/app'],
@@ -437,6 +591,7 @@ describe('push tag', () => {
]
],
[
+ 'tag02',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
@@ -462,11 +617,16 @@ describe('push tag', () => {
]
],
[
+ 'tag03',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `\\d{8}`,
- tagLatest: false,
+ tags: [
+ `type=match,pattern=\\d{8}`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: '20200110',
@@ -488,12 +648,16 @@ describe('push tag', () => {
]
],
[
+ 'tag04',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `(.*)-RC`,
- tagMatchGroup: 1,
- tagLatest: false,
+ tags: [
+ `type=match,pattern=(.*)-RC,group=1`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: '20200110',
@@ -515,10 +679,13 @@ describe('push tag', () => {
]
],
[
+ 'tag05',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}.\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}.\\d{1,3}"`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -543,11 +710,13 @@ describe('push tag', () => {
]
],
[
+ 'tag06',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$`,
- tagMatchGroup: 1,
+ tags: [
+ `type=match,"pattern=^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$",group=1`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -572,10 +741,13 @@ describe('push tag', () => {
]
],
[
+ 'tag07',
'event_tag_v2.0.8-beta.67.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}.\\d{1,3}-(alpha|beta).\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}.\\d{1,3}-(alpha|beta).\\d{1,3}"`
+ ]
} as Inputs,
{
main: '2.0.8-beta.67',
@@ -600,10 +772,13 @@ describe('push tag', () => {
]
],
[
+ 'tag08',
'event_tag_v2.0.8-beta.67.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}"`
+ ]
} as Inputs,
{
main: '2.0',
@@ -628,11 +803,13 @@ describe('push tag', () => {
]
],
[
+ 'tag09',
'event_tag_v2.0.8-beta.67.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$`,
- tagMatchGroup: 1,
+ tags: [
+ `type=match,"pattern=/^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$/ig",group=1`,
+ ]
} as Inputs,
{
main: 'v2.0.8-beta.67',
@@ -655,10 +832,13 @@ describe('push tag', () => {
]
],
[
+ 'tag10',
'event_tag_sometag.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}"`
+ ]
} as Inputs,
{
main: 'sometag',
@@ -681,10 +861,15 @@ describe('push tag', () => {
]
],
[
+ 'tag11',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
+ tags: [
+ `type=semver,pattern={{version}}`,
+ `type=semver,pattern={{major}}.{{minor}}`,
+ `type=semver,pattern={{major}}`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -713,10 +898,14 @@ describe('push tag', () => {
]
],
[
+ 'tag12',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}.{{patch}}'],
+ tags: [
+ `type=semver,pattern={{version}}`,
+ `type=semver,pattern={{major}}.{{minor}}.{{patch}}`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -741,10 +930,14 @@ describe('push tag', () => {
]
],
[
+ 'tag13',
'event_tag_v2.0.8-beta.67.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{major}}.{{minor}}', '{{major}}'],
+ tags: [
+ `type=semver,pattern={{major}}.{{minor}}`,
+ `type=semver,pattern={{major}}`
+ ]
} as Inputs,
{
main: '2.0.8-beta.67',
@@ -767,11 +960,19 @@ describe('push tag', () => {
]
],
[
+ 'tag14',
'event_tag_sometag.env',
{
images: ['ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
- tagLatest: false,
+ tags: [
+ `type=ref,event=tag`,
+ `type=semver,pattern={{version}}`,
+ `type=semver,pattern={{major}}.{{minor}}`,
+ `type=semver,pattern={{major}}`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: 'sometag',
@@ -792,17 +993,88 @@ describe('push tag', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ [
+ 'tag15',
+ 'event_tag_v2.0.8-beta.67.env',
+ {
+ images: ['org/app', 'ghcr.io/user/app'],
+ tags: [
+ `type=raw,priority=2000,foo`,
+ `type=semver,pattern={{version}}`,
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}"`
+ ]
+ } as Inputs,
+ {
+ main: 'foo',
+ partial: ['2.0.8-beta.67', '2.0'],
+ latest: false
+ } as Version,
+ [
+ 'org/app:foo',
+ 'org/app:2.0.8-beta.67',
+ 'org/app:2.0',
+ 'ghcr.io/user/app:foo',
+ 'ghcr.io/user/app:2.0.8-beta.67',
+ 'ghcr.io/user/app:2.0'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=foo",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'tag16',
+ 'event_tag_v1.1.1.env',
+ {
+ images: ['org/app', 'ghcr.io/user/app'],
+ tags: [
+ `type=raw,priority=2000,foo`,
+ `type=ref,event=tag`,
+ `type=edge`
+ ]
+ } as Inputs,
+ {
+ main: 'foo',
+ partial: ['v1.1.1'],
+ latest: false
+ } as Version,
+ [
+ 'org/app:foo',
+ 'org/app:v1.1.1',
+ 'ghcr.io/user/app:foo',
+ 'ghcr.io/user/app:v1.1.1',
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=foo",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ]
+ ])('given %p with %p event', tagsLabelsTest);
});
describe('latest', () => {
// prettier-ignore
test.each([
[
+ 'latest01',
'event_tag_release1.env',
{
images: ['user/app'],
- tagMatch: `^release\\d{1,2}`,
+ tags: [
+ `type=match,"pattern=^release\\d{1,2}"`
+ ],
} as Inputs,
{
main: 'release1',
@@ -825,10 +1097,13 @@ describe('latest', () => {
]
],
[
+ 'latest02',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `^\\d+-RC\\d{1,2}`,
+ tags: [
+ `type=match,"pattern=^\\d+-RC\\d{1,2}"`
+ ]
} as Inputs,
{
main: '20200110-RC2',
@@ -851,10 +1126,13 @@ describe('latest', () => {
]
],
[
+ 'latest03',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `\\d{8}`,
+ tags: [
+ `type=match,pattern=\\d{8}`
+ ]
} as Inputs,
{
main: '20200110',
@@ -877,10 +1155,13 @@ describe('latest', () => {
]
],
[
+ 'latest04',
'event_tag_v1.1.1.env',
{
images: ['user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}.\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}.\\d{1,3}"`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -903,6 +1184,7 @@ describe('latest', () => {
]
],
[
+ 'latest05',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
@@ -930,10 +1212,13 @@ describe('latest', () => {
]
],
[
+ 'latest06',
'event_tag_v2.0.8-beta.67.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagMatch: `\\d{1,3}.\\d{1,3}.\\d{1,3}`,
+ tags: [
+ `type=match,"pattern=\\d{1,3}.\\d{1,3}.\\d{1,3}"`
+ ]
} as Inputs,
{
main: '2.0.8',
@@ -958,10 +1243,16 @@ describe('latest', () => {
]
],
[
+ 'latest07',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagLatest: false,
+ tags: [
+ `type=ref,event=tag`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: 'v1.1.1',
@@ -984,10 +1275,16 @@ describe('latest', () => {
]
],
[
+ 'latest08',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/MyUSER/MyApp'],
- tagLatest: false,
+ tags: [
+ `type=ref,event=tag`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: 'v1.1.1',
@@ -1010,16 +1307,22 @@ describe('latest', () => {
]
],
[
+ 'latest09',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/MyUSER/MyApp'],
- tagLatest: false,
- labelCustom: [
+ tags: [
+ `type=ref,event=tag`
+ ],
+ flavor: [
+ `latest=false`
+ ],
+ labels: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustomTitle",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany",
- ],
+ ]
} as Inputs,
{
main: 'v1.1.1',
@@ -1045,13 +1348,14 @@ describe('latest', () => {
"org.opencontainers.image.vendor=MyCompany"
]
],
- ])('given %p event ', tagsLabelsTest);
+ ])('given %p with %p event', tagsLabelsTest);
});
-describe('pull_request', () => {
+describe('pr', () => {
// prettier-ignore
test.each([
[
+ 'pr01',
'event_pull_request.env',
{
images: ['user/app'],
@@ -1076,6 +1380,7 @@ describe('pull_request', () => {
]
],
[
+ 'pr02',
'event_pull_request.env',
{
images: ['org/app', 'ghcr.io/user/app'],
@@ -1101,14 +1406,18 @@ describe('pull_request', () => {
]
],
[
+ 'pr03',
'event_pull_request.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
+ tags: [
+ `type=ref,event=pr`,
+ `type=sha`
+ ]
} as Inputs,
{
main: 'pr-2',
- partial: [],
+ partial: ['sha-1e9249f'],
latest: false
} as Version,
[
@@ -1128,24 +1437,58 @@ describe('pull_request', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ [
+ 'pr04',
+ 'event_pull_request.env',
+ {
+ images: ['org/app', 'ghcr.io/user/app'],
+ tags: [
+ `type=sha,priority=2000`,
+ `type=ref,event=pr`
+ ]
+ } as Inputs,
+ {
+ main: 'sha-1e9249f',
+ partial: ['pr-2'],
+ latest: false
+ } as Version,
+ [
+ 'org/app:sha-1e9249f',
+ 'org/app:pr-2',
+ 'ghcr.io/user/app:sha-1e9249f',
+ 'ghcr.io/user/app:pr-2'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=sha-1e9249f",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=1e9249f05bfc090e0688b8fb9c1b347586add504",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ ])('given %p with %p event', tagsLabelsTest);
});
describe('schedule', () => {
// prettier-ignore
test.each([
[
+ 'schedule01',
'event_schedule.env',
{
images: ['user/app'],
} as Inputs,
{
main: 'nightly',
- partial: [],
+ partial: ['master'],
latest: false
} as Version,
[
- 'user/app:nightly'
+ 'user/app:nightly',
+ 'user/app:master'
],
[
"org.opencontainers.image.title=Hello-World",
@@ -1159,10 +1502,13 @@ describe('schedule', () => {
]
],
[
+ 'schedule02',
'event_schedule.env',
{
images: ['user/app'],
- tagSchedule: `{{date 'YYYYMMDD'}}`
+ tags: [
+ `type=schedule,pattern={{date 'YYYYMMDD'}}`
+ ]
} as Inputs,
{
main: '20200110',
@@ -1184,10 +1530,13 @@ describe('schedule', () => {
]
],
[
+ 'schedule03',
'event_schedule.env',
{
images: ['user/app'],
- tagSchedule: `{{date 'YYYYMMDD-HHmmss'}}`
+ tags: [
+ `type=schedule,pattern={{date 'YYYYMMDD-HHmmss'}}`
+ ]
} as Inputs,
{
main: '20200110-003000',
@@ -1209,18 +1558,21 @@ describe('schedule', () => {
]
],
[
+ 'schedule04',
'event_schedule.env',
{
images: ['org/app', 'ghcr.io/user/app'],
} as Inputs,
{
main: 'nightly',
- partial: [],
+ partial: ['master'],
latest: false
} as Version,
[
'org/app:nightly',
- 'ghcr.io/user/app:nightly'
+ 'org/app:master',
+ 'ghcr.io/user/app:nightly',
+ 'ghcr.io/user/app:master'
],
[
"org.opencontainers.image.title=Hello-World",
@@ -1234,14 +1586,18 @@ describe('schedule', () => {
]
],
[
+ 'schedule05',
'event_schedule.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSha: true,
+ tags: [
+ `type=schedule`,
+ `type=sha`
+ ]
} as Inputs,
{
main: 'nightly',
- partial: [],
+ partial: ['sha-90dd603'],
latest: false
} as Version,
[
@@ -1261,13 +1617,46 @@ describe('schedule', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ [
+ 'schedule06',
+ 'event_schedule.env',
+ {
+ images: ['org/app', 'ghcr.io/user/app'],
+ tags: [
+ `type=schedule`,
+ `type=sha,priority=2000`
+ ]
+ } as Inputs,
+ {
+ main: 'sha-90dd603',
+ partial: ['nightly'],
+ latest: false
+ } as Version,
+ [
+ 'org/app:sha-90dd603',
+ 'org/app:nightly',
+ 'ghcr.io/user/app:sha-90dd603',
+ 'ghcr.io/user/app:nightly'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=sha-90dd603",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ ])('given %p with %p event', tagsLabelsTest);
});
describe('release', () => {
// prettier-ignore
test.each([
[
+ 'release01',
'event_release.env',
{
images: ['user/app'],
@@ -1292,17 +1681,23 @@ describe('release', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ ])('given %s with %p event', tagsLabelsTest);
});
-describe('custom', () => {
+describe('raw', () => {
// prettier-ignore
test.each([
[
+ 'raw01',
'event_push.env',
{
images: ['user/app'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=ref,event=branch`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
main: 'dev',
@@ -1327,10 +1722,14 @@ describe('custom', () => {
]
],
[
+ 'raw02',
'event_push.env',
{
images: ['user/app'],
- tagCustom: ['my']
+ tags: [
+ `type=ref,event=branch`,
+ `type=raw,my`
+ ]
} as Inputs,
{
main: 'dev',
@@ -1353,10 +1752,16 @@ describe('custom', () => {
]
],
[
+ 'raw03',
'event_tag_release1.env',
{
images: ['user/app'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=ref,event=tag`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
main: 'release1',
@@ -1382,12 +1787,19 @@ describe('custom', () => {
]
],
[
+ 'raw04',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `\\d{8}`,
- tagLatest: false,
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=match,pattern=\\d{8}`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
main: '20200110',
@@ -1412,11 +1824,18 @@ describe('custom', () => {
]
],
[
+ 'raw05',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=semver,pattern={{version}}`,
+ `type=semver,pattern={{major}}.{{minor}}`,
+ `type=semver,pattern={{major}}`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
main: '1.1.1',
@@ -1451,12 +1870,15 @@ describe('custom', () => {
]
],
[
+ 'raw06',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}.{{patch}}'],
- tagCustom: ['my', 'custom', 'tags'],
- tagCustomOnly: true,
+ tags: [
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
main: 'my',
@@ -1482,17 +1904,138 @@ describe('custom', () => {
"org.opencontainers.image.licenses=MIT"
]
],
- ])('given %p event ', tagsLabelsTest);
+ [
+ 'raw07',
+ 'event_push.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=ref,priority=90,event=branch`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ flavor: [
+ `latest=true`
+ ]
+ } as Inputs,
+ {
+ main: 'my',
+ partial: ['custom', 'tags', 'dev'],
+ latest: true
+ } as Version,
+ [
+ 'user/app:my',
+ 'user/app:custom',
+ 'user/app:tags',
+ 'user/app:dev',
+ 'user/app:latest'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=my",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'raw08',
+ 'event_push.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=match,pattern=\\d{8}`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ flavor: [
+ `latest=false`
+ ]
+ } as Inputs,
+ {
+ main: 'my',
+ partial: ['custom', 'tags'],
+ latest: false
+ } as Version,
+ [
+ 'user/app:my',
+ 'user/app:custom',
+ 'user/app:tags'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=my",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ],
+ [
+ 'raw09',
+ 'event_push.env',
+ {
+ images: ['user/app'],
+ tags: [
+ `type=match,pattern=\\d{8}`,
+ `type=raw,my,prefix=foo-,suffix=-bar`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ flavor: [
+ `latest=false`,
+ `prefix=glo-`,
+ `suffix=-bal`
+ ]
+ } as Inputs,
+ {
+ main: 'foo-my-bar',
+ partial: ['glo-custom-bal', 'glo-tags-bal'],
+ latest: false
+ } as Version,
+ [
+ 'user/app:foo-my-bar',
+ 'user/app:glo-custom-bal',
+ 'user/app:glo-tags-bal'
+ ],
+ [
+ "org.opencontainers.image.title=Hello-World",
+ "org.opencontainers.image.description=This your first repo!",
+ "org.opencontainers.image.url=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.source=https://github.com/octocat/Hello-World",
+ "org.opencontainers.image.version=foo-my-bar",
+ "org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
+ "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
+ "org.opencontainers.image.licenses=MIT"
+ ]
+ ]
+ ])('given %p wth %p event', tagsLabelsTest);
});
-describe('bake-file', () => {
+describe('bake', () => {
// prettier-ignore
test.each([
[
+ 'bake01',
'event_push.env',
{
images: ['user/app'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=ref,event=branch`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ labels: [
+ "invalid"
+ ]
} as Inputs,
{
"target": {
@@ -1522,10 +2065,14 @@ describe('bake-file', () => {
}
],
[
+ 'bake02',
'event_push.env',
{
images: ['user/app'],
- tagCustom: ['my']
+ tags: [
+ `type=ref,event=branch`,
+ `type=raw,my`
+ ]
} as Inputs,
{
"target": {
@@ -1553,10 +2100,16 @@ describe('bake-file', () => {
}
],
[
+ 'bake03',
'event_tag_release1.env',
{
images: ['user/app'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=ref,event=tag`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
"target": {
@@ -1587,12 +2140,19 @@ describe('bake-file', () => {
}
],
[
+ 'bake04',
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
- tagMatch: `\\d{8}`,
- tagLatest: false,
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=match,pattern=\\d{8}`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ],
+ flavor: [
+ `latest=false`
+ ]
} as Inputs,
{
"target": {
@@ -1622,11 +2182,18 @@ describe('bake-file', () => {
}
],
[
+ 'bake05',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
- tagCustom: ['my', 'custom', 'tags']
+ tags: [
+ `type=semver,pattern={{version}}`,
+ `type=semver,pattern={{major}}.{{minor}}`,
+ `type=semver,pattern={{major}}`,
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
"target": {
@@ -1666,12 +2233,15 @@ describe('bake-file', () => {
}
],
[
+ 'bake06',
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
- tagSemver: ['{{version}}', '{{major}}.{{minor}}.{{patch}}'],
- tagCustom: ['my', 'custom', 'tags'],
- tagCustomOnly: true,
+ tags: [
+ `type=raw,my`,
+ `type=raw,custom`,
+ `type=raw,tags`
+ ]
} as Inputs,
{
"target": {
@@ -1703,10 +2273,11 @@ describe('bake-file', () => {
}
],
[
+ 'bake07',
'event_tag_v1.1.1.env',
{
images: ['org/app'],
- labelCustom: [
+ labels: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustom=Title",
"org.opencontainers.image.description=Another description",
@@ -1740,7 +2311,7 @@ describe('bake-file', () => {
}
}
]
- ])('given %p event ', async (envFile: string, inputs: Inputs, exBakeDefinition: {}) => {
+ ])('given %p with %p event', async (name: string, envFile: string, inputs: Inputs, exBakeDefinition: {}) => {
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
const context = github.context();
console.log(process.env, context);
@@ -1748,7 +2319,7 @@ describe('bake-file', () => {
const repo = await github.repo(process.env.GITHUB_TOKEN || '');
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
- const bakeFile = meta.bakeFile();
+ const bakeFile = meta.getBakeFile();
console.log('bakeFile', bakeFile, fs.readFileSync(bakeFile, 'utf8'));
expect(JSON.parse(fs.readFileSync(bakeFile, 'utf8'))).toEqual(exBakeDefinition);
});
diff --git a/__tests__/tag.test.ts b/__tests__/tag.test.ts
new file mode 100644
index 0000000..826eb0f
--- /dev/null
+++ b/__tests__/tag.test.ts
@@ -0,0 +1,471 @@
+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{1,3}.\\d{1,3}.\\d{1,3}"`
+ ],
+ [
+ {
+ type: Type.Schedule,
+ attrs: {
+ "priority": DefaultPriorities[Type.Schedule],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "nightly"
+ }
+ },
+ {
+ type: Type.Semver,
+ attrs: {
+ "priority": DefaultPriorities[Type.Semver],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "{{version}}",
+ "value": ""
+ }
+ },
+ {
+ type: Type.Match,
+ attrs: {
+ "priority": DefaultPriorities[Type.Match],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "\\d{1,3}.\\d{1,3}.\\d{1,3}",
+ "group": "0",
+ "value": ""
+ }
+ },
+ {
+ type: Type.Edge,
+ attrs: {
+ "priority": DefaultPriorities[Type.Edge],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "branch": ""
+ }
+ },
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "event": RefEvent.Branch
+ }
+ },
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "event": RefEvent.Tag
+ }
+ },
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "pr-",
+ "suffix": "",
+ "event": RefEvent.PR
+ }
+ },
+ {
+ type: Type.Raw,
+ attrs: {
+ "priority": DefaultPriorities[Type.Raw],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "value": "foo"
+ }
+ },
+ {
+ type: Type.Sha,
+ attrs: {
+ "priority": DefaultPriorities[Type.Sha],
+ "enable": "true",
+ "prefix": "sha-",
+ "suffix": ""
+ }
+ }
+ ] 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",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "{{date 'YYYYMMDD'}}"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=semver,enable=true,pattern={{version}}`,
+ {
+ type: Type.Semver,
+ attrs: {
+ "priority": DefaultPriorities[Type.Semver],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "{{version}}",
+ "value": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=semver,priority=1,enable=true,pattern={{version}}`,
+ {
+ type: Type.Semver,
+ attrs: {
+ "priority": "1",
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "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",
+ "prefix": "",
+ "suffix": "",
+ "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",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "v(.*)",
+ "group": "1",
+ "value": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=match,enable=true,"pattern=^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$",group=1`,
+ {
+ type: Type.Match,
+ attrs: {
+ "priority": DefaultPriorities[Type.Match],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "pattern": "^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$",
+ "group": "1",
+ "value": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=match,priority=700,enable=true,pattern=v(.*),group=1`,
+ {
+ type: Type.Match,
+ attrs: {
+ "priority": "700",
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "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",
+ "prefix": "",
+ "suffix": "",
+ "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",
+ "prefix": "",
+ "suffix": "",
+ "branch": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=edge,enable=true,branch=master`,
+ {
+ type: Type.Edge,
+ attrs: {
+ "priority": DefaultPriorities[Type.Edge],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "branch": "master"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=ref,event=tag`,
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "event": RefEvent.Tag
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=ref,event=branch`,
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "event": RefEvent.Branch
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=ref,event=pr`,
+ {
+ type: Type.Ref,
+ attrs: {
+ "priority": DefaultPriorities[Type.Ref],
+ "enable": "true",
+ "prefix": "pr-",
+ "suffix": "",
+ "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",
+ "prefix": "",
+ "suffix": "",
+ "value": "acustomtag"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=raw`,
+ {} as Tag,
+ true
+ ],
+ [
+ `type=raw,value=acustomtag2`,
+ {
+ type: Type.Raw,
+ attrs: {
+ "priority": DefaultPriorities[Type.Raw],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "value": "acustomtag2"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=raw,enable=true,value=acustomtag4`,
+ {
+ type: Type.Raw,
+ attrs: {
+ "priority": DefaultPriorities[Type.Raw],
+ "enable": "true",
+ "prefix": "",
+ "suffix": "",
+ "value": "acustomtag4"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=raw,enable=false,value=acustomtag5`,
+ {
+ type: Type.Raw,
+ attrs: {
+ "priority": DefaultPriorities[Type.Raw],
+ "enable": "false",
+ "prefix": "",
+ "suffix": "",
+ "value": "acustomtag5"
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=sha`,
+ {
+ type: Type.Sha,
+ attrs: {
+ "priority": DefaultPriorities[Type.Sha],
+ "enable": "true",
+ "prefix": "sha-",
+ "suffix": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=sha,prefix=`,
+ {
+ type: Type.Sha,
+ attrs: {
+ "priority": DefaultPriorities[Type.Sha],
+ "enable": "true",
+ "prefix": "",
+ "suffix": ""
+ }
+ } as Tag,
+ false
+ ],
+ [
+ `type=sha,enable=false`,
+ {
+ type: Type.Sha,
+ attrs: {
+ "priority": DefaultPriorities[Type.Sha],
+ "enable": "false",
+ "prefix": "sha-",
+ "suffix": ""
+ }
+ } 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);
+ }
+ });
+});
diff --git a/action.yml b/action.yml
index 257170b..d7fdf12 100644
--- a/action.yml
+++ b/action.yml
@@ -10,47 +10,13 @@ inputs:
images:
description: 'List of Docker images to use as base name for tags'
required: true
- tag-sha:
- description: 'Add git short SHA as Docker tag'
- default: 'false'
- required: false
- tag-edge:
- description: 'Enable edge branch tagging'
- default: 'false'
- required: false
- tag-edge-branch:
- 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'
+ tags:
+ description: 'List of tags as key-value pair attributes'
required: false
- tag-custom-only:
- description: 'Only use tag-custom as Docker tags'
+ flavor:
+ description: 'Flavors to apply'
required: false
- label-custom:
+ labels:
description: 'List of custom labels'
required: false
sep-tags:
diff --git a/dist/index.js b/dist/index.js
index aaef6d2..cb43f68 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -56,17 +56,9 @@ exports.tmpDir = tmpDir;
function getInputs() {
return {
images: getInputList('images'),
- tagSha: /true/i.test(core.getInput('tag-sha') || 'false'),
- tagEdge: /true/i.test(core.getInput('tag-edge') || 'false'),
- tagEdgeBranch: core.getInput('tag-edge-branch'),
- 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),
+ tags: getInputList('tags', true),
+ flavor: getInputList('flavor', true),
+ labels: getInputList('labels', true),
sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`,
githubToken: core.getInput('github-token')
@@ -106,6 +98,52 @@ exports.asyncForEach = (array, callback) => __awaiter(void 0, void 0, void 0, fu
/***/ }),
+/***/ 3716:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.Transform = void 0;
+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}`);
+ }
+ }
+ }
+ return flavor;
+}
+exports.Transform = Transform;
+//# sourceMappingURL=flavor.js.map
+
+/***/ }),
+
/***/ 5928:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
@@ -221,20 +259,30 @@ function run() {
core.endGroup();
const meta = new meta_1.Meta(inputs, context, repo);
const version = meta.version;
- core.startGroup(`Docker image version`);
- core.info(version.main || '');
- core.endGroup();
+ if (meta.version.main == undefined || meta.version.main.length == 0) {
+ core.warning(`No Docker image version has been generated. Check tags input.`);
+ }
+ else {
+ core.startGroup(`Docker image version`);
+ core.info(version.main || '');
+ 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);
+ 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();
}
- core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels
- const labels = meta.labels();
+ const labels = meta.getLabels();
core.startGroup(`Docker labels`);
for (let label of labels) {
core.info(label);
@@ -242,7 +290,7 @@ function run() {
core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file
- const bakeFile = meta.bakeFile();
+ const bakeFile = meta.getBakeFile();
core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup();
@@ -293,99 +341,286 @@ const path = __importStar(__webpack_require__(5622));
const moment_1 = __importDefault(__webpack_require__(9623));
const semver = __importStar(__webpack_require__(1383));
const context_1 = __webpack_require__(3842);
+const tcl = __importStar(__webpack_require__(2829));
+const fcl = __importStar(__webpack_require__(3716));
const core = __importStar(__webpack_require__(2186));
class Meta {
constructor(inputs, context, repo) {
this.inputs = inputs;
- if (!this.inputs.tagEdgeBranch) {
- this.inputs.tagEdgeBranch = repo.default_branch;
- }
this.context = context;
this.repo = repo;
+ this.tags = tcl.Transform(inputs.tags);
+ this.flavor = fcl.Transform(inputs.flavor);
this.date = new Date();
this.version = this.getVersion();
}
getVersion() {
- const currentDate = this.date;
let version = {
main: undefined,
partial: [],
- latest: false
+ latest: undefined
};
- if (/schedule/.test(this.context.eventName)) {
- version.main = handlebars.compile(this.inputs.tagSchedule)({
- date: function (format) {
- return moment_1.default(currentDate).utc().format(format);
+ for (const tag of this.tags) {
+ switch (tag.type) {
+ case tcl.Type.Schedule: {
+ version = this.procSchedule(version, tag);
+ break;
}
- });
- }
- 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)) {
- const sver = semver.parse(version.main, {
- includePrerelease: true
- });
- if (semver.prerelease(version.main)) {
- version.main = handlebars.compile('{{version}}')(sver);
+ case tcl.Type.Semver: {
+ version = this.procSemver(version, tag);
+ break;
}
- else {
- version.latest = this.inputs.tagLatest;
- version.main = handlebars.compile(this.inputs.tagSemver[0])(sver);
- for (const semverTpl of this.inputs.tagSemver) {
- const partial = handlebars.compile(semverTpl)(sver);
- if (partial == version.main) {
- continue;
- }
- version.partial.push(partial);
+ case tcl.Type.Match: {
+ version = this.procMatch(version, tag);
+ break;
+ }
+ 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;
}
- }
- else if (this.inputs.tagMatch) {
- let tagMatch;
- const isRegEx = this.inputs.tagMatch.match(/^\/(.+)\/(.*)$/);
- if (isRegEx) {
- tagMatch = version.main.match(new RegExp(isRegEx[1], isRegEx[2]));
+ case tcl.Type.Edge: {
+ version = this.procEdge(version, tag);
+ break;
}
- else {
- tagMatch = version.main.match(this.inputs.tagMatch);
+ case tcl.Type.Raw: {
+ version = this.procRaw(version, tag);
+ break;
}
- if (tagMatch) {
- version.main = tagMatch[this.inputs.tagMatchGroup];
- version.latest = this.inputs.tagLatest;
+ 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';
+ version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
+ if (version.latest == undefined) {
+ version.latest = false;
+ }
+ return version;
+ }
+ procSchedule(version, tag) {
+ if (!/schedule/.test(this.context.eventName)) {
+ return version;
+ }
+ const currentDate = this.date;
+ const vraw = handlebars.compile(tag.attrs['pattern'])({
+ date: function (format) {
+ return moment_1.default(currentDate).utc().format(format);
}
+ });
+ if (version.main == undefined) {
+ version.main = vraw;
}
- else if (/^refs\/pull\//.test(this.context.ref)) {
- version.main = `pr-${this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, '')}`;
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
}
- if (this.inputs.tagCustom.length > 0) {
- if (this.inputs.tagCustomOnly) {
- version = {
- main: this.inputs.tagCustom.shift(),
- partial: this.inputs.tagCustom,
- latest: false
- };
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ 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 = 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 = handlebars.compile('{{version}}')(sver);
+ if (version.main == undefined) {
+ version.main = vraw;
}
- else {
- version.partial.push(...this.inputs.tagCustom);
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
}
}
- version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
+ else {
+ vraw = handlebars.compile(tag.attrs['pattern'])(sver);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ latest = true;
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ 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 = 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) {
+ vraw = tmatch[tag.attrs['group']];
+ latest = true;
+ }
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true';
+ }
return version;
}
- tags() {
+ procRefBranch(version, tag) {
+ if (!/^refs\/heads\//.test(this.context.ref)) {
+ return version;
+ }
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ procRefTag(version, tag) {
+ if (!/^refs\/tags\//.test(this.context.ref)) {
+ return version;
+ }
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ procRefPr(version, tag) {
+ if (!/^refs\/pull\//.test(this.context.ref)) {
+ return version;
+ }
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ 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.setFlavor(val, tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ procRaw(version, tag) {
+ const vraw = this.setFlavor(tag.attrs['value'], tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ procSha(version, tag) {
+ if (!this.context.sha) {
+ return version;
+ }
+ const vraw = this.setFlavor(this.context.sha.substr(0, 7), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ }
+ else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+ return version;
+ }
+ setFlavor(val, tag) {
+ if (tag.attrs['prefix'].length > 0) {
+ val = `${tag.attrs['prefix']}${val}`;
+ }
+ else if (this.flavor.prefix.length > 0) {
+ val = `${this.flavor.prefix}${val}`;
+ }
+ if (tag.attrs['suffix'].length > 0) {
+ val = `${val}${tag.attrs['suffix']}`;
+ }
+ else if (this.flavor.suffix.length > 0) {
+ val = `${val}${this.flavor.suffix}`;
+ }
+ return val;
+ }
+ getTags() {
if (!this.version.main) {
return [];
}
@@ -399,13 +634,10 @@ class Meta {
if (this.version.latest) {
tags.push(`${imageLc}:latest`);
}
- if (this.context.sha && this.inputs.tagSha) {
- tags.push(`${imageLc}:sha-${this.context.sha.substr(0, 7)}`);
- }
}
return tags;
}
- labels() {
+ getLabels() {
var _a;
let labels = [
`org.opencontainers.image.title=${this.repo.name || ''}`,
@@ -417,12 +649,12 @@ class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`,
`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;
}
- bakeFile() {
+ getBakeFile() {
let jsonLabels = {};
- for (let label of this.labels()) {
+ for (let label of this.getLabels()) {
const matches = label.match(/([^=]*)=(.*)/);
if (!matches) {
continue;
@@ -433,7 +665,7 @@ class Meta {
fs.writeFileSync(bakeFile, JSON.stringify({
target: {
'ghaction-docker-meta': {
- tags: this.tags(),
+ tags: this.getTags(),
labels: jsonLabels,
args: {
DOCKER_META_IMAGES: this.inputs.images.join(','),
@@ -450,6 +682,187 @@ exports.Meta = Meta;
/***/ }),
+/***/ 2829:
+/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
+
+"use strict";
+
+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.RefEvent = exports.Type = void 0;
+const sync_1 = __importDefault(__webpack_require__(8750));
+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 = {}));
+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));
+ }
+ return 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;
+ });
+}
+exports.Transform = Transform;
+function Parse(s) {
+ const fields = sync_1.default(s, {
+ relaxColumnCount: true,
+ skipLinesWithEmptyValues: true
+ })[0];
+ const tag = {
+ attrs: {}
+ };
+ 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 (!tag.attrs.hasOwnProperty('prefix')) {
+ tag.attrs['prefix'] = '';
+ }
+ if (!tag.attrs.hasOwnProperty('suffix')) {
+ tag.attrs['suffix'] = '';
+ }
+ 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:
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
diff --git a/src/context.ts b/src/context.ts
index acf8cac..22a6e31 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -8,17 +8,9 @@ let _tmpDir: string;
export interface Inputs {
images: string[];
- tagSha: boolean;
- tagEdge: boolean;
- tagEdgeBranch: string;
- tagSemver: string[];
- tagMatch: string;
- tagMatchGroup: number;
- tagLatest: boolean;
- tagSchedule: string;
- tagCustom: string[];
- tagCustomOnly: boolean;
- labelCustom: string[];
+ tags: string[];
+ flavor: string[];
+ labels: string[];
sepTags: string;
sepLabels: string;
githubToken: string;
@@ -34,17 +26,9 @@ export function tmpDir(): string {
export function getInputs(): Inputs {
return {
images: getInputList('images'),
- tagSha: /true/i.test(core.getInput('tag-sha') || 'false'),
- tagEdge: /true/i.test(core.getInput('tag-edge') || 'false'),
- tagEdgeBranch: core.getInput('tag-edge-branch'),
- 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),
+ tags: getInputList('tags', true),
+ flavor: getInputList('flavor', true),
+ labels: getInputList('labels', true),
sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`,
githubToken: core.getInput('github-token')
diff --git a/src/flavor.ts b/src/flavor.ts
new file mode 100644
index 0000000..3ae13b7
--- /dev/null
+++ b/src/flavor.ts
@@ -0,0 +1,42 @@
+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}`);
+ }
+ }
+ }
+
+ return flavor;
+}
diff --git a/src/main.ts b/src/main.ts
index cb99345..cf0e7ec 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -29,22 +29,30 @@ async function run() {
const meta: Meta = new Meta(inputs, context, repo);
const version: Version = meta.version;
- core.startGroup(`Docker image version`);
- core.info(version.main || '');
- core.endGroup();
+ if (meta.version.main == undefined || meta.version.main.length == 0) {
+ core.warning(`No Docker image version has been generated. Check tags input.`);
+ } else {
+ core.startGroup(`Docker image version`);
+ core.info(version.main || '');
+ core.endGroup();
+ }
core.setOutput('version', version.main || '');
// Docker tags
- const tags: Array = meta.tags();
- core.startGroup(`Docker tags`);
- for (let tag of tags) {
- core.info(tag);
+ const tags: Array = 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();
}
- core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels
- const labels: Array = meta.labels();
+ const labels: Array = meta.getLabels();
core.startGroup(`Docker labels`);
for (let label of labels) {
core.info(label);
@@ -53,7 +61,7 @@ async function run() {
core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file
- const bakeFile: string = meta.bakeFile();
+ const bakeFile: string = meta.getBakeFile();
core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup();
diff --git a/src/meta.ts b/src/meta.ts
index 592071d..25de61a 100644
--- a/src/meta.ts
+++ b/src/meta.ts
@@ -4,6 +4,8 @@ import * as path from 'path';
import moment from 'moment';
import * as semver from 'semver';
import {Inputs, tmpDir} from './context';
+import * as tcl from './tag';
+import * as fcl from './flavor';
import * as core from '@actions/core';
import {Context} from '@actions/github/lib/context';
import {ReposGetResponseData} from '@octokit/types';
@@ -11,7 +13,7 @@ import {ReposGetResponseData} from '@octokit/types';
export interface Version {
main: string | undefined;
partial: string[];
- latest: boolean;
+ latest: boolean | undefined;
}
export class Meta {
@@ -20,96 +22,305 @@ export class Meta {
private readonly inputs: Inputs;
private readonly context: Context;
private readonly repo: ReposGetResponseData;
+ private readonly tags: tcl.Tag[];
+ private readonly flavor: fcl.Flavor;
private readonly date: Date;
constructor(inputs: Inputs, context: Context, repo: ReposGetResponseData) {
this.inputs = inputs;
- if (!this.inputs.tagEdgeBranch) {
- this.inputs.tagEdgeBranch = repo.default_branch;
- }
this.context = context;
this.repo = repo;
+ this.tags = tcl.Transform(inputs.tags);
+ this.flavor = fcl.Transform(inputs.flavor);
this.date = new Date();
this.version = this.getVersion();
}
private getVersion(): Version {
- const currentDate = this.date;
let version: Version = {
main: undefined,
partial: [],
- latest: false
+ latest: undefined
};
- if (/schedule/.test(this.context.eventName)) {
- version.main = handlebars.compile(this.inputs.tagSchedule)({
- date: function (format) {
- return moment(currentDate).utc().format(format);
+ for (const tag of this.tags) {
+ switch (tag.type) {
+ case tcl.Type.Schedule: {
+ version = this.procSchedule(version, tag);
+ break;
}
- });
- } 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)) {
- const sver = semver.parse(version.main, {
- includePrerelease: true
- });
- if (semver.prerelease(version.main)) {
- version.main = handlebars.compile('{{version}}')(sver);
- } else {
- version.latest = this.inputs.tagLatest;
- version.main = handlebars.compile(this.inputs.tagSemver[0])(sver);
- for (const semverTpl of this.inputs.tagSemver) {
- const partial = handlebars.compile(semverTpl)(sver);
- if (partial == version.main) {
- continue;
- }
- version.partial.push(partial);
+ case tcl.Type.Semver: {
+ version = this.procSemver(version, tag);
+ break;
+ }
+ case tcl.Type.Match: {
+ version = this.procMatch(version, tag);
+ break;
+ }
+ 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: {
+ version = this.procEdge(version, tag);
+ break;
}
- } else if (this.inputs.tagMatch) {
- let tagMatch;
- const isRegEx = this.inputs.tagMatch.match(/^\/(.+)\/(.*)$/);
- if (isRegEx) {
- tagMatch = version.main.match(new RegExp(isRegEx[1], isRegEx[2]));
- } else {
- tagMatch = version.main.match(this.inputs.tagMatch);
+ case tcl.Type.Raw: {
+ version = this.procRaw(version, tag);
+ break;
}
- if (tagMatch) {
- version.main = tagMatch[this.inputs.tagMatchGroup];
- version.latest = this.inputs.tagLatest;
+ 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';
+ }
+
+ version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
+ if (version.latest == undefined) {
+ version.latest = false;
+ }
+
+ return version;
+ }
+
+ private procSchedule(version: Version, tag: tcl.Tag): Version {
+ if (!/schedule/.test(this.context.eventName)) {
+ return version;
+ }
+
+ const currentDate = this.date;
+ const vraw = handlebars.compile(tag.attrs['pattern'])({
+ date: function (format) {
+ return moment(currentDate).utc().format(format);
+ }
+ });
+
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ 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 = 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 = handlebars.compile('{{version}}')(sver);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
}
- } 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);
+ } else {
+ vraw = handlebars.compile(tag.attrs['pattern'])(sver);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
}
+ latest = true;
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ 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 = 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) {
+ vraw = tmatch[tag.attrs['group']];
+ latest = true;
+ }
+
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ private procRefBranch(version: Version, tag: tcl.Tag): Version {
+ if (!/^refs\/heads\//.test(this.context.ref)) {
+ return version;
+ }
+
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ private procRefTag(version: Version, tag: tcl.Tag): Version {
+ if (!/^refs\/tags\//.test(this.context.ref)) {
+ return version;
+ }
+
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ private procRefPr(version: Version, tag: tcl.Tag): Version {
+ if (!/^refs\/pull\//.test(this.context.ref)) {
+ return version;
+ }
+
+ const vraw = this.setFlavor(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ 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.setFlavor(val, tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
}
- version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index);
return version;
}
- public tags(): Array {
+ private procRaw(version: Version, tag: tcl.Tag): Version {
+ const vraw = this.setFlavor(tag.attrs['value'], tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ private procSha(version: Version, tag: tcl.Tag): Version {
+ if (!this.context.sha) {
+ return version;
+ }
+
+ const vraw = this.setFlavor(this.context.sha.substr(0, 7), tag);
+ if (version.main == undefined) {
+ version.main = vraw;
+ } else if (vraw !== version.main) {
+ version.partial.push(vraw);
+ }
+ if (version.latest == undefined) {
+ version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true';
+ }
+
+ return version;
+ }
+
+ private setFlavor(val: string, tag: tcl.Tag): string {
+ if (tag.attrs['prefix'].length > 0) {
+ val = `${tag.attrs['prefix']}${val}`;
+ } else if (this.flavor.prefix.length > 0) {
+ val = `${this.flavor.prefix}${val}`;
+ }
+ if (tag.attrs['suffix'].length > 0) {
+ val = `${val}${tag.attrs['suffix']}`;
+ } else if (this.flavor.suffix.length > 0) {
+ val = `${val}${this.flavor.suffix}`;
+ }
+ return val;
+ }
+
+ public getTags(): Array {
if (!this.version.main) {
return [];
}
@@ -124,14 +335,11 @@ export class Meta {
if (this.version.latest) {
tags.push(`${imageLc}:latest`);
}
- if (this.context.sha && this.inputs.tagSha) {
- tags.push(`${imageLc}:sha-${this.context.sha.substr(0, 7)}`);
- }
}
return tags;
}
- public labels(): Array {
+ public getLabels(): Array {
let labels: Array = [
`org.opencontainers.image.title=${this.repo.name || ''}`,
`org.opencontainers.image.description=${this.repo.description || ''}`,
@@ -142,13 +350,13 @@ export class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`,
`org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}`
];
- labels.push(...this.inputs.labelCustom);
+ labels.push(...this.inputs.labels);
return labels;
}
- public bakeFile(): string {
+ public getBakeFile(): string {
let jsonLabels = {};
- for (let label of this.labels()) {
+ for (let label of this.getLabels()) {
const matches = label.match(/([^=]*)=(.*)/);
if (!matches) {
continue;
@@ -163,7 +371,7 @@ export class Meta {
{
target: {
'ghaction-docker-meta': {
- tags: this.tags(),
+ tags: this.getTags(),
labels: jsonLabels,
args: {
DOCKER_META_IMAGES: this.inputs.images.join(','),
diff --git a/src/tag.ts b/src/tag.ts
new file mode 100644
index 0000000..0a935d1
--- /dev/null
+++ b/src/tag.ts
@@ -0,0 +1,180 @@
+import csvparse from 'csv-parse/lib/sync';
+
+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 interface Tag {
+ type: Type;
+ attrs: Record;
+}
+
+export const DefaultPriorities: Record = {
+ [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));
+ }
+ return 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;
+ });
+}
+
+export function Parse(s: string): Tag {
+ const fields = csvparse(s, {
+ relaxColumnCount: true,
+ skipLinesWithEmptyValues: true
+ })[0];
+
+ const tag = {
+ attrs: {}
+ } as 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 (!tag.attrs.hasOwnProperty('prefix')) {
+ tag.attrs['prefix'] = '';
+ }
+ if (!tag.attrs.hasOwnProperty('suffix')) {
+ tag.attrs['suffix'] = '';
+ }
+ if (!['true', 'false'].includes(tag.attrs['enable'])) {
+ throw new Error(`Invalid value for enable attribute: ${tag.attrs['enable']}`);
+ }
+
+ return tag;
+}