diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26e5b38e9..8ce385b5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,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 +55,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 @@ -59,12 +68,12 @@ jobs: fail-fast: false matrix: include: - - tag-match: '\d{1,3}.\d{1,3}.\d{1,3}' - tag-match-group: '0' - - tag-match: '\d{1,3}.\d{1,3}' - tag-match-group: '0' - - tag-match: 'v(.*)' - tag-match-group: '1' + - tag-match: "\d{1,3}.\d{1,3}.\d{1,3}" + tag-match-group: "0" + - tag-match: "\d{1,3}.\d{1,3}" + tag-match-group: "0" + - tag-match: "v(.*)" + tag-match-group: "1" steps: - name: Checkout @@ -76,9 +85,13 @@ 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 @@ -86,8 +99,9 @@ jobs: fail-fast: false matrix: tag-latest: - - 'true' - - 'false' + - "auto" + - "true" + - "false" steps: - name: Checkout @@ -99,11 +113,15 @@ jobs: images: | ${{ env.DOCKER_IMAGE }} ghcr.io/name/app - tag-semver: | - {{raw}} - {{version}} - {{major}}.{{minor}}.{{patch}} - tag-latest: ${{ matrix.tag-latest }} + tags: | + type=schedule + type=ref,event=branch + type=ref,event=tag + type=ref,event=pr + type=semver,latest=${{ matrix.tag-latest }},pattern={{raw}} + type=semver,latest=${{ matrix.tag-latest }},pattern={{version}} + type=semver,latest=${{ matrix.tag-latest }},pattern={{major}}.{{minor}}.{{patch}} + type=sha label-custom: runs-on: ubuntu-latest @@ -118,7 +136,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 +159,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 +214,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/__tests__/meta.test.ts b/__tests__/meta.test.ts index 3dab864d3..9b8378552 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,6 +84,7 @@ describe('null', () => { ] ], [ + 'null02', 'event_empty.env', { images: ['user/app'], @@ -104,13 +106,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 +138,13 @@ describe('push', () => { ] ], [ + 'push02', 'event_push_defbranch.env', { images: ['user/app'], - tagEdge: true, + tags: [ + `type=edge` + ], } as Inputs, { main: 'edge', @@ -160,6 +166,7 @@ describe('push', () => { ] ], [ + 'push03', 'event_push_defbranch.env', { images: ['user/app'], @@ -184,10 +191,13 @@ describe('push', () => { ] ], [ + 'push04', 'event_workflow_dispatch.env', { images: ['user/app'], - tagEdge: true, + tags: [ + `type=edge` + ], } as Inputs, { main: 'edge', @@ -209,6 +219,7 @@ describe('push', () => { ] ], [ + 'push05', 'event_push.env', { images: ['org/app', 'ghcr.io/user/app'], @@ -234,10 +245,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 +274,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 +306,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 +338,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 +370,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 +402,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 +433,14 @@ describe('push', () => { "org.opencontainers.image.licenses=MIT" ] ], - ])('given %p event ', tagsLabelsTest); + ])('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 +466,7 @@ describe('push tag', () => { ] ], [ + 'tag02', 'event_tag_20200110-RC2.env', { images: ['user/app'], @@ -462,11 +492,13 @@ describe('push tag', () => { ] ], [ + 'tag03', 'event_tag_20200110-RC2.env', { images: ['user/app'], - tagMatch: `\\d{8}`, - tagLatest: false, + tags: [ + `type=match,latest=false,pattern=\\d{8}` + ] } as Inputs, { main: '20200110', @@ -488,12 +520,13 @@ describe('push tag', () => { ] ], [ + 'tag04', 'event_tag_20200110-RC2.env', { images: ['user/app'], - tagMatch: `(.*)-RC`, - tagMatchGroup: 1, - tagLatest: false, + tags: [ + `type=match,latest=false,pattern=(.*)-RC,group=1` + ] } as Inputs, { main: '20200110', @@ -515,10 +548,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 +579,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 +610,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 +641,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 +672,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})$",group=1` + ] } as Inputs, { main: 'v2.0.8-beta.67', @@ -655,10 +701,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 +730,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 +767,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 +799,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 +829,16 @@ describe('push tag', () => { ] ], [ + 'tag14', 'event_tag_sometag.env', { images: ['ghcr.io/user/app'], - tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'], - tagLatest: false, + tags: [ + `type=ref,latest=false,event=tag`, + `type=semver,latest=false,pattern={{version}}`, + `type=semver,latest=false,pattern={{major}}.{{minor}}`, + `type=semver,latest=false,pattern={{major}}` + ] } as Inputs, { main: 'sometag', @@ -792,17 +859,20 @@ describe('push tag', () => { "org.opencontainers.image.licenses=MIT" ] ], - ])('given %p event ', tagsLabelsTest); + ])('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 +895,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 +924,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 +953,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 +982,7 @@ describe('latest', () => { ] ], [ + 'latest05', 'event_tag_v1.1.1.env', { images: ['org/app', 'ghcr.io/user/app'], @@ -930,10 +1010,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 +1041,13 @@ describe('latest', () => { ] ], [ + 'latest07', 'event_tag_v1.1.1.env', { images: ['org/app', 'ghcr.io/user/app'], - tagLatest: false, + tags: [ + `type=ref,latest=false,event=tag` + ] } as Inputs, { main: 'v1.1.1', @@ -984,10 +1070,13 @@ describe('latest', () => { ] ], [ + 'latest08', 'event_tag_v1.1.1.env', { images: ['org/app', 'ghcr.io/MyUSER/MyApp'], - tagLatest: false, + tags: [ + `type=ref,latest=false,event=tag` + ] } as Inputs, { main: 'v1.1.1', @@ -1010,16 +1099,19 @@ describe('latest', () => { ] ], [ + 'latest09', 'event_tag_v1.1.1.env', { images: ['org/app', 'ghcr.io/MyUSER/MyApp'], - tagLatest: false, - labelCustom: [ + tags: [ + `type=ref,latest=false,event=tag` + ], + 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 +1137,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 +1169,7 @@ describe('pull_request', () => { ] ], [ + 'pr02', 'event_pull_request.env', { images: ['org/app', 'ghcr.io/user/app'], @@ -1101,14 +1195,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 +1226,26 @@ describe('pull_request', () => { "org.opencontainers.image.licenses=MIT" ] ], - ])('given %p event ', tagsLabelsTest); + ])('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 +1259,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 +1287,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 +1315,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 +1343,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 +1374,14 @@ describe('schedule', () => { "org.opencontainers.image.licenses=MIT" ] ], - ])('given %p event ', tagsLabelsTest); + ])('given %p with %p event', tagsLabelsTest); }); describe('release', () => { // prettier-ignore test.each([ [ + 'release01', 'event_release.env', { images: ['user/app'], @@ -1292,17 +1406,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 +1447,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 +1477,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 +1512,16 @@ describe('custom', () => { ] ], [ + 'raw04', 'event_tag_20200110-RC2.env', { images: ['user/app'], - tagMatch: `\\d{8}`, - tagLatest: false, - tagCustom: ['my', 'custom', 'tags'] + tags: [ + `type=match,latest=false,pattern=\\d{8}`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ] } as Inputs, { main: '20200110', @@ -1412,11 +1546,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 +1592,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 +1626,58 @@ 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,latest=true,my`, + `type=raw,custom`, + `type=raw,tags` + ] + } 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" + ] + ] + ])('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` + ] } as Inputs, { "target": { @@ -1522,10 +1707,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 +1742,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 +1782,16 @@ describe('bake-file', () => { } ], [ + 'bake04', 'event_tag_20200110-RC2.env', { images: ['user/app'], - tagMatch: `\\d{8}`, - tagLatest: false, - tagCustom: ['my', 'custom', 'tags'] + tags: [ + `type=match,latest=false,pattern=\\d{8}`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ] } as Inputs, { "target": { @@ -1622,11 +1821,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 +1872,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 +1912,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 +1950,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 +1958,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 000000000..aec4b36f9 --- /dev/null +++ b/__tests__/tag.test.ts @@ -0,0 +1,450 @@ +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", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "nightly" + } + }, + { + type: Type.Semver, + attrs: { + "priority": DefaultPriorities[Type.Semver], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "{{version}}" + } + }, + { + type: Type.Match, + attrs: { + "priority": DefaultPriorities[Type.Match], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "\\d{1,3}.\\d{1,3}.\\d{1,3}", + "group": "0" + } + }, + { + type: Type.Edge, + attrs: { + "priority": DefaultPriorities[Type.Edge], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "branch": "" + } + }, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "event": RefEvent.Branch + } + }, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "event": RefEvent.Tag + } + }, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "prefix": "pr-", + "suffix": "", + "event": RefEvent.PR + } + }, + { + type: Type.Raw, + attrs: { + "priority": DefaultPriorities[Type.Raw], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "value": "foo" + } + }, + { + type: Type.Sha, + attrs: { + "priority": DefaultPriorities[Type.Sha], + "enable": "true", + "latest": "auto", + "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", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "{{date 'YYYYMMDD'}}" + } + } as Tag, + false + ], + [ + `type=semver`, + {} as Tag, + true + ], + [ + `type=semver,enable=true,pattern={{version}}`, + { + type: Type.Semver, + attrs: { + "priority": DefaultPriorities[Type.Semver], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "{{version}}" + } + } as Tag, + false + ], + [ + `type=semver,priority=1,enable=true,pattern={{version}}`, + { + type: Type.Semver, + attrs: { + "priority": "1", + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "{{version}}" + } + } as Tag, + false + ], + [ + `type=match`, + {} as Tag, + true + ], + [ + `type=match,enable=true,pattern=v(.*),group=1`, + { + type: Type.Match, + attrs: { + "priority": DefaultPriorities[Type.Match], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "v(.*)", + "group": "1" + } + } 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", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "^v(\\d{1,3}.\\d{1,3}.\\d{1,3})$", + "group": "1" + } + } as Tag, + false + ], + [ + `type=match,priority=700,enable=true,pattern=v(.*),group=1`, + { + type: Type.Match, + attrs: { + "priority": "700", + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "pattern": "v(.*)", + "group": "1" + } + } 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", + "latest": "auto", + "prefix": "", + "suffix": "", + "branch": "" + } + } as Tag, + false + ], + [ + `type=edge,enable=true,branch=master`, + { + type: Type.Edge, + attrs: { + "priority": DefaultPriorities[Type.Edge], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "branch": "master" + } + } as Tag, + false + ], + [ + `type=ref,event=tag`, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "event": RefEvent.Tag + } + } as Tag, + false + ], + [ + `type=ref,event=branch`, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "event": RefEvent.Branch + } + } as Tag, + false + ], + [ + `type=ref,event=pr`, + { + type: Type.Ref, + attrs: { + "priority": DefaultPriorities[Type.Ref], + "enable": "true", + "latest": "auto", + "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", + "latest": "auto", + "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", + "latest": "auto", + "prefix": "", + "suffix": "", + "value": "acustomtag2" + } + } as Tag, + false + ], + [ + `type=raw,enable=true,value=acustomtag4`, + { + type: Type.Raw, + attrs: { + "priority": DefaultPriorities[Type.Raw], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "", + "value": "acustomtag4" + } + } as Tag, + false + ], + [ + `type=raw,enable=false,value=acustomtag5`, + { + type: Type.Raw, + attrs: { + "priority": DefaultPriorities[Type.Raw], + "enable": "false", + "latest": "auto", + "prefix": "", + "suffix": "", + "value": "acustomtag5" + } + } as Tag, + false + ], + [ + `type=sha`, + { + type: Type.Sha, + attrs: { + "priority": DefaultPriorities[Type.Sha], + "enable": "true", + "latest": "auto", + "prefix": "sha-", + "suffix": "" + } + } as Tag, + false + ], + [ + `type=sha,prefix=`, + { + type: Type.Sha, + attrs: { + "priority": DefaultPriorities[Type.Sha], + "enable": "true", + "latest": "auto", + "prefix": "", + "suffix": "" + } + } as Tag, + false + ], + [ + `type=sha,enable=false`, + { + type: Type.Sha, + attrs: { + "priority": DefaultPriorities[Type.Sha], + "enable": "false", + "latest": "auto", + "prefix": "sha-", + "suffix": "" + } + } as Tag, + false + ] + ])('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 257170b1a..6ce2702d0 100644 --- a/action.yml +++ b/action.yml @@ -10,47 +10,10 @@ 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' - required: false - tag-custom-only: - description: 'Only use tag-custom as Docker tags' + tags: + description: 'List of tags as key-value pair (format: type=ref,...)' 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 aaef6d28b..e7c6901eb 100644 --- a/dist/index.js +++ b/dist/index.js @@ -56,17 +56,8 @@ 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), + labels: getInputList('labels', true), sepTags: core.getInput('sep-tags') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`, githubToken: core.getInput('github-token') @@ -226,7 +217,7 @@ function run() { core.endGroup(); core.setOutput('version', version.main || ''); // Docker tags - const tags = meta.tags(); + const tags = meta.getTags(); core.startGroup(`Docker tags`); for (let tag of tags) { core.info(tag); @@ -234,7 +225,7 @@ function run() { 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 +233,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 +284,270 @@ 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 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.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; } - } - 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.Ref: { + version = this.procRef(version, tag); + break; } - else { - tagMatch = version.main.match(this.inputs.tagMatch); + case tcl.Type.Edge: { + version = this.procEdge(version, tag); + break; } - if (tagMatch) { - version.main = tagMatch[this.inputs.tagMatchGroup]; - version.latest = this.inputs.tagLatest; + case tcl.Type.Raw: { + version = this.procRaw(version, tag); + 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'; + 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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["latest"] == "true"; + } + return version; + } + procSemver(version, tag) { + if (!/^refs\/tags\//.test(this.context.ref)) { + return version; + } + let 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 = tag.attrs["latest"] == "auto" ? latest : tag.attrs["latest"] == "true"; + } return version; } - tags() { + procMatch(version, tag) { + if (!/^refs\/tags\//.test(this.context.ref)) { + return version; + } + let 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 = tag.attrs["latest"] == "auto" ? latest : tag.attrs["latest"] == "true"; + } + return version; + } + procRef(version, tag) { + if (tag.attrs["event"] == tcl.RefEvent.Branch) { + return this.procRefBranch(version, tag); + } + else if (tag.attrs["event"] == tcl.RefEvent.Tag) { + return this.procRefTag(version, tag); + } + else if (tag.attrs["event"] == tcl.RefEvent.PR) { + return this.procRefPr(version, tag); + } + return version; + } + 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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["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 = tag.attrs["latest"] == "auto" ? true : tag.attrs["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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["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 = tag.attrs["latest"] == "auto" ? false : tag.attrs["latest"] == "true"; + } + return version; + } + setFlavor(val, tag) { + if (tag.attrs["prefix"].length > 0) { + val = `${tag.attrs["prefix"]}${val}`; + } + if (tag.attrs["suffix"].length > 0) { + val = `${val}${tag.attrs["suffix"]}`; + } + return val; + } + getTags() { if (!this.version.main) { return []; } @@ -399,13 +561,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 +576,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 +592,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 +609,188 @@ 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.RefEvents = exports.RefEvent = exports.Type = void 0; +const sync_1 = __importDefault(__webpack_require__(8750)); +var Type; +(function (Type) { + Type["Raw"] = "raw"; + Type["Schedule"] = "schedule"; + Type["Semver"] = "semver"; + Type["Match"] = "match"; + Type["Edge"] = "edge"; + Type["Ref"] = "ref"; + 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.RefEvents = Object.keys(RefEvent).map(key => RefEvent[key]); +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) { + 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 if (parts.length == 2) { + 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; + } + } + } + else { + throw new Error(`Invalid entry: ${field}`); + } + } + 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}`); + } + 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}`); + } + 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 (!exports.RefEvents.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("latest")) { + tag.attrs["latest"] = "auto"; + } + 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"]}`); + } + if (!["auto", "true", "false"].includes(tag.attrs["latest"])) { + throw new Error(`Invalid value for latest attribute: ${tag.attrs["latest"]}`); + } + 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 acf8cac0e..46f314767 100644 --- a/src/context.ts +++ b/src/context.ts @@ -8,17 +8,8 @@ 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[]; + labels: string[]; sepTags: string; sepLabels: string; githubToken: string; @@ -34,17 +25,8 @@ 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), + 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/main.ts b/src/main.ts index cb993450f..27611091d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -35,7 +35,7 @@ async function run() { core.setOutput('version', version.main || ''); // Docker tags - const tags: Array = meta.tags(); + const tags: Array = meta.getTags(); core.startGroup(`Docker tags`); for (let tag of tags) { core.info(tag); @@ -44,7 +44,7 @@ async function run() { 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 +53,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 592071d6e..844c4e74d 100644 --- a/src/meta.ts +++ b/src/meta.ts @@ -4,6 +4,7 @@ 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 core from '@actions/core'; import {Context} from '@actions/github/lib/context'; import {ReposGetResponseData} from '@octokit/types'; @@ -11,7 +12,7 @@ import {ReposGetResponseData} from '@octokit/types'; export interface Version { main: string | undefined; partial: string[]; - latest: boolean; + latest: boolean | undefined; } export class Meta { @@ -20,96 +21,292 @@ export class Meta { private readonly inputs: Inputs; private readonly context: Context; private readonly repo: ReposGetResponseData; + private readonly tags: tcl.Tag[]; 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.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: { + version = this.procRef(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.Edge: { + version = this.procEdge(version, tag); + break; } - if (tagMatch) { - version.main = tagMatch[this.inputs.tagMatchGroup]; - version.latest = this.inputs.tagLatest; + case tcl.Type.Raw: { + version = this.procRaw(version, tag); + 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'; + } + + 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); } - } 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); + }); + + if (version.main == undefined) { + version.main = vraw; + } else if (vraw !== version.main) { + version.partial.push(vraw); + } + if (version.latest == undefined) { + version.latest = tag.attrs['latest'] == 'auto' ? false : tag.attrs['latest'] == 'true'; + } + + return version; + } + + private procSemver(version: Version, tag: tcl.Tag): Version { + if (!/^refs\/tags\//.test(this.context.ref)) { + return version; + } + let 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 = tag.attrs['latest'] == 'auto' ? latest : tag.attrs['latest'] == 'true'; } - version.partial = version.partial.filter((item, index) => version.partial.indexOf(item) === index); return version; } - public tags(): Array { + private procMatch(version: Version, tag: tcl.Tag): Version { + if (!/^refs\/tags\//.test(this.context.ref)) { + return version; + } + let 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 = tag.attrs['latest'] == 'auto' ? latest : tag.attrs['latest'] == 'true'; + } + + return version; + } + + private procRef(version: Version, tag: tcl.Tag): Version { + if (tag.attrs['event'] == tcl.RefEvent.Branch) { + return this.procRefBranch(version, tag); + } else if (tag.attrs['event'] == tcl.RefEvent.Tag) { + return this.procRefTag(version, tag); + } else if (tag.attrs['event'] == tcl.RefEvent.PR) { + return this.procRefPr(version, tag); + } + 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 = tag.attrs['latest'] == 'auto' ? false : tag.attrs['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 = tag.attrs['latest'] == 'auto' ? true : tag.attrs['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 = tag.attrs['latest'] == 'auto' ? false : tag.attrs['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 = tag.attrs['latest'] == 'auto' ? false : tag.attrs['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 = tag.attrs['latest'] == 'auto' ? false : tag.attrs['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 = tag.attrs['latest'] == 'auto' ? false : tag.attrs['latest'] == 'true'; + } + + return version; + } + + private setFlavor(val: string, tag: tcl.Tag): string { + if (tag.attrs['prefix'].length > 0) { + val = `${tag.attrs['prefix']}${val}`; + } + if (tag.attrs['suffix'].length > 0) { + val = `${val}${tag.attrs['suffix']}`; + } + return val; + } + + public getTags(): Array { if (!this.version.main) { return []; } @@ -124,14 +321,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 +336,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 +357,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 000000000..2d0b6e882 --- /dev/null +++ b/src/tag.ts @@ -0,0 +1,174 @@ +import csvparse from 'csv-parse/lib/sync'; + +export enum Type { + Raw = 'raw', + Schedule = 'schedule', + Semver = 'semver', + Match = 'match', + Edge = 'edge', + Ref = 'ref', + Sha = 'sha' +} + +export interface Tag { + type: Type; + attrs: Record; +} + +export enum RefEvent { + Branch = 'branch', + Tag = 'tag', + PR = 'pr' +} + +export const RefEvents = Object.keys(RefEvent).map(key => RefEvent[key]); + +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) { + 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 if (parts.length == 2) { + 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; + } + } + } else { + throw new Error(`Invalid entry: ${field}`); + } + } + + 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}`); + } + 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}`); + } + 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 (!RefEvents.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('latest')) { + tag.attrs['latest'] = 'auto'; + } + 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']}`); + } + if (!['auto', 'true', 'false'].includes(tag.attrs['latest'])) { + throw new Error(`Invalid value for latest attribute: ${tag.attrs['latest']}`); + } + + return tag; +}