From 62b21511444e272096116f96de32b71ffc038456 Mon Sep 17 00:00:00 2001 From: s003958 Date: Wed, 4 Apr 2018 17:43:47 -0700 Subject: [PATCH 1/2] Added -S option to replay-pipeline CLI command. Allows streaming a tarball with multiple script files to override at once --- .../cps/replay/ReplayPipelineCommand.java | 88 ++++++++++++++++-- .../workflow/cps/replay/ReplayActionTest.java | 21 +++++ .../plugins/workflow/cps/replay/Jenkinsfile | 4 + .../plugins/workflow/cps/replay/Script1 | 1 + .../plugins/workflow/cps/replay/scripts.tar | Bin 0 -> 5120 bytes 5 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Jenkinsfile create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Script1 create mode 100644 src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/scripts.tar diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java index 7e8e3c7ab..58415a310 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java @@ -30,16 +30,23 @@ import hudson.cli.handlers.GenericItemOptionHandler; import hudson.model.Job; import hudson.model.Run; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.io.IOUtils; -import org.jenkinsci.plugins.workflow.cps.replay.Messages; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; import org.kohsuke.args4j.OptionDef; import org.kohsuke.args4j.spi.Setter; + @Extension public class ReplayPipelineCommand extends CLICommand { @Argument(required=true, index=0, metaVar="JOB", usage="Name of the job to replay.", handler=JobHandler.class) @@ -51,6 +58,9 @@ @Option(name="-s", aliases="--script", metaVar="SCRIPT", usage="Name of script to edit, such as Script3, if not the main Jenkinsfile.") public String script; + @Option(name="-S", aliases="--scripts", usage="Pass tarball of override scripts to override build scripts on remote") + private boolean scripts; + @Override public String getShortDescription() { return Messages.ReplayCommand_shortDescription(); } @@ -70,20 +80,80 @@ if (!action.isEnabled()) { throw new AbortException("Not authorized to replay builds of this job"); } - String text = IOUtils.toString(stdin); - if (script != null) { - Map replacementLoadedScripts = new HashMap(action.getOriginalLoadedScripts()); - if (!replacementLoadedScripts.containsKey(script)) { - throw new AbortException("Unrecognized script name among " + replacementLoadedScripts.keySet()); + if (scripts) { + ArrayList> replacementScripts = parseScripts(stdin); + Map replacementLoadedScripts = new HashMap<>(action.getOriginalLoadedScripts()); + Boolean overrideJenkinsfile = false; + String jenkinsfileContent = ""; + + for (SimpleEntry pair : replacementScripts) { + String scriptName = pair.getKey(); + String scriptContent = pair.getValue(); + + if (scriptName.equals("Jenkinsfile")) { + overrideJenkinsfile = true; + jenkinsfileContent = scriptContent; + } else { + if (!replacementLoadedScripts.containsKey(scriptName)) { + throw new AbortException("Unrecognized script name " + scriptName + " among " + replacementLoadedScripts.keySet()); + } + replacementLoadedScripts.put(scriptName, scriptContent); + } } - replacementLoadedScripts.put(script, text); - action.run(action.getOriginalScript(), replacementLoadedScripts); + + if (!overrideJenkinsfile) + jenkinsfileContent = action.getOriginalScript(); + + action.run(jenkinsfileContent, replacementLoadedScripts); } else { - action.run(text, action.getOriginalLoadedScripts()); + String text = IOUtils.toString(stdin); + if (script != null) { + Map replacementLoadedScripts = new HashMap(action.getOriginalLoadedScripts()); + if (!replacementLoadedScripts.containsKey(script)) { + throw new AbortException("Unrecognized script name among " + replacementLoadedScripts.keySet()); + } + replacementLoadedScripts.put(script, text); + action.run(action.getOriginalScript(), replacementLoadedScripts); + } else { + action.run(text, action.getOriginalLoadedScripts()); + } } return 0; } + protected ArrayList> parseScripts(InputStream tarInputStream) throws Exception { + ArrayList> list = new ArrayList<>(); + + try ( TarArchiveInputStream tarIn = new TarArchiveInputStream(tarInputStream) ) { + TarArchiveEntry tarEntry = tarIn.getNextTarEntry(); + + while (tarEntry != null) { + String scriptName = tarEntry.getName(); + + int BUFFER_SIZE = 512; + int count; + byte data[] = new byte[BUFFER_SIZE]; + + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + BufferedOutputStream outStream = new BufferedOutputStream(byteStream, BUFFER_SIZE); + while ((count = tarIn.read(data, 0, BUFFER_SIZE)) != -1) { + outStream.write(data, 0, count); + } + outStream.flush(); + String scriptContent = new String(byteStream.toByteArray()); + + SimpleEntry entry = new SimpleEntry<>(scriptName, scriptContent); + list.add(entry); + + tarEntry = tarIn.getNextTarEntry(); + } + } catch ( Exception e) { + throw e; + } + + return list; + } + @SuppressWarnings("rawtypes") public static class JobHandler extends GenericItemOptionHandler { diff --git a/src/test/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionTest.java b/src/test/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionTest.java index 957a9fd81..c5de75f71 100644 --- a/src/test/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionTest.java +++ b/src/test/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionTest.java @@ -45,6 +45,7 @@ import hudson.security.GlobalMatrixAuthorizationStrategy; import hudson.security.Permission; import java.io.File; +import java.io.InputStream; import java.util.Collections; import java.util.List; import jenkins.model.Jenkins; @@ -287,6 +288,26 @@ private static boolean canRebuild(WorkflowRun b, String user) { }); } + @Test public void cliMultipleFiles() throws Exception { + story.addStep(new Statement() { + @Override public void evaluate() throws Throwable { + WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p"); + // As in loadStep, will set up a main and auxiliary script. + FilePath f = story.j.jenkins.getWorkspaceFor(p).child("f.groovy"); + f.write("'original text'", null); + p.setDefinition(new CpsFlowDefinition("node {def t = load 'f.groovy'; echo \"got ${t}\"}", true)); + WorkflowRun b1 = story.j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + story.j.assertLogContains("got original text", b1); + InputStream tarStream = this.getClass().getResourceAsStream("scripts.tar"); + assertEquals(0, new CLICommandInvoker(story.j, "replay-pipeline").withStdin(tarStream).invokeWithArgs("p", "-S").returnCode()); + story.j.waitUntilNoActivity(); + WorkflowRun b2 = p.getLastBuild(); + assertEquals(2, b2.getNumber()); + story.j.assertLogContains("received new text", b2); + } + }); + } + @Issue("JENKINS-47339") @Test public void rebuild() throws Exception { diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Jenkinsfile b/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Jenkinsfile new file mode 100644 index 000000000..dca415e51 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Jenkinsfile @@ -0,0 +1,4 @@ +node { + def t = load 'f.groovy' + echo "received ${t}" +} diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Script1 b/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Script1 new file mode 100644 index 000000000..16587c596 --- /dev/null +++ b/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/Script1 @@ -0,0 +1 @@ +'new text' diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/scripts.tar b/src/test/resources/org/jenkinsci/plugins/workflow/cps/replay/scripts.tar new file mode 100644 index 0000000000000000000000000000000000000000..7b5814ac099b0353647338dad11e11f270dd109b GIT binary patch literal 5120 zcmeHJ%}&BV5a!%x@Gv1JTnz5cc7HhZgeGb{m=F&pUK+aK253oJ5JO0O2H(La@CDtX zhyn*ps1n^dblYzyv%BB)%ePnIw!U9!L== zLqSn+MmP{EQTP>@>~dB!kezaGmKiTTOgB3fM|T z!0PnAep3lu69v*IL@K6)M-_}LK$T*)AzYw`1lqY7=zwxWr6d!)z{1a%?-LIhw3=t< zmpAoDj{_=fw-GIj3)sX-(BKvoezDAZa@;p@FQDGOGW56iRi5gXyjeZW_5Wz0;py{J zF&-Vf;9M!iL$2yy5VZOyHm~&f=lYM6NW;_vh;#>X2%w*Y5!5>MZki|^I6rKIlY$tYyQLnS0Dc0YNuwH(-Ib4=zk0U7m}m(f5EtP{$D~X)=GZg z{}q+i)W7=w8@8qYE9P_O|7)H29iTkbFVC~k|D%OA`aflh`~SQCIi=|Qf5~8ZZ?#z8 YL#`)z%^7-mEW65%0mp!2VE-6+1z`ltxc~qF literal 0 HcmV?d00001 From 0af21cf82c34a63a2b1f58899bababe6ac8b9b3c Mon Sep 17 00:00:00 2001 From: s003958 Date: Thu, 10 May 2018 11:33:47 -0700 Subject: [PATCH 2/2] Force UTF-8 encoding on tar file --- .../plugins/workflow/cps/replay/ReplayPipelineCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java index 58415a310..b165bccc2 100644 --- a/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java +++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayPipelineCommand.java @@ -140,7 +140,7 @@ protected ArrayList> parseScripts(InputStream tarInpu outStream.write(data, 0, count); } outStream.flush(); - String scriptContent = new String(byteStream.toByteArray()); + String scriptContent = byteStream.toString("UTF-8"); SimpleEntry entry = new SimpleEntry<>(scriptName, scriptContent); list.add(entry);