Add label-custom input (#35)

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2020-12-23 22:09:38 +01:00 committed by GitHub
parent c48ac80f46
commit 3479bd5aaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1671 additions and 68 deletions

View File

@ -2,7 +2,8 @@
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license)
to the public under the [project's open source license](LICENSE).
## Submitting a pull request ## Submitting a pull request
@ -16,21 +17,6 @@ Contributions to this project are [released](https://help.github.com/articles/gi
8. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-docker-meta/compare) 8. Push to your fork and [submit a pull request](https://github.com/crazy-max/ghaction-docker-meta/compare)
9. Pat your self on the back and wait for your pull request to be reviewed and merged. 9. Pat your self on the back and wait for your pull request to be reviewed and merged.
## Container based developer flow
If you don't want to maintain a Node developer environment that fits this project you can use containerized commands instead of invoking yarn directly.
```
# format code and build javascript artifacts
docker buildx bake pre-checkin
# validate all code has correctly formatted and built
docker buildx bake validate
# run tests
docker buildx bake test
```
Here are a few things you can do that will increase the likelihood of your pull request being accepted: Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Write tests. - Write tests.

View File

@ -105,6 +105,25 @@ jobs:
{{major}}.{{minor}}.{{patch}} {{major}}.{{minor}}.{{patch}}
tag-latest: ${{ matrix.tag-latest }} tag-latest: ${{ matrix.tag-latest }}
label-custom:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
uses: ./
with:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
label-custom: |
maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description
org.opencontainers.image.vendor=MyCompany
docker-push: docker-push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:

View File

@ -31,7 +31,7 @@ ___
* [Schedule tag](#schedule-tag) * [Schedule tag](#schedule-tag)
* [Overwrite labels](#overwrite-labels) * [Overwrite labels](#overwrite-labels)
* [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot)
* [How can I help?](#how-can-i-help) * [Contributing](#contributing)
* [License](#license) * [License](#license)
## Features ## Features
@ -235,6 +235,19 @@ jobs:
Following inputs can be used as `step.with` keys Following inputs can be used as `step.with` keys
> `List` type is a newline-delimited string
> ```yaml
> label-custom: |
> org.opencontainers.image.title=MyCustomTitle
> org.opencontainers.image.description=Another description
> org.opencontainers.image.vendor=MyCompany
> ```
> `CSV` type is a comma-delimited string
> ```yaml
> images: name/app,ghcr.io/name/app
> ```
| Name | Type | Description | | Name | Type | Description |
|---------------------|----------|------------------------------------| |---------------------|----------|------------------------------------|
| `images` | List/CSV | List of Docker images to use as base name for tags | | `images` | List/CSV | List of Docker images to use as base name for tags |
@ -248,11 +261,10 @@ Following inputs can be used as `step.with` keys
| `tag-schedule` | String | [Template](#schedule-tag) to apply to schedule tag (default `nightly`) | | `tag-schedule` | String | [Template](#schedule-tag) to apply to schedule tag (default `nightly`) |
| `tag-custom` | List/CSV | List of custom tags | | `tag-custom` | List/CSV | List of custom tags |
| `tag-custom-only` | Bool | Only use `tag-custom` as Docker tags | | `tag-custom-only` | Bool | Only use `tag-custom` as Docker tags |
| `label-custom` | List | List of custom labels |
| `sep-tags` | String | Separator to use for tags output (default `\n`) | | `sep-tags` | String | Separator to use for tags output (default `\n`) |
| `sep-labels` | String | Separator to use for labels output (default `\n`) | | `sep-labels` | String | Separator to use for labels output (default `\n`) |
> List/CSV type can be a newline or comma delimited string
> `tag-semver` and `tag-match` are mutually exclusive > `tag-semver` and `tag-match` are mutually exclusive
### outputs ### outputs
@ -326,16 +338,13 @@ labels generated are not suitable, you can overwrite them like this:
```yaml ```yaml
- -
name: Build and push name: Docker meta
uses: docker/build-push-action@v2 id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with: with:
context: . images: name/app
file: ./Dockerfile label-custom: |
platforms: linux/amd64,linux/arm64,linux/386 maintainer=CrazyMax
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
org.opencontainers.image.title=MyCustomTitle org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description org.opencontainers.image.description=Another description
org.opencontainers.image.vendor=MyCompany org.opencontainers.image.vendor=MyCompany
@ -357,12 +366,14 @@ updates:
interval: "daily" interval: "daily"
``` ```
## How can I help? # Contributing
All kinds of contributions are welcome :raised_hands:! The most basic way to show your support is to star :star2: Want to contribute? Awesome! The most basic way to show your support is to star :star2: the project,
the project, or to raise issues :speech_balloon: You can also support this project by or to raise issues :speech_balloon:. If you want to open a pull request, please read the
[**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) :clap: or by making a [contributing guidelines](.github/CONTRIBUTING.md).
[Paypal donation](https://www.paypal.me/crazyws) to ensure this journey continues indefinitely! :rocket:
You can also support this project by [**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) or by
making a [Paypal donation](https://www.paypal.me/crazyws) to ensure this journey continues indefinitely!
Thanks again for your support, it is much appreciated! :pray: Thanks again for your support, it is much appreciated! :pray:

View File

@ -1,54 +1,150 @@
import * as context from '../src/context'; import * as context from '../src/context';
describe('getInputList', () => { describe('getInputList', () => {
it('handles single line correctly', async () => { it('single line correctly', async () => {
await setInput('foo', 'bar'); await setInput('foo', 'bar');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar']); expect(res).toEqual(['bar']);
}); });
it('handles multiple lines correctly', async () => { it('multiline correctly', async () => {
setInput('foo', 'bar\nbaz'); setInput('foo', 'bar\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('remove empty lines correctly', async () => { it('empty lines correctly', async () => {
setInput('foo', 'bar\n\nbaz'); setInput('foo', 'bar\n\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles comma correctly', async () => { it('comma correctly', async () => {
setInput('foo', 'bar,baz'); setInput('foo', 'bar,baz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('remove empty result correctly', async () => { it('empty result correctly', async () => {
setInput('foo', 'bar,baz,'); setInput('foo', 'bar,baz,');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles different new lines correctly', async () => { it('different new lines correctly', async () => {
setInput('foo', 'bar\r\nbaz'); setInput('foo', 'bar\r\nbaz');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz']); expect(res).toEqual(['bar', 'baz']);
}); });
it('handles different new lines and comma correctly', async () => { it('different new lines and comma correctly', async () => {
setInput('foo', 'bar\r\nbaz,bat'); setInput('foo', 'bar\r\nbaz,bat');
const res = await context.getInputList('foo'); const res = await context.getInputList('foo');
console.log(res); console.log(res);
expect(res).toEqual(['bar', 'baz', 'bat']); expect(res).toEqual(['bar', 'baz', 'bat']);
}); });
it('multiline and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true);
console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
});
it('different new lines and ignoring comma correctly', async () => {
setInput('cache-from', 'user/app:cache\r\ntype=local,src=path/to/dir');
const res = await context.getInputList('cache-from', true);
console.log(res);
expect(res).toEqual(['user/app:cache', 'type=local,src=path/to/dir']);
});
it('multiline values', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc`,
'FOO=bar'
]);
});
it('multiline values with empty lines', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc"
FOO=bar
"EMPTYLINE=aaaa
bbbb
ccc"`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc`,
'FOO=bar',
`EMPTYLINE=aaaa
bbbb
ccc`
]);
});
it('multiline values without quotes', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
MYSECRET=aaaaaaaa
bbbbbbb
ccccccccc
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual(['GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789', 'MYSECRET=aaaaaaaa', 'bbbbbbb', 'ccccccccc', 'FOO=bar']);
});
it('multiline values escape quotes', async () => {
setInput(
'secrets',
`GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789
"MYSECRET=aaaaaaaa
bbbb""bbb
ccccccccc"
FOO=bar`
);
const res = await context.getInputList('secrets', true);
console.log(res);
expect(res).toEqual([
'GIT_AUTH_TOKEN=abcdefgh,ijklmno=0123456789',
`MYSECRET=aaaaaaaa
bbbb\"bbb
ccccccccc`,
'FOO=bar'
]);
});
}); });
describe('asyncForEach', () => { describe('asyncForEach', () => {

View File

@ -1009,6 +1009,42 @@ describe('latest', () => {
"org.opencontainers.image.licenses=MIT" "org.opencontainers.image.licenses=MIT"
] ]
], ],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/MyUSER/MyApp'],
tagLatest: false,
labelCustom: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustomTitle",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany",
],
} as Inputs,
{
main: 'v1.1.1',
partial: [],
latest: false
} as Version,
[
'org/app:v1.1.1',
'ghcr.io/myuser/myapp:v1.1.1',
],
[
"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",
"org.opencontainers.image.created=2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision=90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses=MIT",
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustomTitle",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany"
]
],
])('given %p event ', tagsLabelsTest); ])('given %p event ', tagsLabelsTest);
}); });

1461
dist/index.js generated vendored

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/github": "^4.0.0", "@actions/github": "^4.0.0",
"csv-parse": "^4.14.2",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
"moment": "^2.29.1", "moment": "^2.29.1",
"semver": "^7.3.4" "semver": "^7.3.4"

View File

@ -1,3 +1,4 @@
import csvparse from 'csv-parse/lib/sync';
import * as core from '@actions/core'; import * as core from '@actions/core';
export interface Inputs { export interface Inputs {
@ -12,6 +13,7 @@ export interface Inputs {
tagSchedule: string; tagSchedule: string;
tagCustom: string[]; tagCustom: string[];
tagCustomOnly: boolean; tagCustomOnly: boolean;
labelCustom: string[];
sepTags: string; sepTags: string;
sepLabels: string; sepLabels: string;
githubToken: string; githubToken: string;
@ -30,21 +32,37 @@ export function getInputs(): Inputs {
tagSchedule: core.getInput('tag-schedule') || 'nightly', tagSchedule: core.getInput('tag-schedule') || 'nightly',
tagCustom: getInputList('tag-custom'), tagCustom: getInputList('tag-custom'),
tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'), tagCustomOnly: /true/i.test(core.getInput('tag-custom-only') || 'false'),
labelCustom: getInputList('label-custom'),
sepTags: core.getInput('sep-tags') || `\n`, sepTags: core.getInput('sep-tags') || `\n`,
sepLabels: core.getInput('sep-labels') || `\n`, sepLabels: core.getInput('sep-labels') || `\n`,
githubToken: core.getInput('github-token') githubToken: core.getInput('github-token')
}; };
} }
export function getInputList(name: string): string[] { export function getInputList(name: string, ignoreComma?: boolean): string[] {
let res: Array<string> = [];
const items = core.getInput(name); const items = core.getInput(name);
if (items == '') { if (items == '') {
return []; return res;
} }
return items
.split(/\r?\n/) for (let output of csvparse(items, {
.filter(x => x) columns: false,
.reduce<string[]>((acc, line) => acc.concat(line.split(',').filter(x => x)).map(pat => pat.trim()), []); relaxColumnCount: true,
skipLinesWithEmptyValues: true
}) as Array<string[]>) {
if (output.length == 1) {
res.push(output[0]);
continue;
} else if (!ignoreComma) {
res.push(...output);
continue;
}
res.push(output.join(','));
}
return res.filter(item => item).map(pat => pat.trim());
} }
export const asyncForEach = async (array, callback) => { export const asyncForEach = async (array, callback) => {

View File

@ -1,5 +1,5 @@
import * as handlebars from 'handlebars'; import * as handlebars from 'handlebars';
import * as moment from 'moment'; import moment from 'moment';
import * as semver from 'semver'; import * as semver from 'semver';
import {Inputs} from './context'; import {Inputs} from './context';
import * as core from '@actions/core'; import * as core from '@actions/core';
@ -130,7 +130,7 @@ export class Meta {
} }
public labels(): Array<string> { public labels(): Array<string> {
return [ let labels: Array<string> = [
`org.opencontainers.image.title=${this.repo.name || ''}`, `org.opencontainers.image.title=${this.repo.name || ''}`,
`org.opencontainers.image.description=${this.repo.description || ''}`, `org.opencontainers.image.description=${this.repo.description || ''}`,
`org.opencontainers.image.url=${this.repo.html_url || ''}`, `org.opencontainers.image.url=${this.repo.html_url || ''}`,
@ -140,5 +140,7 @@ export class Meta {
`org.opencontainers.image.revision=${this.context.sha || ''}`, `org.opencontainers.image.revision=${this.context.sha || ''}`,
`org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}` `org.opencontainers.image.licenses=${this.repo.license?.spdx_id || ''}`
]; ];
labels.push(...this.inputs.labelCustom);
return labels;
} }
} }

View File

@ -11,9 +11,11 @@
"rootDir": "./src", "rootDir": "./src",
"strict": true, "strict": true,
"noImplicitAny": false, "noImplicitAny": false,
"esModuleInterop": false, "esModuleInterop": true,
"resolveJsonModule": true,
"sourceMap": true "sourceMap": true
}, },
"exclude": ["node_modules", "**/*.test.ts"] "exclude": [
"node_modules",
"**/*.test.ts"
]
} }

View File

@ -1176,6 +1176,11 @@ cssstyle@^2.2.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csv-parse@^4.14.2:
version "4.14.2"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.14.2.tgz#c1329cff95a99b8773a92c4e62f8bff114b34726"
integrity sha512-YE2xlTKtM035/94llhgsp9qFQxGi47EkQJ1pZ+mLT/98GpIsbjkMGAb7Rmu9hNxVfYFOLf10hP+rPVqnoccLgw==
dashdash@^1.12.0: dashdash@^1.12.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"