Merge pull request #193 from crazy-max/images-opts

attribute to enable/disable images
This commit is contained in:
CrazyMax 2022-04-28 13:19:16 +02:00 committed by GitHub
commit b2391d37b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 302 additions and 33 deletions

View file

@ -146,6 +146,21 @@ jobs:
prefix=foo- prefix=foo-
suffix=-bar suffix=-bar
images:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Docker meta
uses: ./
with:
images: |
name=${{ env.DOCKER_IMAGE }}
name=ghcr.io/name/app,enable=${{ github.event_name == 'pull_request' }}
name=ghcr.io/name/release,enable=${{ startsWith(github.ref, 'refs/tags/') }}
labels: labels:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -226,7 +241,8 @@ jobs:
id: docker_meta id: docker_meta
uses: ./ uses: ./
with: with:
images: ${{ env.DOCKER_IMAGE }} images: |
${{ env.DOCKER_IMAGE }}
tags: | tags: |
type=schedule type=schedule
type=ref,event=branch type=ref,event=branch

View file

@ -19,6 +19,7 @@ ___
* [Customizing](#customizing) * [Customizing](#customizing)
* [inputs](#inputs) * [inputs](#inputs)
* [outputs](#outputs) * [outputs](#outputs)
* [`images` input](#images-input)
* [`flavor` input](#flavor-input) * [`flavor` input](#flavor-input)
* [`tags` input](#tags-input) * [`tags` input](#tags-input)
* [`type=schedule`](#typeschedule) * [`type=schedule`](#typeschedule)
@ -125,7 +126,8 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v3
with: with:
images: name/app images: |
name/app
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
@ -202,7 +204,8 @@ jobs:
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v3
with: with:
images: name/app images: |
name/app
tags: | tags: |
type=ref,event=branch type=ref,event=branch
type=ref,event=pr type=ref,event=pr
@ -264,33 +267,51 @@ Following inputs can be used as `step.with` keys
> org.opencontainers.image.vendor=MyCompany > org.opencontainers.image.vendor=MyCompany
> ``` > ```
> `CSV` type is a comma-delimited string | Name | Type | Description |
> ```yaml |---------------------|--------|----------------------------------------------------------|
> images: name/app,ghcr.io/name/app | `images` | List | List of Docker images to use as base name for tags |
> ``` | `tags` | List | List of [tags](#tags-input) as key-value pair attributes |
| `flavor` | List | [Flavor](#flavor-input) to apply |
| Name | Type | Description | | `labels` | List | List of custom labels |
|---------------------|----------|------------------------------------| | `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `images` | List/CSV | List of Docker images to use as base name for tags | | `sep-labels` | String | Separator to use for labels output (default `\n`) |
| `tags` | List | List of [tags](#tags-input) as key-value pair attributes | | `bake-target` | String | Bake target name (default `docker-metadata-action`) |
| `flavor` | List | [Flavor](#flavor-input) to apply |
| `labels` | List | List of custom labels |
| `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) |
| `bake-target` | String | Bake target name (default `docker-metadata-action`) |
### outputs ### outputs
Following outputs are available Following outputs are available
| Name | Type | Description | | Name | Type | Description |
|---------------|---------|---------------------------------------| |---------------|---------|-------------------------------------------------------------------------------|
| `version` | String | Docker image version | | `version` | String | Docker image version |
| `tags` | String | Docker tags | | `tags` | String | Docker tags |
| `labels` | String | Docker labels | | `labels` | String | Docker labels |
| `json` | String | JSON output of tags and labels | | `json` | String | JSON output of tags and 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 |
## `images` input
`images` defines a list of Docker images to use as base name for [`tags`](#tags-input):
```yaml
images: |
name/foo
ghcr.io/name/bar
# or
name=name/foo
name=ghcr.io/name/bar
```
Extended attributes and default values:
```yaml
images: |
name=,enable=true
```
* `name=<string>` image base name
* `enable=<true|false>` enable this entry (default `true`)
## `flavor` input ## `flavor` input
`flavor` defines a global behavior for [`tags`](#tags-input): `flavor` defines a global behavior for [`tags`](#tags-input):

101
__tests__/image.test.ts Normal file
View file

@ -0,0 +1,101 @@
import {describe, expect, test} from '@jest/globals';
import {Transform, Image} from '../src/image';
describe('transform', () => {
// prettier-ignore
test.each([
[
[
`name/foo`
],
[
{
name: `name/foo`,
enable: true,
}
] as Image[],
false
],
[
[
`name/foo,name/bar`
],
[
{
name: `name/foo`,
enable: true,
},
{
name: `name/bar`,
enable: true,
}
] as Image[],
false
],
[
[
`name/foo`,
`name/bar`
],
[
{
name: `name/foo`,
enable: true,
},
{
name: `name/bar`,
enable: true,
}
] as Image[],
false
],
[
[
`name=name/bar`,
`name/foo,enable=false`,
`name=ghcr.io/name/foo,enable=true`
],
[
{
name: `name/bar`,
enable: true,
},
{
name: `name/foo`,
enable: false,
},
{
name: `ghcr.io/name/foo`,
enable: true,
},
] as Image[],
false
],
[
[`value=name/foo`], undefined, true
],
[
[`name/foo,enable=bar`], undefined, true
],
[
[`name/foo,bar=baz`], undefined, true
],
[
[`name=,enable=true`], undefined, true
],
[
[`name/foo,name=name/bar,enable=true`], undefined, true
]
])('given %p', async (l: string[], expected: Image[], invalid: boolean) => {
try {
const images = Transform(l);
expect(images).toEqual(expected);
} catch (err) {
if (!invalid) {
console.error(err);
}
// eslint-disable-next-line jest/no-conditional-expect
expect(true).toBe(invalid);
}
});
});

View file

@ -693,6 +693,39 @@ describe('push', () => {
"org.opencontainers.image.revision=860c1904a1ce19322e91ac35af1ab07466440c37", "org.opencontainers.image.revision=860c1904a1ce19322e91ac35af1ab07466440c37",
"org.opencontainers.image.licenses=MIT" "org.opencontainers.image.licenses=MIT"
] ]
],
[
'push20',
'event_push_dev.env',
{
images: [
'org/app',
'ghcr.io/user/app,enable=false'
],
tags: [
`type=edge,branch=master`,
`type=ref,event=branch,enable=false`,
`type=sha,format=long`
],
} as Inputs,
{
main: 'sha-860c1904a1ce19322e91ac35af1ab07466440c37',
partial: [],
latest: false
} as Version,
[
'org/app:sha-860c1904a1ce19322e91ac35af1ab07466440c37'
],
[
"org.opencontainers.image.title=Hello-World",
"org.opencontainers.image.description=This your first repo!",
"org.opencontainers.image.url=https://github.com/octocat/Hello-World",
"org.opencontainers.image.source=https://github.com/octocat/Hello-World",
"org.opencontainers.image.version=sha-860c1904a1ce19322e91ac35af1ab07466440c37",
"org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision=860c1904a1ce19322e91ac35af1ab07466440c37",
"org.opencontainers.image.licenses=MIT"
]
] ]
])('given %p with %p event', tagsLabelsTest); ])('given %p with %p event', tagsLabelsTest);
}); });

