UNPKG

@docker/actions-toolkit

Version:
346 lines 12.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 * as core from '@actions/core'; import * as github from '@actions/github'; import { parse } from 'csv-parse/sync'; import { Buildx } from './buildx.js'; import { Context } from '../context.js'; import { GitHub } from '../github/github.js'; import { Util } from '../util.js'; export class Build { buildx; iidFilename; metadataFilename; constructor(opts) { this.buildx = opts?.buildx || new Buildx(); this.iidFilename = `build-iidfile-${Util.generateRandomString()}.txt`; this.metadataFilename = `build-metadata-${Util.generateRandomString()}.json`; } async gitContext(opts) { const gitContextCommonAttrs = new Set(['ref', 'checksum', 'subdir']); const setPullRequestHeadRef = Util.parseBoolOrDefault(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF); const commonAttrs = { ref: opts?.attrs?.ref, checksum: opts?.attrs?.checksum, subdir: opts?.attrs?.subdir }; const gitChecksum = opts?.checksum || commonAttrs.checksum || github.context.sha; let ref = opts?.ref || commonAttrs.ref || github.context.ref; const subdir = opts?.subdir || commonAttrs.subdir; const attrs = Object.entries(opts?.attrs || {}).filter(([name]) => !gitContextCommonAttrs.has(name)); if (!ref.startsWith('refs/')) { ref = `refs/heads/${ref}`; } else if (ref.startsWith(`refs/pull/`) && setPullRequestHeadRef) { ref = ref.replace(/\/merge$/g, '/head'); } const baseURL = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git`; let format = opts?.format; if (!format) { const sendGitQueryAsInput = Util.parseBoolOrDefault(process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT); if (attrs.length > 0) { format = 'query'; } else if (sendGitQueryAsInput && (await this.buildx.versionSatisfies('>=0.29.0'))) { format = 'query'; } else { format = 'fragment'; } } if (format === 'query') { const query = [`ref=${ref}`]; if (gitChecksum) { query.push(`checksum=${gitChecksum}`); } if (subdir && subdir !== '.') { query.push(`subdir=${subdir}`); } for (const [name, value] of attrs) { query.push(`${name}=${value}`); } return `${baseURL}?${query.join('&')}`; } const fragmentRef = gitChecksum && !ref.startsWith(`refs/pull/`) ? gitChecksum : ref; return `${baseURL}#${fragmentRef}${subdir && subdir !== '.' ? `:${subdir}` : ''}`; } getImageIDFilePath() { return path.join(Context.tmpDir(), this.iidFilename); } resolveImageID() { const iidFile = this.getImageIDFilePath(); if (!fs.existsSync(iidFile)) { return undefined; } return fs.readFileSync(iidFile, { encoding: 'utf-8' }).trim(); } 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); } resolveRef(metadata) { if (!metadata) { metadata = this.resolveMetadata(); if (!metadata) { return undefined; } } if ('buildx.build.ref' in metadata) { return metadata['buildx.build.ref']; } return undefined; } resolveProvenance(metadata) { if (!metadata) { metadata = this.resolveMetadata(); if (!metadata) { return undefined; } } if ('buildx.build.provenance' in metadata) { return metadata['buildx.build.provenance']; } return 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; } resolveDigest(metadata) { if (!metadata) { metadata = this.resolveMetadata(); if (!metadata) { return undefined; } } if ('containerimage.digest' in metadata) { return metadata['containerimage.digest']; } return undefined; } static resolveSecretString(kvp) { const [key, file] = Build.resolveSecret(kvp, { redact: true }); return `id=${key},src=${file}`; } static resolveSecretFile(kvp) { const [key, file] = Build.resolveSecret(kvp, { asFile: true }); return `id=${key},src=${file}`; } static resolveSecretEnv(kvp) { const [key, value] = Build.parseSecretKvp(kvp); return `id=${key},env=${value}`; } static resolveSecret(kvp, opts) { const [key, value] = Build.parseSecretKvp(kvp, opts?.redact); if (opts?.asFile) { if (!fs.existsSync(value)) { throw new Error(`secret file ${value} not found`); } return [key, value]; } const secretFile = Context.tmpName({ tmpdir: Context.tmpDir() }); fs.writeFileSync(secretFile, value); return [key, secretFile]; } static getProvenanceInput(name) { const input = core.getInput(name); if (!input) { // if input is not set returns empty string return input; } try { return core.getBooleanInput(name) ? `builder-id=${GitHub.workflowRunURL(true)}` : 'false'; } catch { // not a valid boolean, so we assume it's a string return Build.resolveProvenanceAttrs(input); } } static resolveProvenanceAttrs(input) { if (!input) { return `builder-id=${GitHub.workflowRunURL(true)}`; } // parse attributes from input const fields = parse(input, { relaxColumnCount: true, skipEmptyLines: true })[0]; // check if builder-id attribute exists in the input for (const field of fields) { const parts = field .toString() .split(/(?<=^[^=]+?)=/) .map(item => item.trim()); if (parts[0] == 'builder-id') { return input; } } // if not add builder-id attribute return `${input},builder-id=${GitHub.workflowRunURL(true)}`; } static resolveCacheToAttrs(input, githubToken) { if (!input) { return input; } let cacheType = 'registry'; let ghaCacheRepository = ''; let ghaCacheGHToken = ''; const fields = parse(input, { relaxColumnCount: true, skipEmptyLines: true })[0]; for (const field of fields) { const parts = field .toString() .split(/(?<=^[^=]+?)=/) .map(item => item.trim()); if (parts[0] === 'type') { cacheType = parts[1]; } else if (parts[0] === 'repository') { ghaCacheRepository = parts[1]; } else if (parts[0] === 'ghtoken') { ghaCacheGHToken = parts[1]; } } if (cacheType === 'gha') { if (!ghaCacheRepository) { input = `${input},repository=${GitHub.repository}`; } if (!ghaCacheGHToken && githubToken) { input = `${input},ghtoken=${githubToken}`; } } return input; } static hasLocalExporter(exporters) { return Build.hasExporterType('local', exporters); } static hasTarExporter(exporters) { return Build.hasExporterType('tar', exporters); } static hasDockerExporter(exporters, load) { return load || Build.hasExporterType('docker', exporters); } static hasExporterType(name, exporters) { const records = parse(exporters.join(`\n`), { delimiter: ',', trim: true, columns: false, relaxColumnCount: true }); for (const record of records) { if (record.length == 1 && !record[0].startsWith('type=')) { // Local if no type is defined // https://github.com/docker/buildx/blob/d2bf42f8b4784d83fde17acb3ed84703ddc2156b/build/output.go#L29-L43 return name == 'local'; } for (const [key, value] of record.map(chunk => chunk.split('=').map(item => item.trim()))) { if (key == 'type' && value == name) { return true; } } } return false; } static hasAttestationType(name, attrs) { const records = parse(attrs, { delimiter: ',', trim: true, columns: false, relaxColumnCount: true }); for (const record of records) { for (const [key, value] of record.map((chunk) => chunk.split('=').map(item => item.trim()))) { if (key == 'type' && value == name) { return true; } } } return false; } static resolveAttestationAttrs(attrs) { const records = parse(attrs, { delimiter: ',', trim: true, columns: false, relaxColumnCount: true }); const res = []; 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 { res.push(attr); } } } return res.join(','); } static hasGitAuthTokenSecret(secrets, domain) { for (const secret of secrets) { if (domain && secret.startsWith(`GIT_AUTH_TOKEN.${domain}=`)) { return true; } else if (secret.startsWith('GIT_AUTH_TOKEN=')) { return true; } } return false; } static parseSecretKvp(kvp, redact) { const delimiterIndex = kvp.indexOf('='); const key = kvp.substring(0, delimiterIndex); const value = kvp.substring(delimiterIndex + 1); if (key.length == 0 || value.length == 0) { throw new Error(`${kvp} is not a valid secret`); } if (redact) { core.setSecret(value); } return [key, value]; } } //# sourceMappingURL=build.js.map