Merge pull request #118 from useblacksmith/builder-misconfig

fix: use correct platform when creating remote buildx builder
This commit is contained in:
Aditya Maru 2025-06-11 16:43:20 -04:00 committed by GitHub
commit 4fac79897d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 694 additions and 490 deletions

View File

@ -0,0 +1,99 @@
name: Builder platform matrix tests
on:
workflow_dispatch:
pull_request:
jobs:
# 1) Build AMD image on default (amd64) runner
amd_on_amd:
name: linux/amd64 build on blacksmith runner
runs-on: blacksmith
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Write sample Dockerfile
run: |
cat <<'EOF' > Dockerfile
FROM alpine:3.20
# Install something non-trivial so that layer caching is observable
RUN apk add --no-cache curl git
EOF
- name: Build image (linux/amd64)
uses: useblacksmith/build-push-action@builder-misconfig
with:
context: .
platforms: linux/amd64
push: false
tags: test/amd_on_amd:${{ github.sha }}
# 2) Build ARM image on default (amd64) runner
arm_on_amd:
name: linux/arm64 build on blacksmith runner
runs-on: blacksmith
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Write sample Dockerfile
run: |
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN apk add --no-cache curl git
EOF
- name: Build image (linux/arm64)
uses: useblacksmith/build-push-action@builder-misconfig
with:
context: .
platforms: linux/arm64
push: false
tags: test/arm_on_amd:${{ github.sha }}
# 3) Build AMD image on ARM runner
amd_on_arm:
name: linux/amd64 build on blacksmith-arm runner
runs-on: blacksmith-arm
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Write sample Dockerfile
run: |
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN apk add --no-cache curl git
EOF
- name: Build image (linux/amd64)
uses: useblacksmith/build-push-action@builder-misconfig
with:
context: .
platforms: linux/amd64
push: false
tags: test/amd_on_arm:${{ github.sha }}
# 4) Build ARM image on ARM runner
arm_on_arm:
name: linux/arm64 build on blacksmith-arm runner
runs-on: blacksmith-arm
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Write sample Dockerfile
run: |
cat <<'EOF' > Dockerfile
FROM alpine:3.20
RUN apk add --no-cache curl git
EOF
- name: Build image (linux/arm64)
uses: useblacksmith/build-push-action@builder-misconfig
with:
context: .
platforms: linux/arm64
push: false
tags: test/arm_on_arm:${{ github.sha }}

2
dist/index.js generated vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

984
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9",
"@babel/preset-env": "^7.27.2",
"@babel/preset-typescript": "^7.27.0",
"@types/jest": "^29.5.14",
"@types/node": "^20.12.12",

View File

@ -9,4 +9,4 @@ export class Metric {
type: Metric_MetricType = Metric_MetricType.UNSPECIFIED;
value: number = 0;
labels: Record<string, string> = {};
}
}

View File

@ -1,23 +1,23 @@
export class StickyDiskService {
constructor() {}
async commitStickyDisk() {
return {};
}
async getStickyDisk() {
return {};
}
async queueDockerJob() {
return {};
}
async reportMetric() {
return {};
}
async up() {
return {};
}
}
}

View File

@ -4,4 +4,4 @@ export const execa = jest.fn().mockImplementation(() => {
stderr: '',
exitCode: 0
};
});
});

View File

@ -0,0 +1,40 @@
import * as os from 'os';
import {getRemoteBuilderArgs, resolveRemoteBuilderPlatforms} from '../context';
jest.mock('@actions/core', () => ({
info: jest.fn(),
debug: jest.fn(),
warning: jest.fn(),
error: jest.fn()
}));
describe('Remote builder platform argument resolution', () => {
const builderName = 'test-builder';
const builderUrl = 'tcp://127.0.0.1:1234';
afterEach(() => {
jest.restoreAllMocks();
});
test('returns comma-separated list when platforms are supplied', async () => {
const platforms = ['linux/arm64', 'linux/amd64'];
const platformStr = resolveRemoteBuilderPlatforms(platforms);
expect(platformStr).toBe('linux/arm64,linux/amd64');
const args = await getRemoteBuilderArgs(builderName, builderUrl, platforms);
const idx = args.indexOf('--platform');
expect(idx).toBeGreaterThan(-1);
expect(args[idx + 1]).toBe('linux/arm64,linux/amd64');
});
test('falls back to host architecture when no platforms supplied', async () => {
const platformStr = resolveRemoteBuilderPlatforms([]);
const expectedPlatform = os.arch() === 'arm64' ? 'linux/arm64' : 'linux/amd64';
expect(platformStr).toBe(expectedPlatform);
const args = await getRemoteBuilderArgs(builderName, builderUrl, []);
const idx = args.indexOf('--platform');
expect(idx).toBeGreaterThan(-1);
expect(args[idx + 1]).toBe(expectedPlatform);
});
});

View File

@ -1,6 +1,6 @@
import * as core from '@actions/core';
import * as handlebars from 'handlebars';
import * as fs from 'fs';
import * as os from 'os';
import {Build} from '@docker/actions-toolkit/lib/buildx/build';
import {Context} from '@docker/actions-toolkit/lib/context';
@ -336,12 +336,39 @@ export const tlsClientKeyPath = '/tmp/blacksmith_client_key.pem';
export const tlsClientCaCertificatePath = '/tmp/blacksmith_client_ca_certificate.pem';
export const tlsRootCaCertificatePath = '/tmp/blacksmith_root_ca_certificate.pem';
export async function getRemoteBuilderArgs(name: string, builderUrl: string): Promise<Array<string>> {
/**
* Resolve the platform list that should be passed to `docker buildx create`.
*
* Priority:
* 1. Use the user-supplied platforms list (comma-joined) if provided.
* 2. Fallback to the architecture of the host runner.
*
* The function is exported to allow isolated unit testing.
*/
export function resolveRemoteBuilderPlatforms(platforms?: string[]): string {
// If user explicitly provided platforms, honour them verbatim.
if (platforms && platforms.length > 0) {
return platforms.join(',');
}
// Otherwise derive from host architecture.
const nodeArch = os.arch(); // e.g. 'x64', 'arm64', 'arm'
const archMap: {[key: string]: string} = {
x64: 'amd64',
arm64: 'arm64',
arm: 'arm'
};
const mappedArch = archMap[nodeArch] || nodeArch;
return `linux/${mappedArch}`;
}
export async function getRemoteBuilderArgs(name: string, builderUrl: string, platforms?: string[]): Promise<Array<string>> {
const args: Array<string> = ['create', '--name', name, '--driver', 'remote'];
// TODO(aayush): Instead of hardcoding the platform, we should fail the build if the platform is
// unsupported.
args.push('--platform', 'linux/amd64');
const platformFlag = resolveRemoteBuilderPlatforms(platforms);
core.info(`Determined remote builder platform(s): ${platformFlag}`);
args.push('--platform', platformFlag);
// Always use the remote builder, overriding whatever has been configured so far.
args.push('--use');
// Use the provided builder URL

View File

@ -202,7 +202,7 @@ actionsToolkit.run(
if (builderInfo.addr) {
await core.group(`Creating a builder instance`, async () => {
const name = `blacksmith-${Date.now().toString(36)}`;
const createCmd = await toolkit.buildx.getCommand(await context.getRemoteBuilderArgs(name, builderInfo.addr!));
const createCmd = await toolkit.buildx.getCommand(await context.getRemoteBuilderArgs(name, builderInfo.addr!, inputs.platforms));
core.info(`Creating builder with command: ${createCmd.command}`);
await Exec.getExecOutput(createCmd.command, createCmd.args, {
ignoreReturnCode: true