2
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View file

@ -27,7 +27,7 @@ export function tmpDir(): string {
export function getInputs(): Inputs { export function getInputs(): Inputs {
return { return {
images: getInputList('images'), images: getInputList('images', true),
tags: getInputList('tags', true), tags: getInputList('tags', true),
flavor: getInputList('flavor', true), flavor: getInputList('flavor', true),
labels: getInputList('labels', true), labels: getInputList('labels', true),

86
src/image.ts Normal file
View file

@ -0,0 +1,86 @@
import {parse} from 'csv-parse/sync';
import * as core from '@actions/core';
export interface Image {
name: string;
enable: boolean;
}
export function Transform(inputs: string[]): Image[] {
let images: Image[] = [];
// backward compatibility with old format
if (inputs.length == 1) {
let newformat = false;
const fields = parse(inputs[0], {
relaxColumnCount: true,
skipEmptyLines: true
})[0];
for (const field of fields) {
const parts = field
.toString()
.split('=')
.map(item => item.trim());
if (parts.length == 1) {
images.push({name: parts[0].toLowerCase(), enable: true});
} else {
newformat = true;
break;
}
}
if (!newformat) {
return output(images);
}
}
images = [];
for (const input of inputs) {
const image: Image = {name: '', enable: true};
const fields = parse(input, {
relaxColumnCount: true,
skipEmptyLines: true
})[0];
for (const field of fields) {
const parts = field
.toString()
.split('=')
.map(item => item.trim());
if (parts.length == 1) {
image.name = parts[0].toLowerCase();
} else {
const key = parts[0].toLowerCase();
const value = parts[1];
switch (key) {
case 'name': {
image.name = value.toLowerCase();
break;
}
case 'enable': {
if (!['true', 'false'].includes(value)) {
throw new Error(`Invalid enable attribute value: ${input}`);
}
image.enable = /true/i.test(value);
break;
}
default: {
throw new Error(`Unknown image attribute: ${input}`);
}
}
}
}
if (image.name.length == 0) {
throw new Error(`Image name attribute empty: ${input}`);
}
images.push(image);
}
return output(images);
}
function output(images: Image[]): Image[] {
core.startGroup(`Processing images input`);
for (const image of images) {
core.info(`name=${image.name},enable=${image.enable}`);
}
core.endGroup();
return images;
}

View file

@ -6,6 +6,7 @@ import * as pep440 from '@renovate/pep440';
import * as semver from 'semver'; import * as semver from 'semver';
import {Inputs, tmpDir} from './context'; import {Inputs, tmpDir} from './context';
import {ReposGetResponseData} from './github'; import {ReposGetResponseData} from './github';
import * as icl from './image';
import * as tcl from './tag'; import * as tcl from './tag';
import * as fcl from './flavor'; import * as fcl from './flavor';
import * as core from '@actions/core'; import * as core from '@actions/core';
@ -23,6 +24,7 @@ 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 images: icl.Image[];
private readonly tags: tcl.Tag[]; private readonly tags: tcl.Tag[];
private readonly flavor: fcl.Flavor; private readonly flavor: fcl.Flavor;
private readonly date: Date; private readonly date: Date;
@ -38,6 +40,7 @@ export class Meta {
this.inputs = inputs; this.inputs = inputs;
this.context = context; this.context = context;
this.repo = repo; this.repo = repo;
this.images = icl.Transform(inputs.images);
this.tags = tcl.Transform(inputs.tags); this.tags = tcl.Transform(inputs.tags);
this.flavor = fcl.Transform(inputs.flavor); this.flavor = fcl.Transform(inputs.flavor);
this.date = new Date(); this.date = new Date();
@ -404,20 +407,29 @@ export class Meta {
}); });
} }
private getImageNames(): Array<string> {
const images: Array<string> = [];
for (const image of this.images) {
if (!image.enable) {
continue;
}
images.push(image.name);
}
return images;
}
public getTags(): Array<string> { public getTags(): Array<string> {
if (!this.version.main) { if (!this.version.main) {
return []; return [];
} }
const tags: Array<string> = []; const tags: Array<string> = [];
for (const image of this.inputs.images) { for (const imageName of this.getImageNames()) {
const imageLc = image.toLowerCase(); tags.push(`${imageName}:${this.version.main}`);
tags.push(`${imageLc}:${this.version.main}`);
for (const partial of this.version.partial) { for (const partial of this.version.partial) {
tags.push(`${imageLc}:${partial}`); tags.push(`${imageName}:${partial}`);
} }
if (this.version.latest) { if (this.version.latest) {
tags.push(`${imageLc}:${this.flavor.prefixLatest ? this.flavor.prefix : ''}latest${this.flavor.suffixLatest ? this.flavor.suffix : ''}`); tags.push(`${imageName}:${this.flavor.prefixLatest ? this.flavor.prefix : ''}latest${this.flavor.suffixLatest ? this.flavor.suffix : ''}`);
} }
} }
return tags; return tags;
@ -470,7 +482,7 @@ export class Meta {
return res; return res;
}, {}), }, {}),
args: { args: {
DOCKER_META_IMAGES: this.inputs.images.join(','), DOCKER_META_IMAGES: this.getImageNames().join(','),
DOCKER_META_VERSION: this.version.main DOCKER_META_VERSION: this.version.main
} }
} }