Skip to content

Commit b732db2

Browse files
authored
Merge pull request #809 from crazy-max/git-query-url
build: git context query format support
2 parents 8696544 + 3bb4ae3 commit b732db2

File tree

7 files changed

+135
-69
lines changed

7 files changed

+135
-69
lines changed

__tests__/buildx/build.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import * as rimraf from 'rimraf';
2222

2323
import {Context} from '../../src/context.js';
2424
import {Build} from '../../src/buildx/build.js';
25+
import {Buildx} from '../../src/buildx/buildx.js';
26+
27+
import {GitContextFormat} from '../../src/types/buildx/build.js';
2528

2629
const fixturesDir = path.join(__dirname, '..', '.fixtures');
2730
const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'buildx-build-'));
@@ -41,6 +44,66 @@ afterEach(() => {
4144
rimraf.sync(tmpDir);
4245
});
4346

47+
describe('gitContext', () => {
48+
const originalEnv = process.env;
49+
beforeEach(() => {
50+
vi.resetModules();
51+
process.env = {
52+
...originalEnv,
53+
DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF: '',
54+
BUILDX_SEND_GIT_QUERY_AS_INPUT: ''
55+
};
56+
});
57+
afterEach(() => {
58+
process.env = originalEnv;
59+
});
60+
61+
type GitContextTestCase = {
62+
ref: string;
63+
format: GitContextFormat | undefined;
64+
prHeadRef: boolean;
65+
sendGitQueryAsInput: boolean;
66+
buildxQuerySupport: boolean;
67+
};
68+
69+
// prettier-ignore
70+
const gitContextCases: [GitContextTestCase, string][] = [
71+
// no format set (defaults to fragment)
72+
[{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
73+
[{ref: 'master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
74+
[{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'],
75+
[{ref: 'refs/tags/v1.0.0', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
76+
[{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head'],
77+
// no format set (defaults to query only when client-side query resolution is enabled and supported)
78+
[{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'],
79+
[{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'],
80+
[{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'],
81+
[{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: false}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
82+
// query format
83+
[{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'],
84+
[{ref: 'master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'],
85+
[{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'],
86+
[{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'],
87+
[{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'],
88+
// fragment format
89+
[{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
90+
[{ref: 'master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
91+
[{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'],
92+
[{ref: 'refs/tags/v1.0.0', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'],
93+
[{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head'],
94+
];
95+
96+
test.each(gitContextCases)('given %o should return %o', async (input: GitContextTestCase, expected: string) => {
97+
const {ref, format, prHeadRef, sendGitQueryAsInput, buildxQuerySupport} = input;
98+
process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF = prHeadRef ? 'true' : '';
99+
process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT = sendGitQueryAsInput ? 'true' : '';
100+
const buildx = new Buildx();
101+
vi.spyOn(buildx, 'versionSatisfies').mockResolvedValue(buildxQuerySupport);
102+
const build = new Build({buildx});
103+
expect(await build.gitContext(ref, '860c1904a1ce19322e91ac35af1ab07466440c37', format)).toEqual(expected);
104+
});
105+
});
106+
44107
describe('resolveImageID', () => {
45108
it('matches', async () => {
46109
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';

__tests__/context.test.ts

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import {describe, expect, vi, it, afterEach, beforeEach, test} from 'vitest';
17+
import {describe, expect, it, afterEach} from 'vitest';
1818
import fs from 'fs';
1919
import os from 'os';
2020
import path from 'path';
@@ -23,57 +23,35 @@ import * as rimraf from 'rimraf';
2323
import {Context} from '../src/context.js';
2424

2525
const tmpDir = fs.mkdtempSync(path.join(process.env.TEMP || os.tmpdir(), 'context-'));
26-
const tmpName = path.join(tmpDir, '.tmpname-vi');
27-
28-
vi.spyOn(Context, 'tmpDir').mockImplementation((): string => {
29-
fs.mkdirSync(tmpDir, {recursive: true});
30-
return tmpDir;
31-
});
32-
33-
vi.spyOn(Context, 'tmpName').mockImplementation((): string => {
34-
return tmpName;
35-
});
3626

3727
afterEach(() => {
3828
rimraf.sync(tmpDir);
29+
fs.mkdirSync(tmpDir, {recursive: true});
3930
});
4031

41-
describe('gitRef', () => {
42-
it('returns refs/heads/master', async () => {
43-
expect(Context.gitRef()).toEqual('refs/heads/master');
32+
describe('tmpDir', () => {
33+
it('returns an existing directory and keeps it stable', () => {
34+
const dir = Context.tmpDir();
35+
expect(fs.existsSync(dir)).toBe(true);
36+
expect(fs.statSync(dir).isDirectory()).toBe(true);
37+
expect(Context.tmpDir()).toEqual(dir);
4438
});
4539
});
4640

47-
describe('parseGitRef', () => {
48-
const originalEnv = process.env;
49-
beforeEach(() => {
50-
vi.resetModules();
51-
process.env = {
52-
...originalEnv,
53-
DOCKER_GIT_CONTEXT_PR_HEAD_REF: ''
54-
};
55-
});
56-
afterEach(() => {
57-
process.env = originalEnv;
41+
describe('tmpName', () => {
42+
it('returns a path for the provided tmpdir and template', () => {
43+
const name = Context.tmpName({
44+
tmpdir: tmpDir,
45+
template: '.tmpname-XXXXXX'
46+
});
47+
expect(path.dirname(name)).toEqual(tmpDir);
48+
expect(path.basename(name)).toMatch(/^\.tmpname-/);
49+
expect(fs.existsSync(name)).toBe(false);
5850
});
59-
// prettier-ignore
60-
test.each([
61-
['refs/heads/master', '860c1904a1ce19322e91ac35af1ab07466440c37', false, '860c1904a1ce19322e91ac35af1ab07466440c37'],
62-
['master', '860c1904a1ce19322e91ac35af1ab07466440c37', false, '860c1904a1ce19322e91ac35af1ab07466440c37'],
63-
['refs/pull/15/merge', '860c1904a1ce19322e91ac35af1ab07466440c37', false, 'refs/pull/15/merge'],
64-
['refs/heads/master', '', false, 'refs/heads/master'],
65-
['master', '', false, 'master'],
66-
['refs/tags/v1.0.0', '', false, 'refs/tags/v1.0.0'],
67-
['refs/pull/15/merge', '', false, 'refs/pull/15/merge'],
68-
['refs/pull/15/merge', '', true, 'refs/pull/15/head'],
69-
])('given %o and %o, should return %o', async (ref: string, sha: string, prHeadRef: boolean, expected: string) => {
70-
process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF = prHeadRef ? 'true' : '';
71-
expect(Context.parseGitRef(ref, sha)).toEqual(expected);
72-
});
73-
});
7451

75-
describe('gitContext', () => {
76-
it('returns refs/heads/master', async () => {
77-
expect(Context.gitContext()).toEqual('https://github.com/docker/actions-toolkit.git#refs/heads/master');
52+
it('returns different paths on consecutive calls', () => {
53+
const first = Context.tmpName({tmpdir: tmpDir, template: '.tmpname-XXXXXX'});
54+
const second = Context.tmpName({tmpdir: tmpDir, template: '.tmpname-XXXXXX'});
55+
expect(first).not.toEqual(second);
7856
});
7957
});

__tests__/util.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ describe('hash', () => {
315315
// https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob_test.go#L36-L58
316316
describe('parseBool', () => {
317317
[
318+
{input: undefined, expected: false, throwsError: false},
318319
{input: '', expected: false, throwsError: true},
319320
{input: 'asdf', expected: false, throwsError: true},
320321
{input: '0', expected: false, throwsError: false},
@@ -342,6 +343,13 @@ describe('parseBool', () => {
342343
});
343344
});
344345

346+
describe('parseBoolOrDefault', () => {
347+
it('returns default value when input is invalid', () => {
348+
expect(Util.parseBoolOrDefault('asdf')).toBe(false);
349+
expect(Util.parseBoolOrDefault('asdf', true)).toBe(true);
350+
});
351+
});
352+
345353
describe('formatFileSize', () => {
346354
test('should return "0 Bytes" when given 0 bytes', () => {
347355
expect(Util.formatFileSize(0)).toBe('0 Bytes');

src/buildx/build.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
import fs from 'fs';
1818
import path from 'path';
1919
import * as core from '@actions/core';
20+
import * as github from '@actions/github';
2021
import {parse} from 'csv-parse/sync';
2122

2223
import {Buildx} from './buildx.js';
2324
import {Context} from '../context.js';
2425
import {GitHub} from '../github/github.js';
2526
import {Util} from '../util.js';
2627

27-
import {BuildMetadata} from '../types/buildx/build.js';
28+
import {BuildMetadata, GitContextFormat} from '../types/buildx/build.js';
2829
import {VertexWarning} from '../types/buildkit/client.js';
2930
import {ProvenancePredicate} from '../types/intoto/slsa_provenance/v0.2/provenance.js';
3031

@@ -48,6 +49,33 @@ export class Build {
4849
this.metadataFilename = `build-metadata-${Util.generateRandomString()}.json`;
4950
}
5051

52+
public async gitContext(ref?: string, sha?: string, format?: GitContextFormat): Promise<string> {
53+
const setPullRequestHeadRef = Util.parseBoolOrDefault(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF);
54+
ref = ref || github.context.ref;
55+
sha = sha || github.context.sha;
56+
if (!ref.startsWith('refs/')) {
57+
ref = `refs/heads/${ref}`;
58+
} else if (ref.startsWith(`refs/pull/`) && setPullRequestHeadRef) {
59+
ref = ref.replace(/\/merge$/g, '/head');
60+
}
61+
const baseURL = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git`;
62+
if (!format) {
63+
const sendGitQueryAsInput = Util.parseBoolOrDefault(process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT);
64+
if (sendGitQueryAsInput && (await this.buildx.versionSatisfies('>=0.29.0'))) {
65+
format = 'query';
66+
} else {
67+
format = 'fragment';
68+
}
69+
}
70+
if (format === 'query') {
71+
return `${baseURL}?ref=${ref}${sha ? `&checksum=${sha}` : ''}`;
72+
}
73+
if (sha && !ref.startsWith(`refs/pull/`)) {
74+
return `${baseURL}#${sha}`;
75+
}
76+
return `${baseURL}#${ref}`;
77+
}
78+
5179
public getImageIDFilePath(): string {
5280
return path.join(Context.tmpDir(), this.iidFilename);
5381
}

src/context.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import fs from 'fs';
1818
import os from 'os';
1919
import path from 'path';
2020
import * as tmp from 'tmp';
21-
import * as github from '@actions/github';
22-
23-
import {GitHub} from './github/github.js';
2421

2522
export class Context {
2623
private static readonly _tmpDir = fs.mkdtempSync(path.join(Context.ensureDirExists(process.env.RUNNER_TEMP || os.tmpdir()), 'docker-actions-toolkit-'));
@@ -37,25 +34,4 @@ export class Context {
3734
public static tmpName(options?: tmp.TmpNameOptions): string {
3835
return tmp.tmpNameSync(options);
3936
}
40-
41-
public static gitRef(): string {
42-
return Context.parseGitRef(github.context.ref, github.context.sha);
43-
}
44-
45-
public static parseGitRef(ref: string, sha: string): string {
46-
const setPullRequestHeadRef: boolean = !!(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF && process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF === 'true');
47-
if (sha && ref && !ref.startsWith('refs/')) {
48-
ref = `refs/heads/${ref}`;
49-
}
50-
if (sha && !ref.startsWith(`refs/pull/`)) {
51-
ref = sha;
52-
} else if (ref.startsWith(`refs/pull/`) && setPullRequestHeadRef) {
53-
ref = ref.replace(/\/merge$/g, '/head');
54-
}
55-
return ref;
56-
}
57-
58-
public static gitContext(): string {
59-
return `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git#${Context.gitRef()}`;
60-
}
6137
}

src/types/buildx/build.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
export type GitContextFormat = 'fragment' | 'query';
18+
1719
export type BuildMetadata = {
1820
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1921
[key: string]: any;

src/util.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,10 @@ export class Util {
157157
}
158158

159159
// https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob.go#L7-L18
160-
public static parseBool(str: string): boolean {
160+
public static parseBool(str: string | undefined): boolean {
161+
if (str === undefined) {
162+
return false;
163+
}
161164
switch (str) {
162165
case '1':
163166
case 't':
@@ -178,6 +181,14 @@ export class Util {
178181
}
179182
}
180183

184+
public static parseBoolOrDefault(str: string | undefined, defaultValue = false): boolean {
185+
try {
186+
return this.parseBool(str);
187+
} catch {
188+
return defaultValue;
189+
}
190+
}
191+
181192
public static formatFileSize(bytes: number): string {
182193
if (bytes === 0) return '0 Bytes';
183194
const k = 1024;

0 commit comments

Comments
 (0)