Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

attribute to enable/disable images #193

Merged
merged 2 commits into from Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 17 additions & 1 deletion .github/workflows/ci.yml
Expand Up @@ -146,6 +146,21 @@ jobs:
prefix=foo-
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:
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -226,7 +241,8 @@ jobs:
id: docker_meta
uses: ./
with:
images: ${{ env.DOCKER_IMAGE }}
images: |
${{ env.DOCKER_IMAGE }}
tags: |
type=schedule
type=ref,event=branch
Expand Down
65 changes: 43 additions & 22 deletions README.md
Expand Up @@ -19,6 +19,7 @@ ___
* [Customizing](#customizing)
* [inputs](#inputs)
* [outputs](#outputs)
* [`images` input](#images-input)
* [`flavor` input](#flavor-input)
* [`tags` input](#tags-input)
* [`type=schedule`](#typeschedule)
Expand Down Expand Up @@ -125,7 +126,8 @@ jobs:
id: meta
uses: docker/metadata-action@v3
with:
images: name/app
images: |
name/app
tags: |
type=ref,event=branch
type=ref,event=pr
Expand Down Expand Up @@ -202,7 +204,8 @@ jobs:
id: meta
uses: docker/metadata-action@v3
with:
images: name/app
images: |
name/app
tags: |
type=ref,event=branch
type=ref,event=pr
Expand Down Expand Up @@ -264,33 +267,51 @@ Following inputs can be used as `step.with` keys
> org.opencontainers.image.vendor=MyCompany
> ```

> `CSV` type is a comma-delimited string
> ```yaml
> images: name/app,ghcr.io/name/app
> ```

| Name | Type | Description |
|---------------------|----------|------------------------------------|
| `images` | List/CSV | 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 |
| `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`) |
| Name | Type | Description |
|---------------------|--------|----------------------------------------------------------|
| `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 |
| `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

Following outputs are available

| Name | Type | Description |
|---------------|---------|---------------------------------------|
| `version` | String | Docker image version |
| `tags` | String | Docker tags |
| `labels` | String | Docker labels |
| `json` | String | JSON output of tags and labels |
| Name | Type | Description |
|---------------|---------|-------------------------------------------------------------------------------|
| `version` | String | Docker image version |
| `tags` | String | Docker tags |
| `labels` | String | Docker labels |
| `json` | String | JSON output of tags and labels |
| `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` defines a global behavior for [`tags`](#tags-input):
Expand Down
101 changes: 101 additions & 0 deletions __tests__/image.test.ts
@@ -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);
}
});
});
33 changes: 33 additions & 0 deletions __tests__/meta.test.ts
Expand Up @@ -693,6 +693,39 @@ describe('push', () => {
"org.opencontainers.image.revision=860c1904a1ce19322e91ac35af1ab07466440c37",
"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);
});
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/context.ts
Expand Up @@ -27,7 +27,7 @@ export function tmpDir(): string {

export function getInputs(): Inputs {
return {
images: getInputList('images'),
images: getInputList('images', true),
tags: getInputList('tags', true),
flavor: getInputList('flavor', true),
labels: getInputList('labels', true),
Expand Down
86 changes: 86 additions & 0 deletions src/image.ts
@@ -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('=')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think splitting by = makes something like this invalid

name=lucacome/test-foo2,enable=${{ github.event_name == 'pull_request' }}

not sure if you ever meant for it to be supported and I guess I could always have the same condition with other operators like startsWith without using =

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${{ github.event_name == 'pull_request' }} is interpreted upstream by the GitHub Runner logic so it would produce name=lucacome/test-foo2,enable=<true|false> before being passed to the action.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah interesting, I thought that was the cause for Error: Image name attribute empty: enable=true that I'm seeing, but then I'm not sure I understand what this error means 😄

I have this and I assumed it wasn't coming from the first one, as it would get the default enable=true, and that it was the second line causing the error.

images: |
  name=lucacome/test-foo
  name=lucacome/test-foo2,enable=${{ github.event_name == 'pull_request' }}
  name=ghcr.io/lucacome/workflows,enable=${{ startsWith(github.ref, 'refs/tags/') && contains(matrix.target, 'aws') }}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sounds like a bug then, will take a look, thanks for your feedback

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucacome Hum I was not able to repro, do you have a link to your repo?

Copy link

@lucacome lucacome Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucacome Should be fixed, let me know if it looks good to you. Thanks!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! 🚀

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