Compare commits

...

15 Commits

Author SHA1 Message Date
CrazyMax
c70efab546 Merge pull request #392 from crazy-max/history-export-detach-dialstdio
Some checks failed
publish / publish (push) Has been cancelled
buildx(history): detach dial-stdio process
2024-07-02 15:16:11 +02:00
CrazyMax
55a2181286 Merge pull request #394 from crazy-max/summary-without-upload
github(summary): build record upload optional
2024-07-02 15:11:09 +02:00
CrazyMax
846cac2aa2 Merge pull request #396 from crazy-max/github-isghes
Some checks failed
publish / publish (push) Has been cancelled
github: isGHES func
2024-07-02 13:29:06 +02:00
CrazyMax
83d63d1cf1 github: isGHES func
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-07-02 13:21:26 +02:00
CrazyMax
931b62d64f Merge pull request #395 from crazy-max/bake-fix-resolveRefs
buildx: fix resolveRefs for bake
2024-07-02 12:52:09 +02:00
CrazyMax
16ecd76490 buildx: fix resolveRefs for bake
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-07-02 12:07:44 +02:00
CrazyMax
b26af9f868 github(summary): build record upload optional
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-07-02 10:56:16 +02:00
CrazyMax
ff35e30b01 buildx(history): improve child process termination and exit code handling
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-07-02 10:39:22 +02:00
CrazyMax
200e43c426 buildx(history): detach dial-stdio process
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-07-02 10:36:20 +02:00
CrazyMax
2cb2c5573f Merge pull request #391 from crazy-max/buildkit-git
buildkit: git parseURL and parseRef funcs
2024-07-01 14:20:29 +02:00
CrazyMax
f2de331691 Merge pull request #393 from docker/bot/docker-releases-json
Update `.github/docker-releases.json`
2024-07-01 14:20:08 +02:00
crazy-max
27254cb337 github: update .github/docker-releases.json
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-07-01 12:10:08 +00:00
CrazyMax
c8df3474bd buildkit: git parseURL and parseRef funcs
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-06-29 13:58:49 +02:00
CrazyMax
fe9937dd36 Merge pull request #390 from crazy-max/build-metadata
bake: align build metadata
2024-06-29 13:15:55 +02:00
CrazyMax
8785275da1 bake: align build metadata
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-06-29 12:00:21 +02:00
12 changed files with 671 additions and 51 deletions

View File

