UNPKG

@docker/actions-toolkit

Version:
387 lines 13.1 kB
/** * Copyright 2023 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 fs from 'fs'; import path from 'path'; import { parse } from 'csv-parse/sync'; import { Buildx } from './buildx.js'; import { Context } from '../context.js'; import { Exec } from '../exec.js'; import { Util } from '../util.js'; export class Bake { buildx; metadataFilename; constructor(opts) { this.buildx = opts?.buildx || new Buildx(); this.metadataFilename = `bake-metadata-${Util.generateRandomString()}.json`; } getMetadataFilePath() { return path.join(Context.tmpDir(), this.metadataFilename); } resolveMetadata() { const metadataFile = this.getMetadataFilePath(); if (!fs.existsSync(metadataFile)) { return undefined; } const content = fs.readFileSync(metadataFile, { encoding: 'utf-8' }).trim(); if (content === 'null') { return undefined; } return JSON.parse(content); } resolveRefs(metadata) { if (!metadata) { metadata = this.resolveMetadata(); if (!metadata) { return undefined; } } const refs = new Array(); for (const key in metadata) { if ('buildx.build.ref' in metadata[key]) { refs.push(metadata[key]['buildx.build.ref']); } } return refs.length > 0 ? refs : undefined; } resolveWarnings(metadata) { if (!metadata) { metadata = this.resolveMetadata(); if (!metadata) { return undefined; } } if ('buildx.build.warnings' in metadata) { return metadata['buildx.build.warnings']; } return undefined; } async getDefinition(cmdOpts, execOptions) { execOptions = execOptions || { ignoreReturnCode: true }; execOptions.ignoreReturnCode = true; execOptions.env = Object.assign({}, process.env, execOptions.env || {}, cmdOpts.githubToken ? { BUILDX_BAKE_GIT_AUTH_TOKEN: cmdOpts.githubToken } : {}); const args = ['bake']; let remoteDef; const files = []; const sources = [...(cmdOpts.files || []), cmdOpts.source]; if (sources) { for (const source of sources.map(v => (v ? v.trim() : ''))) { if (source.length == 0) { continue; } if (!Util.isValidRef(source)) { files.push(source); continue; } if (remoteDef) { throw new Error(`Only one remote bake definition can be defined`); } remoteDef = source; } } if (remoteDef) { args.push(remoteDef); } for (const file of files) { args.push('--file', file); } if (cmdOpts.overrides) { for (const override of cmdOpts.overrides) { args.push('--set', override); } } if (cmdOpts.vars) { for (const v of cmdOpts.vars) { args.push('--var', v); } } if (cmdOpts.allow) { for (const allow of cmdOpts.allow) { args.push('--allow', allow); } } if (cmdOpts.call) { args.push('--call', cmdOpts.call); } if (cmdOpts.load) { args.push('--load'); } 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', ...(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 Bake.parseDefinition(res.stdout.trim()); }); } static parseDefinition(dt) { const definition = JSON.parse(dt); // convert to composable attributes: https://github.com/docker/buildx/pull/2758 for (const name in definition.target) { const target = definition.target[name]; if (target['attest'] && Array.isArray(target['attest'])) { target['attest'] = target['attest'].map((item) => { return Bake.parseAttestEntry(item); }); } if (target['cache-from'] && Array.isArray(target['cache-from'])) { target['cache-from'] = target['cache-from'].map((item) => { return Bake.parseCacheEntry(item); }); } if (target['cache-to'] && Array.isArray(target['cache-to'])) { target['cache-to'] = target['cache-to'].map((item) => { return Bake.parseCacheEntry(item); }); } if (target['output'] && Array.isArray(target['output'])) { target['output'] = target['output'].map((item) => { return Bake.parseExportEntry(item); }); } if (target['secret'] && Array.isArray(target['secret'])) { target['secret'] = target['secret'].map((item) => { return Bake.parseSecretEntry(item); }); } if (target['ssh'] && Array.isArray(target['ssh'])) { target['ssh'] = target['ssh'].map((item) => { return Bake.parseSSHEntry(item); }); } } return definition; } static parseAttestEntry(item) { if (typeof item !== 'string') { return item; } const attestEntry = { type: '' }; const fields = parse(item, { relaxColumnCount: true, skipEmptyLines: true })[0]; for (const field of fields) { const [key, value] = field .toString() .split(/(?<=^[^=]+?)=/) .map((item) => item.trim()); switch (key) { case 'type': attestEntry.type = value; break; case 'disabled': attestEntry.disabled = Util.parseBool(value); break; default: attestEntry[key] = value; } } return attestEntry; } static parseCacheEntry(item) { if (typeof item !== 'string') { return item; } const cacheEntry = { type: '' }; const fields = parse(item, { relaxColumnCount: true, skipEmptyLines: true })[0]; if (fields.length === 1 && !fields[0].includes('=')) { cacheEntry.type = 'registry'; cacheEntry.ref = fields[0]; return cacheEntry; } for (const field of fields) { const [key, value] = field .toString() .split(/(?<=^[^=]+?)=/) .map((item) => item.trim()); switch (key) { case 'type': cacheEntry.type = value; break; default: cacheEntry[key] = value; } } return cacheEntry; } static parseExportEntry(item) { if (typeof item !== 'string') { return item; } const exportEntry = { type: '' }; const fields = parse(item, { relaxColumnCount: true, skipEmptyLines: true })[0]; if (fields.length === 1 && fields[0] === item && !item.startsWith('type=')) { if (item !== '-') { exportEntry.type = 'local'; exportEntry.dest = item; return exportEntry; } exportEntry.type = 'tar'; exportEntry.dest = item; return exportEntry; } for (const field of fields) { const [key, value] = field .toString() .split(/(?<=^[^=]+?)=/) .map((item) => item.trim()); switch (key) { case 'type': exportEntry.type = value; break; default: exportEntry[key] = value; } } return exportEntry; } static parseSecretEntry(item) { if (typeof item !== 'string') { return item; } const secretEntry = {}; const fields = parse(item, { relaxColumnCount: true, skipEmptyLines: true })[0]; let typ = ''; for (const field of fields) { const [key, value] = field .toString() .split(/(?<=^[^=]+?)=/) .map((item) => item.trim()); switch (key) { case 'type': typ = value; break; case 'id': secretEntry.id = value; break; case 'source': case 'src': secretEntry.src = value; break; case 'env': secretEntry.env = value; break; } } if (typ === 'env' && !secretEntry.env) { secretEntry.env = secretEntry.src; secretEntry.src = undefined; } return secretEntry; } static parseSSHEntry(item) { if (typeof item !== 'string') { return item; } const sshEntry = {}; const [key, value] = item.split('=', 2); sshEntry.id = key; if (value) { sshEntry.paths = value.split(','); } return sshEntry; } static hasLocalExporter(def) { return Bake.hasExporterType('local', Bake.exporters(def)); } static hasTarExporter(def) { return Bake.hasExporterType('tar', Bake.exporters(def)); } static hasDockerExporter(def, load) { return load || Bake.hasExporterType('docker', Bake.exporters(def)); } static hasExporterType(name, exporters) { for (const exporter of exporters) { if (exporter.type == name) { return true; } } return false; } static exporters(def) { const exporters = new Array(); for (const key in def.target) { const target = def.target[key]; if (target.output) { for (const output of target.output) { exporters.push(Bake.parseExportEntry(output)); } } } return exporters; } static hasGitAuthTokenSecret(def) { for (const key in def.target) { const target = def.target[key]; if (target.secret) { for (const secret of target.secret) { if (Bake.parseSecretEntry(secret).id === 'GIT_AUTH_TOKEN') { return true; } } } } return false; } static hasProvenanceAttestation(def) { return Bake.hasAttestationType('provenance', Bake.attestations(def)); } static hasSBOMAttestation(def) { return Bake.hasAttestationType('sbom', Bake.attestations(def)); } static hasAttestationType(name, attestations) { for (const attestation of attestations) { if (attestation.type == name) { return true; } } return false; } static attestations(def) { const attestations = new Array(); for (const key in def.target) { const target = def.target[key]; if (target.attest) { for (const attest of target.attest) { attestations.push(Bake.parseAttestEntry(attest)); } } } return attestations; } } //# sourceMappingURL=bake.js.map