21 changes: 21 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: publish

on:
release:
types:
- published

jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
-
name: Checkout
uses: actions/checkout@v5
-
name: Publish
uses: actions/publish-immutable-action@v0.0.4
14 changes: 10 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: test

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
Expand All @@ -13,14 +17,16 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
-
name: Test
uses: docker/bake-action@v3
uses: docker/bake-action@v6
with:
source: .
targets: test
-
name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
file: ./coverage/clover.xml
files: ./coverage/clover.xml
token: ${{ secrets.CODECOV_TOKEN }}
22 changes: 12 additions & 10 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
name: validate

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
Expand All @@ -11,16 +15,17 @@ jobs:
prepare:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.targets.outputs.matrix }}
targets: ${{ steps.generate.outputs.targets }}
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
-
name: Targets matrix
id: targets
run: |
echo "matrix=$(docker buildx bake validate --print | jq -cr '.group.validate.targets')" >> $GITHUB_OUTPUT
name: List targets
id: generate
uses: docker/bake-action/subaction/list-targets@v6
with:
target: validate

validate:
runs-on: ubuntu-latest
Expand All @@ -31,11 +36,8 @@ jobs:
matrix:
target: ${{ fromJson(needs.prepare.outputs.targets) }}
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Validate
uses: docker/bake-action@v3
uses: docker/bake-action@v6
with:
targets: ${{ matrix.target }}
68 changes: 15 additions & 53 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
node_modules
lib
# https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore

# Rest of the file pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand All @@ -19,34 +18,14 @@ pids
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

Expand All @@ -56,36 +35,19 @@ typings/
# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
# dotenv environment variable files
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/
.env.development.local
.env.test.local
.env.production.local
.env.local

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Dependency directories
node_modules/
jspm_packages/

# yarn v2
.yarn/
19 changes: 19 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# https://yarnpkg.com/configuration/yarnrc

compressionLevel: mixed
enableGlobalCache: false
enableHardenedMode: true

logFilters:
- code: YN0013
level: discard
- code: YN0019
level: discard
- code: YN0076
level: discard
- code: YN0086
level: discard

nodeLinker: node-modules

