diff --git a/__tests__/buildx/build.test.ts b/__tests__/buildx/build.test.ts index 2857ef3..5344b94 100644 --- a/__tests__/buildx/build.test.ts +++ b/__tests__/buildx/build.test.ts @@ -18,6 +18,7 @@ import {afterEach, beforeEach, describe, expect, it, vi, test} from 'vitest'; import fs from 'fs'; import os from 'os'; import path from 'path'; +import * as github from '@actions/github'; import * as rimraf from 'rimraf'; import {Context} from '../../src/context.js'; @@ -46,6 +47,8 @@ afterEach(() => { describe('gitContext', () => { const originalEnv = process.env; + const githubContextSha = '860c1904a1ce19322e91ac35af1ab07466440c37'; + const pullRequestHeadSha = 'f11797113e5a9b86bd976329c5dbb8a8bfdfadfa'; beforeEach(() => { vi.resetModules(); process.env = { @@ -53,9 +56,17 @@ describe('gitContext', () => { DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF: '', BUILDX_SEND_GIT_QUERY_AS_INPUT: '' }; + github.context.sha = githubContextSha; + github.context.payload.pull_request = { + number: 15, + head: { + sha: pullRequestHeadSha + } + }; }); afterEach(() => { process.env = originalEnv; + delete github.context.payload.pull_request; }); type GitContextTestCase = { @@ -75,13 +86,13 @@ describe('gitContext', () => { // no format set (defaults to fragment) [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/tags/v1.0.0', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: undefined, prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#f11797113e5a9b86bd976329c5dbb8a8bfdfadfa'], // no format set (defaults to query only when client-side query resolution is enabled and supported) [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/merge&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: true, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/head&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: undefined, prHeadRef: true, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=f11797113e5a9b86bd976329c5dbb8a8bfdfadfa'], [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: false}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {}}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/heads/master', checksum: undefined, format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {checksum: 'cafebabe'}}, 'https://github.com/docker/actions-toolkit.git#cafebabe'], @@ -95,9 +106,10 @@ describe('gitContext', () => { // query format [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/merge&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/tags/v1.0.0', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/tags/v1.0.0&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: 'query', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/head&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'query', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=f11797113e5a9b86bd976329c5dbb8a8bfdfadfa'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {checksum: 'cafebabe'}}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/merge&checksum=cafebabe'], [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&subdir=subdir'], [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: '.'}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/heads/master', checksum: undefined, format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {ref: 'refs/tags/v1.0.0', checksum: 'cafebabe', subdir: 'subdir', submodules: 'false'}}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=cafebabe&subdir=subdir&submodules=false'], @@ -108,14 +120,15 @@ describe('gitContext', () => { // fragment format [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/tags/v1.0.0', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#f11797113e5a9b86bd976329c5dbb8a8bfdfadfa'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {checksum: 'cafebabe'}}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'], [{ref: 'refs/heads/master', checksum: undefined, format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {checksum: 'cafebabe', subdir: 'subdir', ref: 'refs/tags/v1.0.0'}}, 'https://github.com/docker/actions-toolkit.git#cafebabe:subdir'], [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, attrs: {'keep-git-dir': 'true'}}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37:subdir'], [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: '.'}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], - [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head:subdir'], + [{ref: 'refs/pull/15/merge', checksum: undefined, format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git#f11797113e5a9b86bd976329c5dbb8a8bfdfadfa:subdir'], ]; test.each(gitContextCases)('given %o should return %o', async (input: GitContextTestCase, expected: string) => { diff --git a/src/buildx/build.ts b/src/buildx/build.ts index 4cf2d95..8c65c50 100644 --- a/src/buildx/build.ts +++ b/src/buildx/build.ts @@ -59,28 +59,37 @@ export class Build { public async gitContext(opts?: GitContextOpts): Promise { const gitContextCommonAttrs = new Set(['ref', 'checksum', 'subdir']); - const setPullRequestHeadRef = Util.parseBoolOrDefault(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF); - const commonAttrs = { - ref: opts?.attrs?.ref, - checksum: opts?.attrs?.checksum, - subdir: opts?.attrs?.subdir - }; + const commonAttrs = opts?.attrs || {}; + const extraAttrs = Object.entries(commonAttrs).filter(([name]) => !gitContextCommonAttrs.has(name)); - const gitChecksum = opts?.checksum || commonAttrs.checksum || github.context.sha; let ref = opts?.ref || commonAttrs.ref || github.context.ref; - const subdir = opts?.subdir || commonAttrs.subdir; - const attrs = Object.entries(opts?.attrs || {}).filter(([name]) => !gitContextCommonAttrs.has(name)); if (!ref.startsWith('refs/')) { ref = `refs/heads/${ref}`; - } else if (ref.startsWith(`refs/pull/`) && setPullRequestHeadRef) { + } else if (ref.startsWith(`refs/pull/`) && Util.parseBoolOrDefault(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF)) { ref = ref.replace(/\/merge$/g, '/head'); } + const inputChecksum = opts?.checksum || commonAttrs.checksum; + const inputSubdir = opts?.subdir || commonAttrs.subdir; + const checksum = inputChecksum || (ref.startsWith(`refs/pull/`) ? undefined : github.context.sha); + + // BuildKit resolves PR refs remotely at build time, so mutable refs like + // refs/pull/*/{merge,head} can drift away from the event SHA. actions/checkout + // avoids that by fetching the exact commit into a local PR ref; here we do the + // equivalent for implicit PR contexts by rewriting them to the event's commit SHA. + if (!inputChecksum && ref.startsWith(`refs/pull/`)) { + if (ref.endsWith('/merge')) { + ref = github.context.sha; + } else if (ref.endsWith('/head') && typeof github.context.payload.pull_request?.head?.sha === 'string') { + ref = github.context.payload.pull_request.head.sha; + } + } + const baseURL = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git`; let format = opts?.format; if (!format) { format = 'fragment'; - if (attrs.length > 0) { + if (extraAttrs.length > 0) { format = 'query'; } else if (Util.parseBoolOrDefault(process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT)) { try { @@ -92,21 +101,23 @@ export class Build { } } } + if (format === 'query') { const query = [`ref=${ref}`]; - if (gitChecksum) { - query.push(`checksum=${gitChecksum}`); + if (checksum) { + query.push(`checksum=${checksum}`); } - if (subdir && subdir !== '.') { - query.push(`subdir=${subdir}`); + if (inputSubdir && inputSubdir !== '.') { + query.push(`subdir=${inputSubdir}`); } - for (const [name, value] of attrs) { + for (const [name, value] of extraAttrs) { query.push(`${name}=${value}`); } return `${baseURL}?${query.join('&')}`; } - const fragmentRef = gitChecksum && !ref.startsWith(`refs/pull/`) ? gitChecksum : ref; - return `${baseURL}#${fragmentRef}${subdir && subdir !== '.' ? `:${subdir}` : ''}`; + + const fragmentRef = inputChecksum && ref.startsWith(`refs/pull/`) ? ref : (checksum ?? ref); + return `${baseURL}#${fragmentRef}${inputSubdir && inputSubdir !== '.' ? `:${inputSubdir}` : ''}`; } public getImageIDFilePath(): string {