From 3b7fd341749351ac7eb8c9972c8d1150a9de3145 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:46:23 +0200 Subject: [PATCH] refactor: use new gitContext for build context resolution Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/ci.yml | 51 +++++++++++++++++++++ __tests__/context.test.ts | 95 ++++++++++++++++++++++++++++++++++++++- src/context.ts | 24 ++++++---- 3 files changed, 159 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecf2bea..0a800f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,6 +101,57 @@ jobs: exit 1 fi + git-context-query: + runs-on: ubuntu-latest + env: + BUILDX_SEND_GIT_QUERY_AS_INPUT: true + services: + registry: + image: registry:2.8.3@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373 + ports: + - 5000:5000 + steps: + - + name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + path: action + - + name: Set up QEMU + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + - + name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + with: + version: v0.29.0 + driver-opts: | + network=host + image=${{ inputs.buildkit-image || env.BUILDKIT_IMAGE }} + - + name: Build and push + id: docker_build + uses: ./action + with: + file: ./test/Dockerfile + builder: ${{ steps.buildx.outputs.name }} + platforms: linux/amd64,linux/arm64 + push: true + tags: | + localhost:5000/name/app:latest + localhost:5000/name/app:1.0.0 + - + name: Inspect + run: | + docker buildx imagetools inspect localhost:5000/name/app:1.0.0 --format '{{json .}}' + - + name: Check digest + run: | + if [ -z "${{ steps.docker_build.outputs.digest }}" ]; then + echo "::error::Digest should not be empty" + exit 1 + fi + git-context-secret: runs-on: ubuntu-latest services: diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index 74fb50e..25ece9a 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -12,8 +12,6 @@ import {Toolkit} from '@docker/actions-toolkit/lib/toolkit.js'; import {BuilderInfo} from '@docker/actions-toolkit/lib/types/buildx/builder.js'; -import * as context from '../src/context.js'; - const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'context-')); const tmpName = path.join(tmpDir, '.tmpname-vi'); const fixturesDir = path.join(__dirname, 'fixtures'); @@ -52,6 +50,53 @@ vi.spyOn(Builder.prototype, 'inspect').mockImplementation(async (): Promise { + const originalEnv = process.env; + + beforeEach(() => { + process.env = Object.keys(process.env).reduce((object, key) => { + if (!key.startsWith('INPUT_')) { + object[key] = process.env[key]; + } + return object; + }, {}); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + function setRequiredBooleanInputs(): void { + setInput('load', 'false'); + setInput('no-cache', 'false'); + setInput('push', 'false'); + setInput('pull', 'false'); + } + + test('uses Build git context when context input is empty', async () => { + const gitContext = 'https://github.com/docker/build-push-action.git?ref=refs/heads/master'; + const gitContextSpy = vi.spyOn(Build.prototype, 'gitContext').mockResolvedValue(gitContext); + setRequiredBooleanInputs(); + const context = await loadContextModule(); + const inputs = await context.getInputs(); + expect(inputs.context).toBe(gitContext); + expect(gitContextSpy).toHaveBeenCalledTimes(1); + gitContextSpy.mockRestore(); + }); + + test('renders defaultContext templates from Build git context', async () => { + const gitContext = 'https://github.com/docker/build-push-action.git#refs/heads/master'; + const gitContextSpy = vi.spyOn(Build.prototype, 'gitContext').mockResolvedValue(gitContext); + setRequiredBooleanInputs(); + setInput('context', '{{defaultContext}}:subdir'); + const context = await loadContextModule(); + const inputs = await context.getInputs(); + expect(inputs.context).toBe(`${gitContext}:subdir`); + expect(gitContextSpy).toHaveBeenCalledTimes(1); + gitContextSpy.mockRestore(); + }); +}); + describe('getArgs', () => { const originalEnv = process.env; beforeEach(() => { @@ -888,6 +933,46 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] ['GITHUB_SERVER_URL', 'https://github.cds.internal.unity3d.com'], ]) ], + [ + 37, + '0.29.0', + new Map([ + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'false'], + ['pull', 'false'], + ]), + [ + 'build', + '--iidfile', imageIDFilePath, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789/attempts/1`, + '--metadata-file', metadataJson, + 'https://github.com/docker/build-push-action.git?ref=refs/heads/master' + ], + new Map([ + ['BUILDX_SEND_GIT_QUERY_AS_INPUT', 'true'] + ]) + ], + [ + 38, + '0.28.0', + new Map([ + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'false'], + ['pull', 'false'], + ]), + [ + 'build', + '--iidfile', imageIDFilePath, + '--attest', `type=provenance,mode=min,inline-only=true,builder-id=https://github.com/docker/build-push-action/actions/runs/123456789/attempts/1`, + '--metadata-file', metadataJson, + 'https://github.com/docker/build-push-action.git#refs/heads/master' + ], + new Map([ + ['BUILDX_SEND_GIT_QUERY_AS_INPUT', 'true'] + ]) + ], ])( '[%d] given %o with %o as inputs, returns %o', async (num: number, buildxVersion: string, inputs: Map, expected: Array, envs: Map | undefined) => { @@ -903,6 +988,7 @@ ANOTHER_SECRET=ANOTHER_SECRET_ENV`] vi.spyOn(Buildx.prototype, 'version').mockImplementation(async (): Promise => { return buildxVersion; }); + const context = await loadContextModule(); const inp = await context.getInputs(); const res = await context.getArgs(inp, toolkit); expect(res).toEqual(expected); @@ -918,3 +1004,8 @@ function getInputName(name: string): string { function setInput(name: string, value: string): void { process.env[getInputName(name)] = value; } + +async function loadContextModule(): Promise { + vi.resetModules(); + return await import('../src/context.js'); +} diff --git a/src/context.ts b/src/context.ts index fff3641..41e0c78 100644 --- a/src/context.ts +++ b/src/context.ts @@ -2,11 +2,17 @@ import * as core from '@actions/core'; import * as handlebars from 'handlebars'; import {Build} from '@docker/actions-toolkit/lib/buildx/build.js'; -import {Context} from '@docker/actions-toolkit/lib/context.js'; import {GitHub} from '@docker/actions-toolkit/lib/github/github.js'; import {Toolkit} from '@docker/actions-toolkit/lib/toolkit.js'; import {Util} from '@docker/actions-toolkit/lib/util.js'; +let defaultContextPromise: Promise | undefined; + +async function getDefaultContext(): Promise { + defaultContextPromise ??= new Build().gitContext(); + return await defaultContextPromise; +} + export interface Inputs { 'add-hosts': string[]; allow: string[]; @@ -44,6 +50,7 @@ export interface Inputs { } export async function getInputs(): Promise { + const defaultContext = await getDefaultContext(); return { 'add-hosts': Util.getInputList('add-hosts'), allow: Util.getInputList('allow'), @@ -56,7 +63,7 @@ export async function getInputs(): Promise { 'cache-to': Util.getInputList('cache-to', {ignoreComma: true}), call: core.getInput('call'), 'cgroup-parent': core.getInput('cgroup-parent'), - context: core.getInput('context') || Context.gitContext(), + context: handlebars.compile(core.getInput('context'))({defaultContext}) || defaultContext, file: core.getInput('file'), labels: Util.getInputList('labels', {ignoreComma: true}), load: core.getBooleanInput('load'), @@ -82,18 +89,17 @@ export async function getInputs(): Promise { } export async function getArgs(inputs: Inputs, toolkit: Toolkit): Promise> { - const context = handlebars.compile(inputs.context)({ - defaultContext: Context.gitContext() - }); // prettier-ignore return [ - ...await getBuildArgs(inputs, context, toolkit), + ...await getBuildArgs(inputs, inputs.context, toolkit), ...await getCommonArgs(inputs, toolkit), - context + inputs.context ]; } async function getBuildArgs(inputs: Inputs, context: string, toolkit: Toolkit): Promise> { + const defaultContext = await getDefaultContext(); + const args: Array = ['build']; await Util.asyncForEach(inputs['add-hosts'], async addHost => { args.push('--add-host', addHost); @@ -116,7 +122,7 @@ async function getBuildArgs(inputs: Inputs, context: string, toolkit: Toolkit): args.push( '--build-context', handlebars.compile(buildContext)({ - defaultContext: Context.gitContext() + defaultContext: defaultContext }) ); }); @@ -182,7 +188,7 @@ async function getBuildArgs(inputs: Inputs, context: string, toolkit: Toolkit): core.warning(err.message); } }); - if (inputs['github-token'] && !Build.hasGitAuthTokenSecret(inputs.secrets) && context.startsWith(Context.gitContext())) { + if (inputs['github-token'] && !Build.hasGitAuthTokenSecret(inputs.secrets) && context.startsWith(defaultContext)) { args.push('--secret', Build.resolveSecretString(`GIT_AUTH_TOKEN.${new URL(GitHub.serverURL).host.trimEnd()}=${inputs['github-token']}`)); } if (inputs['shm-size']) {