npmAuthToken: "${NODE_AUTH_TOKEN:-fallback}"
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,37 @@ jobs:
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
```
> [!NOTE]
> If you are using [`docker/setup-buildx-action`](https://github.com/docker/setup-buildx-action),
> this action should come before it:
>
> ```yaml
> -
> name: Set up QEMU
> uses: docker/setup-qemu-action@v3
> -
> name: Set up Docker Buildx
> uses: docker/setup-buildx-action@v3
> ```

## Customizing

### inputs

Following inputs can be used as `step.with` keys
The following inputs can be used as `step.with` keys:

| Name | Type | Default | Description |
|-------------|--------|-------------------------------------------------------------------------------|--------------------------------------------------|
| `image` | String | [`tonistiigi/binfmt:latest`](https://hub.docker.com/r/tonistiigi/binfmt/tags) | QEMU static binaries Docker image |
| `platforms` | String | `all` | Platforms to install (e.g., `arm64,riscv64,arm`) |
| Name | Type | Default | Description |
|---------------|--------|-------------------------------------------------------------------------------|----------------------------------------------------|
| `image` | String | [`tonistiigi/binfmt:latest`](https://hub.docker.com/r/tonistiigi/binfmt/tags) | QEMU static binaries Docker image |
| `platforms` | String | `all` | Platforms to install (e.g., `arm64,riscv64,arm`) |
| `cache-image` | Bool | `true` | Cache binfmt image to GitHub Actions cache backend |

### outputs

Following outputs are available
The following outputs are available:

| Name | Type | Description |
|---------------|---------|---------------------------------------|
Expand Down
13 changes: 10 additions & 3 deletions __tests__/context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,38 @@ describe('getInputs', () => {
test.each([
[
0,
new Map<string, string>([]),
new Map<string, string>([
['cache-image', 'true'],
]),
{
image: 'tonistiigi/binfmt:latest',
image: 'docker.io/tonistiigi/binfmt:latest',
platforms: 'all',
cacheImage: true,
} as context.Inputs
],
[
1,
new Map<string, string>([
['image', 'docker/binfmt:latest'],
['platforms', 'arm64,riscv64,arm'],
['cache-image', 'false'],
]),
{
image: 'docker/binfmt:latest',
platforms: 'arm64,riscv64,arm',
cacheImage: false,
} as context.Inputs
],
[
2,
new Map<string, string>([
['platforms', 'arm64, riscv64, arm '],
['cache-image', 'true'],
]),
{
image: 'tonistiigi/binfmt:latest',
image: 'docker.io/tonistiigi/binfmt:latest',
platforms: 'arm64,riscv64,arm',
cacheImage: true,
} as context.Inputs
]
])(
Expand Down
9 changes: 7 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,22 @@ branding:
inputs:
image:
description: 'QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)'
default: 'tonistiigi/binfmt:latest'
default: 'docker.io/tonistiigi/binfmt:latest'
required: false
platforms:
description: 'Platforms to install (e.g. arm64,riscv64,arm)'
default: 'all'
required: false
cache-image:
description: 'Cache binfmt image to GitHub Actions cache backend'
default: 'true'
required: false

outputs:
platforms:
description: 'Available platforms (comma separated)'

runs:
using: 'node16'
using: 'node20'
main: 'dist/index.js'
post: 'dist/index.js'
19 changes: 15 additions & 4 deletions dev.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
# syntax=docker/dockerfile:1

ARG NODE_VERSION=16
ARG NODE_VERSION=20

FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git
WORKDIR /src
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache <<EOT
corepack enable
yarn --version
yarn config set --home enableTelemetry 0
EOT

FROM base AS deps
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn install && mkdir /vendor && cp yarn.lock /vendor

Expand All @@ -20,14 +27,15 @@ RUN --mount=type=bind,target=.,rw <<EOT
git add -A
cp -rf /vendor/* .
if [ -n "$(git status --porcelain -- yarn.lock)" ]; then
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor-update"'
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor"'
git status --porcelain -- yarn.lock
exit 1
fi
EOT

FROM deps AS build
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run build && mkdir /out && cp -Rf dist /out/

Expand All @@ -48,22 +56,25 @@ EOT

FROM deps AS format
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run format \
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' | cpio -pdm /out
&& mkdir /out && find . -name '*.ts' -not -path './node_modules/*' -not -path './.yarn/*' | cpio -pdm /out

FROM scratch AS format-update
COPY --from=format /out /

FROM deps AS lint
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run lint

FROM deps AS test
RUN --mount=type=bind,target=.,rw \
--mount=type=cache,target=/src/.yarn/cache \
--mount=type=cache,target=/src/node_modules \
yarn run test --coverageDirectory=/tmp/coverage
yarn run test --coverage --coverageDirectory=/tmp/coverage

FROM scratch AS test-coverage
COPY --from=test /tmp/coverage /
97 changes: 96 additions & 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.

3,933 changes: 3,730 additions & 203 deletions dist/licenses.txt

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/sourcemap-register.js

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions docker-bake.hcl
Original file line number Diff line number Diff line change
@@ -1,52 +1,65 @@
target "_common" {
args = {
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
}
}

group "default" {
targets = ["build"]
}

group "pre-checkin" {
targets = ["vendor-update", "format", "build"]
targets = ["vendor", "format", "build"]
}

group "validate" {
targets = ["lint", "build-validate", "vendor-validate"]
}

target "build" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "build-update"
output = ["."]
}

target "build-validate" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "build-validate"
output = ["type=cacheonly"]
}

target "format" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "format-update"
output = ["."]
}

target "lint" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "lint"
output = ["type=cacheonly"]
}

target "vendor-update" {
target "vendor" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "vendor-update"
output = ["."]
}

target "vendor-validate" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "vendor-validate"
output = ["type=cacheonly"]
}

target "test" {
inherits = ["_common"]
dockerfile = "dev.Dockerfile"
target = "test-coverage"
output = ["./coverage"]
Expand Down
52 changes: 25 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"name": "docker-setup-qemu",
"description": "Install QEMU static binaries",
"main": "lib/main.js",
"main": "src/main.ts",
"scripts": {
"build": "ncc build src/main.ts --source-map --minify --license licenses.txt",
"lint": "eslint src/**/*.ts __tests__/**/*.ts",
"format": "eslint --fix src/**/*.ts __tests__/**/*.ts",
"test": "jest --coverage",
"all": "yarn run build && yarn run format && yarn test"
"lint": "yarn run prettier && yarn run eslint",
"format": "yarn run prettier:fix && yarn run eslint:fix",
"eslint": "eslint --max-warnings=0 .",
"eslint:fix": "eslint --fix .",
"prettier": "prettier --check \"./**/*.ts\"",
"prettier:fix": "prettier --write \"./**/*.ts\"",
"test": "jest"
},
"repository": {
"type": "git",
Expand All @@ -18,31 +21,26 @@
"docker",
"qemu"
],
"author": "Docker",
"contributors": [
{
"name": "CrazyMax",
"url": "https://crazymax.dev"
}
],
"author": "Docker Inc.",
"license": "Apache-2.0",
"packageManager": "yarn@4.9.2",
"dependencies": {
"@actions/core": "^1.10.0",
"@docker/actions-toolkit": "^0.3.0"
"@actions/core": "^1.11.1",
"@docker/actions-toolkit": "^0.67.0"
},
"devDependencies": {
"@types/node": "^16.18.21",
"@typescript-eslint/eslint-plugin": "^5.56.0",
"@typescript-eslint/parser": "^5.56.0",
"@vercel/ncc": "^0.36.1",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-jest": "^27.2.1",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"prettier": "^2.8.7",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
"@types/node": "^20.19.9",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.2",
"eslint-plugin-jest": "^28.14.0",
"eslint-plugin-prettier": "^5.5.4",
"jest": "^29.7.0",
"prettier": "^3.6.2",
"ts-jest": "^29.4.1",
"ts-node": "^10.9.2",
"typescript": "^5.9.2"
}
}
6 changes: 4 additions & 2 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import {Util} from '@docker/actions-toolkit/lib/util';
export interface Inputs {
image: string;
platforms: string;
cacheImage: boolean;
}

export function getInputs(): Inputs {
return {
image: core.getInput('image') || 'tonistiigi/binfmt:latest',
platforms: Util.getInputList('platforms').join(',') || 'all'
image: core.getInput('image') || 'docker.io/tonistiigi/binfmt:latest',
platforms: Util.getInputList('platforms').join(',') || 'all',
cacheImage: core.getBooleanInput('cache-image')
};
}
34 changes: 28 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as context from './context';
import * as core from '@actions/core';
import * as actionsToolkit from '@docker/actions-toolkit';

import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
import {Exec} from '@docker/actions-toolkit/lib/exec';

interface Platforms {
supported: string[];
Expand All @@ -20,24 +20,46 @@ actionsToolkit.run(
});

await core.group(`Pulling binfmt Docker image`, async () => {
await Exec.exec('docker', ['pull', input.image]);
await Docker.pull(input.image, input.cacheImage);
});

await core.group(`Image info`, async () => {
await Exec.exec('docker', ['image', 'inspect', input.image]);
await Docker.getExecOutput(['image', 'inspect', input.image], {
ignoreReturnCode: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
}
});
});

await core.group(`Binfmt version`, async () => {
await Docker.getExecOutput(['run', '--rm', '--privileged', input.image, '--version'], {
ignoreReturnCode: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
}
});
});

await core.group(`Installing QEMU static binaries`, async () => {
await Exec.exec('docker', ['run', '--rm', '--privileged', input.image, '--install', input.platforms]);
await Docker.getExecOutput(['run', '--rm', '--privileged', input.image, '--install', input.platforms], {
ignoreReturnCode: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
}
});
});

await core.group(`Extracting available platforms`, async () => {
await Exec.getExecOutput('docker', ['run', '--rm', '--privileged', input.image], {
await Docker.getExecOutput(['run', '--rm', '--privileged', input.image], {
ignoreReturnCode: true,
silent: true
}).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr.trim());
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
}
const platforms: Platforms = JSON.parse(res.stdout.trim());
core.info(`${platforms.supported.join(',')}`);
Expand Down
10,424 changes: 7,003 additions & 3,421 deletions yarn.lock

Large diffs are not rendered by default.