UNPKG

@atomist/sdm-pack-docker

Version:

Extension Pack for an Atomist SDM to integrate Docker

310 lines 13.7 kB
"use strict"; /* * 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