Compare commits

...

17 Commits

Author SHA1 Message Date
CrazyMax
f6cafdfce1 Merge pull request #285 from crazy-max/bake-parse-opts
Some checks failed
publish / publish (push) Has been cancelled
bake: additional opts when parsing definition
2024-04-02 10:21:14 +02:00
CrazyMax
416c2914df bake: add shm-size and ulimits to target struct
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-04-02 09:45:56 +02:00
CrazyMax
341bae465f bake: additional opts when parsing definition
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-04-02 09:45:56 +02:00
CrazyMax
264a0eec2a Merge pull request #287 from crazy-max/buildx-hasAttestationType
buildx: hasAttestationType and resolveAttestationAttrs funcs
2024-04-02 09:38:05 +02:00
CrazyMax
545f7cd6ea buildx: hasAttestationType and resolveAttestationAttrs funcs
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-03-26 16:23:12 +01:00
CrazyMax
de2888af87 Merge pull request #286 from crazy-max/bump-buildx-docker
dockerfile: bump buildx to 0.13.1 and docker to 26.0.0
2024-03-26 11:59:14 +01:00
CrazyMax
43f61228ec dockerfile: bump buildx to 0.13.1 and docker to 26.0.0
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-03-26 10:14:17 +01:00
CrazyMax
8d1d731562 Merge pull request #284 from docker/dependabot/npm_and_yarn/undici-5.28.3
build(deps): bump undici from 5.27.0 to 5.28.3
2024-03-25 09:52:02 +01:00
CrazyMax
5706b95a7f Merge pull request #283 from docker/dependabot/npm_and_yarn/ip-2.0.1
build(deps): bump ip from 2.0.0 to 2.0.1
2024-03-25 09:49:11 +01:00
dependabot[bot]
5d8b7b4828 build(deps): bump undici from 5.27.0 to 5.28.3
Bumps [undici](https://github.com/nodejs/undici) from 5.27.0 to 5.28.3.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.27.0...v5.28.3)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 08:45:39 +00:00
dependabot[bot]
48e339fd34 build(deps): bump ip from 2.0.0 to 2.0.1
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 08:41:19 +00:00
CrazyMax
41cae83741 Merge pull request #282 from docker/bot/docker-releases-json
Update `.github/docker-releases.json`
2024-03-25 09:37:39 +01:00
crazy-max
d58f77be10 github: update .github/docker-releases.json
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2024-03-22 00:21:07 +00:00
Tõnis Tiigi
a24f5c12ca Merge pull request #281 from crazy-max/cache-post-group
Some checks failed
publish / publish (push) Has been cancelled
cache: run cache.post in core.group
2024-03-15 13:28:15 -07:00
CrazyMax
e73765a5ce cache: run cache.post in core.group
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-03-15 18:10:31 +01:00
CrazyMax
cd14c5b580 Merge pull request #280 from crazy-max/post-cache
Some checks failed
publish / publish (push) Has been cancelled
cache: move gha cache save to post state
2024-03-15 17:14:39 +01:00
CrazyMax
9b446bf084 cache: move gha cache save to post state
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-03-15 10:12:08 +01:00
14 changed files with 310 additions and 39 deletions

View File

@@ -1,8 +1,32 @@
{
"latest": {
"id": 144022554,
"tag_name": "v25.0.4",
"html_url": "https://github.com/moby/moby/releases/tag/v25.0.4",
"id": 145844215,
"tag_name": "v26.0.0",
"html_url": "https://github.com/moby/moby/releases/tag/v26.0.0",
"assets": []
},
"v23.0.10": {
"id": 147776752,
"tag_name": "v23.0.10",
"html_url": "https://github.com/moby/moby/releases/tag/v23.0.10",
"assets": []
},
"v26.0.0": {
"id": 145844215,
"tag_name": "v26.0.0",
"html_url": "https://github.com/moby/moby/releases/tag/v26.0.0",
"assets": []
},
"v26.0.0-rc3": {
"id": 146688910,
"tag_name": "v26.0.0-rc3",
"html_url": "https://github.com/moby/moby/releases/tag/v26.0.0-rc3",
"assets": []
},
"v25.0.5": {
"id": 147202747,
"tag_name": "v25.0.5",
"html_url": "https://github.com/moby/moby/releases/tag/v25.0.5",
"assets": []
},
"v26.0.0-rc2": {

View File

@@ -29,17 +29,20 @@ beforeEach(() => {
jest.clearAllMocks();
});
maybe('parseDefinitions', () => {
maybe('getDefinition', () => {
// prettier-ignore
test.each([
[
['https://github.com/docker/buildx.git#v0.10.4'],
'https://github.com/docker/buildx.git#v0.10.4',
['binaries-cross'],
path.join(fixturesDir, 'bake-buildx-0.10.4-binaries-cross.json')
]
])('given %p', async (sources: string[], targets: string[], out: string) => {
],
])('given %p', async (source: string, targets: string[], out: string) => {
const bake = new Bake();
const expectedDef = <BakeDefinition>JSON.parse(fs.readFileSync(out, {encoding: 'utf-8'}).trim())
expect(await bake.parseDefinitions(sources, targets)).toEqual(expectedDef);
expect(await bake.getDefinition({
source: source,
targets: targets
})).toEqual(expectedDef);
});
});

View File

@@ -19,6 +19,8 @@ import * as fs from 'fs';
import * as path from 'path';
import {Bake} from '../../src/buildx/bake';
import {ExecOptions} from '@actions/exec';
import {BakeDefinition} from '../../src/types/bake';
const fixturesDir = path.join(__dirname, '..', 'fixtures');
@@ -27,31 +29,38 @@ beforeEach(() => {
jest.clearAllMocks();
});
describe('parseDefinitions', () => {
describe('getDefinition', () => {
// prettier-ignore
test.each([
[
[path.join(fixturesDir, 'bake-01.hcl')],
['validate'],
[],
{silent: true},
path.join(fixturesDir, 'bake-01-validate.json')
],
[
[path.join(fixturesDir, 'bake-02.hcl')],
['build'],
[],
undefined,
path.join(fixturesDir, 'bake-02-build.json')
],
[
[path.join(fixturesDir, 'bake-01.hcl')],
['image'],
['*.output=type=docker', '*.platform=linux/amd64'],
undefined,
path.join(fixturesDir, 'bake-01-overrides.json')
]
])('given %p', async (sources: string[], targets: string[], overrides: string[], out: string) => {
])('given %p', async (files: string[], targets: string[], overrides: string[], execOptions: ExecOptions | undefined, out: string) => {
const bake = new Bake();
const expectedDef = <BakeDefinition>JSON.parse(fs.readFileSync(out, {encoding: 'utf-8'}).trim())
expect(await bake.parseDefinitions(sources, targets, overrides)).toEqual(expectedDef);
expect(await bake.getDefinition({
files: files,
targets: targets,
overrides: overrides
}, execOptions)).toEqual(expectedDef);
});
});

View File

@@ -244,6 +244,41 @@ describe('hasDockerExporter', () => {
});
});
describe('hasAttestationType', () => {
// prettier-ignore
test.each([
['type=provenance,mode=min', 'provenance', true],
['type=sbom,true', 'sbom', true],
['type=foo,bar', 'provenance', false],
])('given %p for %p returns %p', async (attrs: string, name: string, expected: boolean) => {
expect(Inputs.hasAttestationType(name, attrs)).toEqual(expected);
});
});
describe('resolveAttestationAttrs', () => {
// prettier-ignore
test.each([
[
'type=provenance,mode=min',
'type=provenance,mode=min'
],
[
'type=provenance,true',
'type=provenance,disabled=false'
],
[
'type=provenance,false',
'type=provenance,disabled=true'
],
[
'',
''
],
])('given %p', async (input: string, expected: string) => {
expect(Inputs.resolveAttestationAttrs(input)).toEqual(expected);
});
});
describe('hasGitAuthTokenSecret', () => {
// prettier-ignore
test.each([

View File

@@ -0,0 +1,40 @@
/**
* 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 path from 'path';
import {describe, expect, it} from '@jest/globals';
import {Cache} from '../src/cache';
// prettier-ignore
const tmpDir = path.join(process.env.TEMP || '/tmp', 'cache-jest');
const fixturesDir = path.join(__dirname, 'fixtures');
describe('cache', () => {
it('github-repo', async () => {
const r = (Math.random() + 1).toString(36).substring(7);
const htcName = `cache-test-github-repo-${r}`;
const c = new Cache({
htcName: htcName,
htcVersion: `v1.0.0+${r}`,
baseCacheDir: path.join(tmpDir, '.cache-test'),
cacheFile: 'github-repo.json'
});
expect(await c.save(path.join(fixturesDir, 'github-repo.json'))).not.toEqual('');
expect(await c.find()).not.toEqual('');
});
});

View File

@@ -279,6 +279,36 @@ describe('hash', () => {
});
});
// https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob_test.go#L36-L58
describe('parseBool', () => {
[
{input: '', expected: false, throwsError: true},
{input: 'asdf', expected: false, throwsError: true},
{input: '0', expected: false, throwsError: false},
{input: 'f', expected: false, throwsError: false},
{input: 'F', expected: false, throwsError: false},
{input: 'FALSE', expected: false, throwsError: false},
{input: 'false', expected: false, throwsError: false},
{input: 'False', expected: false, throwsError: false},
{input: '1', expected: true, throwsError: false},
{input: 't', expected: true, throwsError: false},
{input: 'T', expected: true, throwsError: false},
{input: 'TRUE', expected: true, throwsError: false},
{input: 'true', expected: true, throwsError: false},
{input: 'True', expected: true, throwsError: false}
].forEach(({input, expected, throwsError}) => {
test(`parseBool("${input}")`, () => {
if (throwsError) {
// eslint-disable-next-line jest/no-conditional-expect
expect(() => Util.parseBool(input)).toThrow();
} else {
// eslint-disable-next-line jest/no-conditional-expect
expect(Util.parseBool(input)).toBe(expected);
}
});
});
});
// See: https://github.com/actions/toolkit/blob/a1b068ec31a042ff1e10a522d8fdf0b8869d53ca/packages/core/src/core.ts#L89
function getInputName(name: string): string {
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;

View File

@@ -15,8 +15,8 @@
# limitations under the License.
ARG NODE_VERSION=20
ARG DOCKER_VERSION=24.0.5
ARG BUILDX_VERSION=0.11.2
ARG DOCKER_VERSION=26.0.0
ARG BUILDX_VERSION=0.13.1
FROM node:${NODE_VERSION}-alpine AS base
RUN apk add --no-cache cpio findutils git

View File

@@ -19,12 +19,25 @@ import {Exec} from '../exec';
import {Inputs} from './inputs';
import {Util} from '../util';
import {ExecOptions} from '@actions/exec';
import {BakeDefinition} from '../types/bake';
export interface BakeOpts {
buildx?: Buildx;
}
export interface BakeCmdOpts {
files?: Array<string>;
load?: boolean;
noCache?: boolean;
overrides?: Array<string>;
provenance?: string;
push?: boolean;
sbom?: string;
source?: string;
targets?: Array<string>;
}
export class Bake {
private readonly buildx: Buildx;
@@ -32,13 +45,17 @@ export class Bake {
this.buildx = opts?.buildx || new Buildx();
}
public async parseDefinitions(sources: Array<string>, targets?: Array<string>, overrides?: Array<string>, load?: boolean, push?: boolean, workdir?: string): Promise<BakeDefinition> {
public async getDefinition(cmdOpts: BakeCmdOpts, execOptions?: ExecOptions): Promise<BakeDefinition> {
execOptions = execOptions || {ignoreReturnCode: true};
execOptions.ignoreReturnCode = true;
const args = ['bake'];
let remoteDef;
let remoteDef: string | undefined;
const files: Array<string> = [];
const sources = [...(cmdOpts.files || []), cmdOpts.source];
if (sources) {
for (const source of sources.map(v => v.trim())) {
for (const source of sources.map(v => (v ? v.trim() : ''))) {
if (source.length == 0) {
continue;
}
@@ -47,7 +64,7 @@ export class Bake {
continue;
}
if (remoteDef) {
throw new Error(`Only one remote bake definition is allowed`);
throw new Error(`Only one remote bake definition can be defined`);
}
remoteDef = source;
}
@@ -58,31 +75,40 @@ export class Bake {
for (const file of files) {
args.push('--file', file);
}
if (overrides) {
for (const override of overrides) {
if (cmdOpts.overrides) {
for (const override of cmdOpts.overrides) {
args.push('--set', override);
}
}
if (load) {
if (cmdOpts.load) {
args.push('--load');
}
if (push) {
if (cmdOpts.noCache) {
args.push('--no-cache');
}
if (cmdOpts.provenance) {
args.push('--provenance', cmdOpts.provenance);
}
if (cmdOpts.push) {
args.push('--push');
}
if (cmdOpts.sbom) {
args.push('--sbom', cmdOpts.sbom);
}
const printCmd = await this.buildx.getCommand([...args, '--print', ...(targets || [])]);
return await Exec.getExecOutput(printCmd.command, printCmd.args, {
cwd: workdir,
ignoreReturnCode: true,
silent: true
}).then(res => {
const printCmd = await this.buildx.getCommand([...args, '--print', ...(cmdOpts.targets || [])]);
return await Exec.getExecOutput(printCmd.command, printCmd.args, execOptions).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(`cannot parse bake definitions: ${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
}
return <BakeDefinition>JSON.parse(res.stdout.trim());
return Bake.parseDefinition(res.stdout.trim());
});
}
public static parseDefinition(dt: string): BakeDefinition {
return <BakeDefinition>JSON.parse(dt);
}
public static hasLocalExporter(def: BakeDefinition): boolean {
return Inputs.hasExporterType('local', Bake.exporters(def));
}

View File

@@ -21,6 +21,7 @@ import {parse} from 'csv-parse/sync';
import {Context} from '../context';
import {GitHub} from '../github';
import {Util} from '../util';
const parseKvp = (kvp: string): [string, string] => {
const delimiterIndex = kvp.indexOf('=');
@@ -176,6 +177,45 @@ export class Inputs {
return false;
}
public static hasAttestationType(name: string, attrs: string): boolean {
const records = parse(attrs, {
delimiter: ',',
trim: true,
columns: false,
relaxColumnCount: true
});
for (const record of records) {
for (const [key, value] of record.map((chunk: string) => chunk.split('=').map(item => item.trim()))) {
if (key == 'type' && value == name) {
return true;
}
}
}
return false;
}
public static resolveAttestationAttrs(attrs: string): string {
const records = parse(attrs, {
delimiter: ',',
trim: true,
columns: false,
relaxColumnCount: true
});
const res: Array<string> = [];
for (const record of records) {
for (const attr of record) {
try {
// https://github.com/docker/buildx/blob/8abef5908705e49f7ba88ef8c957e1127b597a2a/util/buildflags/attests.go#L13-L21
const v = Util.parseBool(attr);
res.push(`disabled=${!v}`);
} catch (err) {
res.push(attr);
}
}
}
return res.join(',');
}
public static hasGitAuthTokenSecret(secrets: string[]): boolean {
for (const secret of secrets) {
if (secret.startsWith('GIT_AUTH_TOKEN=')) {

View File

@@ -30,6 +30,11 @@ export interface CacheOpts {
ghaNoCache?: boolean;
}
export interface CachePostState {
dir: string;
key: string;
}
export class Cache {
private readonly opts: CacheOpts;
private readonly ghaCacheKey: string;
@@ -37,6 +42,8 @@ export class Cache {
private readonly cacheDir: string;
private readonly cachePath: string;
private static readonly POST_CACHE_KEY = 'postCache';
constructor(opts: CacheOpts) {
this.opts = opts;
this.ghaCacheKey = util.format('%s-%s-%s', this.opts.htcName, this.opts.htcVersion, this.platform());
@@ -56,8 +63,14 @@ export class Cache {
core.debug(`Cache.save cached to hosted tool cache ${htcPath}`);
if (!this.ghaNoCache && cache.isFeatureAvailable()) {
core.debug(`Cache.save caching ${this.ghaCacheKey} to GitHub Actions cache`);
await cache.saveCache([this.cacheDir], this.ghaCacheKey);
core.debug(`Cache.save sending ${this.ghaCacheKey} to post state`);
core.saveState(
Cache.POST_CACHE_KEY,
JSON.stringify({
dir: this.cacheDir,
key: this.ghaCacheKey
} as CachePostState)
);
}
return cachePath;
@@ -75,7 +88,7 @@ export class Cache {
if (await cache.restoreCache([this.cacheDir], this.ghaCacheKey)) {
core.info(`Restored ${this.ghaCacheKey} from GitHub Actions cache`);
htcPath = await tc.cacheDir(this.cacheDir, this.opts.htcName, this.opts.htcVersion, this.platform());
core.info(`Restored to hosted tool cache ${htcPath}`);
core.info(`Cached to hosted tool cache ${htcPath}`);
return this.copyToCache(`${htcPath}/${this.opts.cacheFile}`);
}
} else if (this.ghaNoCache) {
@@ -87,6 +100,26 @@ export class Cache {
return '';
}
public static async post(): Promise<CachePostState | undefined> {
const state = core.getState(Cache.POST_CACHE_KEY);
if (!state) {
core.info(`State not set`);
return Promise.resolve(undefined);
}
let cacheState: CachePostState;
try {
cacheState = <CachePostState>JSON.parse(state);
} catch (e) {
throw new Error(`Failed to parse cache post state: ${e}`);
}
if (!cacheState.dir || !cacheState.key) {
throw new Error(`Invalid cache post state: ${state}`);
}
core.info(`Caching ${cacheState.key} to GitHub Actions cache`);
await cache.saveCache([cacheState.dir], cacheState.key);
return cacheState;
}
private copyToCache(file: string): string {
core.debug(`Copying ${file} to ${this.cachePath}`);
fs.copyFileSync(file, this.cachePath);

View File

@@ -16,6 +16,8 @@
import * as core from '@actions/core';
import {Cache} from './cache';
const isPost = !!process.env['STATE_isPost'];
if (!isPost) {
core.saveState('isPost', 'true');
@@ -36,7 +38,12 @@ export async function run(main: () => Promise<void>, post?: () => Promise<void>)
} catch (e) {
core.setFailed(e.message);
}
} else if (post) {
await post();
} else {
if (post) {
await post();
}
await core.group(`Post cache`, async () => {
await Cache.post();
});
}
}

View File

@@ -39,7 +39,9 @@ export interface Target {
platforms?: Array<string>;
pull?: boolean;
secret?: Array<string>;
'shm-size'?: string;
ssh?: Array<string>;
tags?: Array<string>;
target?: string;
ulimits?: Array<string>;
}

View File

@@ -144,4 +144,26 @@ export class Util {
public static hash(input: string): string {
return crypto.createHash('sha256').update(input).digest('hex');
}
// https://github.com/golang/go/blob/f6b93a4c358b28b350dd8fe1780c1f78e520c09c/src/strconv/atob.go#L7-L18
public static parseBool(str: string): boolean {
switch (str) {
case '1':
case 't':
case 'T':
case 'true':
case 'TRUE':
case 'True':
return true;
case '0':
case 'f':
case 'F':
case 'false':
case 'FALSE':
case 'False':
return false;
default:
throw new Error(`parseBool syntax error: ${str}`);
}
}
}

View File

@@ -4465,9 +4465,9 @@ __metadata:
linkType: hard
"ip@npm:^2.0.0":
version: 2.0.0
resolution: "ip@npm:2.0.0"
checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349
version: 2.0.1
resolution: "ip@npm:2.0.1"
checksum: d765c9fd212b8a99023a4cde6a558a054c298d640fec1020567494d257afd78ca77e37126b1a3ef0e053646ced79a816bf50621d38d5e768cdde0431fa3b0d35
languageName: node
linkType: hard
@@ -7169,11 +7169,11 @@ __metadata:
linkType: hard
"undici@npm:^5.25.4":
version: 5.27.0
resolution: "undici@npm:5.27.0"
version: 5.28.3
resolution: "undici@npm:5.28.3"
dependencies:
"@fastify/busboy": ^2.0.0
checksum: 3acad25bfe5957aa5edc24eb160b5da7a9c67a5061e2e001929bef4bafed07d93a2accb36d407179c35b3ae56adbe89b49e1dd80d8cea9fdc44dca2037174330
checksum: fa1e65aff896c5e2ee23637b632e306f9e3a2b32a3dc0b23ea71e5555ad350bcc25713aea894b3dccc0b7dc2c5e92a5a58435ebc2033b731a5524506f573dfd2
languageName: node
linkType: hard