1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | var __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 | };
|
25 | Object.defineProperty(exports, "__esModule", { value: true });
|
26 | const automation_client_1 = require("@atomist/automation-client");
|
27 | const sdm_1 = require("@atomist/sdm");
|
28 | const sdm_core_1 = require("@atomist/sdm-core");
|
29 | const fs = require("fs-extra");
|
30 | const _ = require("lodash");
|
31 | const os = require("os");
|
32 | const path = require("path");
|
33 | const name_1 = require("./name");
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | function 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 |
|
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 |
|
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 |
|
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 | }
|
109 | exports.executeDockerBuild = executeDockerBuild;
|
110 | function 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 |
|
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 | }
|
136 | function dockerPush(images, options, gi) {
|
137 | return __awaiter(this, void 0, void 0, function* () {
|
138 | if (yield pushEnabled(gi, options)) {
|
139 |
|
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 | }
|
162 | exports.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 |
|
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 | });
|
179 | function 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 | }
|
189 | function pushEnabled(gi, options) {
|
190 | return __awaiter(this, void 0, void 0, function* () {
|
191 | let push;
|
192 |
|
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 | }
|
202 | function 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 | }
|
210 | function 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 |
|
\ | No newline at end of file |