buildx: metadata and refs resolution for build and bake
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -14,21 +14,67 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {beforeEach, describe, expect, jest, test} from '@jest/globals';
|
||||
import {afterEach, beforeEach, describe, expect, it, jest, test} from '@jest/globals';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as rimraf from 'rimraf';
|
||||
|
||||
import {Bake} from '../../src/buildx/bake';
|
||||
import {Context} from '../../src/context';
|
||||
|
||||
import {ExecOptions} from '@actions/exec';
|
||||
import {BakeDefinition} from '../../src/types/bake';
|
||||
import {BakeDefinition, BakeMetadata} from '../../src/types/bake';
|
||||
|
||||
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 = {
|
||||
app: {
|
||||
'buildx.build.ref': 'default/default/7frbdw1fmfozgtqavghowsepk'
|
||||
},
|
||||
db: {
|
||||
'buildx.build.ref': 'default/default/onic7g2axylf56rxetob7qruy'
|
||||
}
|
||||
};
|
||||
|
||||
jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
|
||||
if (!fs.existsSync(tmpDir)) {
|
||||
fs.mkdirSync(tmpDir, {recursive: true});
|
||||
}
|
||||
return tmpDir;
|
||||
});
|
||||
|
||||
jest.spyOn(Context, 'tmpName').mockImplementation((): string => {
|
||||
return tmpName;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rimraf.sync(tmpDir);
|
||||
});
|
||||
|
||||
describe('resolveMetadata', () => {
|
||||
it('matches', async () => {
|
||||
const metadataFile = Bake.getMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, JSON.stringify(metadata));
|
||||
const expected = Bake.resolveMetadata();
|
||||
expect(expected).toEqual(metadata as BakeMetadata);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRefs', () => {
|
||||
it('matches', async () => {
|
||||
const metadataFile = Bake.getMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, JSON.stringify(metadata));
|
||||
const expected = Bake.resolveRefs();
|
||||
expect(expected).toEqual(['default/default/7frbdw1fmfozgtqavghowsepk', 'default/default/onic7g2axylf56rxetob7qruy']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefinition', () => {
|
||||
// prettier-ignore
|
||||
test.each([
|
||||
|
||||
@@ -22,14 +22,17 @@ import * as rimraf from 'rimraf';
|
||||
import {Context} from '../../src/context';
|
||||
import {Build} from '../../src/buildx/build';
|
||||
|
||||
import {BuildMetadata} from '../../src/types/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 = `{
|
||||
"containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd",
|
||||
"containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c"
|
||||
}`;
|
||||
const metadata: BuildMetadata = {
|
||||
'buildx.build.ref': 'default/default/n6ibcp9b2pw108rrz7ywdznvo',
|
||||
'containerimage.config.digest': 'sha256:059b68a595b22564a1cbc167f369349fdc2ecc1f7bc092c2235cbf601a795fd',
|
||||
'containerimage.digest': 'sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c'
|
||||
};
|
||||
|
||||
jest.spyOn(Context, 'tmpDir').mockImplementation((): string => {
|
||||
if (!fs.existsSync(tmpDir)) {
|
||||
@@ -50,29 +53,38 @@ afterEach(() => {
|
||||
rimraf.sync(tmpDir);
|
||||
});
|
||||
|
||||
describe('resolveBuildImageID', () => {
|
||||
describe('resolveImageID', () => {
|
||||
it('matches', async () => {
|
||||
const imageID = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9';
|
||||
const imageIDFile = Build.getBuildImageIDFilePath();
|
||||
const imageIDFile = Build.getImageIDFilePath();
|
||||
await fs.writeFileSync(imageIDFile, imageID);
|
||||
const expected = Build.resolveBuildImageID();
|
||||
const expected = Build.resolveImageID();
|
||||
expect(expected).toEqual(imageID);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveBuildMetadata', () => {
|
||||
describe('resolveMetadata', () => {
|
||||
it('matches', async () => {
|
||||
const metadataFile = Build.getBuildMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, metadata);
|
||||
const expected = Build.resolveBuildMetadata();
|
||||
const metadataFile = Build.getMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, JSON.stringify(metadata));
|
||||
const expected = Build.resolveMetadata();
|
||||
expect(expected).toEqual(metadata);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRef', () => {
|
||||
it('matches', async () => {
|
||||
const metadataFile = Build.getMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, JSON.stringify(metadata));
|
||||
const expected = Build.resolveRef();
|
||||
expect(expected).toEqual('default/default/n6ibcp9b2pw108rrz7ywdznvo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveDigest', () => {
|
||||
it('matches', async () => {
|
||||
const metadataFile = Build.getBuildMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, metadata);
|
||||
const metadataFile = Build.getMetadataFilePath();
|
||||
await fs.writeFileSync(metadataFile, JSON.stringify(metadata));
|
||||
const expected = Build.resolveDigest();
|
||||
expect(expected).toEqual('sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c');
|
||||
});
|
||||
@@ -152,7 +164,7 @@ describe('resolveProvenanceAttrs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveBuildSecret', () => {
|
||||
describe('resolveSecret', () => {
|
||||
test.each([
|
||||
['A_SECRET=abcdef0123456789', false, 'A_SECRET', 'abcdef0123456789', null],
|
||||
['GIT_AUTH_TOKEN=abcdefghijklmno=0123456789', false, 'GIT_AUTH_TOKEN', 'abcdefghijklmno=0123456789', null],
|
||||
@@ -166,9 +178,9 @@ describe('resolveBuildSecret', () => {
|
||||
try {
|
||||
let secret: string;
|
||||
if (file) {
|
||||
secret = Build.resolveBuildSecretFile(kvp);
|
||||
secret = Build.resolveSecretFile(kvp);
|
||||
} else {
|
||||
secret = Build.resolveBuildSecretString(kvp);
|
||||
secret = Build.resolveSecretString(kvp);
|
||||
}
|
||||
expect(secret).toEqual(`id=${exKey},src=${tmpName}`);
|
||||
expect(fs.readFileSync(tmpName, 'utf-8')).toEqual(exValue);
|
||||
@@ -185,7 +197,7 @@ describe('resolveBuildSecret', () => {
|
||||
['FOO=bar=baz', 'FOO', 'bar=baz', null]
|
||||
])('given %p key and %p env', async (kvp: string, exKey: string, exValue: string, error: Error | null) => {
|
||||
try {
|
||||
const secret = Build.resolveBuildSecretEnv(kvp);
|
||||
const secret = Build.resolveSecretEnv(kvp);
|
||||
expect(secret).toEqual(`id=${exKey},env=${exValue}`);
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line jest/no-conditional-expect
|
||||
|
||||
@@ -14,13 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {Buildx} from './buildx';
|
||||
import {Exec} from '../exec';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {Build} from './build';
|
||||
import {Buildx} from './buildx';
|
||||
import {Context} from '../context';
|
||||
import {Exec} from '../exec';
|
||||
import {Util} from '../util';
|
||||
|
||||
import {ExecOptions} from '@actions/exec';
|
||||
import {BakeDefinition} from '../types/bake';
|
||||
import {BakeDefinition, BakeMetadata} from '../types/bake';
|
||||
|
||||
export interface BakeOpts {
|
||||
buildx?: Buildx;
|
||||
@@ -47,6 +51,36 @@ export class Bake {
|
||||
this.buildx = opts?.buildx || new Buildx();
|
||||
}
|
||||
|
||||
public static getMetadataFilePath(): string {
|
||||
return path.join(Context.tmpDir(), 'metadata-file');
|
||||
}
|
||||
|
||||
public static resolveMetadata(): BakeMetadata | undefined {
|
||||
const metadataFile = Bake.getMetadataFilePath();
|
||||
if (!fs.existsSync(metadataFile)) {
|
||||
return undefined;
|
||||
}
|
||||
const content = fs.readFileSync(metadataFile, {encoding: 'utf-8'}).trim();
|
||||
if (content === 'null') {
|
||||
return undefined;
|
||||
}
|
||||
return <BakeMetadata>JSON.parse(content);
|
||||
}
|
||||
|
||||
public static resolveRefs(): Array<string> | undefined {
|
||||
const metadata = Bake.resolveMetadata();
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
const refs = new Array<string>();
|
||||
for (const key in metadata) {
|
||||
if ('buildx.build.ref' in metadata[key]) {
|
||||
refs.push(metadata[key]['buildx.build.ref']);
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
public async getDefinition(cmdOpts: BakeCmdOpts, execOptions?: ExecOptions): Promise<BakeDefinition> {
|
||||
execOptions = execOptions || {ignoreReturnCode: true};
|
||||
execOptions.ignoreReturnCode = true;
|
||||
|
||||
@@ -23,25 +23,27 @@ import {Context} from '../context';
|
||||
import {GitHub} from '../github';
|
||||
import {Util} from '../util';
|
||||
|
||||
import {BuildMetadata} from '../types/build';
|
||||
|
||||
export class Build {
|
||||
public static getBuildImageIDFilePath(): string {
|
||||
public static getImageIDFilePath(): string {
|
||||
return path.join(Context.tmpDir(), 'iidfile');
|
||||
}
|
||||
|
||||
public static getBuildMetadataFilePath(): string {
|
||||
public static getMetadataFilePath(): string {
|
||||
return path.join(Context.tmpDir(), 'metadata-file');
|
||||
}
|
||||
|
||||
public static resolveBuildImageID(): string | undefined {
|
||||
const iidFile = Build.getBuildImageIDFilePath();
|
||||
public static resolveImageID(): string | undefined {
|
||||
const iidFile = Build.getImageIDFilePath();
|
||||
if (!fs.existsSync(iidFile)) {
|
||||
return undefined;
|
||||
}
|
||||
return fs.readFileSync(iidFile, {encoding: 'utf-8'}).trim();
|
||||
}
|
||||
|
||||
public static resolveBuildMetadata(): string | undefined {
|
||||
const metadataFile = Build.getBuildMetadataFilePath();
|
||||
public static resolveMetadata(): BuildMetadata | undefined {
|
||||
const metadataFile = Build.getMetadataFilePath();
|
||||
if (!fs.existsSync(metadataFile)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -49,37 +51,47 @@ export class Build {
|
||||
if (content === 'null') {
|
||||
return undefined;
|
||||
}
|
||||
return content;
|
||||
return <BuildMetadata>JSON.parse(content);
|
||||
}
|
||||
|
||||
public static resolveDigest(): string | undefined {
|
||||
const metadata = Build.resolveBuildMetadata();
|
||||
if (metadata === undefined) {
|
||||
public static resolveRef(): string | undefined {
|
||||
const metadata = Build.resolveMetadata();
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
const metadataJSON = JSON.parse(metadata);
|
||||
if (metadataJSON['containerimage.digest']) {
|
||||
return metadataJSON['containerimage.digest'];
|
||||
if ('buildx.build.ref' in metadata) {
|
||||
return metadata['buildx.build.ref'];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static resolveBuildSecretString(kvp: string): string {
|
||||
const [key, file] = Build.resolveBuildSecret(kvp, false);
|
||||
public static resolveDigest(): string | undefined {
|
||||
const metadata = Build.resolveMetadata();
|
||||
if (!metadata) {
|
||||
return undefined;
|
||||
}
|
||||
if ('containerimage.digest' in metadata) {
|
||||
return metadata['containerimage.digest'];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static resolveSecretString(kvp: string): string {
|
||||
const [key, file] = Build.resolveSecret(kvp, false);
|
||||
return `id=${key},src=${file}`;
|
||||
}
|
||||
|
||||
public static resolveBuildSecretFile(kvp: string): string {
|
||||
const [key, file] = Build.resolveBuildSecret(kvp, true);
|
||||
public static resolveSecretFile(kvp: string): string {
|
||||
const [key, file] = Build.resolveSecret(kvp, true);
|
||||
return `id=${key},src=${file}`;
|
||||
}
|
||||
|
||||
public static resolveBuildSecretEnv(kvp: string): string {
|
||||
public static resolveSecretEnv(kvp: string): string {
|
||||
const [key, value] = Build.parseSecretKvp(kvp);
|
||||
return `id=${key},env=${value}`;
|
||||
}
|
||||
|
||||
public static resolveBuildSecret(kvp: string, file: boolean): [string, string] {
|
||||
public static resolveSecret(kvp: string, file: boolean): [string, string] {
|
||||
const [key, _value] = Build.parseSecretKvp(kvp);
|
||||
let value = _value;
|
||||
if (file) {
|
||||
|
||||
@@ -19,6 +19,10 @@ export interface BakeDefinition {
|
||||
target: Record<string, Target>;
|
||||
}
|
||||
|
||||
export interface BakeMetadata {
|
||||
[target: string]: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
targets: Array<string>;
|
||||
}
|
||||
|
||||
19
src/types/build.ts
Normal file
19
src/types/build.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* 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 type BuildMetadata = {
|
||||
[key: string]: string;
|
||||
};
|
||||
Reference in New Issue
Block a user