| skipped 6 lines |
7 | 7 | | import java.io.OutputStream; |
8 | 8 | | import java.nio.ByteBuffer; |
9 | 9 | | import java.nio.charset.Charset; |
10 | | - | import java.nio.charset.StandardCharsets; |
| 10 | + | import static java.nio.charset.StandardCharsets.UTF_8; |
11 | 11 | | import java.nio.file.Files; |
12 | 12 | | import java.util.ArrayList; |
13 | 13 | | import java.util.Base64; |
14 | 14 | | import java.util.Collection; |
15 | 15 | | import java.util.Date; |
16 | 16 | | import java.util.HashMap; |
| 17 | + | import java.util.HashSet; |
17 | 18 | | import java.util.Iterator; |
18 | 19 | | import java.util.List; |
19 | 20 | | import java.util.Map; |
20 | 21 | | import java.util.function.Consumer; |
| 22 | + | import java.util.regex.Matcher; |
| 23 | + | import java.util.regex.Pattern; |
21 | 24 | | import java.util.stream.Collectors; |
22 | 25 | | |
23 | 26 | | import javax.ws.rs.client.Client; |
| skipped 6 lines |
30 | 33 | | import javax.ws.rs.core.Response; |
31 | 34 | | import javax.ws.rs.core.StreamingOutput; |
32 | 35 | | |
| 36 | + | import org.apache.commons.compress.utils.IOUtils; |
33 | 37 | | import org.apache.commons.lang3.SerializationUtils; |
34 | 38 | | import org.apache.commons.lang3.StringUtils; |
35 | 39 | | import org.apache.commons.lang3.SystemUtils; |
| skipped 2 lines |
38 | 42 | | import org.slf4j.LoggerFactory; |
39 | 43 | | |
40 | 44 | | import com.google.common.base.Splitter; |
| 45 | + | import com.google.common.base.Throwables; |
41 | 46 | | import com.google.common.collect.Lists; |
42 | 47 | | |
| 48 | + | import io.onedev.commons.utils.ExceptionUtils; |
| 49 | + | import io.onedev.commons.utils.ExplicitException; |
43 | 50 | | import io.onedev.commons.utils.FileUtils; |
44 | 51 | | import io.onedev.commons.utils.PathUtils; |
45 | 52 | | import io.onedev.commons.utils.TarUtils; |
| skipped 10 lines |
56 | 63 | | |
57 | 64 | | public static final String LOG_END_MESSAGE = "===== End of OneDev K8s Helper Log ====="; |
58 | 65 | | |
| 66 | + | public static final String BUILD_VERSION = "buildVersion"; |
| 67 | + | |
| 68 | + | public static final String WORKSPACE = "workspace"; |
| 69 | + | |
| 70 | + | public static final String PLACEHOLDER_PREFIX = "<&onedev#"; |
| 71 | + | |
| 72 | + | public static final String PLACEHOLDER_SUFFIX = "#onedev&>"; |
| 73 | + | |
| 74 | + | private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(PLACEHOLDER_PREFIX + "(.*?)" + PLACEHOLDER_SUFFIX); |
| 75 | + | |
59 | 76 | | private static final Logger logger = LoggerFactory.getLogger(KubernetesHelper.class); |
60 | 77 | | |
61 | 78 | | private static File getBuildHome() { |
| skipped 22 lines |
84 | 101 | | } |
85 | 102 | | |
86 | 103 | | private static File getWorkspace() { |
87 | | - | return new File(getBuildHome(), "workspace"); |
| 104 | + | return new File(getBuildHome(), WORKSPACE); |
88 | 105 | | } |
89 | 106 | | |
90 | 107 | | private static File getCommandHome() { |
| skipped 55 lines |
146 | 163 | | } |
147 | 164 | | } |
148 | 165 | | |
149 | | - | private static void generateCommandScript(String commandPosition, List<String> setupCommands, List<String> stepCommands) { |
| 166 | + | private static void generateCommandScript(List<Integer> position, String stepNames, |
| 167 | + | List<String> setupCommands, List<String> stepCommands) { |
150 | 168 | | try { |
| 169 | + | String positionStr = stringifyPosition(position); |
151 | 170 | | File commandHome = getCommandHome(); |
152 | 171 | | if (isWindows()) { |
153 | | - | File setupScriptFile = new File(commandHome, "setup-" + commandPosition + ".bat"); |
| 172 | + | StringBuilder escapedStepNames = new StringBuilder(); |
| 173 | + | for (int i=0; i<stepNames.length(); i++) |
| 174 | + | escapedStepNames.append('^').append(stepNames.charAt(i)); |
| 175 | + | |
| 176 | + | File setupScriptFile = new File(commandHome, "setup-" + positionStr + ".bat"); |
154 | 177 | | FileUtils.writeLines(setupScriptFile, setupCommands, "\r\n"); |
155 | 178 | | |
156 | | - | File stepScriptFile = new File(commandHome, "step-" + commandPosition + ".bat"); |
| 179 | + | File stepScriptFile = new File(commandHome, "step-" + positionStr + ".bat"); |
157 | 180 | | FileUtils.writeLines(stepScriptFile, stepCommands, "\r\n"); |
158 | 181 | | |
159 | | - | File scriptFile = new File(commandHome, commandPosition + ".bat"); |
160 | | - | String markPrefix = getMarkHome().getAbsolutePath() + "\\" + commandPosition; |
| 182 | + | File scriptFile = new File(commandHome, positionStr + ".bat"); |
| 183 | + | String markPrefix = getMarkHome().getAbsolutePath() + "\\" + positionStr; |
161 | 184 | | List<String> scriptContent = Lists.newArrayList( |
162 | 185 | | "@echo off", |
163 | 186 | | ":wait", |
164 | 187 | | "if exist \"" + markPrefix + ".skip\" (", |
165 | | - | " echo Skipping step #" + commandPosition + "...", |
| 188 | + | " echo Skipping step ^\"" + escapedStepNames + "^\"...", |
166 | 189 | | " echo " + LOG_END_MESSAGE, |
167 | 190 | | " goto :eof", |
| 191 | + | ")", |
| 192 | + | "if exist \"" + markPrefix + ".error\" (", |
| 193 | + | " echo Running step ^\"" + escapedStepNames + "^\"...", |
| 194 | + | " type " + markPrefix + ".error", |
| 195 | + | " copy /y nul " + markPrefix + ".failed > nul", |
| 196 | + | " echo " + LOG_END_MESSAGE, |
| 197 | + | " exit 1", |
168 | 198 | | ")", |
169 | 199 | | "if exist \"" + markPrefix + ".start\" goto start", |
170 | 200 | | "ping 127.0.0.1 -n 2 > nul", |
| skipped 1 lines |
172 | 202 | | ":start", |
173 | 203 | | "cd " + getWorkspace().getAbsolutePath() |
174 | 204 | | + " && cmd /c " + setupScriptFile.getAbsolutePath() |
175 | | - | + " && cmd /c echo Running step #" + commandPosition + "..." |
| 205 | + | + " && cmd /c echo Running step ^\"" + escapedStepNames + "^\"..." |
176 | 206 | | + " && cmd /c " + stepScriptFile.getAbsolutePath(), |
177 | 207 | | "set exit_code=%errorlevel%", |
178 | 208 | | "if \"%exit_code%\"==\"0\" (", |
| skipped 5 lines |
184 | 214 | | "exit %exit_code%"); |
185 | 215 | | FileUtils.writeLines(scriptFile, scriptContent, "\r\n"); |
186 | 216 | | } else { |
187 | | - | File setupScriptFile = new File(commandHome, "setup-" + commandPosition + ".sh"); |
| 217 | + | String escapedStepNames = stepNames.replace("'", "'\\''"); |
| 218 | + | |
| 219 | + | File setupScriptFile = new File(commandHome, "setup-" + positionStr + ".sh"); |
188 | 220 | | FileUtils.writeLines(setupScriptFile, setupCommands, "\n"); |
189 | 221 | | |
190 | | - | File stepScriptFile = new File(commandHome, "step-" + commandPosition + ".sh"); |
| 222 | + | File stepScriptFile = new File(commandHome, "step-" + positionStr + ".sh"); |
191 | 223 | | FileUtils.writeLines(stepScriptFile, stepCommands, "\n"); |
192 | 224 | | |
193 | | - | File scriptFile = new File(commandHome, commandPosition + ".sh"); |
194 | | - | String markPrefix = getMarkHome().getAbsolutePath() + "/" + commandPosition; |
| 225 | + | File scriptFile = new File(commandHome, positionStr + ".sh"); |
| 226 | + | String markPrefix = getMarkHome().getAbsolutePath() + "/" + positionStr; |
195 | 227 | | List<String> wrapperScriptContent = Lists.newArrayList( |
196 | | - | "while [ ! -f " + markPrefix + ".start ] && [ ! -f " + markPrefix + ".skip ]", |
| 228 | + | "while [ ! -f " + markPrefix + ".start ] && [ ! -f " + markPrefix + ".skip ] && [ ! -f " + markPrefix + ".error ]", |
197 | 229 | | "do", |
198 | 230 | | " sleep 0.1", |
199 | 231 | | "done", |
200 | 232 | | "if [ -f " + markPrefix + ".skip ]", |
201 | 233 | | "then", |
202 | | - | " echo \"Skipping step #" + commandPosition + "...\"", |
| 234 | + | " echo 'Skipping step \"" + escapedStepNames + "\"...'", |
203 | 235 | | " echo " + LOG_END_MESSAGE, |
204 | 236 | | " exit 0", |
205 | 237 | | "fi", |
| 238 | + | "if [ -f " + markPrefix + ".error ]", |
| 239 | + | "then", |
| 240 | + | " echo 'Running step \"" + escapedStepNames + "\"...'", |
| 241 | + | " cat " + markPrefix + ".error", |
| 242 | + | " touch " + markPrefix + ".failed", |
| 243 | + | " echo " + LOG_END_MESSAGE, |
| 244 | + | " exit 1", |
| 245 | + | "fi", |
206 | 246 | | "cd " + getWorkspace().getAbsolutePath() |
207 | 247 | | + " && sh " + setupScriptFile.getAbsolutePath() |
208 | | - | + " && echo \"Running step #" + commandPosition + "...\"" |
| 248 | + | + " && echo 'Running step \"" + escapedStepNames + "\"...'" |
209 | 249 | | + " && sh " + stepScriptFile.getAbsolutePath(), |
210 | 250 | | "exitCode=\"$?\"", |
211 | 251 | | "if [ $exitCode -eq 0 ]", |
| skipped 57 lines |
269 | 309 | | }; |
270 | 310 | | } |
271 | 311 | | |
272 | | - | public static String encodeCommandArg(Collection<String> decoded) { |
| 312 | + | public static String encodeAsCommandArg(Collection<String> list) { |
273 | 313 | | Collection<String> base64 = new ArrayList<>(); |
274 | | - | for (String each: decoded) |
275 | | - | base64.add(Base64.getEncoder().encodeToString(each.getBytes(StandardCharsets.UTF_8))); |
276 | | - | String encoded = StringUtils.join(base64, "-"); |
277 | | - | if (encoded.length() == 0) |
278 | | - | encoded = "-"; |
279 | | - | return encoded; |
| 314 | + | for (String each: list) |
| 315 | + | base64.add(Base64.getEncoder().encodeToString(each.getBytes(UTF_8))); |
| 316 | + | String commandArg = StringUtils.join(base64, "-"); |
| 317 | + | if (commandArg.length() == 0) |
| 318 | + | commandArg = "-"; |
| 319 | + | return commandArg; |
280 | 320 | | } |
281 | 321 | | |
282 | | - | public static Collection<String> decodeCommandArg(String encoded) { |
283 | | - | Collection<String> decoded = new ArrayList<>(); |
284 | | - | for (String each: Splitter.on('-').trimResults().omitEmptyStrings().split(encoded)) |
285 | | - | decoded.add(new String(Base64.getDecoder().decode(each), StandardCharsets.UTF_8)); |
| 322 | + | public static Collection<String> decodeCommandArgAsCollection(String commandArg) { |
| 323 | + | Collection<String> decoded = new HashSet<>(); |
| 324 | + | for (String each: Splitter.on('-').trimResults().omitEmptyStrings().split(commandArg)) |
| 325 | + | decoded.add(new String(Base64.getDecoder().decode(each), UTF_8)); |
286 | 326 | | return decoded; |
287 | 327 | | } |
288 | 328 | | |
| skipped 36 lines |
325 | 365 | | tempFile.delete(); |
326 | 366 | | } |
327 | 367 | | FileUtils.createDir(getWorkspace()); |
328 | | - | if (isWindows()) |
329 | | - | generateCommandScript("1", Lists.newArrayList(), Lists.newArrayList("@echo off", "echo hello from container")); |
330 | | - | else |
331 | | - | generateCommandScript("1", Lists.newArrayList(), Lists.newArrayList("echo hello from container")); |
| 368 | + | if (isWindows()) { |
| 369 | + | generateCommandScript(Lists.newArrayList(0), "test", Lists.newArrayList(), |
| 370 | + | Lists.newArrayList("@echo off", "echo hello from container")); |
| 371 | + | } else { |
| 372 | + | generateCommandScript(Lists.newArrayList(0), "test", Lists.newArrayList(), |
| 373 | + | Lists.newArrayList("echo hello from container")); |
| 374 | + | } |
332 | 375 | | } else { |
333 | 376 | | WebTarget target = client.target(serverUrl).path("rest/k8s/job-context"); |
334 | 377 | | Invocation.Builder builder = target.request(); |
| skipped 50 lines |
385 | 428 | | } else { |
386 | 429 | | FileUtils.createDir(workspace); |
387 | 430 | | } |
388 | | - | boolean retrieveSource = (boolean) jobContext.get("retrieveSource"); |
389 | | - | if (retrieveSource) { |
390 | | - | String commitHash = (String) jobContext.get("commitHash"); |
391 | | - | CloneInfo cloneInfo = (CloneInfo) jobContext.get("cloneInfo"); |
392 | | - | |
393 | | - | logger.info("Retrieving source code from {}...", cloneInfo.getCloneUrl()); |
394 | | - | |
395 | | - | LineConsumer infoLogger = newInfoLogger(); |
396 | | - | LineConsumer errorLogger = newErrorLogger(); |
397 | | - | |
398 | | - | File userHome; |
399 | | - | if (SystemUtils.IS_OS_WINDOWS) |
400 | | - | userHome = new File(System.getProperty("user.home")); |
401 | | - | else |
402 | | - | userHome = new File("/root"); |
403 | | - | |
404 | | - | Commandline git = new Commandline("git").workingDir(workspace); |
405 | | - | |
406 | | - | // Populate auth data here in order to clone source in init container |
407 | | - | cloneInfo.writeAuthData(userHome, git, infoLogger, errorLogger); |
408 | | - | |
409 | | - | File mountedUserHome = new File(userHome, "onedev"); |
410 | | - | Commandline gitOfMountedUserHome = new Commandline("git"); |
411 | | - | gitOfMountedUserHome.environments().put("HOME", mountedUserHome.getAbsolutePath()); |
412 | | - | |
413 | | - | // Populate auth data here in case user want to do additional pull/push in main container |
414 | | - | cloneInfo.writeAuthData(mountedUserHome, gitOfMountedUserHome, infoLogger, errorLogger); |
415 | | - | |
416 | | - | File trustCertsHome = getTrustCertsHome(); |
417 | | - | if (trustCertsHome.exists()) { |
418 | | - | List<String> trustCertContent = new ArrayList<>(); |
419 | | - | for (File file: trustCertsHome.listFiles()) { |
420 | | - | if (file.isFile()) |
421 | | - | trustCertContent.addAll(FileUtils.readLines(file, Charset.defaultCharset())); |
422 | | - | } |
423 | | - | installGitCert(new File(getBuildHome(), "trust-cert.pem"), trustCertContent, git, |
424 | | - | infoLogger, errorLogger); |
425 | | - | } |
426 | | - | |
427 | | - | Integer cloneDepth = (Integer) jobContext.get("cloneDepth"); |
428 | | - | clone(workspace, cloneInfo.getCloneUrl(), commitHash, cloneDepth, git, infoLogger, errorLogger); |
429 | | - | |
430 | | - | git.clearArgs(); |
431 | | - | git.addArgs("remote", "add", "origin", cloneInfo.getCloneUrl()); |
432 | | - | git.execute(infoLogger, errorLogger).checkReturnCode(); |
433 | | - | |
434 | | - | if (new File(workspace, ".gitmodules").exists()) { |
435 | | - | logger.info("Retrieving submodules..."); |
436 | | - | |
437 | | - | git.clearArgs(); |
438 | | - | git.addArgs("submodule", "update", "--init", "--recursive", "--force", "--quiet"); |
439 | | - | if (cloneDepth != null) |
440 | | - | git.addArgs("--depth=" + cloneDepth); |
441 | | - | git.execute(infoLogger, new LineConsumer() { |
442 | | - | |
443 | | - | @Override |
444 | | - | public void consume(String line) { |
445 | | - | if (line.contains("Submodule") && line.contains("registered for path") |
446 | | - | || line.startsWith("From ") || line.startsWith(" * branch") |
447 | | - | || line.startsWith(" +") && line.contains("->")) { |
448 | | - | infoLogger.consume(line); |
449 | | - | } else { |
450 | | - | errorLogger.consume(line); |
451 | | - | } |
452 | | - | } |
453 | | - | |
454 | | - | }).checkReturnCode(); |
455 | | - | } |
456 | | - | |
457 | | - | } |
458 | 431 | | |
459 | 432 | | logger.info("Generating command scripts..."); |
460 | 433 | | |
461 | 434 | | List<Action> actions = (List<Action>) jobContext.get("actions"); |
462 | | - | new CompositeExecutable(actions).traverse(new LeafVisitor<Void>() { |
| 435 | + | CompositeExecutable entryExecutable = new CompositeExecutable(actions); |
| 436 | + | entryExecutable.traverse(new LeafVisitor<Void>() { |
463 | 437 | | |
464 | 438 | | @Override |
465 | 439 | | public Void visit(LeafExecutable executable, List<Integer> position) { |
| 440 | + | String stepNames = entryExecutable.getNamesAsString(position); |
| 441 | + | |
466 | 442 | | List<String> setupCommands = new ArrayList<>(); |
467 | 443 | | if (isWindows()) { |
468 | 444 | | setupCommands.add("@echo off"); |
| skipped 8 lines |
477 | 453 | | File linkTarget = entry.getKey().getDirectory(cacheHome); |
478 | 454 | | // create possible missing parent directories |
479 | 455 | | if (isWindows()) { |
| 456 | + | setupCommands.add(String.format("echo Setting up cache \"%s\"...", link)); |
480 | 457 | | setupCommands.add(String.format("if not exist \"%s\" mkdir \"%s\"", link, link)); |
481 | 458 | | setupCommands.add(String.format("rmdir /q /s \"%s\"", link)); |
482 | 459 | | setupCommands.add(String.format("mklink /D \"%s\" \"%s\"", link, linkTarget.getAbsolutePath())); |
483 | 460 | | } else { |
| 461 | + | setupCommands.add(String.format("echo Setting up cache \"%s\"...", link)); |
484 | 462 | | setupCommands.add(String.format("mkdir -p \"%s\"", link)); |
485 | 463 | | setupCommands.add(String.format("rm -rf \"%s\"", link)); |
486 | 464 | | setupCommands.add(String.format("ln -s \"%s\" \"%s\"", linkTarget.getAbsolutePath(), link)); |
| skipped 8 lines |
495 | 473 | | if (executable instanceof CommandExecutable) { |
496 | 474 | | commands = ((CommandExecutable) executable).getCommands(); |
497 | 475 | | } else { |
498 | | - | ServerExecutable serverExecutable = (ServerExecutable) executable; |
499 | | - | |
| 476 | + | String command; |
500 | 477 | | String classPath; |
501 | 478 | | if (SystemUtils.IS_OS_LINUX) |
502 | 479 | | classPath = "/k8s-helper/*"; |
503 | 480 | | else |
504 | 481 | | classPath = "C:\\k8s-helper\\*"; |
| 482 | + | if (executable instanceof CheckoutExecutable) { |
| 483 | + | CheckoutExecutable checkoutExecutable = (CheckoutExecutable) executable; |
| 484 | + | checkoutExecutable.getCloneInfo(); |
| 485 | + | command = String.format("java -classpath \"%s\" io.onedev.k8shelper.CheckoutCode %s %d %s", |
| 486 | + | classPath, positionStr, checkoutExecutable.getCloneDepth(), |
| 487 | + | checkoutExecutable.getCloneInfo().toString()); |
| 488 | + | } else { |
| 489 | + | ServerExecutable serverExecutable = (ServerExecutable) executable; |
505 | 490 | | |
506 | | - | String includeFiles = encodeCommandArg(serverExecutable.getIncludeFiles()); |
507 | | - | String excludeFiles = encodeCommandArg(serverExecutable.getExcludeFiles()); |
508 | | - | String command = String.format("java -classpath \"%s\" io.onedev.k8shelper.RunServerStep %s %s %s", |
509 | | - | classPath, positionStr, includeFiles, excludeFiles); |
| 491 | + | String includeFiles = encodeAsCommandArg(serverExecutable.getIncludeFiles()); |
| 492 | + | String excludeFiles = encodeAsCommandArg(serverExecutable.getExcludeFiles()); |
| 493 | + | String placeholders = encodeAsCommandArg(serverExecutable.getPlaceholders()); |
| 494 | + | command = String.format("java -classpath \"%s\" io.onedev.k8shelper.RunServerStep %s %s %s %s", |
| 495 | + | classPath, positionStr, includeFiles, excludeFiles, placeholders); |
| 496 | + | } |
510 | 497 | | if (SystemUtils.IS_OS_LINUX) |
511 | 498 | | commands = Lists.newArrayList(command); |
512 | 499 | | else |
513 | 500 | | commands = Lists.newArrayList("@echo off", command); |
514 | 501 | | } |
515 | 502 | | |
516 | | - | generateCommandScript(positionStr, setupCommands, commands); |
| 503 | + | generateCommandScript(position, stepNames, setupCommands, commands); |
517 | 504 | | |
518 | 505 | | return null; |
519 | 506 | | } |
| skipped 22 lines |
542 | 529 | | } |
543 | 530 | | |
544 | 531 | | public static String stringifyPosition(List<Integer> position) { |
545 | | - | return StringUtils.join(position, "."); |
| 532 | + | return StringUtils.join(position, "-"); |
546 | 533 | | } |
547 | 534 | | |
548 | 535 | | public static List<Integer> parsePosition(String position) { |
549 | | - | return Splitter.on('.').splitToList(position) |
| 536 | + | return Splitter.on('-').splitToList(position) |
550 | 537 | | .stream() |
551 | 538 | | .map(it->Integer.parseInt(it)) |
552 | 539 | | .collect(Collectors.toList()); |
| skipped 13 lines |
566 | 553 | | } |
567 | 554 | | } |
568 | 555 | | |
569 | | - | public static void clone(File workspace, String cloneUrl, String commitHash, |
570 | | - | Integer cloneDepth, Commandline git, LineConsumer infoLogger, LineConsumer errorLogger) { |
| 556 | + | public static void cloneRepository(File workspace, String cloneUrl, String commitHash, |
| 557 | + | int cloneDepth, Commandline git, LineConsumer infoLogger, LineConsumer errorLogger) { |
571 | 558 | | git.clearArgs(); |
572 | 559 | | if (!new File(workspace, ".git").exists()) { |
573 | 560 | | git.addArgs("init", "."); |
| skipped 10 lines |
584 | 571 | | |
585 | 572 | | git.clearArgs(); |
586 | 573 | | git.addArgs("fetch", cloneUrl, "--force", "--quiet"); |
587 | | - | if (cloneDepth != null) |
| 574 | + | if (cloneDepth != 0) |
588 | 575 | | git.addArgs("--depth=" + cloneDepth); |
589 | 576 | | git.addArgs(commitHash); |
590 | 577 | | git.execute(infoLogger, errorLogger).checkReturnCode(); |
| skipped 20 lines |
611 | 598 | | } |
612 | 599 | | } |
613 | 600 | | |
| 601 | + | private static Map<String, Object> readJobContext() { |
| 602 | + | byte[] jobContextBytes; |
| 603 | + | try { |
| 604 | + | jobContextBytes = FileUtils.readFileToByteArray(getJobContextFile()); |
| 605 | + | } catch (IOException e) { |
| 606 | + | throw new RuntimeException(e); |
| 607 | + | } |
| 608 | + | return SerializationUtils.deserialize(jobContextBytes); |
| 609 | + | } |
| 610 | + | |
614 | 611 | | @SuppressWarnings("unchecked") |
615 | 612 | | public static void sidecar(String serverUrl, String jobToken, boolean test) { |
616 | 613 | | LeafHandler commandHandler = new LeafHandler() { |
| skipped 2 lines |
619 | 616 | | public boolean execute(LeafExecutable executable, List<Integer> position) { |
620 | 617 | | String positionStr = stringifyPosition(position); |
621 | 618 | | |
622 | | - | File file = new File(getMarkHome(), positionStr + ".start"); |
| 619 | + | File file; |
| 620 | + | |
| 621 | + | File stepScriptFile; |
| 622 | + | if (SystemUtils.IS_OS_WINDOWS) |
| 623 | + | stepScriptFile = new File(getCommandHome(), "step-" + positionStr + ".bat"); |
| 624 | + | else |
| 625 | + | stepScriptFile = new File(getCommandHome(), "step-" + positionStr + ".sh"); |
| 626 | + | |
623 | 627 | | try { |
| 628 | + | String stepScript = FileUtils.readFileToString(stepScriptFile, UTF_8); |
| 629 | + | |
| 630 | + | stepScript = replacePlaceholders(stepScript, getBuildHome()); |
| 631 | + | |
| 632 | + | FileUtils.writeFile(stepScriptFile, stepScript, UTF_8.name()); |
| 633 | + | |
| 634 | + | file = new File(getMarkHome(), positionStr + ".start"); |
624 | 635 | | if (!file.createNewFile()) |
625 | 636 | | throw new RuntimeException("Failed to create file: " + file.getAbsolutePath()); |
626 | | - | } catch (IOException e) { |
627 | | - | throw new RuntimeException(e); |
| 637 | + | } catch (Exception e) { |
| 638 | + | file = new File(getMarkHome(), positionStr + ".error"); |
| 639 | + | |
| 640 | + | ExplicitException explicitException = ExceptionUtils.find(e, ExplicitException.class); |
| 641 | + | String errorMessage; |
| 642 | + | if (explicitException != null) |
| 643 | + | errorMessage = explicitException.getMessage().trim(); |
| 644 | + | else |
| 645 | + | errorMessage = Throwables.getStackTraceAsString(e).trim(); |
| 646 | + | errorMessage += "\n"; |
| 647 | + | if (SystemUtils.IS_OS_WINDOWS) |
| 648 | + | errorMessage = errorMessage.replace("\n", "\r\n"); |
| 649 | + | |
| 650 | + | FileUtils.writeFile(file, errorMessage, UTF_8.name()); |
628 | 651 | | } |
629 | 652 | | |
630 | 653 | | File successfulFile = new File(getMarkHome(), positionStr + ".successful"); |
| skipped 26 lines |
657 | 680 | | "this does not matter", Lists.newArrayList("this does not matter")); |
658 | 681 | | executable.execute(commandHandler, Lists.newArrayList(1)); |
659 | 682 | | } else { |
660 | | - | byte[] jobContextBytes; |
661 | | - | try { |
662 | | - | jobContextBytes = FileUtils.readFileToByteArray(getJobContextFile()); |
663 | | - | } catch (IOException e) { |
664 | | - | throw new RuntimeException(e); |
665 | | - | } |
666 | | - | Map<String, Object> jobContext = SerializationUtils.deserialize(jobContextBytes); |
| 683 | + | Map<String, Object> jobContext = readJobContext(); |
667 | 684 | | |
668 | 685 | | List<Action> actions = (List<Action>) jobContext.get("actions"); |
669 | 686 | | |
| skipped 22 lines |
692 | 709 | | } |
693 | 710 | | } |
694 | 711 | | |
| 712 | + | public static void writeInt(OutputStream os, int value) { |
| 713 | + | try { |
| 714 | + | os.write(ByteBuffer.allocate(4).putInt(value).array()); |
| 715 | + | } catch (IOException e) { |
| 716 | + | throw new RuntimeException(e); |
| 717 | + | } |
| 718 | + | } |
| 719 | + | |
| 720 | + | public static void writeString(OutputStream os, String value) { |
| 721 | + | try { |
| 722 | + | byte[] valueBytes = value.getBytes(UTF_8); |
| 723 | + | os.write(ByteBuffer.allocate(4).putInt(valueBytes.length).array()); |
| 724 | + | os.write(valueBytes); |
| 725 | + | } catch (IOException e) { |
| 726 | + | throw new RuntimeException(e); |
| 727 | + | } |
| 728 | + | } |
| 729 | + | |
| 730 | + | public static String readString(InputStream is) { |
| 731 | + | try { |
| 732 | + | byte[] lengthBytes = new byte[4]; |
| 733 | + | if (IOUtils.readFully(is, lengthBytes) != lengthBytes.length) |
| 734 | + | throw new ExplicitException("Invalid input stream"); |
| 735 | + | int length = ByteBuffer.wrap(lengthBytes).getInt(); |
| 736 | + | byte[] stringBytes = new byte[length]; |
| 737 | + | if (IOUtils.readFully(is, stringBytes) != stringBytes.length) |
| 738 | + | throw new ExplicitException("Invalid input stream"); |
| 739 | + | return new String(stringBytes, UTF_8); |
| 740 | + | } catch (IOException e) { |
| 741 | + | throw new RuntimeException(e); |
| 742 | + | } |
| 743 | + | } |
| 744 | + | |
| 745 | + | public static int readInt(InputStream is) { |
| 746 | + | try { |
| 747 | + | byte[] intBytes = new byte[4]; |
| 748 | + | if (IOUtils.readFully(is, intBytes) != intBytes.length) |
| 749 | + | throw new ExplicitException("Invalid input stream"); |
| 750 | + | return ByteBuffer.wrap(intBytes).getInt(); |
| 751 | + | } catch (IOException e) { |
| 752 | + | throw new RuntimeException(e); |
| 753 | + | } |
| 754 | + | } |
| 755 | + | |
| 756 | + | public static void checkoutCode(String serverUrl, String jobToken, String positionStr, |
| 757 | + | int cloneDepth, CloneInfo cloneInfo) throws IOException { |
| 758 | + | Map<String, Object> jobContext = readJobContext(); |
| 759 | + | String commitHash = (String) jobContext.get("commitHash"); |
| 760 | + | |
| 761 | + | logger.info("Checking out code from {}...", cloneInfo.getCloneUrl()); |
| 762 | + | |
| 763 | + | LineConsumer infoLogger = newInfoLogger(); |
| 764 | + | LineConsumer errorLogger = newErrorLogger(); |
| 765 | + | |
| 766 | + | File userHome; |
| 767 | + | if (SystemUtils.IS_OS_WINDOWS) |
| 768 | + | userHome = new File(System.getProperty("user.home")); |
| 769 | + | else |
| 770 | + | userHome = new File("/root"); |
| 771 | + | |
| 772 | + | File workspace = getWorkspace(); |
| 773 | + | Commandline git = new Commandline("git").workingDir(workspace); |
| 774 | + | |
| 775 | + | cloneInfo.writeAuthData(userHome, git, infoLogger, errorLogger); |
| 776 | + | |
| 777 | + | File mountedUserHome = new File(userHome, "onedev"); |
| 778 | + | Commandline gitOfMountedUserHome = new Commandline("git"); |
| 779 | + | gitOfMountedUserHome.environments().put("HOME", mountedUserHome.getAbsolutePath()); |
| 780 | + | |
| 781 | + | // Populate auth data here in case user want to do additional pull/push in other step container |
| 782 | + | cloneInfo.writeAuthData(mountedUserHome, gitOfMountedUserHome, infoLogger, errorLogger); |
| 783 | + | |
| 784 | + | File trustCertsHome = getTrustCertsHome(); |
| 785 | + | if (trustCertsHome.exists()) { |
| 786 | + | List<String> trustCertContent = new ArrayList<>(); |
| 787 | + | for (File file: trustCertsHome.listFiles()) { |
| 788 | + | if (file.isFile()) |
| 789 | + | trustCertContent.addAll(FileUtils.readLines(file, Charset.defaultCharset())); |
| 790 | + | } |
| 791 | + | installGitCert(new File(getBuildHome(), "trust-cert.pem"), trustCertContent, git, |
| 792 | + | infoLogger, errorLogger); |
| 793 | + | } |
| 794 | + | |
| 795 | + | cloneRepository(workspace, cloneInfo.getCloneUrl(), commitHash, cloneDepth, git, infoLogger, errorLogger); |
| 796 | + | |
| 797 | + | git.clearArgs(); |
| 798 | + | git.addArgs("remote", "add", "origin", cloneInfo.getCloneUrl()); |
| 799 | + | git.execute(infoLogger, errorLogger).checkReturnCode(); |
| 800 | + | |
| 801 | + | if (new File(workspace, ".gitmodules").exists()) { |
| 802 | + | logger.info("Retrieving submodules..."); |
| 803 | + | |
| 804 | + | git.clearArgs(); |
| 805 | + | git.addArgs("submodule", "update", "--init", "--recursive", "--force", "--quiet"); |
| 806 | + | if (cloneDepth != 0) |
| 807 | + | git.addArgs("--depth=" + cloneDepth); |
| 808 | + | git.execute(infoLogger, new LineConsumer() { |
| 809 | + | |
| 810 | + | @Override |
| 811 | + | public void consume(String line) { |
| 812 | + | if (line.contains("Submodule") && line.contains("registered for path") |
| 813 | + | || line.startsWith("From ") || line.startsWith(" * branch") |
| 814 | + | || line.startsWith(" +") && line.contains("->")) { |
| 815 | + | infoLogger.consume(line); |
| 816 | + | } else { |
| 817 | + | errorLogger.consume(line); |
| 818 | + | } |
| 819 | + | } |
| 820 | + | |
| 821 | + | }).checkReturnCode(); |
| 822 | + | } |
| 823 | + | |
| 824 | + | } |
| 825 | + | |
695 | 826 | | public static void runServerStep(String serverUrl, String jobToken, String positionStr, |
696 | | - | String encodedIncludeFiles, String encodedExcludeFiles) { |
| 827 | + | String encodedIncludeFiles, String encodedExcludeFiles, String encodedPlaceholders) { |
697 | 828 | | installJVMCert(); |
698 | 829 | | |
699 | 830 | | Client client = ClientBuilder.newClient(); |
| skipped 4 lines |
704 | 835 | | builder.header(HttpHeaders.AUTHORIZATION, BEARER + " " + jobToken); |
705 | 836 | | |
706 | 837 | | List<Integer> position = parsePosition(positionStr); |
707 | | - | Collection<String> includeFiles = decodeCommandArg(encodedIncludeFiles); |
708 | | - | Collection<String> excludeFiles = decodeCommandArg(encodedExcludeFiles); |
| 838 | + | Collection<String> includeFiles = decodeCommandArgAsCollection(encodedIncludeFiles); |
| 839 | + | Collection<String> excludeFiles = decodeCommandArgAsCollection(encodedExcludeFiles); |
| 840 | + | Collection<String> placeholders = decodeCommandArgAsCollection(encodedPlaceholders); |
| 841 | + | |
| 842 | + | Map<String, String> placeholderValues = readPlaceholderValues(getBuildHome(), placeholders); |
709 | 843 | | |
710 | 844 | | StreamingOutput os = new StreamingOutput() { |
711 | 845 | | |
712 | 846 | | @Override |
713 | 847 | | public void write(OutputStream os) throws IOException { |
714 | | - | os.write(ByteBuffer.allocate(4).putInt(position.size()).array()); |
715 | | - | for (int each: position) { |
716 | | - | os.write(ByteBuffer.allocate(4).putInt(each).array()); |
| 848 | + | writeInt(os, position.size()); |
| 849 | + | for (int each: position) |
| 850 | + | writeInt(os, each); |
| 851 | + | |
| 852 | + | writeInt(os, placeholderValues.size()); |
| 853 | + | for (Map.Entry<String, String> entry: placeholderValues.entrySet()) { |
| 854 | + | writeString(os, entry.getKey()); |
| 855 | + | writeString(os, entry.getValue()); |
717 | 856 | | } |
718 | | - | TarUtils.tar(getWorkspace(), includeFiles, excludeFiles, os); |
| 857 | + | |
| 858 | + | TarUtils.tar( |
| 859 | + | getWorkspace(), |
| 860 | + | replacePlaceholders(includeFiles, placeholderValues), |
| 861 | + | replacePlaceholders(excludeFiles, placeholderValues), |
| 862 | + | os); |
719 | 863 | | } |
720 | 864 | | |
721 | 865 | | }; |
722 | 866 | | |
723 | 867 | | try (Response response = builder.post(Entity.entity(os, MediaType.APPLICATION_OCTET_STREAM))) { |
724 | 868 | | checkStatus(response); |
725 | | - | byte[] logBytes = response.readEntity(byte[].class); |
726 | | - | List<String> logMessages = SerializationUtils.deserialize(logBytes); |
727 | | - | for (String logMessage: logMessages) |
| 869 | + | ServerExecutionResult result = SerializationUtils.deserialize(response.readEntity(byte[].class)); |
| 870 | + | for (String logMessage: result.getLogMessages()) |
728 | 871 | | logger.info(logMessage); |
| 872 | + | if (result.getOutputFiles() != null) { |
| 873 | + | for (Map.Entry<String, byte[]> entry: result.getOutputFiles().entrySet()) { |
| 874 | + | try { |
| 875 | + | FileUtils.writeByteArrayToFile( |
| 876 | + | new File(getBuildHome(), entry.getKey()), |
| 877 | + | entry.getValue()); |
| 878 | + | } catch (IOException e) { |
| 879 | + | throw new RuntimeException(e); |
| 880 | + | } |
| 881 | + | } |
| 882 | + | } |
729 | 883 | | } |
730 | 884 | | } finally { |
731 | 885 | | client.close(); |
732 | 886 | | } |
| 887 | + | } |
| 888 | + | |
| 889 | + | public static Collection<String> parsePlaceholders(String string) { |
| 890 | + | Collection<String> placeholderFiles = new HashSet<>(); |
| 891 | + | Matcher matcher = PLACEHOLDER_PATTERN.matcher(string); |
| 892 | + | while (matcher.find()) |
| 893 | + | placeholderFiles.add(matcher.group(1)); |
| 894 | + | return placeholderFiles; |
| 895 | + | } |
| 896 | + | |
| 897 | + | public static Map<String, String> readPlaceholderValues(File dir, Collection<String> placeholders) { |
| 898 | + | Map<String, String> placeholderValues = new HashMap<>(); |
| 899 | + | for (String placeholder: placeholders) { |
| 900 | + | File file = new File(dir, placeholder); |
| 901 | + | if (file.exists()) { |
| 902 | + | try { |
| 903 | + | placeholderValues.put(placeholder, FileUtils.readFileToString(file, UTF_8).trim()); |
| 904 | + | } catch (IOException e) { |
| 905 | + | throw new RuntimeException(e); |
| 906 | + | } |
| 907 | + | } |
| 908 | + | } |
| 909 | + | return placeholderValues; |
| 910 | + | } |
| 911 | + | |
| 912 | + | public static String replacePlaceholders(String string, Map<String, String> placeholderValues) { |
| 913 | + | Matcher matcher = PLACEHOLDER_PATTERN.matcher(string); |
| 914 | + | StringBuffer buffer = new StringBuffer(); |
| 915 | + | while (matcher.find()) { |
| 916 | + | String placeholder = matcher.group(1); |
| 917 | + | String placeholderValue = placeholderValues.get(placeholder); |
| 918 | + | if (placeholderValue != null) { |
| 919 | + | matcher.appendReplacement(buffer, placeholderValue); |
| 920 | + | } else if (placeholder.startsWith(WORKSPACE + "/")) { |
| 921 | + | throw new ExplicitException("Error replacing placeholder: unable to find file '" |
| 922 | + | + placeholder.substring(WORKSPACE.length()+1) + "' in workspace"); |
| 923 | + | } else if (placeholder.equals(BUILD_VERSION)){ |
| 924 | + | throw new ExplicitException("Error replacing placeholder: build version not set yet"); |
| 925 | + | } |
| 926 | + | } |
| 927 | + | matcher.appendTail(buffer); |
| 928 | + | return buffer.toString(); |
| 929 | + | } |
| 930 | + | |
| 931 | + | public static String replacePlaceholders(String string, File buildHome) { |
| 932 | + | Collection<String> placeholders = parsePlaceholders(string); |
| 933 | + | Map<String, String> placeholderValues = readPlaceholderValues(buildHome, placeholders); |
| 934 | + | return replacePlaceholders(string, placeholderValues); |
| 935 | + | } |
| 936 | + | |
| 937 | + | public static Collection<String> replacePlaceholders(Collection<String> collection, |
| 938 | + | Map<String, String> placeholderValues) { |
| 939 | + | Collection<String> replacedCollection = new ArrayList<>(); |
| 940 | + | for (String each: collection) |
| 941 | + | replacedCollection.add(replacePlaceholders(each, placeholderValues)); |
| 942 | + | return replacedCollection; |
| 943 | + | } |
| 944 | + | |
| 945 | + | public static Collection<String> replacePlaceholders(Collection<String> collection, File buildHome) { |
| 946 | + | Collection<String> replacedCollection = new ArrayList<>(); |
| 947 | + | for (String each: collection) |
| 948 | + | replacedCollection.add(replacePlaceholders(each, buildHome)); |
| 949 | + | return replacedCollection; |
733 | 950 | | } |
734 | 951 | | |
735 | 952 | | } |
| skipped 1 lines |