@atomist/sdm-pack-docker
Version:
Extension Pack for an Atomist SDM to integrate Docker
310 lines • 13.7 kB
JavaScript
;
/*
* Copyright © 2019 Atomist, Inc.
*
* 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.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const automation_client_1 = require("@atomist/automation-client");
const sdm_1 = require("@atomist/sdm");
const sdm_core_1 = require("@atomist/sdm-core");
const array_1 = require("@atomist/sdm-core/lib/util/misc/array");
const fs = require("fs-extra");
const _ = require("lodash");
const os = require("os");
const path = require("path");
const name_1 = require("../support/name");
/**
* Execute a Docker build for the project
*/
function executeDockerBuild(options) {
return sdm_1.doWithProject((gi) => __awaiter(this, void 0, void 0, function* () {
const { goalEvent, context, project } = gi;
const optsToUse = sdm_1.mergeOptions(options, {}, "docker.build");
switch (optsToUse.builder) {
case "docker":
yield checkIsBuilderAvailable("docker", "help");
break;
case "kaniko":
yield checkIsBuilderAvailable("/kaniko/executor", "--help");
break;
}
// Check the graph for registries if we don't have any configured
if (!optsToUse.config && array_1.toArray(optsToUse.registry || []).length === 0) {
optsToUse.registry = yield readRegistries(context);
}
const imageNames = yield optsToUse.dockerImageNameCreator(project, goalEvent, optsToUse, context);
const images = _.flatten(imageNames.map(imageName => imageName.tags.map(tag => `${imageName.registry ? `${imageName.registry}/` : ""}${imageName.name}:${tag}`)));
const dockerfilePath = yield (optsToUse.dockerfileFinder ? optsToUse.dockerfileFinder(project) : "Dockerfile");
let externalUrls = [];
if (yield pushEnabled(gi, optsToUse)) {
externalUrls = getExternalUrls(imageNames, optsToUse);
}
// 1. run docker login
let result = yield dockerLogin(optsToUse, gi);
if (result.code !== 0) {
return result;
}
if (optsToUse.builder === "docker") {
result = yield buildWithDocker(images, dockerfilePath, gi, optsToUse);
if (result.code !== 0) {
return result;
}
}
else if (optsToUse.builder === "kaniko") {
result = yield buildWithKaniko(images, imageNames, dockerfilePath, gi, optsToUse);
if (result.code !== 0) {
return result;
}
}
// 4. create image link
if (yield sdm_core_1.postLinkImageWebhook(goalEvent.repo.owner, goalEvent.repo.name, goalEvent.sha, images[0], context.workspaceId)) {
return Object.assign({}, result, { externalUrls });
}
else {
return { code: 1, message: "Image link failed" };
}
}), {
readOnly: true,
detachHead: false,
});
}
exports.executeDockerBuild = executeDockerBuild;
function buildWithDocker(images, dockerfilePath, gi, optsToUse) {
return __awaiter(this, void 0, void 0, function* () {
// 2. run docker build
const tags = _.flatten(images.map(i => ["-t", i]));
let result = yield gi.spawn("docker", ["build", "-f", dockerfilePath, ...tags, ...optsToUse.builderArgs, optsToUse.builderPath], {
env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(optsToUse, gi.goalEvent) }),
log: gi.progressLog,
});
// 3. run docker push
result = yield dockerPush(images, optsToUse, gi);
if (result.code !== 0) {
return result;
}
return result;
});
}
function buildWithKaniko(images, imageNames, dockerfilePath, gi, optsToUse) {
return __awaiter(this, void 0, void 0, function* () {
// 2. run kaniko build
const builderArgs = [];
if (yield pushEnabled(gi, optsToUse)) {
builderArgs.push(...images.map(i => `-d=${i}`), "--cache=true", `--cache-repo=${imageNames[0].registry ? `${imageNames[0].registry}/` : ""}${imageNames[0].name}-cache`);
}
else {
builderArgs.push("--no-push");
}
builderArgs.push(...(optsToUse.builderArgs.length > 0 ? optsToUse.builderArgs : ["--snapshotMode=time", "--reproducible"]));
// Check if base image cache dir is available
const cacheFilPath = _.get(gi, "configuration.sdm.cache.path", "/opt/data");
if (_.get(gi, "configuration.sdm.cache.enabled") === true && (yield fs.pathExists(cacheFilPath))) {
const baseImageCache = path.join(cacheFilPath, "base-image-cache");
yield fs.mkdirs(baseImageCache);
builderArgs.push(`--cache-dir=${baseImageCache}`, "--cache=true");
}
const kanikoContext = `dir://${gi.project.baseDir}` + ((optsToUse.builderPath === ".") ? "" : `/${optsToUse.builderPath}`);
return gi.spawn("/kaniko/executor", ["--dockerfile", dockerfilePath, "--context", kanikoContext, ..._.uniq(builderArgs)], {
env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(optsToUse, gi.goalEvent) }),
log: gi.progressLog,
});
});
}
function dockerLogin(options, gi) {
return __awaiter(this, void 0, void 0, function* () {
const registries = array_1.toArray(options.registry || []).filter(r => !!r.user && !!r.password);
if (registries.length > 0) {
let result;
for (const registry of registries) {
gi.progressLog.write("Running 'docker login'");
const loginArgs = ["login", "--username", registry.user, "--password", registry.password];
if (/[^A-Za-z0-9]/.test(registry.registry)) {
loginArgs.push(registry.registry);
}
// 2. run docker login
result = yield gi.spawn("docker", loginArgs, {
logCommand: false,
log: gi.progressLog,
});
if (!!result && result.code !== 0) {
return result;
}
}
return result;
}
else if (options.config) {
gi.progressLog.write("Authenticating with provided Docker 'config.json'");
const dockerConfig = path.join(dockerConfigPath(options, gi.goalEvent), "config.json");
yield fs.ensureDir(path.dirname(dockerConfig));
yield fs.writeFile(dockerConfig, options.config);
}
else {
gi.progressLog.write("Skipping 'docker auth' because no credentials configured");
}
return automation_client_1.Success;
});
}
function dockerPush(images, options, gi) {
return __awaiter(this, void 0, void 0, function* () {
let result = automation_client_1.Success;
if (yield pushEnabled(gi, options)) {
if (!!options.concurrentPush) {
const results = yield automation_client_1.executeAll(images.map(image => () => __awaiter(this, void 0, void 0, function* () {
const log = new sdm_1.StringCapturingProgressLog();
const r = yield gi.spawn("docker", ["push", image], {
env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(options, gi.goalEvent) }),
log,
});
gi.progressLog.write(log.log);
return r;
})));
return {
code: results.some(r => !!r && r.code !== 0) ? 1 : 0,
};
}
else {
for (const image of images) {
result = yield gi.spawn("docker", ["push", image], {
env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(options, gi.goalEvent) }),
log: gi.progressLog,
});
if (!!result && result.code !== 0) {
return result;
}
}
}
}
else {
gi.progressLog.write("Skipping 'docker push'");
}
return result;
});
}
exports.DefaultDockerImageNameCreator = (p, sdmGoal, options, context) => __awaiter(this, void 0, void 0, function* () {
const name = name_1.cleanImageName(p.name);
const tags = [];
const version = yield sdm_core_1.readSdmVersion(sdmGoal.repo.owner, sdmGoal.repo.name, sdmGoal.repo.providerId, sdmGoal.sha, sdmGoal.branch, context);
if (!!version) {
tags.push(version);
}
const latestTag = yield sdm_1.projectConfigurationValue("docker.tag.latest", p, false);
if ((latestTag && sdmGoal.branch === sdmGoal.push.repo.defaultBranch) || tags.length === 0) {
tags.push("latest");
}
if (!!options.registry) {
return array_1.toArray(options.registry).map(r => ({
registry: !!r.registry ? r.registry : undefined,
name,
tags,
}));
}
else {
return [{
registry: undefined,
name,
tags,
}];
}
});
function checkIsBuilderAvailable(cmd, ...args) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield sdm_1.spawnLog(cmd, args, { log: new sdm_1.LoggingProgressLog("docker-build-check") });
}
catch (e) {
throw new Error(`Configured Docker image builder '${cmd}' is not available`);
}
});
}
function pushEnabled(gi, options) {
return __awaiter(this, void 0, void 0, function* () {
let push = false;
// tslint:disable-next-line:no-boolean-literal-compare
if (options.push === true || options.push === false) {
push = options.push;
}
else if (array_1.toArray(options.registry || []).some(r => !!r.user && !!r.password) || !!options.config) {
push = true;
}
return sdm_1.projectConfigurationValue("docker.build.push", gi.project, push);
});
}
function dockerConfigPath(options, goalEvent) {
if (array_1.toArray(options.registry || []).some(r => !!r.user && !!r.password)) {
return path.join(os.homedir(), ".docker");
}
else if (!!options.config) {
return path.join(os.homedir(), `.docker-${goalEvent.goalSetId}`);
}
}
function getExternalUrls(images, options) {
const externalUrls = images.map(i => {
const reg = array_1.toArray(options.registry || []).find(r => r.registry === i.registry);
if (!!reg && !!reg.display) {
return i.tags.map(t => {
let url = `${!!reg.displayUrl ? reg.displayUrl : i.registry}/${i.name}`;
if (!!reg.displayBrowsePath) {
const replace = url.split(":").pop();
url = url.replace(`:${replace}`, reg.displayBrowsePath);
}
if (!!reg.label) {
return { label: reg.label, url };
}
else {
return { url };
}
});
}
return undefined;
});
return _.uniqBy(_.flatten(externalUrls).filter(u => !!u), "url");
}
function readRegistries(ctx) {
return __awaiter(this, void 0, void 0, function* () {
const registries = [];
const dockerRegistries = yield ctx.graphClient.query({
name: "DockerRegistryProvider",
options: automation_client_1.QueryNoCacheOptions,
});
if (!!dockerRegistries && !!dockerRegistries.DockerRegistryProvider) {
for (const dockerRegistry of dockerRegistries.DockerRegistryProvider) {
const credential = yield ctx.graphClient.query({
name: "Password",
variables: {
id: dockerRegistry.credential.id,
},
});
// Strip out the protocol
const registryUrl = new URL(dockerRegistry.url);
registries.push({
registry: registryUrl.host,
user: credential.Password[0].owner.login,
password: credential.Password[0].secret,
label: dockerRegistry.name,
display: false,
});
}
}
return registries;
});
}
//# sourceMappingURL=executeDockerBuild.js.map