mirror of
https://gitea.com/docker/metadata-action.git
synced 2024-11-25 21:49:33 +01:00
Merge pull request #193 from crazy-max/images-opts
attribute to enable/disable images
This commit is contained in:
commit
b2391d37b4
9 changed files with 302 additions and 33 deletions
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
|
@ -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
|
||||||
|
|
65
README.md
65
README.md
|
@ -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
101
__tests__/image.test.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -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
2
dist/index.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/index.js.map
generated
vendored
2
dist/index.js.map
generated
vendored
File diff suppressed because one or more lines are too long
|
@ -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
86
src/image.ts
Normal 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;
|
||||||
|
}
|
26
src/meta.ts
26
src/meta.ts
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue