UNPKG

10.2 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright © 2019 Atomist, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18 return new (P || (P = Promise))(function (resolve, reject) {
19 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
20 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
21 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
22 step((generator = generator.apply(thisArg, _arguments || [])).next());
23 });
24};
25Object.defineProperty(exports, "__esModule", { value: true });
26const automation_client_1 = require("@atomist/automation-client");
27const sdm_1 = require("@atomist/sdm");
28const sdm_core_1 = require("@atomist/sdm-core");
29const fs = require("fs-extra");
30const _ = require("lodash");
31const os = require("os");
32const path = require("path");
33const name_1 = require("./name");
34/**
35 * Execute a Docker build for the project
36 * @param {DockerOptions} options
37 * @returns {ExecuteGoal}
38 */
39function executeDockerBuild(options) {
40 return sdm_1.doWithProject((gi) => __awaiter(this, void 0, void 0, function* () {
41 const { goalEvent, context, project } = gi;
42 const optsToUse = sdm_1.mergeOptions(options, {}, "docker.build");
43 switch (optsToUse.builder) {
44 case "docker":
45 yield checkIsBuilderAvailable("docker", "help");
46 break;
47 case "kaniko":
48 yield checkIsBuilderAvailable("/kaniko/executor", "--help");
49 break;
50 }
51 const imageName = yield optsToUse.dockerImageNameCreator(project, goalEvent, optsToUse, context);
52 const dockerfilePath = yield (optsToUse.dockerfileFinder ? optsToUse.dockerfileFinder(project) : "Dockerfile");
53 const externalUrls = getExternalUrls(imageName.tags, optsToUse);
54 let result;
55 if (optsToUse.builder === "docker") {
56 const tags = _.flatten(imageName.tags.map(i => ["-t", i]));
57 result = yield gi.spawn("docker", ["build", "-f", dockerfilePath, ...tags, ...optsToUse.builderArgs, "."], {
58 env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(optsToUse, gi.goalEvent) }),
59 log: gi.progressLog,
60 });
61 if (result.code !== 0) {
62 return result;
63 }
64 result = yield dockerPush(imageName.tags, optsToUse, gi);
65 if (result.code !== 0) {
66 return result;
67 }
68 }
69 else if (optsToUse.builder === "kaniko") {
70 // 2. run kaniko build
71 const builderArgs = [];
72 if (yield pushEnabled(gi, optsToUse)) {
73 builderArgs.push(...imageName.tags.map(i => `-d=${i}`), "--cache=true");
74 }
75 else {
76 builderArgs.push("--no-push");
77 }
78 builderArgs.push(...(optsToUse.builderArgs.length > 0 ? optsToUse.builderArgs : ["--snapshotMode=time", "--reproducible"]));
79 // Check if base image cache dir is available
80 const cacheFilPath = _.get(gi, "configuration.sdm.cache.path", "/opt/data");
81 if (_.get(gi, "configuration.sdm.cache.enabled") === true && (yield fs.pathExists(cacheFilPath))) {
82 const baseImageCache = path.join(cacheFilPath, "base-image-cache");
83 yield fs.mkdirs(baseImageCache);
84 builderArgs.push(`--cache-dir=${baseImageCache}`, "--cache=true");
85 }
86 result = yield gi.spawn("/kaniko/executor", ["--dockerfile", dockerfilePath, "--context", `dir://${project.baseDir}`, ..._.uniq(builderArgs)], {
87 env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(optsToUse, gi.goalEvent) }),
88 log: gi.progressLog,
89 });
90 if (result.code !== 0) {
91 return result;
92 }
93 }
94 // 4. create image link
95 if (yield sdm_core_1.postLinkImageWebhook(goalEvent.repo.owner, goalEvent.repo.name, goalEvent.sha, imageName.tags[0], context.workspaceId)) {
96 return {
97 code: 0,
98 externalUrls,
99 };
100 }
101 else {
102 return { code: 1, message: "Image link failed" };
103 }
104 }), {
105 readOnly: true,
106 detachHead: false,
107 });
108}
109exports.executeDockerBuild = executeDockerBuild;
110function dockerLogin(options, optsToUse, gi) {
111 return __awaiter(this, void 0, void 0, function* () {
112 if (options.username && options.password) {
113 gi.progressLog.write("Running 'docker login'");
114 const loginArgs = ["login", "--username", options.username, "--password", options.password];
115 if (/[^A-Za-z0-9]/.test(options.url)) {
116 loginArgs.push(options.url);
117 }
118 // 2. run docker login
119 return gi.spawn("docker", loginArgs, {
120 logCommand: false,
121 log: gi.progressLog,
122 });
123 }
124 else if (optsToUse.config) {
125 gi.progressLog.write("Authenticating with provided Docker 'config.json'");
126 const dockerConfig = path.join(dockerConfigPath(optsToUse, gi.goalEvent), "config.json");
127 yield fs.ensureDir(path.dirname(dockerConfig));
128 yield fs.writeFile(dockerConfig, optsToUse.config);
129 }
130 else {
131 gi.progressLog.write("Skipping 'docker auth' because no credentials configured");
132 }
133 return automation_client_1.Success;
134 });
135}
136function dockerPush(images, options, gi) {
137 return __awaiter(this, void 0, void 0, function* () {
138 if (yield pushEnabled(gi, options)) {
139 // Login to registry(s)
140 yield Promise.all(options.registries.map((r) => __awaiter(this, void 0, void 0, function* () {
141 const loginResult = yield dockerLogin(r, options, gi);
142 if (loginResult.code !== 0) {
143 throw new Error(`Failed login for registry => ${r.url}!`);
144 }
145 })));
146 yield Promise.all(images.map((image) => __awaiter(this, void 0, void 0, function* () {
147 const pushResult = yield gi.spawn("docker", ["push", image], {
148 env: Object.assign({}, process.env, { DOCKER_CONFIG: dockerConfigPath(options, gi.goalEvent) }),
149 log: gi.progressLog,
150 });
151 if (pushResult.code !== 0) {
152 throw new Error(`Failed to push image ${image}! Error (code: ${pushResult.code}) => ${pushResult.stderr}`);
153 }
154 })));
155 }
156 else {
157 gi.progressLog.write("Skipping 'docker push'");
158 }
159 return { code: 0 };
160 });
161}
162exports.DefaultDockerImageNameCreator = (p, sdmGoal, options, context) => __awaiter(this, void 0, void 0, function* () {
163 const name = name_1.cleanImageName(p.name);
164 const version = yield sdm_core_1.readSdmVersion(sdmGoal.repo.owner, sdmGoal.repo.name, sdmGoal.repo.providerId, sdmGoal.sha, sdmGoal.branch, context);
165 // If there are configured registries, set tags for each; otherwise return just version
166 const tags = [];
167 const latestTag = yield sdm_1.projectConfigurationValue("docker.tag.latest", p, false);
168 options.registries.map(r => {
169 tags.push(`${r.url}/${name}:${version}`);
170 if (latestTag && sdmGoal.branch === sdmGoal.push.repo.defaultBranch) {
171 tags.push(`${r.url}/${name}:latest`);
172 }
173 });
174 return {
175 name,
176 tags,
177 };
178});
179function checkIsBuilderAvailable(cmd, ...args) {
180 return __awaiter(this, void 0, void 0, function* () {
181 try {
182 yield sdm_1.spawnLog(cmd, args, { log: new sdm_1.LoggingProgressLog("docker-build-check") });
183 }
184 catch (e) {
185 throw new Error(`Configured Docker image builder '${cmd}' is not available`);
186 }
187 });
188}
189function pushEnabled(gi, options) {
190 return __awaiter(this, void 0, void 0, function* () {
191 let push;
192 // tslint:disable-next-line:no-boolean-literal-compare
193 if (options.push === true || options.push === false) {
194 push = options.push;
195 }
196 else if (options.registries.some(r => !!(r.username && r.password)) || !!options.config) {
197 push = true;
198 }
199 return sdm_1.projectConfigurationValue("docker.build.push", gi.project, push);
200 });
201}
202function dockerConfigPath(options, goalEvent) {
203 if (options.registries.some(r => !!(r.username && r.password))) {
204 return path.join(os.homedir(), ".docker");
205 }
206 else if (!!options.config) {
207 return path.join(os.homedir(), `.docker-${goalEvent.goalSetId}`);
208 }
209}
210function getExternalUrls(tags, options) {
211 const externalUrls = tags.map(t => {
212 const reg = options.registries.filter(r => t.includes(r.url))[0];
213 if (reg.display !== false) {
214 let url = !!reg.displayUrl ? t.replace(reg.url, reg.displayUrl) : t;
215 if (!!reg.displayBrowsePath) {
216 const replace = url.split(":").pop();
217 url = url.replace(`:${replace}`, `${reg.displayBrowsePath}`);
218 }
219 if (!!reg.label) {
220 return { label: reg.label, url };
221 }
222 else {
223 return { url };
224 }
225 }
226 });
227 return _.uniqBy(externalUrls, "url");
228}
229//# sourceMappingURL=executeDockerBuild.js.map
\No newline at end of file