1
0
Fork 0
mirror of https://github.com/docker/build-push-action.git synced 2025-04-01 20:50:09 +02:00

Merge branch 'master' into patch-1

Signed-off-by: Nitesh Oswal <nit.oswal@gmail.com>
This commit is contained in:
Nitesh Oswal 2020-10-23 11:22:54 +05:30
commit b1339bc24d
14 changed files with 2178 additions and 390 deletions

View file

@ -1,6 +1,7 @@
name: ci
on:
workflow_dispatch:
push:
branches:
- master
@ -9,6 +10,27 @@ on:
- master
jobs:
minimal:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
with:
path: action
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: ./action
with:
file: ./test/Dockerfile
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
git-context:
runs-on: ubuntu-latest
services:
@ -25,8 +47,6 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
@ -53,6 +73,13 @@ jobs:
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Dump context
if: always()
@ -74,14 +101,11 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: ${{ matrix.buildx-version }}
driver-opts: network=host
-
name: Build and push
@ -104,6 +128,13 @@ jobs:
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Dump context
if: always()
@ -129,8 +160,6 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
@ -157,6 +186,115 @@ jobs:
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
error:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
id: docker_build
continue-on-error: true
uses: ./
with:
context: ./test
file: ./test/Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x
push: true
tags: localhost:5000/name/app:latest
-
name: Check
run: |
echo "${{ toJson(steps.docker_build) }}"
if [ "${{ steps.docker_build.outcome }}" != "failure" ] || [ "${{ steps.docker_build.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
docker-driver:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
push:
- true
- false
services:
registry:
image: registry:2
ports:
- 5000:5000
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
-
name: Build
id: docker_build
continue-on-error: ${{ matrix.push }}
uses: ./
with:
context: ./test
file: ./test/Dockerfile
push: ${{ matrix.push }}
tags: localhost:5000/name/app:latest
-
name: Check
run: |
echo "${{ toJson(steps.docker_build) }}"
if [ "${{ matrix.push }}" = "false" ]; then
exit 0
fi
if [ "${{ steps.docker_build.outcome }}" != "failure" ] || [ "${{ steps.docker_build.conclusion }}" != "success" ]; then
echo "::error::Should have failed"
exit 1
fi
-
name: Dump context
if: always()
uses: crazy-max/ghaction-dump-context@v1
export-docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2.3.3
-
name: Build
uses: ./
with:
context: ./test
file: ./test/Dockerfile
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
-
name: Dump context
if: always()
@ -185,8 +323,6 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
@ -214,6 +350,13 @@ jobs:
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Dump context
if: always()
@ -233,13 +376,12 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
# TODO: Remove image=moby/buildkit:buildx-stable-1 when moby/buildkit#1727 fixed
driver-opts: |
network=host
image=moby/buildkit:buildx-stable-1
@ -265,6 +407,13 @@ jobs:
-
name: Image digest (1)
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest (1)
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Prune
run: |
@ -292,7 +441,14 @@ jobs:
name: Image digest (2)
run: echo ${{ steps.docker_build2.outputs.digest }}
-
name: Check digests
name: Check digest (2)
run: |
if [ -z "${{ steps.docker_build2.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Compare digests
run: |
echo Compare "${{ steps.docker_build.outputs.digest }}" with "${{ steps.docker_build2.outputs.digest }}"
if [ "${{ steps.docker_build.outputs.digest }}" != "${{ steps.docker_build2.outputs.digest }}" ]; then
@ -320,13 +476,12 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
# TODO: Remove image=moby/buildkit:buildx-stable-1 when moby/buildkit#1727 fixed
driver-opts: |
network=host
image=moby/buildkit:buildx-stable-1
@ -348,7 +503,7 @@ jobs:
uses: ./
with:
context: ./test
file: ./test/Dockerfile-multi-golang
file: ./test/Dockerfile-multi
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64
push: true
@ -364,6 +519,13 @@ jobs:
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Dump context
if: always()
@ -384,13 +546,12 @@ jobs:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
# TODO: Remove image=moby/buildkit:buildx-stable-1 when moby/buildkit#1727 fixed
driver-opts: |
network=host
image=moby/buildkit:buildx-stable-1
@ -409,7 +570,7 @@ jobs:
uses: ./
with:
context: ./test
file: ./test/Dockerfile-multi-golang
file: ./test/Dockerfile-multi
builder: ${{ steps.buildx.outputs.name }}
platforms: linux/amd64,linux/arm64
push: true
@ -426,7 +587,14 @@ jobs:
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
-
name: Check digests
name: Check digest
run: |
if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then
echo "::error::Digest should not be empty"
exit 1
fi
-
name: Compare digests
run: |
echo Compare "${{ needs.github-cache-first.outputs.digest }}" with "${{ steps.docker_build.outputs.digest }}"
if [ "${{ needs.github-cache-first.outputs.digest }}" != "${{ steps.docker_build.outputs.digest }}" ]; then

130
README.md
View file

@ -7,10 +7,10 @@
## Upgrade from v1
`v2` of this action includes significant updates and now uses Docker [Buildx](https://github.com/docker/buildx). It
works with 3 new optional actions ([login](https://github.com/docker/login-action), [setup-buildx](https://github.com/docker/setup-buildx-action)
works with 3 new actions ([login](https://github.com/docker/login-action), [setup-buildx](https://github.com/docker/setup-buildx-action)
and [setup-qemu](https://github.com/docker/setup-qemu-action)) that we have created. It's also rewritten as a
[typescript-action](https://github.com/actions/typescript-action/) to be as close as possible of the
[GitHub Runner](https://github.com/actions/virtual-environments) during its execution (#71 #92).
[typescript-action](https://github.com/actions/typescript-action/) to be as closed as possible of the
[GitHub Runner](https://github.com/actions/virtual-environments) during its execution.
[Upgrade notes](UPGRADE.md) and many [usage examples](#usage) have been added to handle most use cases but `v1` is
still available through [`releases/v1` branch](https://github.com/docker/build-push-action/tree/releases/v1).
@ -37,6 +37,7 @@ ___
* [Push to multi-registries](#push-to-multi-registries)
* [Cache to registry](#push-to-multi-registries)
* [Local registry](#local-registry)
* [Export image to Docker](#export-image-to-docker)
* [Leverage GitHub cache](#leverage-github-cache)
* [Complete workflow](#complete-workflow)
* [Update DockerHub repo description](#update-dockerhub-repo-description)
@ -56,7 +57,8 @@ build-secrets, remote cache, etc. and different builder deployment/namespacing o
### Git context
The default behavior of this action is to use the [Git context invoked by your workflow](https://github.com/docker/build-push-action/blob/master/src/context.ts#L10-L12).
The default behavior of this action is to use the [Git context invoked](https://github.com/docker/build-push-action/blob/master/src/context.ts#L31-L35)
by your workflow.
```yaml
name: ci
@ -88,6 +90,9 @@ jobs:
with:
push: true
tags: user/app:latest
build-args: |
arg1=value1
arg2=value2
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}
@ -377,6 +382,46 @@ For testing purposes you may need to create a [local registry](https://hub.docke
```
</details>
### Export image to Docker
You may want your build result to be available in the Docker client through `docker images` to be able to use it
in another step of your workflow:
<details>
<summary><b>Show workflow</b></summary>
```yaml
name: ci
on:
push:
branches: master
jobs:
export-docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
load: true
tags: myimage:latest
-
name: Inspect
run: |
docker image inspect myimage:latest
```
</details>
### Leverage GitHub cache
You can leverage [GitHub cache](https://docs.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows)
@ -424,15 +469,20 @@ using [actions/cache](https://github.com/actions/cache) with this action:
```
</details>
> If you want to [export layers for all stages](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue),
> you have to specify `mode=max` attribute in `cache-to`.
### Complete workflow
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and you want an
If you come from [`v1`](https://github.com/docker/build-push-action/tree/releases/v1#readme) and want an
"automatic" tag management through Git reference and [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md)
for labels, you will have to do it in a dedicated step [for now](https://github.com/docker/build-push-action/issues/116).
for labels, you will have to do it in a dedicated step.
The following workflow with the `Prepare` step will generate some [outputs](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjobs_idoutputs)
to handle tags and labels based on GitHub actions events. This is just an example to show many cases that you
might want to use:
to handle tags and labels based on GitHub actions events.
This is just an example to show many cases that you might want to use and that you will have to adapt according
to your needs:
<details>
<summary><b>Show workflow</b></summary>
@ -540,8 +590,8 @@ might want to use:
### Update DockerHub repo description
You can update the [Docker Hub repository description](https://docs.docker.com/docker-hub/repos/) using
a third-party action called [Docker Hub Description](https://github.com/peter-evans/dockerhub-description)
You can update the [DockerHub repository description](https://docs.docker.com/docker-hub/repos/) using
a third-party action called [DockerHub Description](https://github.com/peter-evans/dockerhub-description)
with this action:
<details>
@ -592,46 +642,38 @@ with this action:
Following inputs can be used as `step.with` keys
| Name | Type | Description |
|---------------------|---------|------------------------------------|
| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) |
| `context` | String | Build's context is the set of files located in the specified [`PATH` or `URL`](https://docs.docker.com/engine/reference/commandline/build/) (default [Git context](#git-context)) |
| `file` | String | Path to the Dockerfile (default `Dockerfile`) |
| `build-args` | List | List of build-time variables |
| `labels` | List | List of metadata for an image |
| `tags` | List | List of tags |
| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) |
| `target` | String | Sets the target stage to build |
| `allow` | List | List of [extra privileged entitlement](https://github.com/docker/buildx#--allowentitlement) (eg. `network.host,security.insecure`) |
| `no-cache` | Bool | Do not use cache when building the image (default `false`) |
| `platforms` | List | List of [target platforms](https://github.com/docker/buildx#---platformvaluevalue) for build |
| `load` | Bool | [Load](https://github.com/docker/buildx#--load) is a shorthand for `--output=type=docker` (default `false`) |
| `push` | Bool | [Push](https://github.com/docker/buildx#--push) is a shorthand for `--output=type=registry` (default `false`) |
| `outputs` | CSV | List of [output destinations](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) (format: `type=local,dest=path`) |
| `cache-from` | CSV | List of [external cache sources](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) (eg. `type=local,src=path/to/dir`) |
| `cache-to` | CSV | List of [cache export destinations](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) (eg. `type=local,dest=path/to/dir`) |
| `secrets` | CSV | List of secrets to expose to the build (eg. `key=value`, `GIT_AUTH_TOKEN=mytoken`) |
> `List` type can be a comma or newline-delimited string
> ```yaml
> tags: name/app:latest,name/app:1.0.0
> ```
> ```yaml
> tags: |
> name/app:latest
> name/app:1.0.0
> ```
> `CSV` type must be a newline-delimited string
> ```yaml
> cache-from: user/app:cache
> ```
> `List` type is a newline-delimited string
> ```yaml
> cache-from: |
> user/app:cache
> type=local,src=path/to/dir
> ```
> `CSV` type is a comma-delimited string
> ```yaml
> tags: name/app:latest,name/app:1.0.0
> ```
| Name | Type | Description |
|---------------------|----------|------------------------------------|
| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) |
| `context` | String | Build's context is the set of files located in the specified [`PATH` or `URL`](https://docs.docker.com/engine/reference/commandline/build/) (default [Git context](#git-context)) |
| `file` | String | Path to the Dockerfile (default `Dockerfile`) |
| `build-args` | List | List of build-time variables |
| `labels` | List | List of metadata for an image |
| `tags` | List/CSV | List of tags |
| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) |
| `target` | String | Sets the target stage to build |
| `allow` | List/CSV | List of [extra privileged entitlement](https://github.com/docker/buildx#--allowentitlement) (eg. `network.host,security.insecure`) |
| `no-cache` | Bool | Do not use cache when building the image (default `false`) |
| `platforms` | List/CSV | List of [target platforms](https://github.com/docker/buildx#---platformvaluevalue) for build |
| `load` | Bool | [Load](https://github.com/docker/buildx#--load) is a shorthand for `--output=type=docker` (default `false`) |
| `push` | Bool | [Push](https://github.com/docker/buildx#--push) is a shorthand for `--output=type=registry` (default `false`) |
| `outputs` | List | List of [output destinations](https://github.com/docker/buildx#-o---outputpath-typetypekeyvalue) (format: `type=local,dest=path`) |
| `cache-from` | List | List of [external cache sources](https://github.com/docker/buildx#--cache-fromnametypetypekeyvalue) (eg. `type=local,src=path/to/dir`) |
| `cache-to` | List | List of [cache export destinations](https://github.com/docker/buildx#--cache-tonametypetypekeyvalue) (eg. `type=local,dest=path/to/dir`) |
| `secrets` | List | List of secrets to expose to the build (eg. `key=value`, `GIT_AUTH_TOKEN=mytoken`) |
### outputs
Following outputs are available

View file

@ -65,7 +65,9 @@ steps:
file: ./Dockerfile
pull: true
push: true
build-args: arg1=value1,arg2=value2
build-args: |
arg1=value1
arg2=value2
cache-from: type=registry,ref=myorg/myrepository:latest
cache-to: type=inline
tags: myorg/myrepository:latest

View file

@ -1,10 +1,24 @@
import fs from 'fs';
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import * as buildx from '../src/buildx';
import * as exec from '@actions/exec';
import * as context from '../src/context';
const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-build-push-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
return path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
});
describe('getImageID', () => {
it('matches', async () => {
const imageIDFile = await buildx.getImageIDFile();
@ -16,9 +30,68 @@ describe('getImageID', () => {
});
});
describe('isLocalOrTarExporter', () => {
// prettier-ignore
test.each([
[
[
'type=registry,ref=user/app',
],
false
],
[
[
'type=docker',
],
false
],
[
[
'type=local,dest=./release-out'
],
true
],
[
[
'type=tar,dest=/tmp/image.tar'
],
true
],
[
[
'type=docker',
'type=tar,dest=/tmp/image.tar'
],
true
],
[
[
'"type=tar","dest=/tmp/image.tar"'
],
true
],
[
[
'" type= local" , dest=./release-out'
],
true
],
[
[
'.'
],
true
],
])(
'given %p returns %p',
async (outputs: Array<string>, expected: boolean) => {
expect(buildx.isLocalOrTarExporter(outputs)).toEqual(expected);
}
);
});
describe('getVersion', () => {
it('valid', async () => {
await exec.exec('docker', ['buildx', 'version']);
const version = await buildx.getVersion();
console.log(`version: ${version}`);
expect(semver.valid(version)).not.toBeNull();

View file

@ -1,5 +1,177 @@
import * as fs from 'fs';
import * as path from 'path';
import * as buildx from '../src/buildx';
import * as context from '../src/context';
jest.spyOn(context, 'defaultContext').mockImplementation((): string => {
return 'https://github.com/docker/build-push-action.git#test-jest';
});
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-build-push-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
jest.spyOn(context, 'tmpNameSync').mockImplementation((): string => {
return path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep);
});
describe('getArgs', () => {
beforeEach(() => {
process.env = Object.keys(process.env).reduce((object, key) => {
if (!key.startsWith('INPUT_')) {
object[key] = process.env[key];
}
return object;
}, {});
});
// prettier-ignore
test.each([
[
'0.4.1',
new Map<string, string>([
['context', '.'],
]),
[
'buildx',
'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile',
'.'
]
],
[
'0.4.2',
new Map<string, string>([
['build-args', 'MY_ARG=val1,val2,val3\nARG=val'],
]),
[
'buildx',
'build',
'--build-arg', 'MY_ARG=val1,val2,val3',
'--build-arg', 'ARG=val',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile',
'https://github.com/docker/build-push-action.git#test-jest'
]
],
[
'0.4.2',
new Map<string, string>([
['context', '.'],
['labels', 'org.opencontainers.image.title=buildkit\norg.opencontainers.image.description=concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit'],
['outputs', 'type=local,dest=./release-out']
]),
[
'buildx',
'build',
'--label', 'org.opencontainers.image.title=buildkit',
'--label', 'org.opencontainers.image.description=concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit',
'--output', 'type=local,dest=./release-out',
'--file', 'Dockerfile',
'.'
]
],
[
'0.4.1',
new Map<string, string>([
['context', '.'],
['platforms', 'linux/amd64,linux/arm64']
]),
[
'buildx',
'build',
'--platform', 'linux/amd64,linux/arm64',
'--file', 'Dockerfile',
'.'
]
],
[
'0.4.1',
new Map<string, string>([
['context', '.']
]),
[
'buildx',
'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--file', 'Dockerfile',
'.'
]
],
[
'0.4.2',
new Map<string, string>([
['context', '.'],
['secrets', 'GIT_AUTH_TOKEN=abcdefghijklmno0123456789'],
]),
[
'buildx',
'build',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', 'Dockerfile',
'.'
]
],
[
'0.4.2',
new Map<string, string>([
['github-token', 'abcdefghijklmno0123456789'],
['outputs', '.']
]),
[
'buildx',
'build',
'--output', '.',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', 'Dockerfile',
'https://github.com/docker/build-push-action.git#test-jest'
]
],
[
'0.4.2',
new Map<string, string>([
['context', 'https://github.com/docker/build-push-action.git#heads/master'],
['tag', 'localhost:5000/name/app:latest'],
['platforms', 'linux/amd64,linux/arm64'],
['secrets', 'GIT_AUTH_TOKEN=abcdefghijklmno0123456789'],
['file', './test/Dockerfile'],
['builder', 'builder-git-context-2'],
['push', 'true']
]),
[
'buildx',
'build',
'--platform', 'linux/amd64,linux/arm64',
'--iidfile', '/tmp/.docker-build-push-jest/iidfile',
'--secret', 'id=GIT_AUTH_TOKEN,src=/tmp/.docker-build-push-jest/.tmpname-jest',
'--file', './test/Dockerfile',
'--builder', 'builder-git-context-2',
'--push',
'https://github.com/docker/build-push-action.git#heads/master'
]
]
])(
'given %p with %p as inputs, returns %p',
async (buildxVersion: string, inputs: Map<string, any>, expected: Array<string>) => {
await inputs.forEach((value: string, name: string) => {
setInput(name, value);
});
const defContext = context.defaultContext();
const inp = await context.getInputs(defContext);
console.log(inp);
const res = await context.getArgs(inp, defContext, buildxVersion);
console.log(res);
expect(res).toEqual(expected);
}
);
});
describe('getInputList', () => {
it('handles single line correctly', async () => {
await setInput('foo', 'bar');

1819
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
module.exports = {
clearMocks: true,
clearMocks: false,
moduleFileExtensions: ['js', 'ts'],
setupFiles: ["dotenv/config"],
testEnvironment: 'node',

View file

@ -31,10 +31,12 @@
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.4",
"@actions/github": "^4.0.0",
"csv-parse": "^4.12.0",
"semver": "^7.3.2",
"tmp": "^0.2.1"
},
"devDependencies": {
"@types/csv-parse": "^1.2.2",
"@types/jest": "^26.0.3",
"@types/node": "^14.0.14",
"@types/tmp": "^0.2.0",

View file

@ -1,12 +1,12 @@
import fs from 'fs';
import path from 'path';
import tmp from 'tmp';
import csvparse from 'csv-parse/lib/sync';
import * as semver from 'semver';
import * as context from './context';
import * as exec from './exec';
export async function getImageIDFile(): Promise<string> {
return path.join(context.tmpDir, 'iidfile');
return path.join(context.tmpDir(), 'iidfile').split(path.sep).join(path.posix.sep);
}
export async function getImageID(): Promise<string | undefined> {
@ -19,13 +19,43 @@ export async function getImageID(): Promise<string | undefined> {
export async function getSecret(kvp: string): Promise<string> {
const [key, value] = kvp.split('=');
const secretFile = tmp.tmpNameSync({
tmpdir: context.tmpDir
const secretFile = context.tmpNameSync({
tmpdir: context.tmpDir()
});
await fs.writeFileSync(secretFile, value);
return `id=${key},src=${secretFile}`;
}
export function isLocalOrTarExporter(outputs: string[]): Boolean {
for (let output of csvparse(outputs.join(`\n`), {
delimiter: ',',
trim: true,
columns: false,
relax_column_count: true
})) {
// Local if no type is defined
// https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43
if (output.length == 1 && !output[0].startsWith('type=')) {
return true;
}
for (let [key, value] of output.map(chunk => chunk.split('=').map(item => item.trim()))) {
if (key == 'type' && (value == 'local' || value == 'tar')) {
return true;
}
}
}
return false;
}
export function hasGitAuthToken(secrets: string[]): Boolean {
for (let secret of secrets) {
if (secret.startsWith('GIT_AUTH_TOKEN=')) {
return true;
}
}
return false;
}
export async function isAvailable(): Promise<Boolean> {
return await exec.exec(`docker`, ['buildx'], true).then(res => {
if (res.stderr != '' && !res.success) {

View file

@ -2,14 +2,12 @@ import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as semver from 'semver';
import * as tmp from 'tmp';
import * as buildx from './buildx';
import * as core from '@actions/core';
import * as github from '@actions/github';
export const tmpDir: string = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-build-push-'));
const defaultContext: string = `https://github.com/${github.context.repo.owner}/${
github.context.repo.repo
}.git#${github.context.ref.replace(/^refs\//, '')}`;
let _defaultContext, _tmpDir: string;
export interface Inputs {
context: string;
@ -32,12 +30,32 @@ export interface Inputs {
githubToken: string;
}
export async function getInputs(): Promise<Inputs> {
export function defaultContext(): string {
if (!_defaultContext) {
_defaultContext = `https://github.com/${github.context.repo.owner}/${
github.context.repo.repo
}.git#${github.context?.ref?.replace(/^refs\//, '')}`;
}
return _defaultContext;
}
export function tmpDir(): string {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-build-push-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
export function tmpNameSync(options?: tmp.TmpNameOptions): string {
return tmp.tmpNameSync(options);
}
export async function getInputs(defaultContext: string): Promise<Inputs> {
return {
context: core.getInput('context') || defaultContext,
file: core.getInput('file') || 'Dockerfile',
buildArgs: await getInputList('build-args'),
labels: await getInputList('labels'),
buildArgs: await getInputList('build-args', true),
labels: await getInputList('labels', true),
tags: await getInputList('tags'),
pull: /true/i.test(core.getInput('pull')),
target: core.getInput('target'),
@ -55,15 +73,15 @@ export async function getInputs(): Promise<Inputs> {
};
}
export async function getArgs(inputs: Inputs, buildxVersion: string): Promise<Array<string>> {
export async function getArgs(inputs: Inputs, defaultContext: string, buildxVersion: string): Promise<Array<string>> {
let args: Array<string> = ['buildx'];
args.push.apply(args, await getBuildArgs(inputs, buildxVersion));
args.push.apply(args, await getBuildArgs(inputs, defaultContext, buildxVersion));
args.push.apply(args, await getCommonArgs(inputs));
args.push(inputs.context);
return args;
}
async function getBuildArgs(inputs: Inputs, buildxVersion: string): Promise<Array<string>> {
async function getBuildArgs(inputs: Inputs, defaultContext: string, buildxVersion: string): Promise<Array<string>> {
let args: Array<string> = ['build'];
await asyncForEach(inputs.buildArgs, async buildArg => {
args.push('--build-arg', buildArg);
@ -83,26 +101,25 @@ async function getBuildArgs(inputs: Inputs, buildxVersion: string): Promise<Arra
if (inputs.platforms.length > 0) {
args.push('--platform', inputs.platforms.join(','));
}
if (inputs.platforms.length == 0 || semver.satisfies(buildxVersion, '>=0.4.2')) {
args.push('--iidfile', await buildx.getImageIDFile());
}
await asyncForEach(inputs.outputs, async output => {
args.push('--output', output);
});
if (
!buildx.isLocalOrTarExporter(inputs.outputs) &&
(inputs.platforms.length == 0 || semver.satisfies(buildxVersion, '>=0.4.2'))
) {
args.push('--iidfile', await buildx.getImageIDFile());
}
await asyncForEach(inputs.cacheFrom, async cacheFrom => {
args.push('--cache-from', cacheFrom);
});
await asyncForEach(inputs.cacheTo, async cacheTo => {
args.push('--cache-to', cacheTo);
});
let hasGitAuthToken: boolean = false;
await asyncForEach(inputs.secrets, async secret => {
if (secret.startsWith('GIT_AUTH_TOKEN=')) {
hasGitAuthToken = true;
}
args.push('--secret', await buildx.getSecret(secret));
});
if (inputs.githubToken && !hasGitAuthToken && inputs.context == defaultContext) {
if (inputs.githubToken && !buildx.hasGitAuthToken(inputs.secrets) && inputs.context == defaultContext) {
args.push('--secret', await buildx.getSecret(`GIT_AUTH_TOKEN=${inputs.githubToken}`));
}
if (inputs.file) {

View file

@ -7,7 +7,7 @@ export interface ExecResult {
stderr: string;
}
export const exec = async (command: string, args: string[] = [], silent: boolean): Promise<ExecResult> => {
export const exec = async (command: string, args: string[] = [], silent?: boolean): Promise<ExecResult> => {
let stdout: string = '';
let stderr: string = '';

View file

@ -2,31 +2,34 @@ import * as fs from 'fs';
import * as os from 'os';
import * as buildx from './buildx';
import * as context from './context';
import * as exec from './exec';
import * as stateHelper from './state-helper';
import * as core from '@actions/core';
import * as exec from '@actions/exec';
async function run(): Promise<void> {
try {
if (os.platform() !== 'linux') {
core.setFailed('Only supported on linux platform');
return;
throw new Error(`Only supported on linux platform`);
}
if (!(await buildx.isAvailable())) {
core.setFailed(`Buildx is required. See https://github.com/docker/setup-buildx-action to set up buildx.`);
return;
throw new Error(`Buildx is required. See https://github.com/docker/setup-buildx-action to set up buildx.`);
}
stateHelper.setTmpDir(context.tmpDir);
stateHelper.setTmpDir(context.tmpDir());
const buildxVersion = await buildx.getVersion();
core.info(`📣 Buildx version: ${buildxVersion}`);
let inputs: context.Inputs = await context.getInputs();
const defContext = context.defaultContext();
let inputs: context.Inputs = await context.getInputs(defContext);
core.info(`🏃 Starting build...`);
const args: string[] = await context.getArgs(inputs, buildxVersion);
await exec.exec('docker', args);
const args: string[] = await context.getArgs(inputs, defContext, buildxVersion);
await exec.exec('docker', args).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(`buildx call failed with: ${res.stderr.match(/(.*)\s*$/)![0]}`);
}
});
const imageID = await buildx.getImageID();
if (imageID) {

View file

@ -1,30 +0,0 @@
FROM --platform=${BUILDPLATFORM:-linux/amd64} tonistiigi/xx:golang AS xgo
FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.13-alpine AS builder
ENV CGO_ENABLED 0
ENV GO111MODULE on
ENV GOPROXY https://goproxy.io
COPY --from=xgo / /
ARG TARGETPLATFORM
RUN go env
RUN apk --update --no-cache add \
build-base \
gcc \
git \
&& rm -rf /tmp/* /var/cache/apk/*
WORKDIR /app
ENV DIUN_VERSION="v4.4.0"
RUN git clone --branch ${DIUN_VERSION} https://github.com/crazy-max/diun .
RUN go mod download
RUN go build -ldflags "-w -s -X 'main.version=test'" -v -o diun cmd/main.go
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:latest
COPY --from=builder /app/diun /usr/local/bin/diun
COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /usr/local/go/lib/time/zoneinfo.zip
RUN diun --version

View file

@ -636,6 +636,13 @@
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/csv-parse@^1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@types/csv-parse/-/csv-parse-1.2.2.tgz#713486235759d615dc8e6a6a979170ada76701d5"
integrity sha512-k33tLtRKTQxf7hQfMlkWoS2TQYsnpk1ibZN+rzbuCkeBs8m23nHTeDTF1wb/e7/MSLdtgCzqu3oM1I101kd6yw==
dependencies:
csv-parse "*"
"@types/graceful-fs@^4.1.2":
version "4.1.3"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.3.tgz#039af35fe26bec35003e8d86d2ee9c586354348f"
@ -1229,6 +1236,11 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
csv-parse@*, csv-parse@^4.12.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.12.0.tgz#fd42d6291bbaadd51d3009f6cadbb3e53b4ce026"
integrity sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg==
dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"