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.
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
@ -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)
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:
- Write tests.

View File

@ -105,6 +105,25 @@ jobs:
{{major}}.{{minor}}.{{patch}}
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:
runs-on: ubuntu-latest
services:

View File

@ -31,7 +31,7 @@ ___
* [Schedule tag](#schedule-tag)
* [Overwrite labels](#overwrite-labels)
* [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)
## Features
@ -235,6 +235,19 @@ jobs:
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 |
|---------------------|----------|------------------------------------|
| `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-custom` | List/CSV | List of custom 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-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
### outputs
@ -326,16 +338,13 @@ labels generated are not suitable, you can overwrite them like this:
```yaml
-
name: Build and push
uses: docker/build-push-action@v2
name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm64,linux/386
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
images: name/app
label-custom: |
maintainer=CrazyMax
org.opencontainers.image.title=MyCustomTitle
org.opencontainers.image.description=Another description
org.opencontainers.image.vendor=MyCompany
@ -357,12 +366,14 @@ updates:
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:
the project, or to raise issues :speech_balloon: You can also support this project by
[**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) :clap: or by making a
[Paypal donation](https://www.paypal.me/crazyws) to ensure this journey continues indefinitely! :rocket:
Want to contribute? Awesome! The most basic way to show your support is to star :star2: the project,
or to raise issues :speech_balloon:. If you want to open a pull request, please read the
[contributing guidelines](.github/CONTRIBUTING.md).
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:

View File

@ -1,54 +1,150 @@
import * as context from '../src/context';
describe('getInputList', () => {
it('handles single line correctly', async () => {
it('single line correctly', async () => {
await setInput('foo', 'bar');
const res = await context.getInputList('foo');
console.log(res);
expect(res).toEqual(['bar']);
});
it('handles multiple lines correctly', async () => {
it('multiline correctly', async () => {
setInput('foo', 'bar\nbaz');
const res = await context.getInputList('foo');
console.log(res);
expect(res).toEqual(['bar', 'baz']);
});
it('remove empty lines correctly', async () => {
it('empty lines correctly', async () => {
setInput('foo', 'bar\n\nbaz');
const res = await context.getInputList('foo');
console.log(res);
expect(res).toEqual(['bar', 'baz']);
});
it('handles comma correctly', async () => {
it('comma correctly', async () => {
setInput('foo', 'bar,baz');
const res = await context.getInputList('foo');
console.log(res);
expect(res).toEqual(['bar', 'baz']);
});
it('remove empty result correctly', async () => {
it('empty result correctly', async () => {
setInput('foo', 'bar,baz,');
const res = await context.getInputList('foo');
console.log(res);
expect(res).toEqual(['bar', 'baz']);
});
it('handles different new lines correctly', async () => {
it('different new lines correctly', async () => {
setInput('foo', 'bar\r\nbaz');
const res = await context.getInputList('foo');
console.log(res);
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');
const res = await context.getInputList('foo');
console.log(res);
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', () => {

View File

@ -1009,6 +1009,42 @@ describe('latest', () => {
"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);
});

1461
dist/index.js generated vendored

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -1176,6 +1176,11 @@ cssstyle@^2.2.0:
dependencies:
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:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"