From 5e6d5157fbcb3bd0f57677f608369428a205f59d Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Fri, 7 May 2021 21:53:30 +0200 Subject: [PATCH] Handle global expressions (#71) Co-authored-by: CrazyMax --- README.md | 20 +++++ __tests__/meta.test.ts | 62 +++++++++++++++- dist/index.js | 159 ++++++++++++++------------------------- src/meta.ts | 164 ++++++++++++++--------------------------- 4 files changed, 194 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index d53c0527e..64285abde 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ ___ * [`type=sha`](#typesha) * [Notes](#notes) * [Latest tag](#latest-tag) + * [Global expressions](#global-expressions) * [Overwrite labels](#overwrite-labels) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Contributing](#contributing) @@ -554,6 +555,25 @@ tags: | * [`type=semver,pattern=...`](#typesemver) * [`type=match,pattern=...`](#typematch) +### Global expressions + +The following [Handlebars template](https://handlebarsjs.com/guide/) expressions for `prefix`, `suffix` and `value` +attributes are available: + +| Expression | Output | +|--------------------------|----------------------| +| `{{branch}}` | `master` | +| `{{tag}}` | `v1.2.3` | +| `{{sha}}` | `90dd603` | + +```yaml +tags: | + # dynamically set the branch name as a prefix + type=sha,prefix={{branch}}- + # dynamically set the branch name and sha as a custom tag + type=raw,value=mytag-{{branch}}-{{sha}} +``` + ### Overwrite labels If some of the [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md) diff --git a/__tests__/meta.test.ts b/__tests__/meta.test.ts index e815847e3..9fbb40e92 100644 --- a/__tests__/meta.test.ts +++ b/__tests__/meta.test.ts @@ -89,7 +89,8 @@ describe('null', () => { { images: ['user/app'], tags: [ - `type=sha` + `type=sha`, + `type=raw,{{branch}}`, ] } as Inputs, { @@ -586,6 +587,36 @@ describe('push', () => { "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071", "org.opencontainers.image.licenses=MIT" ] + ], + [ + 'push17', + 'event_push_defbranch.env', + { + images: ['user/app'], + tags: [ + `type=raw,value=mytag-{{branch}}`, + `type=raw,value=mytag-{{tag}}` + ], + } as Inputs, + { + main: 'mytag-master', + partial: ['mytag-'], + latest: false + } as Version, + [ + 'user/app:mytag-master', + 'user/app:mytag-' + ], + [ + "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=mytag-master", + "org.opencontainers.image.created=2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses=MIT" + ] ] ])('given %p with %p event', tagsLabelsTest); }); @@ -1210,6 +1241,35 @@ describe('tag', () => { "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071", "org.opencontainers.image.licenses=MIT" ] + ], + [ + 'tag20', + 'event_tag_v1.1.1.env', + { + images: ['org/app', 'ghcr.io/user/app'], + tags: [ + `type=raw,{{tag}}-{{sha}}-foo` + ] + } as Inputs, + { + main: 'v1.1.1-90dd603-foo', + partial: [], + latest: false + } as Version, + [ + 'org/app:v1.1.1-90dd603-foo', + 'ghcr.io/user/app:v1.1.1-90dd603-foo' + ], + [ + "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=v1.1.1-90dd603-foo", + "org.opencontainers.image.created=2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses=MIT" + ] ] ])('given %p with %p event', tagsLabelsTest); }); diff --git a/dist/index.js b/dist/index.js index ed5b6d508..3ef66efa6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -393,7 +393,7 @@ class Meta { latest: undefined }; for (const tag of this.tags) { - if (tag.attrs['enable'] == 'false') { + if (!/true/i.test(tag.attrs['enable'])) { continue; } switch (tag.type) { @@ -446,21 +446,12 @@ class Meta { return version; } const currentDate = this.date; - const vraw = this.setFlavor(handlebars.compile(tag.attrs['pattern'])({ + const vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])({ date: function (format) { return moment_1.default(currentDate).utc().format(format); } }), 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; + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } procSemver(version, tag) { if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) { @@ -468,7 +459,7 @@ class Meta { } let vraw; if (tag.attrs['value'].length > 0) { - vraw = tag.attrs['value']; + vraw = this.setGlobalExp(tag.attrs['value']); } else { vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); @@ -482,22 +473,13 @@ class Meta { includePrerelease: true }); if (semver.prerelease(vraw)) { - vraw = this.setFlavor(handlebars.compile('{{version}}')(sver), tag); + vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag); } else { - vraw = this.setFlavor(handlebars.compile(tag.attrs['pattern'])(sver), tag); + vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag); 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; + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true'); } procMatch(version, tag) { if (!/^refs\/tags\//.test(this.context.ref) && tag.attrs['value'].length == 0) { @@ -505,7 +487,7 @@ class Meta { } let vraw; if (tag.attrs['value'].length > 0) { - vraw = tag.attrs['value']; + vraw = this.setGlobalExp(tag.attrs['value']); } else { vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); @@ -527,66 +509,29 @@ class Meta { core.warning(`Group ${tag.attrs['group']} does not exist for ${tag.attrs['pattern']} pattern.`); return version; } - vraw = this.setFlavor(tmatch[tag.attrs['group']], tag); - 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; + vraw = this.setValue(tmatch[tag.attrs['group']], tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true'); } procRefBranch(version, tag) { if (!/^refs\/heads\//.test(this.context.ref)) { return version; } - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } procRefTag(version, tag) { if (!/^refs\/tags\//.test(this.context.ref)) { return version; } - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true'); } procRefPr(version, tag) { if (!/^refs\/pull\//.test(this.context.ref)) { return version; } - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } procEdge(version, tag) { if (!/^refs\/heads\//.test(this.context.ref)) { @@ -599,62 +544,70 @@ class Meta { 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; + const vraw = this.setValue(val, tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } procRaw(version, tag) { - const vraw = this.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; + const vraw = this.setValue(this.setGlobalExp(tag.attrs['value']), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } procSha(version, tag) { if (!this.context.sha) { return version; } - const vraw = this.setFlavor(this.context.sha.substr(0, 7), tag); + const vraw = this.setValue(this.context.sha.substr(0, 7), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); + } + static setVersion(version, val, latest) { + if (val.length == 0) { + return version; + } if (version.main == undefined) { - version.main = vraw; + version.main = val; } - else if (vraw !== version.main) { - version.partial.push(vraw); + else if (val !== version.main) { + version.partial.push(val); } if (version.latest == undefined) { - version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'; + version.latest = latest; } return version; } - setFlavor(val, tag) { + setValue(val, tag) { if (tag.attrs.hasOwnProperty('prefix')) { - val = `${tag.attrs['prefix']}${val}`; + val = `${this.setGlobalExp(tag.attrs['prefix'])}${val}`; } else if (this.flavor.prefix.length > 0) { - val = `${this.flavor.prefix}${val}`; + val = `${this.setGlobalExp(this.flavor.prefix)}${val}`; } if (tag.attrs.hasOwnProperty('suffix')) { - val = `${val}${tag.attrs['suffix']}`; + val = `${val}${this.setGlobalExp(tag.attrs['suffix'])}`; } else if (this.flavor.suffix.length > 0) { - val = `${val}${this.flavor.suffix}`; + val = `${val}${this.setGlobalExp(this.flavor.suffix)}`; } return val; } + setGlobalExp(val) { + const ctx = this.context; + return handlebars.compile(val)({ + branch: function () { + if (!/^refs\/heads\//.test(ctx.ref)) { + return ''; + } + return ctx.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'); + }, + tag: function () { + if (!/^refs\/tags\//.test(ctx.ref)) { + return ''; + } + return ctx.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); + }, + sha: function () { + return ctx.sha.substr(0, 7); + } + }); + } getTags() { if (!this.version.main) { return []; diff --git a/src/meta.ts b/src/meta.ts index e16ec9027..a0b1daaf5 100644 --- a/src/meta.ts +++ b/src/meta.ts @@ -44,7 +44,7 @@ export class Meta { }; for (const tag of this.tags) { - if (tag.attrs['enable'] == 'false') { + if (!/true/i.test(tag.attrs['enable'])) { continue; } switch (tag.type) { @@ -99,7 +99,7 @@ export class Meta { } const currentDate = this.date; - const vraw = this.setFlavor( + const vraw = this.setValue( handlebars.compile(tag.attrs['pattern'])({ date: function (format) { return moment(currentDate).utc().format(format); @@ -108,16 +108,7 @@ export class Meta { 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; + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } private procSemver(version: Version, tag: tcl.Tag): Version { @@ -127,7 +118,7 @@ export class Meta { let vraw: string; if (tag.attrs['value'].length > 0) { - vraw = tag.attrs['value']; + vraw = this.setGlobalExp(tag.attrs['value']); } else { vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); } @@ -141,21 +132,13 @@ export class Meta { includePrerelease: true }); if (semver.prerelease(vraw)) { - vraw = this.setFlavor(handlebars.compile('{{version}}')(sver), tag); + vraw = this.setValue(handlebars.compile('{{version}}')(sver), tag); } else { - vraw = this.setFlavor(handlebars.compile(tag.attrs['pattern'])(sver), tag); + vraw = this.setValue(handlebars.compile(tag.attrs['pattern'])(sver), tag); 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; + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? latest : this.flavor.latest == 'true'); } private procMatch(version: Version, tag: tcl.Tag): Version { @@ -165,7 +148,7 @@ export class Meta { let vraw: string; if (tag.attrs['value'].length > 0) { - vraw = tag.attrs['value']; + vraw = this.setGlobalExp(tag.attrs['value']); } else { vraw = this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); } @@ -187,73 +170,32 @@ export class Meta { return version; } - vraw = this.setFlavor(tmatch[tag.attrs['group']], tag); - 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; + vraw = this.setValue(tmatch[tag.attrs['group']], tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true'); } private procRefBranch(version: Version, tag: tcl.Tag): Version { if (!/^refs\/heads\//.test(this.context.ref)) { return version; } - - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } private procRefTag(version: Version, tag: tcl.Tag): Version { if (!/^refs\/tags\//.test(this.context.ref)) { return version; } - - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? true : this.flavor.latest == 'true'); } private procRefPr(version: Version, tag: tcl.Tag): Version { if (!/^refs\/pull\//.test(this.context.ref)) { return version; } - - const vraw = this.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; + const vraw = this.setValue(this.context.ref.replace(/^refs\/pull\//g, '').replace(/\/merge$/g, ''), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } private procEdge(version: Version, tag: tcl.Tag): Version { @@ -269,65 +211,73 @@ export class Meta { 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; + const vraw = this.setValue(val, tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } private procRaw(version: Version, tag: tcl.Tag): Version { - const vraw = this.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; + const vraw = this.setValue(this.setGlobalExp(tag.attrs['value']), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); } private procSha(version: Version, tag: tcl.Tag): Version { if (!this.context.sha) { return version; } + const vraw = this.setValue(this.context.sha.substr(0, 7), tag); + return Meta.setVersion(version, vraw, this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'); + } - const vraw = this.setFlavor(this.context.sha.substr(0, 7), tag); + private static setVersion(version: Version, val: string, latest: boolean): Version { + if (val.length == 0) { + return version; + } if (version.main == undefined) { - version.main = vraw; - } else if (vraw !== version.main) { - version.partial.push(vraw); + version.main = val; + } else if (val !== version.main) { + version.partial.push(val); } if (version.latest == undefined) { - version.latest = this.flavor.latest == 'auto' ? false : this.flavor.latest == 'true'; + version.latest = latest; } - return version; } - private setFlavor(val: string, tag: tcl.Tag): string { + private setValue(val: string, tag: tcl.Tag): string { if (tag.attrs.hasOwnProperty('prefix')) { - val = `${tag.attrs['prefix']}${val}`; + val = `${this.setGlobalExp(tag.attrs['prefix'])}${val}`; } else if (this.flavor.prefix.length > 0) { - val = `${this.flavor.prefix}${val}`; + val = `${this.setGlobalExp(this.flavor.prefix)}${val}`; } if (tag.attrs.hasOwnProperty('suffix')) { - val = `${val}${tag.attrs['suffix']}`; + val = `${val}${this.setGlobalExp(tag.attrs['suffix'])}`; } else if (this.flavor.suffix.length > 0) { - val = `${val}${this.flavor.suffix}`; + val = `${val}${this.setGlobalExp(this.flavor.suffix)}`; } return val; } + private setGlobalExp(val): string { + const ctx = this.context; + return handlebars.compile(val)({ + branch: function () { + if (!/^refs\/heads\//.test(ctx.ref)) { + return ''; + } + return ctx.ref.replace(/^refs\/heads\//g, '').replace(/[^a-zA-Z0-9._-]+/g, '-'); + }, + tag: function () { + if (!/^refs\/tags\//.test(ctx.ref)) { + return ''; + } + return ctx.ref.replace(/^refs\/tags\//g, '').replace(/\//g, '-'); + }, + sha: function () { + return ctx.sha.substr(0, 7); + } + }); + } + public getTags(): Array { if (!this.version.main) { return [];