340 lines
10 KiB
JavaScript
340 lines
10 KiB
JavaScript
import * as core from '@actions/core';
|
|
import * as github from '@actions/github';
|
|
import * as child_process from 'child_process';
|
|
import * as fs from 'fs';
|
|
import {Base64} from 'js-base64';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
|
|
export function processAdditionalRegistries(targetRegistries) {
|
|
const additionalRegistries = core.getInput('additional_registries');
|
|
if (additionalRegistries != null && additionalRegistries.length > 0) {
|
|
const additionalRegistriesArr = additionalRegistries.split(',');
|
|
for (let registry of additionalRegistriesArr) {
|
|
registry = registry.trim();
|
|
if (!registry.includes(':')) {
|
|
registry += ':';
|
|
}
|
|
targetRegistries.push(registry);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function addCiRegistryAuth(ci_registry, registryAuthJson) {
|
|
if (!core.getBooleanInput('add_ci_registry_auth')) {
|
|
return;
|
|
}
|
|
|
|
if (ci_registry === false || ci_registry.length <= 0) {
|
|
console.log('WARNING: add_ci_registry_auth enabled but ci_registry is not set');
|
|
return;
|
|
}
|
|
|
|
const argCiRegistryPassword = (core.getInput('ci_registry_password') ?? '').trim();
|
|
if (argCiRegistryPassword == null || argCiRegistryPassword.length <= 0) {
|
|
console.log('WARNING: add_ci_registry_auth enabled but ci_registry_password env is empty');
|
|
return;
|
|
}
|
|
|
|
registryAuthJson.auths[ci_registry] = {'auth': Base64.encode('token:' + argCiRegistryPassword)};
|
|
}
|
|
|
|
export function mergeArgRegistryAuthJson(registryAuthJson) {
|
|
const argRegistryAuthJson = core.getInput('registry_auth_json');
|
|
if (argRegistryAuthJson != null && argRegistryAuthJson.trim().length > 0) {
|
|
try {
|
|
const argRegistryAuth = JSON.parse(argRegistryAuthJson);
|
|
if (argRegistryAuth.auths != null) {
|
|
for (const key in argRegistryAuth.auths) {
|
|
if (argRegistryAuth.auths.hasOwnProperty(key)) {
|
|
registryAuthJson.auths[key] = argRegistryAuth.auths[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.log('Failed to parse registry auth json', error);
|
|
core.setFailed(error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
export function mergeExistingDockerAuthJson(registryAuthJson, targetFile) {
|
|
if (!core.getBooleanInput('merge_existing_auth_json')) {
|
|
return;
|
|
}
|
|
|
|
if (!fs.existsSync(targetFile)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const existingJsonStr = fs.readFileSync(targetFile, {encoding: 'utf-8'});
|
|
const existingJson = JSON.parse(existingJsonStr);
|
|
|
|
if (existingJson.auths != null && typeof existingJson === 'object') {
|
|
for (const key in existingJson.auths) {
|
|
if (existingJson.auths.hasOwnProperty(key)) {
|
|
registryAuthJson.auths[key] = existingJson.auths[key];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log(`Failed to parse existing docker auth json in file: ${targetFile}"`);
|
|
core.setFailed(`Failed to parse existing docker auth json in file: ${targetFile}"` + e.message);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export function writeRegistryAuthJson(registryAuthJson, targetFile) {
|
|
fs.mkdirSync(path.dirname(targetFile), {recursive: true});
|
|
const jsonContents = JSON.stringify(registryAuthJson, null, 2);
|
|
|
|
// create and log a censored copy if enabled
|
|
if (core.getBooleanInput('debug_log_auth_json')) {
|
|
const copy = JSON.parse(jsonContents);
|
|
for (const registry in copy.auths) {
|
|
if (copy.auths.hasOwnProperty(registry)) {
|
|
let credentials = copy.auths[registry].auth;
|
|
if (credentials != null) {
|
|
// truncate credentials to avoid leaking sensitive information
|
|
if (credentials.length > 16) {
|
|
credentials = credentials.substr(0, 16) + '...';
|
|
}
|
|
else {
|
|
credentials = '***censored***';
|
|
}
|
|
copy.auths[registry].auth = credentials;
|
|
}
|
|
}
|
|
}
|
|
console.log('debug_log_auth_json:', copy);
|
|
}
|
|
|
|
fs.writeFileSync(targetFile, jsonContents);
|
|
}
|
|
|
|
function isNonEmptyStr(str) {
|
|
return str != null && str !== 'false' && str.length > 0;
|
|
}
|
|
|
|
export function collectTags(information) {
|
|
const tags = [];
|
|
let mostSpecificSemverTag = false;
|
|
let tagPrefix = (core.getInput('tag_prefix') ?? '').trim();
|
|
let tagSuffix = (core.getInput('tag_suffix') ?? '').trim();
|
|
let tagCommitPrefix = (core.getInput('tag_commit_prefix') ?? '').trim();
|
|
|
|
|
|
// tag semver
|
|
if (core.getBooleanInput('tag_semver_enable') && information.semver_valid) {
|
|
if (core.getBooleanInput('tag_semver_major') && information.semver_major != null) {
|
|
mostSpecificSemverTag = tagPrefix + information.semver_major;
|
|
tags.push(mostSpecificSemverTag);
|
|
|
|
if (core.getBooleanInput('tag_semver_minor') && information.semver_minor != null) {
|
|
mostSpecificSemverTag += '.' + information.semver_minor;
|
|
tags.push(mostSpecificSemverTag);
|
|
|
|
if (core.getBooleanInput('tag_semver_patch') && information.semver_patch != null) {
|
|
mostSpecificSemverTag += '.' + information.semver_patch;
|
|
tags.push(mostSpecificSemverTag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle git tag/branch
|
|
if (core.getBooleanInput('tag_ref_normalized_enable')) {
|
|
// only apply tag IF it doesn't match the semver
|
|
if (isNonEmptyStr(information.git_tag)) {
|
|
const normalizedTag = tagPrefix + normalizeGitRefForDockerTag(information.git_tag) + tagSuffix;
|
|
if (mostSpecificSemverTag !== normalizedTag) {
|
|
tags.push(normalizedTag);
|
|
}
|
|
}
|
|
if (isNonEmptyStr(information.git_current_branch)) {
|
|
const normalizedBranch = tagPrefix + normalizeGitRefForDockerTag(information.git_current_branch) + tagSuffix;
|
|
if (mostSpecificSemverTag !== normalizedBranch) {
|
|
tags.push(normalizedBranch);
|
|
}
|
|
}
|
|
}
|
|
|
|
// handle additional tags
|
|
core.getInput('tags_additional')
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.filter(s => s.length > 0)
|
|
.forEach(t => {
|
|
tags.push(t);
|
|
});
|
|
|
|
// handle commit sha
|
|
if (core.getBooleanInput('tag_commit_enable') && isNonEmptyStr(github.context.sha)) {
|
|
tags.push(tagPrefix + tagCommitPrefix + github.context.sha + tagSuffix);
|
|
}
|
|
|
|
return tags;
|
|
}
|
|
|
|
export function normalizeGitRefForDockerTag(ref) {
|
|
return ref
|
|
.replaceAll('/', '-');
|
|
}
|
|
|
|
export function prepareDestinations(registries, tags) {
|
|
const destinations = [];
|
|
registries.forEach((registry) => {
|
|
tags.forEach((tag) => {
|
|
destinations.push(registry + tag);
|
|
});
|
|
});
|
|
|
|
return destinations;
|
|
}
|
|
|
|
export function getDockerContextDir() {
|
|
let contextDir = core.getInput('docker_context_dir');
|
|
if (!isNonEmptyStr(core.getInput('docker_context_dir'))) {
|
|
return process.env['GITHUB_WORKSPACE'];
|
|
}
|
|
if (!contextDir.startsWith('/')) {
|
|
contextDir = process.env['GITHUB_WORKSPACE'] + '/' + contextDir;
|
|
}
|
|
return contextDir;
|
|
}
|
|
|
|
export function prepareDockerArgs(destinations) {
|
|
let dockerArgs = (core.getInput('docker_args') ?? '').trim();
|
|
if (dockerArgs.length > 0) {
|
|
dockerArgs = [dockerArgs];
|
|
}
|
|
else {
|
|
dockerArgs = [];
|
|
}
|
|
|
|
if (isNonEmptyStr(core.getInput('dockerfile'))) {
|
|
dockerArgs.unshift('--file ' + core.getInput('dockerfile'));
|
|
}
|
|
|
|
dockerArgs.unshift(getDockerContextDir());
|
|
|
|
if (isNonEmptyStr(core.getInput('docker_multiarch'))) {
|
|
if (!core.getBooleanInput('use_buildx')) {
|
|
throw new Error('Unsupported configuration: Cannot build multiarch without enabling buildx');
|
|
}
|
|
let archList = (core.getInput('docker_multiarch'));
|
|
if (archList === 'true' || archList === '1') {
|
|
archList = 'linux/amd64,linux/arm64';
|
|
}
|
|
if (archList.length > 0) {
|
|
dockerArgs.push('--platform ' + archList);
|
|
}
|
|
}
|
|
|
|
if (core.getBooleanInput('squash_layers')) {
|
|
dockerArgs.push('--squash');
|
|
}
|
|
|
|
destinations.forEach(dest => {
|
|
dockerArgs.push('--tag ' + dest);
|
|
});
|
|
|
|
if (isNonEmptyStr(core.getInput('additional_destinations'))) {
|
|
core.getInput('additional_destinations')
|
|
.split(',')
|
|
.map(s => s.trim())
|
|
.forEach(dst => {
|
|
dockerArgs.push('--tag ' + dst);
|
|
});
|
|
}
|
|
|
|
if (isNonEmptyStr(core.getInput('build_args'))) {
|
|
let buildArgs = core.getInput('build_args')
|
|
.split('\n')
|
|
.map(s => s.trim())
|
|
.map(s => {
|
|
const equalIndex = s.indexOf('=');
|
|
const key = s.substring(0, equalIndex);
|
|
const value = s.substring(equalIndex + 1);
|
|
return {
|
|
key,
|
|
value
|
|
};
|
|
});
|
|
|
|
console.log('parsed build_args as: ', JSON.stringify(buildArgs, null, 2));
|
|
buildArgs.forEach(arg => {
|
|
dockerArgs.push(`--build-arg ${arg.key}="${arg.value}"`);
|
|
});
|
|
}
|
|
|
|
return dockerArgs;
|
|
}
|
|
|
|
export function executeDockerBuild(dockerArgs, destinations) {
|
|
const dockerArgsStr = dockerArgs.join(' ');
|
|
const isBuildX = core.getBooleanInput('use_buildx');
|
|
let dockerSubCmd = isBuildX ? 'buildx build' : 'build';
|
|
if (core.getBooleanInput('docker_push')) {
|
|
dockerSubCmd += ' --push';
|
|
}
|
|
if (core.getBooleanInput('docker_pull')) {
|
|
dockerSubCmd += ' --pull';
|
|
}
|
|
const execStr = `docker ${dockerSubCmd} ${dockerArgsStr}`;
|
|
console.log(`executing: ${execStr}`);
|
|
|
|
const proc = child_process.spawnSync(execStr, {
|
|
shell: true,
|
|
stdio: 'inherit',
|
|
cwd : getDockerContextDir()
|
|
});
|
|
|
|
// push for legacy builder
|
|
// if (!isBuildX && core.getBooleanInput('docker_push')) {
|
|
// destinations.forEach(dst => {
|
|
// const pushProc = child_process.spawnSync('docker push ' + dst, {
|
|
// shell: true,
|
|
// stdio: 'inherit',
|
|
// cwd : getDockerContextDir()
|
|
// });
|
|
// if (pushProc.status != null && pushProc.status > 0) {
|
|
// throw new Error('docker push ' + dst + ' failed');
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
if (proc.status != null && proc.status > 0) {
|
|
throw new Error('docker build failed');
|
|
}
|
|
|
|
if (proc.error != null) {
|
|
throw proc.error;
|
|
}
|
|
}
|
|
|
|
export function determineDockerConfigFileLocation(path) {
|
|
if (path == null || !path.length) {
|
|
return os.homedir + '/.docker/config.json';
|
|
}
|
|
|
|
// absolute path
|
|
if (path.startsWith('/')) {
|
|
return path;
|
|
}
|
|
|
|
// relative path to home dir
|
|
return os.homedir + '/' + path;
|
|
}
|
|
|
|
export function isTrueString(str) {
|
|
return str === '1'
|
|
|| str === 'true'
|
|
|| str === 'True'
|
|
|| str === 'TRUE';
|
|
}
|