Add bake-file output (#36)

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2020-12-24 04:13:41 +01:00 committed by GitHub
parent 3479bd5aaa
commit 10e9d5d585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 460 additions and 37 deletions

View File

@ -177,3 +177,38 @@ jobs:
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
bake:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Docker meta
id: docker_meta
uses: ./
with:
images: |
${{ env.DOCKER_IMAGE }}
ghcr.io/name/app
tag-sha: true
tag-semver: |
{{version}}
{{major}}.{{minor}}
{{major}}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: crazy-max/ghaction-docker-buildx-bake@v1
with:
files: |
./test/docker-bake.hcl
${{ steps.docker_meta.outputs.bake-file }}
targets: |
release

View File

@ -20,7 +20,7 @@ ___
* [Usage](#usage)
* [Basic](#basic)
* [Semver](#semver)
* [Complete](#complete)
* [Bake definition](#bake-definition)
* [Customizing](#customizing)
* [inputs](#inputs)
* [outputs](#outputs)
@ -85,7 +85,7 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -147,7 +147,7 @@ jobs:
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@ -163,23 +163,29 @@ jobs:
labels: ${{ steps.docker_meta.outputs.labels }}
```
### Complete
### Bake definition
| Event | Ref | Commit SHA | Docker Tags |
|-----------------|-------------------------------|------------|-----------------------------------------|
| `schedule` | `refs/heads/master` | `45f132a` | `sha-45f132a`, `nightly` |
| `pull_request` | `refs/pull/2/merge` | `a123b57` | `sha-a123b57`, `pr-2` |
| `push` | `refs/heads/master` | `cf20257` | `sha-cf20257`, `master` |
| `push` | `refs/heads/my/branch` | `a5df687` | `sha-a5df687`, `my-branch` |
| `push tag` | `refs/tags/v1.2.3` | `ad132f5` | `sha-ad132f5`, `1.2.3`, `1.2`, `latest` |
| `push tag` | `refs/tags/v2.0.8-beta.67` | `fc89efd` | `sha-fc89efd`, `2.0.8-beta.67` |
This action also handles a bake definition file that can be used with the
[Docker Buildx Bake action](https://github.com/crazy-max/ghaction-docker-buildx-bake). You just have to declare a
target named `ghaction-docker-meta`.
```hcl
// docker-bake.hcl
target "ghaction-docker-meta" {}
target "build" {
inherits = ["ghaction-docker-meta"]
context = "./"
dockerfile = "Dockerfile"
platforms = ["linux/amd64", "linux/arm/v6", "linux/arm/v7", "linux/arm64", "linux/386", "linux/ppc64le"]
}
```
```yaml
name: ci
on:
schedule:
- cron: '0 10 * * *' # everyday at 10am
push:
branches:
- '**'
@ -211,22 +217,14 @@ jobs:
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
name: Build
uses: crazy-max/ghaction-docker-buildx-bake@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
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 }}
files: |
./docker-bake.hcl
${{ steps.docker_meta.outputs.bake-file }}
targets: |
build
```
## Customizing
@ -251,7 +249,7 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description |
|---------------------|----------|------------------------------------|
| `images` | List/CSV | List of Docker images to use as base name for tags |
| `tag-sha` | Bool | Add git short SHA as Docker tag (default `false`) |
| `tag-sha` | Bool | Add git short commit as Docker tag (default `false`) |
| `tag-edge` | Bool | Enable edge branch tagging (default `false`) |
| `tag-edge-branch` | String | Branch that will be tagged as edge (default `repo.default_branch`) |
| `tag-semver` | List/CSV | Handle Git tag as semver [template](#handle-semver-tag) if possible |
@ -273,9 +271,10 @@ Following outputs are available
| Name | Type | Description |
|---------------|---------|---------------------------------------|
| `version` | String | Generated Docker image version |
| `tags` | String | Generated Docker tags |
| `labels` | String | Generated Docker labels |
| `version` | String | Docker image version |
| `tags` | String | Docker tags |
| `labels` | String | Docker labels |
| `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path |
## Notes
@ -327,7 +326,7 @@ the following expressions:
| Expression | Example | Description |
|-------------------------|-------------------------------------------|------------------------------------------|
| `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/)
| `{{date 'format'}}` | `{{date 'YYYYMMDD'}}` > `20200110` | Render date by its [moment format](https://momentjs.com/docs/#/displaying/format/)
You can find more examples in the [CI workflow](.github/workflows/ci.yml).
@ -366,7 +365,7 @@ updates:
interval: "daily"
```
# Contributing
## Contributing
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

View File

@ -1,5 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
import * as context from '../src/context';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.ghaction-docker-meta-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
describe('getInputList', () => {
it('single line correctly', async () => {
await setInput('foo', 'bar');

View File

@ -1484,3 +1484,244 @@ describe('custom', () => {
],
])('given %p event ', tagsLabelsTest);
});
describe('bake-file', () => {
// prettier-ignore
test.each([
[
'event_push.env',
{
images: ['user/app'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:dev",
"user/app:my",
"user/app:custom",
"user/app:tags"
],
"labels": {
"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": "dev",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_push.env',
{
images: ['user/app'],
tagCustom: ['my']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:dev",
"user/app:my",
],
"labels": {
"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": "dev",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_release1.env',
{
images: ['user/app'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:release1",
"user/app:my",
"user/app:custom",
"user/app:tags",
"user/app:latest"
],
"labels": {
"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": "release1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_20200110-RC2.env',
{
images: ['user/app'],
tagMatch: `\\d{8}`,
tagLatest: false,
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"user/app:20200110",
"user/app:my",
"user/app:custom",
"user/app:tags"
],
"labels": {
"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": "20200110",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
tagSemver: ['{{version}}', '{{major}}.{{minor}}', '{{major}}'],
tagCustom: ['my', 'custom', 'tags']
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:1.1.1",
"org/app:1.1",
"org/app:1",
"org/app:my",
"org/app:custom",
"org/app:tags",
"org/app:latest",
"ghcr.io/user/app:1.1.1",
"ghcr.io/user/app:1.1",
"ghcr.io/user/app:1",
"ghcr.io/user/app:my",
"ghcr.io/user/app:custom",
"ghcr.io/user/app:tags",
"ghcr.io/user/app:latest"
],
"labels": {
"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": "1.1.1",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app', 'ghcr.io/user/app'],
tagSemver: ['{{version}}', '{{major}}.{{minor}}.{{patch}}'],
tagCustom: ['my', 'custom', 'tags'],
tagCustomOnly: true,
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:my",
"org/app:custom",
"org/app:tags",
"ghcr.io/user/app:my",
"ghcr.io/user/app:custom",
"ghcr.io/user/app:tags"
],
"labels": {
"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": "my",
"org.opencontainers.image.created": "2020-01-10T00:30:00.000Z",
"org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071",
"org.opencontainers.image.licenses": "MIT"
}
}
}
}
],
[
'event_tag_v1.1.1.env',
{
images: ['org/app'],
labelCustom: [
"maintainer=CrazyMax",
"org.opencontainers.image.title=MyCustom=Title",
"org.opencontainers.image.description=Another description",
"org.opencontainers.image.vendor=MyCompany",
],
} as Inputs,
{
"target": {
"ghaction-docker-meta": {
"tags": [
"org/app:v1.1.1",
"org/app:latest"
],
"labels": {
"maintainer": "CrazyMax",
"org.opencontainers.image.title": "MyCustom=Title",
"org.opencontainers.image.description": "Another description",
"org.opencontainers.image.url": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.source": "https://github.com/octocat/Hello-World",
"org.opencontainers.image.vendor": "MyCompany",
"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"
}
}
}
}
]
])('given %p event ', async (envFile: string, inputs: Inputs, exBakeDefinition: {}) => {
process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile)));
const context = github.context();
console.log(process.env, context);
const repo = await github.repo(process.env.GITHUB_TOKEN || '');
const meta = new Meta({...getInputs(), ...inputs}, context, repo);
const bakeFile = meta.bakeFile();
console.log('bakeFile', bakeFile, fs.readFileSync(bakeFile, 'utf8'));
expect(JSON.parse(fs.readFileSync(bakeFile, 'utf8'))).toEqual(exBakeDefinition);
});
});

View File

@ -68,6 +68,8 @@ outputs:
description: 'Generated Docker tags'
labels:
description: 'Generated Docker labels'
bake-file:
description: 'Bake definiton file'
runs:
using: 'node12'

45
dist/index.js generated vendored
View File

@ -39,9 +39,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.asyncForEach = exports.getInputList = exports.getInputs = void 0;
exports.asyncForEach = exports.getInputList = exports.getInputs = exports.tmpDir = void 0;
const sync_1 = __importDefault(__webpack_require__(8750));
const core = __importStar(__webpack_require__(2186));
const fs = __importStar(__webpack_require__(5747));
const os = __importStar(__webpack_require__(2087));
const path = __importStar(__webpack_require__(5622));
let _tmpDir;
function tmpDir() {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ghaction-docker-meta-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
exports.tmpDir = tmpDir;
function getInputs() {
return {
images: getInputList('images'),
@ -184,6 +195,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const fs = __importStar(__webpack_require__(5747));
const context_1 = __webpack_require__(3842);
const github = __importStar(__webpack_require__(5928));
const meta_1 = __webpack_require__(3714);
@ -213,6 +225,7 @@ function run() {
core.info(version.main || '');
core.endGroup();
core.setOutput('version', version.main || '');
// Docker tags
const tags = meta.tags();
core.startGroup(`Docker tags`);
for (let tag of tags) {
@ -220,6 +233,7 @@ function run() {
}
core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels
const labels = meta.labels();
core.startGroup(`Docker labels`);
for (let label of labels) {
@ -227,6 +241,12 @@ function run() {
}
core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file
const bakeFile = meta.bakeFile();
core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup();
core.setOutput('bake-file', bakeFile);
}
catch (error) {
core.setFailed(error.message);
@ -268,8 +288,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.Meta = void 0;
const handlebars = __importStar(__webpack_require__(7492));
const fs = __importStar(__webpack_require__(5747));
const path = __importStar(__webpack_require__(5622));
const moment_1 = __importDefault(__webpack_require__(9623));
const semver = __importStar(__webpack_require__(1383));
const context_1 = __webpack_require__(3842);
const core = __importStar(__webpack_require__(2186));
class Meta {
constructor(inputs, context, repo) {
@ -397,6 +420,26 @@ class Meta {
labels.push(...this.inputs.labelCustom);
return labels;
}
bakeFile() {
let jsonLabels = {};
for (let label of this.labels()) {
const matches = label.match(/([^=]*)=(.*)/);
if (!matches) {
continue;
}
jsonLabels[matches[1]] = matches[2];
}
const bakeFile = path.join(context_1.tmpDir(), 'ghaction-docker-meta-bake.json').split(path.sep).join(path.posix.sep);
fs.writeFileSync(bakeFile, JSON.stringify({
target: {
'ghaction-docker-meta': {
tags: this.tags(),
labels: jsonLabels
}
}
}, null, 2));
return bakeFile;
}
}
exports.Meta = Meta;
//# sourceMappingURL=meta.js.map

View File

@ -1,5 +1,10 @@
import csvparse from 'csv-parse/lib/sync';
import * as core from '@actions/core';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
let _tmpDir: string;
export interface Inputs {
images: string[];
@ -19,6 +24,13 @@ export interface Inputs {
githubToken: string;
}
export function tmpDir(): string {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ghaction-docker-meta-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
export function getInputs(): Inputs {
return {
images: getInputList('images'),

View File

@ -1,3 +1,4 @@
import * as fs from 'fs';
import {getInputs, Inputs} from './context';
import * as github from './github';
import {Meta, Version} from './meta';
@ -33,6 +34,7 @@ async function run() {
core.endGroup();
core.setOutput('version', version.main || '');
// Docker tags
const tags: Array<string> = meta.tags();
core.startGroup(`Docker tags`);
for (let tag of tags) {
@ -41,6 +43,7 @@ async function run() {
core.endGroup();
core.setOutput('tags', tags.join(inputs.sepTags));
// Docker labels
const labels: Array<string> = meta.labels();
core.startGroup(`Docker labels`);
for (let label of labels) {
@ -48,6 +51,13 @@ async function run() {
}
core.endGroup();
core.setOutput('labels', labels.join(inputs.sepLabels));
// Bake definition file
const bakeFile: string = meta.bakeFile();
core.startGroup(`Bake definition file`);
core.info(fs.readFileSync(bakeFile, 'utf8'));
core.endGroup();
core.setOutput('bake-file', bakeFile);
} catch (error) {
core.setFailed(error.message);
}

View File

@ -1,7 +1,9 @@
import * as handlebars from 'handlebars';
import * as fs from 'fs';
import * as path from 'path';
import moment from 'moment';
import * as semver from 'semver';
import {Inputs} from './context';
import {Inputs, tmpDir} from './context';
import * as core from '@actions/core';
import {Context} from '@actions/github/lib/context';
import {ReposGetResponseData} from '@octokit/types';
@ -143,4 +145,34 @@ export class Meta {
labels.push(...this.inputs.labelCustom);
return labels;
}
public bakeFile(): string {
let jsonLabels = {};
for (let label of this.labels()) {
const matches = label.match(/([^=]*)=(.*)/);
if (!matches) {
continue;
}
jsonLabels[matches[1]] = matches[2];
}
const bakeFile = path.join(tmpDir(), 'ghaction-docker-meta-bake.json').split(path.sep).join(path.posix.sep);
fs.writeFileSync(
bakeFile,
JSON.stringify(
{
target: {
'ghaction-docker-meta': {
tags: this.tags(),
labels: jsonLabels
}
}
},
null,
2
)
);
return bakeFile;
}
}

38
test/docker-bake.hcl Normal file
View File

@ -0,0 +1,38 @@
target "ghaction-docker-meta" {}
group "default" {
targets = ["db", "app"]
}
group "release" {
targets = ["db", "app-plus"]
}
target "db" {
context = "./test"
tags = ["docker.io/tonistiigi/db"]
}
target "app" {
inherits = ["ghaction-docker-meta"]
context = "./test"
dockerfile = "Dockerfile"
args = {
name = "foo"
}
}
target "cross" {
platforms = [
"linux/amd64",
"linux/arm64",
"linux/386"
]
}
target "app-plus" {
inherits = ["app", "cross"]
args = {
IAMPLUS = "true"
}
}