Compare commits

..

16 Commits

Author SHA1 Message Date
CrazyMax
2cbde9dffa Update CHANGELOG 2021-03-29 13:06:09 +02:00
CrazyMax
2f83320d17 v2 (#50)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-29 13:04:53 +02:00
CrazyMax
9be43f076d Update CHANGELOG 2021-03-19 11:59:51 +01:00
dependabot[bot]
d541d2c17f Bump handlebars from 4.7.6 to 4.7.7 (#44)
* Bump handlebars from 4.7.6 to 4.7.7

Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7)

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

* Update generated content

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-19 11:28:33 +01:00
dependabot[bot]
cffefcd230 Bump csv-parse from 4.15.1 to 4.15.3 (#45)
Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.15.1 to 4.15.3.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.15.1...v4.15.3)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2021-03-19 09:58:20 +01:00
CrazyMax
00e310993c Ignore commas for label-custom input (#48)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-03-19 09:55:45 +01:00
CrazyMax
e696b8439a Rename Dockerfile 2021-03-19 09:51:52 +01:00
CrazyMax
b334567bf3 Fix SHA 2021-03-19 00:32:29 +01:00
CrazyMax
7b49f7ed90 Use SHAs 2021-03-18 19:23:59 +01:00
CrazyMax
e08ef4ca63 Add label sponsor 2021-03-18 19:02:39 +01:00
CrazyMax
5f29dbc7d7 Move to docker/bake-action 2021-02-09 20:54:44 +01:00
dependabot[bot]
94e634192d Bump csv-parse from 4.14.2 to 4.15.1 (#42)
* Bump csv-parse from 4.14.2 to 4.15.1

Bumps [csv-parse](https://github.com/wdavidw/node-csv-parse) from 4.14.2 to 4.15.1.
- [Release notes](https://github.com/wdavidw/node-csv-parse/releases)
- [Changelog](https://github.com/adaltas/node-csv-parse/blob/master/CHANGELOG.md)
- [Commits](https://github.com/wdavidw/node-csv-parse/compare/v4.14.2...v4.15.1)

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

* Update generated content

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2021-02-03 18:11:37 +00:00
CrazyMax
cae03854eb Update labels 2021-02-03 18:20:30 +01:00
CrazyMax
0a412843f8 2021 2021-01-06 19:03:30 +01:00
CrazyMax
9ae6899cfa Update CHANGELOG 2020-12-24 16:47:13 +01:00
CrazyMax
db66d4df79 Inject DOCKER_META_IMAGES and DOCKER_META_VERSION args in bake definition (#37)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
2020-12-24 15:45:28 +00:00
24 changed files with 3206 additions and 594 deletions

22
.github/labels.yml vendored
View File

@@ -47,26 +47,34 @@
name: ":pray: help wanted" name: ":pray: help wanted"
color: "4caf50" color: "4caf50"
description: "" description: ""
- # hold
name: ":hand: hold"
color: "24292f"
description: ""
- # invalid - # invalid
name: ":no_entry_sign: invalid" name: ":no_entry_sign: invalid"
color: "e6e6e6" color: "e6e6e6"
description: "" description: ""
- # maybe bug - # investigate
name: ":interrobang: maybe bug" name: ":mag: investigate"
color: "ff5722" color: "e6625b"
description: "" description: ""
- # needs more info - # needs more info
name: ":thinking: needs more info" name: ":thinking: needs more info"
color: "795548" color: "795548"
description: "" description: ""
- # pinned
name: ":pushpin: pinned"
color: "28008e"
description: ""
- # question - # question
name: ":question: question" name: ":question: question"
color: "3f51b5" color: "3f51b5"
description: "" description: ""
- # sponsor
name: ":sparkling_heart: sponsor"
color: "fedbf0"
description: ""
- # stale
name: ":skull: stale"
color: "237da0"
description: ""
- # upstream - # upstream
name: ":eyes: upstream" name: ":eyes: upstream"
color: "fbca04" color: "fbca04"

3
.github/stale.yml vendored
View File

@@ -4,7 +4,8 @@ daysUntilStale: 30
daysUntilClose: 7 daysUntilClose: 7
# Issues with these labels will never be considered stale # Issues with these labels will never be considered stale
exemptLabels: exemptLabels:
- ":hand: hold" - ":pushpin: pinned"
- ":game_die: dependencies"
# Set to true to ignore issues in a milestone (defaults to false) # Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true exemptMilestones: true
# Label to use when marking an issue as stale # Label to use when marking an issue as stale

View File

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

21
.github/workflows/label-sponsor.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: label-sponsor
on:
pull_request:
types:
- 'opened'
issues:
types:
- 'opened'
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: Set sponsor label
uses: JasonEtco/is-sponsor-label-action@024ac24f8b170abce078cad4ee748852369853c8
with:
label: ":sparkling_heart: sponsor"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

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

View File

@@ -1,5 +1,30 @@
# Changelog # Changelog
## 2.0.0 (2021/03/29)
This release includes significant changes (#50). Please read the [upgrade notes](UPGRADE.md) for a smooth migration.
`v1` is still available through [`releases/v1` branch](https://github.com/crazy-max/ghaction-docker-meta/tree/releases/v1).
* Add `tags` input
* Inputs `tag-sha`, `tag-edge`, `tag-edge-branch`, `tag-semver`, `tag-match`, `tag-match-group`, `tag-schedule`, `tag-custom`, `tag-custom-only` have been removed and are now handled through the new `tags` input
* Input `label-custom` renamed `labels`
* Add `flavor` input to handle a global prefix, suffix and latest tag behavior (#13 #15 #41)
* Input `tag-latest` removed (use `flavor` input instead)
* Manage tag sorting through `priority` attribute in `tags` input (#27)
* Explicit control over the conditions of each tag through `enable` attribute in `tags` input (#30)
* Allow `semver` and `match` parsing for custom values (#25 #30)
* Display warning message if not tag generated
## 1.12.0 (2021/03/19)
* Ignore commas for `label-custom` input (#48)
* Bump handlebars from 4.7.6 to 4.7.7 (#44)
* Bump csv-parse from 4.14.2 to 4.15.3 (#42 #45)
## 1.11.0 (2020/12/24)
* Inject `DOCKER_META_IMAGES` and `DOCKER_META_VERSION` args in bake definition (#37)
## 1.10.1 (2020/12/24) ## 1.10.1 (2020/12/24)
* Missing entry in `action.yml` * Missing entry in `action.yml`

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020 CrazyMax Copyright (c) 2020-2021 CrazyMax
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

482
README.md
View File

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

295
UPGRADE.md Normal file
View File

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

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

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

File diff suppressed because it is too large Load Diff

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

@@ -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);
}
});
});

View File

@@ -10,47 +10,13 @@ inputs:
images: images:
description: 'List of Docker images to use as base name for tags' description: 'List of Docker images to use as base name for tags'
required: true required: true
tag-sha: tags:
description: 'Add git short SHA as Docker tag' description: 'List of tags as key-value pair attributes'
default: 'false'
required: false required: false
tag-edge: flavor:
description: 'Enable edge branch tagging' description: 'Flavors to apply'
default: 'false'
required: false required: false
tag-edge-branch: labels:
description: 'Branch that will be tagged as edge (default repo.default_branch)'
required: false
tag-semver:
description: 'Handle Git tag as semver template if possible'
required: false
tag-match:
description: 'RegExp to match against a Git tag and use match group as Docker tag'
required: false
tag-match-group:
description: 'Group to get if tag-match matches (default 0)'
default: '0'
required: false
tag-latest:
description: 'Set latest Docker tag if tag-semver, tag-match or Git tag event occurs'
default: 'true'
required: false
tag-match-latest:
deprecationMessage: 'tag-match-latest is deprecated. Use tag-latest instead'
description: '(DEPRECATED) Set latest Docker tag if tag-match matches or on Git tag event'
default: 'true'
required: false
tag-schedule:
description: 'Template to apply to schedule tag'
default: 'nightly'
required: false
tag-custom:
description: 'List of custom tags'
required: false
tag-custom-only:
description: 'Only use tag-custom as Docker tags'
required: false
label-custom:
description: 'List of custom labels' description: 'List of custom labels'
required: false required: false
sep-tags: sep-tags:

722
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

View File

@@ -11,7 +11,7 @@ group "validate" {
} }
target "dockerfile" { target "dockerfile" {
dockerfile = "Dockerfile.dev" dockerfile = "dev.Dockerfile"
} }
target "update-yarn" { target "update-yarn" {

View File

@@ -25,8 +25,8 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.14.2", "csv-parse": "^4.15.3",
"handlebars": "^4.7.6", "handlebars": "^4.7.7",
"moment": "^2.29.1", "moment": "^2.29.1",
"semver": "^7.3.4" "semver": "^7.3.4"
}, },

View File

@@ -8,17 +8,9 @@ let _tmpDir: string;
export interface Inputs { export interface Inputs {
images: string[]; images: string[];
tagSha: boolean; tags: string[];
tagEdge: boolean; flavor: string[];
tagEdgeBranch: string; labels: string[];
tagSemver: string[];
tagMatch: string;
tagMatchGroup: number;
tagLatest: boolean;
tagSchedule: string;
tagCustom: string[];
tagCustomOnly: boolean;
labelCustom: string[];
sepTags: string; sepTags: string;
sepLabels: string; sepLabels: string;
githubToken: string; githubToken: string;
@@ -34,17 +26,9 @@ export function tmpDir(): string {
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
images: getInputList('images'), images: getInputList('images'),
tagSha: /true/i.test(core.getInput('tag-sha') || 'false'), tags: getInputList('tags', true),
tagEdge: /true/i.test(core.getInput('tag-edge') || 'false'), flavor: getInputList('flavor', true),
tagEdgeBranch: core.getInput('tag-edge-branch'), labels: getInputList('labels', true),
tagSemver: getInputList('tag-semver'),
tagMatch: core.getInput('tag-match'),
tagMatchGroup: Number(core.getInput('tag-match-group')) || 0,
tagLatest: /true/i.test(core.getInput('tag-latest') || core.getInput('tag-match-latest') || 'true'),
tagSchedule: core.getInput('tag-schedule') || 'nightly',
tagCustom: getInputList('tag-custom'),
tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'),
labelCustom: getInputList('label-custom'),
sepTags: core.getInput('sep-tags') || `\n`, sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`,
githubToken: core.getInput('github-token') githubToken: core.getInput('github-token')

42
src/flavor.ts Normal file
View File

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

View File

@@ -29,22 +29,30 @@ async function run() {
const meta: Meta = new Meta(inputs, context, repo); const meta: Meta = new Meta(inputs, context, repo);
const version: Version = meta.version; const version: Version = meta.version;
core.startGroup(`Docker image version`); if (meta.version.main == undefined || meta.version.main.length == 0) {
core.info(version.main || ''); core.warning(`No Docker image version has been generated. Check tags input.`);
core.endGroup(); } else {
core.startGroup(`Docker image version`);
core.info(version.main || '');
core.endGroup();
}
core.setOutput('version', version.main || ''); core.setOutput('version', version.main || '');
// Docker tags // Docker tags
const tags: Array<string> = meta.tags(); const tags: Array<string> = meta.getTags();
core.startGroup(`Docker tags`); if (tags.length == 0) {
for (let tag of tags) { core.warning('No Docker tag has been generated. Check tags input.');
core.info(tag); } else {
core.startGroup(`Docker tags`);
for (let tag of tags) {
core.info(tag);
}
core.endGroup();
} }
core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags)); core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels // Docker labels
const labels: Array<string> = meta.labels(); const labels: Array<string> = meta.getLabels();
core.startGroup(`Docker labels`); core.startGroup(`Docker labels`);
for (let label of labels) { for (let label of labels) {
core.info(label); core.info(label);
@@ -53,7 +61,7 @@ async function run() {
core.setOutput('labels', labels.join(inputs.sepLabels)); core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file // Bake definition file
const bakeFile: string = meta.bakeFile(); const bakeFile: string = meta.getBakeFile();
core.startGroup(`Bake definition file`); core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8')); core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup(); core.endGroup();

View File

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

180
src/tag.ts Normal file
View File

@@ -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<string, string>;
}
export const DefaultPriorities: Record<Type, string> = {
[Type.Schedule]: '1000',
[Type.Semver]: '900',
[Type.Match]: '800',
[Type.Edge]: '700',
[Type.Ref]: '600',
[Type.Raw]: '200',
[Type.Sha]: '100'
};
export function Transform(inputs: string[]): Tag[] {
const tags: Tag[] = [];
if (inputs.length == 0) {
// prettier-ignore
inputs = [
`type=schedule`,
`type=ref,event=${RefEvent.Branch}`,
`type=ref,event=${RefEvent.Tag}`,
`type=ref,event=${RefEvent.PR}`
];
}
for (const input of inputs) {
tags.push(Parse(input));
}
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;
}

View File

@@ -1176,10 +1176,10 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@^4.14.2: csv-parse@^4.15.3:
version "4.14.2" version "4.15.3"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.14.2.tgz#c1329cff95a99b8773a92c4e62f8bff114b34726" resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.15.3.tgz#8a62759617a920c328cb31c351b05053b8f92b10"
integrity sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw== integrity sha512-jlTqDvLdHnYMSr08ynNfk4IAUSJgJjTKy2U5CQBSu4cN9vQOJonLVZP4Qo4gKKrIgIQ5dr07UwOJdi+lRqT12w==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
@@ -1627,10 +1627,10 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
handlebars@^4.7.6: handlebars@^4.7.7:
version "4.7.6" version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.6.tgz#d4c05c1baf90e9945f77aa68a7a219aa4a7df74e" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
integrity sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA== integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==
dependencies: dependencies:
minimist "^1.2.5" minimist "^1.2.5"
neo-async "^2.6.0" neo-async "^2.6.0"