@@ -1,8 +1,14 @@
{
"latest": {
"id": 162600493,
"tag_name": "v27.0.2",
"html_url": "https://github.com/moby/moby/releases/tag/v27.0.2",
"id": 163311279,
"tag_name": "v27.0.3",
"html_url": "https://github.com/moby/moby/releases/tag/v27.0.3",
"assets": []
},
"v27.0.3": {
"id": 163311279,
"tag_name": "v27.0.3",
"html_url": "https://github.com/moby/moby/releases/tag/v27.0.3",
"assets": []
},
"v27.0.2": {

View File

@@ -0,0 +1,344 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {beforeEach, describe, expect, jest, test} from '@jest/globals';
import {Git} from '../../src/buildkit/git';
import {GitRef, GitURL} from '../../src/types/buildkit/git';
beforeEach(() => {
jest.restoreAllMocks();
});
describe('parseURL', () => {
// prettier-ignore
test.each([
[
'http://github.com/moby/buildkit',
{
scheme: 'http',
host: 'github.com',
path: '/moby/buildkit'
} as GitURL,
false
],
[
'https://github.com/moby/buildkit',
{
scheme: 'https',
host: 'github.com',
path: '/moby/buildkit'
} as GitURL,
false
],
[
'http://github.com/moby/buildkit#v1.0.0',
{
scheme: 'http',
host: 'github.com',
path: '/moby/buildkit',
fragment: {
ref: 'v1.0.0',
}
} as GitURL,
false
],
[
'http://github.com/moby/buildkit#v1.0.0:subdir',
{
scheme: 'http',
host: 'github.com',
path: '/moby/buildkit',
fragment: {
ref: 'v1.0.0',
subdir: 'subdir'
}
} as GitURL,
false
],
[
'http://foo:bar@github.com/moby/buildkit#v1.0.0',
{
scheme: 'http',
host: 'github.com',
path: '/moby/buildkit',
fragment: {
ref: 'v1.0.0',
},
user: {
username: 'foo',
password: 'bar',
passwordSet: true
}
} as GitURL,
false
],
[
'ssh://git@github.com/moby/buildkit.git',
{
scheme: 'ssh',
host: 'github.com',
path: '/moby/buildkit.git',
user: {
username: 'git',
password: '',
passwordSet: false
}
} as GitURL,
false
],
[
'ssh://git@github.com:22/moby/buildkit.git',
{
scheme: 'ssh',
host: 'github.com:22',
path: '/moby/buildkit.git',
user: {
username: 'git',
password: '',
passwordSet: false
}
} as GitURL,
false
],
// TODO: handle SCP-style URLs
// [
// 'git@github.com:moby/buildkit.git',
// {
// scheme: 'ssh',
// host: 'github.com:22',
// path: 'moby/buildkit.git',
// user: {
// username: 'git',
// password: '',
// passwordSet: false
// }
// } as GitURL,
// false
// ],
[
'ssh://root@subdomain.example.hostname:2222/root/my/really/weird/path/foo.git',
{
scheme: 'ssh',
host: 'subdomain.example.hostname:2222',
path: '/root/my/really/weird/path/foo.git',
user: {
username: 'root',
password: '',
passwordSet: false
}
} as GitURL,
false
],
[
'git://host.xz:1234/path/to/repo.git',
{
scheme: 'git',
host: 'host.xz:1234',
path: '/path/to/repo.git',
} as GitURL,
false
],
[
'ssh://someuser@192.168.0.123:456/~/repo-in-my-home-dir.git',
{
scheme: 'ssh',
host: '192.168.0.123:456',
path: '/~/repo-in-my-home-dir.git',
user: {
username: 'someuser',
password: '',
passwordSet: false
}
} as GitURL,
false
],
[
'httpx://github.com/moby/buildkit',
{} as GitURL,
true
],
[
'HTTP://github.com/moby/buildkit',
{
scheme: 'http',
host: 'github.com',
path: '/moby/buildkit'
} as GitURL,
false
],
])('given %p', async (ref: string, expected: GitURL, expectedErr: boolean) => {
try {
const got = Git.parseURL(ref);
expect(got.scheme).toEqual(expected.scheme);
expect(got.host).toEqual(expected.host);
expect(got.path).toEqual(expected.path);
expect(got.fragment).toEqual(expected.fragment);
expect(got.user?.username).toEqual(expected.user?.username);
expect(got.user?.password).toEqual(expected.user?.password);
expect(got.user?.passwordSet).toEqual(expected.user?.passwordSet);
} catch (err) {
if (!expectedErr) {
console.log(err);
}
// eslint-disable-next-line jest/no-conditional-expect
expect(expectedErr).toBeTruthy();
}
});
});
describe('parseRef', () => {
// prettier-ignore
test.each([
[
'https://example.com/',
undefined
],
[
'https://example.com/foo',
undefined
],
[
'https://example.com/foo.git',
{
remote: 'https://example.com/foo.git',
shortName: 'foo'
} as GitRef
],
[
'https://example.com/foo.git#deadbeef',
{
remote: 'https://example.com/foo.git',
shortName: 'foo',
commit: 'deadbeef'
} as GitRef
],
[
'https://example.com/foo.git#release/1.2',
{
remote: 'https://example.com/foo.git',
shortName: 'foo',
commit: 'release/1.2'
} as GitRef
],
[
'https://example.com/foo.git/',
undefined
],
[
'https://example.com/foo.git.bar',
undefined
],
[
'git://example.com/foo',
{
remote: 'git://example.com/foo',
shortName: 'foo',
unencryptedTCP: true
} as GitRef
],
[
'github.com/moby/buildkit',
{
remote: 'github.com/moby/buildkit',
shortName: 'buildkit',
indistinguishableFromLocal: true
} as GitRef
],
[
'custom.xyz/moby/buildkit.git',
undefined
],
[
'https://github.com/moby/buildkit',
undefined
],
[
'https://github.com/moby/buildkit.git',
{
remote: 'https://github.com/moby/buildkit.git',
shortName: 'buildkit',
} as GitRef
],
[
'https://foo:bar@github.com/moby/buildkit.git',
{
remote: 'https://foo:bar@github.com/moby/buildkit.git',
shortName: 'buildkit',
} as GitRef
],
// TODO handle SCP-style URLs
// [
// 'git@github.com:moby/buildkit',
// {
// remote: 'git@github.com:moby/buildkit',
// shortName: 'buildkit',
// } as GitRef
// ],
// [
// 'git@github.com:moby/buildkit.git',
// {
// remote: 'git@github.com:moby/buildkit',
// shortName: 'buildkit',
// } as GitRef
// ],
// [
// 'git@bitbucket.org:atlassianlabs/atlassian-docker.git',
// {
// remote: 'git@bitbucket.org:atlassianlabs/atlassian-docker.git',
// shortName: 'atlassian-docker',
// } as GitRef
// ],
[
'https://github.com/foo/bar.git#baz/qux:quux/quuz',
{
remote: 'https://github.com/foo/bar.git',
shortName: 'bar',
commit: 'baz/qux',
subDir: 'quux/quuz',
} as GitRef
],
[
'https://github.com/docker/docker.git#:myfolder',
{
remote: 'https://github.com/docker/docker.git',
shortName: 'docker',
subDir: 'myfolder',
commit: ''
} as GitRef
],
[
'./.git',
undefined
],
[
'.git',
undefined
],
])('given %p', async (ref: string, expected: GitRef | undefined) => {
try {
const got = Git.parseRef(ref);
expect(got).toEqual(expected);
} catch (err) {
if (expected) {
console.log(err);
}
// eslint-disable-next-line jest/no-conditional-expect
expect(expected).toBeUndefined();
}
});
});

View File

@@ -23,13 +23,14 @@ import {Bake} from '../../src/buildx/bake';
import {Context} from '../../src/context';
import {ExecOptions} from '@actions/exec';
import {BakeDefinition, BakeMetadata} from '../../src/types/buildx/bake';
import {BakeDefinition} from '../../src/types/buildx/bake';
import {BuildMetadata} from '../../src/types/buildx/build';
const fixturesDir = path.join(__dirname, '..', 'fixtures');
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'buildx-inputs-jest');
const tmpName = path.join(tmpDir, '.tmpname-jest');
const metadata: BakeMetadata = {
const metadata: BuildMetadata = {
app: {
'buildx.build.ref': 'default/default/7frbdw1fmfozgtqavghowsepk'
},
@@ -57,7 +58,7 @@ describe('resolveMetadata', () => {
it('matches', async () => {
const bake = new Bake();
fs.writeFileSync(bake.getMetadataFilePath(), JSON.stringify(metadata));
expect(bake.resolveMetadata()).toEqual(metadata as BakeMetadata);
expect(bake.resolveMetadata()).toEqual(metadata as BuildMetadata);
});
});

View File

@@ -248,6 +248,52 @@ maybe('writeBuildSummary', () => {
}
});
});
it('without build record', async () => {
const startedTime = new Date();
const buildx = new Buildx();
const build = new Build({buildx: buildx});
fs.mkdirSync(tmpDir, {recursive: true});
await expect(
(async () => {
// prettier-ignore
const buildCmd = await buildx.getCommand([
'--builder', process.env.CTN_BUILDER_NAME ?? 'default',
'build',
'-f', path.join(fixturesDir, 'hello.Dockerfile'),
fixturesDir,
'--metadata-file', build.getMetadataFilePath()
]);
await Exec.exec(buildCmd.command, buildCmd.args);
})()
).resolves.not.toThrow();
const refs = Buildx.refs({
dir: Buildx.refsDir,
builderName: process.env.CTN_BUILDER_NAME ?? 'default',
since: startedTime
});
expect(refs).toBeDefined();
expect(Object.keys(refs).length).toBeGreaterThan(0);
const history = new History({buildx: buildx});
const exportRes = await history.export({
refs: [Object.keys(refs)[0] ?? '']
});
expect(exportRes).toBeDefined();
expect(exportRes?.dockerbuildFilename).toBeDefined();
expect(exportRes?.dockerbuildSize).toBeDefined();
expect(exportRes?.summaries).toBeDefined();
await GitHub.writeBuildSummary({
exportRes: exportRes,
inputs: {
context: fixturesDir,
file: path.join(fixturesDir, 'hello.Dockerfile')
}
});
});
});
maybe('annotateBuildWarnings', () => {

View File

@@ -85,6 +85,28 @@ describe('apiURL', () => {
});
});
describe('isGHES', () => {
afterEach(() => {
process.env.GITHUB_SERVER_URL = '';
});
it('should return false when the request domain is github.com', () => {
process.env.GITHUB_SERVER_URL = 'https://github.com';
expect(GitHub.isGHES).toBe(false);
});
it('should return false when the request domain ends with ghe.com', () => {
process.env.GITHUB_SERVER_URL = 'https://my.domain.ghe.com';
expect(GitHub.isGHES).toBe(false);
});
it('should return false when the request domain ends with ghe.localhost', () => {
process.env.GITHUB_SERVER_URL = 'https://my.domain.ghe.localhost';
expect(GitHub.isGHES).toBe(false);
});
it('should return true when the request domain is specific to an enterprise', () => {
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com';
expect(GitHub.isGHES).toBe(true);
});
});
describe('repository', () => {
it('returns GitHub repository', async () => {
expect(GitHub.repository).toEqual('docker/actions-toolkit');

113
src/buildkit/git.ts Normal file
View File

@@ -0,0 +1,113 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {GitRef, GitURL, GitURLFragment, URLUserInfo} from '../types/buildkit/git';
export class Git {
private static protoRegexp = new RegExp('^[a-zA-Z0-9]+://');
private static supportedProtos = {
http: {},
https: {},
ssh: {},
git: {}
};
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L79
public static parseURL(remote: string): GitURL {
const match = remote.match(Git.protoRegexp);
if (match && match.length > 0) {
let proto = match[0].toLowerCase();
proto = proto.slice(0, proto.lastIndexOf('://'));
if (!(proto in Git.supportedProtos)) {
throw new Error(`Invalid protocol: ${proto}`);
}
return Git.fromURL(new URL(remote));
}
throw new Error('Unknown protocol');
}
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L108
private static fromURL(url: URL): GitURL {
const withoutFragment = new URL(url.toString());
withoutFragment.hash = '';
let user: URLUserInfo | undefined;
if (url.username || url.password) {
user = {
username: url.username,
password: url.password,
passwordSet: url.password !== ''
};
}
// TODO: handle SCP-style URLs
return {
scheme: url.protocol.slice(0, -1),
user: user,
host: `${url.hostname}${url.port ? ':' + url.port : ''}`,
path: url.pathname,
fragment: Git.splitGitFragment(url.hash),
remote: withoutFragment.toString()
};
}
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L69
private static splitGitFragment(fragment: string): GitURLFragment | undefined {
if (fragment === '') {
return undefined;
}
const [ref, subdir] = fragment.slice(1).split(':');
return {
ref: ref,
subdir: subdir
};
}
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_ref.go#L52
public static parseRef(ref: string): GitRef | undefined {
const res: GitRef = {};
let remote: GitURL;
if (ref.startsWith('./') || ref.startsWith('../')) {
throw new Error('Invalid argument');
} else if (ref.startsWith('github.com/')) {
res.indistinguishableFromLocal = true; // Deprecated
remote = Git.fromURL(new URL('https://' + ref));
} else {
remote = Git.parseURL(ref);
if (['http', 'git'].includes(remote.scheme)) {
res.unencryptedTCP = true; // Discouraged, but not deprecated
}
if (['http', 'https'].includes(remote.scheme) && !remote.path.endsWith('.git')) {
throw new Error('Invalid argument');
}
}
res.remote = remote.remote;
if (res.indistinguishableFromLocal) {
res.remote = res.remote.split('://')[1];
}
if (remote.fragment) {
res.commit = remote.fragment.ref;
res.subDir = remote.fragment.subdir;
}
const repoSplitBySlash = res.remote.split('/');
res.shortName = repoSplitBySlash[repoSplitBySlash.length - 1].replace('.git', '');
return res;
}
}

View File

@@ -24,7 +24,8 @@ import {Exec} from '../exec';
import {Util} from '../util';
import {ExecOptions} from '@actions/exec';
import {BakeDefinition, BakeMetadata} from '../types/buildx/bake';
import {BakeDefinition} from '../types/buildx/bake';
import {BuildMetadata} from '../types/buildx/build';
export interface BakeOpts {
buildx?: Buildx;
@@ -57,7 +58,7 @@ export class Bake {
return path.join(Context.tmpDir(), this.metadataFilename);
}
public resolveMetadata(): BakeMetadata | undefined {
public resolveMetadata(): BuildMetadata | undefined {
const metadataFile = this.getMetadataFilePath();
if (!fs.existsSync(metadataFile)) {
return undefined;
@@ -66,10 +67,10 @@ export class Bake {
if (content === 'null') {
return undefined;
}
return <BakeMetadata>JSON.parse(content);
return <BuildMetadata>JSON.parse(content);
}
public resolveRefs(metadata?: BakeMetadata): Array<string> | undefined {
public resolveRefs(metadata?: BuildMetadata): Array<string> | undefined {
if (!metadata) {
metadata = this.resolveMetadata();
if (!metadata) {
@@ -82,7 +83,7 @@ export class Bake {
refs.push(metadata[key]['buildx.build.ref']);
}
}
return refs;
return refs.length > 0 ? refs : undefined;
}
public async getDefinition(cmdOpts: BakeCmdOpts, execOptions?: ExecOptions): Promise<BakeDefinition> {

View File

@@ -16,9 +16,9 @@
import {ChildProcessByStdio, spawn} from 'child_process';
import fs from 'fs';
import {Readable, Writable} from 'node:stream';
import os from 'os';
import path from 'path';
import {Readable, Writable} from 'stream';
import * as core from '@actions/core';
import {Buildx} from './buildx';
@@ -92,14 +92,29 @@ export class History {
});
await Exec.exec('mkfifo', [buildxOutFifoPath]);
const buildxCmd = await this.buildx.getCommand(['--builder', builderName, 'dial-stdio']);
const buildxDialStdioProc = History.spawn(buildxCmd.command, buildxCmd.args);
const buildxDialStdioCmd = await this.buildx.getCommand(['--builder', builderName, 'dial-stdio']);
core.info(`[command]${buildxDialStdioCmd.command} ${buildxDialStdioCmd.args.join(' ')}`);
const buildxDialStdioProc = spawn(buildxDialStdioCmd.command, buildxDialStdioCmd.args, {
stdio: ['pipe', 'pipe', 'inherit'],
detached: true
});
let buildxDialStdioKilled = false;
fs.createReadStream(buildxInFifoPath).pipe(buildxDialStdioProc.stdin);
buildxDialStdioProc.stdout.pipe(fs.createWriteStream(buildxOutFifoPath));
buildxDialStdioProc.on('exit', (code, signal) => {
buildxDialStdioKilled = true;
if (signal) {
core.info(`Process "buildx dial-stdio" was killed with signal ${signal}`);
} else {
core.info(`Process "buildx dial-stdio" exited with code ${code}`);
}
});
const tmpDockerbuildFilename = path.join(outDir, 'rec.dockerbuild');
const summaryFilename = path.join(outDir, 'summary.json');
let dockerRunProc: ChildProcessByStdio<Writable, Readable, null> | undefined;
let dockerRunProcKilled = false;
await new Promise<void>((resolve, reject) => {
const ebargs: Array<string> = ['--ref-state-dir=/buildx-refs', `--node=${builderName}/${nodeName}`];
for (const ref of refs) {
@@ -112,13 +127,17 @@ export class History {
ebargs.push(`--gid=${process.getgid()}`);
}
// prettier-ignore
const dockerRunProc = History.spawn('docker', [
const dockerRunArgs = [
'run', '--rm', '-i',
'-v', `${Buildx.refsDir}:/buildx-refs`,
'-v', `${outDir}:/out`,
opts.image || History.EXPORT_TOOL_IMAGE,
...ebargs
]);
]
core.info(`[command]docker ${dockerRunArgs.join(' ')}`);
dockerRunProc = spawn('docker', dockerRunArgs, {
stdio: ['pipe', 'pipe', 'inherit']
});
fs.createReadStream(buildxOutFifoPath).pipe(dockerRunProc.stdin);
dockerRunProc.stdout.pipe(fs.createWriteStream(buildxInFifoPath));
dockerRunProc.on('close', code => {
@@ -129,16 +148,35 @@ export class History {
resolve();
}
} else {
reject(new Error(`Process "docker run" exited with code ${code}`));
reject(new Error(`Process "docker run" closed with code ${code}`));
}
});
dockerRunProc.on('error', err => {
core.error(`Error executing buildx dial-stdio: ${err}`);
core.error(`Error executing "docker run": ${err}`);
reject(err);
});
}).catch(err => {
throw err;
});
dockerRunProc.on('exit', (code, signal) => {
dockerRunProcKilled = true;
if (signal) {
core.info(`Process "docker run" was killed with signal ${signal}`);
} else {
core.info(`Process "docker run" exited with code ${code}`);
}
});
})
.catch(err => {
throw err;
})
.finally(() => {
if (buildxDialStdioProc && !buildxDialStdioKilled) {
core.debug('Force terminating "buildx dial-stdio" process');
buildxDialStdioProc.kill('SIGKILL');
}
if (dockerRunProc && !dockerRunProcKilled) {
core.debug('Force terminating "docker run" process');
dockerRunProc.kill('SIGKILL');
}
});
let dockerbuildFilename = `${GitHub.context.repo.owner}~${GitHub.context.repo.repo}~${refs[0].substring(0, 6).toUpperCase()}`;
if (refs.length > 1) {
@@ -162,11 +200,4 @@ export class History {
refs: refs
};
}
private static spawn(command: string, args?: ReadonlyArray<string>): ChildProcessByStdio<Writable, Readable, null> {
core.info(`[command]${command}${args ? ` ${args.join(' ')}` : ''}`);
return spawn(command, args || [], {
stdio: ['pipe', 'pipe', 'inherit']
});
}
}

View File

@@ -22,7 +22,6 @@ import os from 'os';
import path from 'path';
import {CreateArtifactRequest, FinalizeArtifactRequest, StringValue} from '@actions/artifact/lib/generated';
import {internalArtifactTwirpClient} from '@actions/artifact/lib/internal/shared/artifact-twirp-client';
import {isGhes} from '@actions/artifact/lib/internal/shared/config';
import {getBackendIdsFromToken} from '@actions/artifact/lib/internal/shared/util';
import {getExpiration} from '@actions/artifact/lib/internal/upload/retention';
import {InvalidResponseError, NetworkError} from '@actions/artifact';
@@ -67,6 +66,14 @@ export class GitHub {
return process.env.GITHUB_API_URL || 'https://api.github.com';
}
static get isGHES(): boolean {
const serverURL = new URL(GitHub.serverURL);
const hostname = serverURL.hostname.trimEnd().toUpperCase();
const isGitHubHost = hostname === 'GITHUB.COM';
const isGHESHost = hostname.endsWith('.GHE.COM') || hostname.endsWith('.GHE.LOCALHOST');
return !isGitHubHost && !isGHESHost;
}
static get repository(): string {
return `${github.context.repo.owner}/${github.context.repo.repo}`;
}
@@ -124,7 +131,7 @@ export class GitHub {
}
public static async uploadArtifact(opts: UploadArtifactOpts): Promise<UploadArtifactResponse> {
if (isGhes()) {
if (GitHub.isGHES) {
throw new Error('@actions/artifact v2.0.0+ is currently not supported on GHES.');
}
@@ -226,18 +233,19 @@ export class GitHub {
const refsSize = Object.keys(opts.exportRes.refs).length;
// we just need the last two parts of the URL as they are always relative
// to the workflow run URL otherwise URL could be broken if GitHub
// repository name is part of a secret value used in the workflow. e.g.:
// artifact: https://github.com/docker/actions-toolkit/actions/runs/9552208295/artifacts/1609622746
// workflow: https://github.com/docker/actions-toolkit/actions/runs/9552208295
// https://github.com/docker/actions-toolkit/issues/367
const artifactRelativeURL = `./${GitHub.runId}/${opts.uploadRes.url.split('/').slice(-2).join('/')}`;
const sum = core.summary.addHeading('Docker Build summary', 2);
// prettier-ignore
const sum = core.summary
.addHeading('Docker Build summary', 2)
.addRaw(`<p>`)
if (opts.uploadRes) {
// we just need the last two parts of the URL as they are always relative
// to the workflow run URL otherwise URL could be broken if GitHub
// repository name is part of a secret value used in the workflow. e.g.:
// artifact: https://github.com/docker/actions-toolkit/actions/runs/9552208295/artifacts/1609622746
// workflow: https://github.com/docker/actions-toolkit/actions/runs/9552208295
// https://github.com/docker/actions-toolkit/issues/367
const artifactRelativeURL = `./${GitHub.runId}/${opts.uploadRes.url.split('/').slice(-2).join('/')}`;
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`For a detailed look at the build, download the following build record archive and import it into Docker Desktop's Builds view. `)
.addBreak()
.addRaw(`Build records include details such as timing, dependencies, results, logs, traces, and other information about a build. `)
@@ -245,11 +253,19 @@ export class GitHub {
.addRaw('</p>')
.addRaw(`<p>`)
.addRaw(`:arrow_down: ${addLink(`<strong>${Util.stringToUnicodeEntities(opts.uploadRes.filename)}</strong>`, artifactRelativeURL)} (${Util.formatFileSize(opts.uploadRes.size)} - includes <strong>${refsSize} build record${refsSize > 1 ? 's' : ''}</strong>)`)
.addRaw(`</p>`)
.addRaw(`<p>`)
.addRaw(`Find this useful? `)
.addRaw(addLink('Let us know', 'https://docs.docker.com/feedback/gha-build-summary'))
.addRaw('</p>');
.addRaw(`</p>`);
} else {
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`The following table provides a brief summary of your build.`)
.addBreak()
.addRaw(`For a detailed look at the build, including timing, dependencies, results, logs, traces, and other information, consider enabling the export of the build record so you can import it into Docker Desktop's Builds view. `)
.addRaw(addLink('Learn more', 'https://www.docker.com/blog/new-beta-feature-deep-dive-into-github-actions-docker-builds-with-docker-desktop/?utm_source=github&utm_medium=actions'))
.addRaw(`</p>`);
}
// Feedback survey
sum.addRaw(`<p>`).addRaw(`Find this useful? `).addRaw(addLink('Let us know', 'https://docs.docker.com/feedback/gha-build-summary')).addRaw('</p>');
// Preview
sum.addRaw('<p>');

44
src/types/buildkit/git.ts Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright 2024 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface GitURL {
scheme: string;
host: string;
path: string;
user?: URLUserInfo;
fragment?: GitURLFragment;
remote: string;
}
export interface GitURLFragment {
ref: string;
subdir: string;
}
export interface GitRef {
remote?: string;
shortName?: string;
commit?: string;
subDir?: string;
indistinguishableFromLocal?: boolean;
unencryptedTCP?: boolean;
}
export interface URLUserInfo {
username: string;
password: string;
passwordSet: boolean;
}

View File

@@ -19,10 +19,6 @@ export interface BakeDefinition {
target: Record<string, Target>;
}
export interface BakeMetadata {
[target: string]: Record<string, string>;
}
export interface Group {
targets: Array<string>;
}

View File

@@ -53,7 +53,7 @@ export interface UploadArtifactResponse {
export interface BuildSummaryOpts {
exportRes: ExportRecordResponse;
uploadRes: UploadArtifactResponse;
uploadRes?: UploadArtifactResponse;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inputs?: any;
bakeDefinition?: BakeDefinition;