diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index aeb8e5c4f..488d7935c 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -254,6 +254,8 @@ public class SlaveTemplate implements Describable { private Boolean enclaveEnabled; + private boolean collectInitScriptLogs; + private transient /* almost final */ Set labelSet; private transient /* almost final */ Set securityGroupSet; @@ -343,7 +345,8 @@ public SlaveTemplate( Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported, - Boolean enclaveEnabled) { + Boolean enclaveEnabled, + boolean collectInitScriptLogs) { if (StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)) { LOGGER.log( @@ -434,6 +437,7 @@ public SlaveTemplate( metadataHopsLimit != null ? metadataHopsLimit : EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT; this.enclaveEnabled = enclaveEnabled != null ? enclaveEnabled : EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED; this.associateIPStrategy = associateIPStrategy != null ? associateIPStrategy : AssociateIPStrategy.DEFAULT; + this.collectInitScriptLogs = collectInitScriptLogs; readResolve(); // initialize } @@ -530,7 +534,8 @@ public SlaveTemplate( metadataTokensRequired, metadataHopsLimit, metadataSupported, - enclaveEnabled); + enclaveEnabled, + false); } @Deprecated @@ -609,7 +614,7 @@ public SlaveTemplate( deleteRootOnTermination, useEphemeralDevices, launchTimeoutStr, - associatePublicIp, + AssociateIPStrategy.backwardsCompatible(associatePublicIp), customDeviceMapping, connectBySSHProcess, monitoring, @@ -624,7 +629,8 @@ public SlaveTemplate( metadataTokensRequired, metadataHopsLimit, metadataSupported, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); } @Deprecated @@ -703,7 +709,7 @@ public SlaveTemplate( deleteRootOnTermination, useEphemeralDevices, launchTimeoutStr, - associatePublicIp, + AssociateIPStrategy.backwardsCompatible(associatePublicIp), customDeviceMapping, connectBySSHProcess, monitoring, @@ -718,7 +724,8 @@ public SlaveTemplate( metadataTokensRequired, metadataHopsLimit, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); } @Deprecated @@ -792,7 +799,7 @@ public SlaveTemplate( deleteRootOnTermination, useEphemeralDevices, launchTimeoutStr, - associatePublicIp, + AssociateIPStrategy.backwardsCompatible(associatePublicIp), customDeviceMapping, connectBySSHProcess, monitoring, @@ -807,7 +814,8 @@ public SlaveTemplate( EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); } @Deprecated @@ -2074,6 +2082,15 @@ public Boolean getEnclaveEnabled() { return enclaveEnabled; } + public boolean getCollectInitScriptLogs() { + return collectInitScriptLogs; + } + + @DataBoundSetter + public void setCollectInitScriptLogs(boolean collectInitScriptLogs) { + this.collectInitScriptLogs = collectInitScriptLogs; + } + public DescribableList, NodePropertyDescriptor> getNodeProperties() { return Objects.requireNonNull(nodeProperties); } diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java index 74598a74f..d6a475202 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2SSHLauncher.java @@ -155,6 +155,81 @@ public void onClosed(Channel channel, IOException cause) { computer.setChannel(invertedOut, invertedIn, logger, channelListener); } + protected boolean executeRemote(ClientSession session, String command, OutputStream logger) { + return executeRemote(session, command, logger, false, null); + } + + // Add overloaded method with collectOutput parameter + protected boolean executeRemote( + ClientSession session, String command, OutputStream logger, boolean collectOutput, TaskListener listener) { + try { + if (collectOutput && listener != null) { + // Execute with output capture + ChannelExec channelExec = session.createExecChannel(command); + java.io.ByteArrayOutputStream stdout = new java.io.ByteArrayOutputStream(); + java.io.ByteArrayOutputStream stderr = new java.io.ByteArrayOutputStream(); + + // Send to both the original logger and our capture streams + java.io.OutputStream combinedOut = new java.io.OutputStream() { + @Override + public void write(int b) throws IOException { + logger.write(b); + stdout.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + logger.write(b, off, len); + stdout.write(b, off, len); + } + }; + + java.io.OutputStream combinedErr = new java.io.OutputStream() { + @Override + public void write(int b) throws IOException { + logger.write(b); + stderr.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + logger.write(b, off, len); + stderr.write(b, off, len); + } + }; + + channelExec.setOut(combinedOut); + channelExec.setErr(combinedErr); + channelExec.open(); + channelExec.waitFor(java.util.EnumSet.of(org.apache.sshd.client.channel.ClientChannelEvent.CLOSED), 0); + + // Log the captured output to Logger + String stdoutStr = stdout.toString(java.nio.charset.StandardCharsets.UTF_8); + String stderrStr = stderr.toString(java.nio.charset.StandardCharsets.UTF_8); + + if (!stdoutStr.trim().isEmpty()) { + // Replace all line breaks with "||" so that it appears as a single line in the logs + LOGGER.info("Init script STDOUT for command '" + command + "': " + + stdoutStr.replaceAll("\\r\\n|\\r|\\n", "||")); + } + if (!stderrStr.trim().isEmpty()) { + // Replace all line breaks with "||" so that it appears as a single line in the logs + LOGGER.warning("Init script STDERR for command '" + command + "': " + + stderrStr.replaceAll("\\r\\n|\\r|\\n", "||")); + } + + return channelExec.getExitStatus() == 0; + } else { + // Use existing implementation + session.executeRemoteCommand(command, logger, logger, null); + return true; + } + } catch (IOException e) { + LOGGER.log(Level.FINE, "Failed to execute remote command: " + command, e); + return false; + } + } + protected boolean executeRemote( EC2Computer computer, ClientSession clientSession, @@ -162,10 +237,21 @@ protected boolean executeRemote( String command, PrintStream logger, TaskListener listener) { + return executeRemote(computer, clientSession, checkCommand, command, logger, listener, false); + } + + protected boolean executeRemote( + EC2Computer computer, + ClientSession clientSession, + String checkCommand, + String command, + PrintStream logger, + TaskListener listener, + boolean collectOutput) { logInfo(computer, listener, "Verifying: " + checkCommand); - if (!executeRemote(clientSession, checkCommand, logger)) { + if (!executeRemote(clientSession, checkCommand, logger, collectOutput, listener)) { logInfo(computer, listener, "Installing: " + command); - if (!executeRemote(clientSession, command, logger)) { + if (!executeRemote(clientSession, command, logger, collectOutput, listener)) { logWarning(computer, listener, "Failed to install: " + command); return false; } @@ -173,16 +259,6 @@ protected boolean executeRemote( return true; } - protected boolean executeRemote(ClientSession session, String command, OutputStream logger) { - try { - session.executeRemoteCommand(command, logger, logger, null); - return true; - } catch (IOException e) { - LOGGER.log(Level.FINE, "Failed to execute remote command: " + command, e); - return false; - } - } - protected File createIdentityKeyFile(EC2Computer computer) throws IOException { EC2PrivateKey ec2PrivateKey = computer.getCloud().resolvePrivateKey(); String privateKey = ""; diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index de8bb56c4..029df3ff4 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -181,8 +181,12 @@ protected void launchScript(EC2Computer computer, TaskListener listener) logInfo(computer, listener, "Executing init script"); String initCommand = buildUpCommand(computer, tmpDir + "/init.sh"); + + // Check if collectInitScriptLogs flag is set + boolean collectInitScriptLogs = template.getCollectInitScriptLogs(); + // Set the flag only when init script executed successfully. - if (executeRemote(clientSession, initCommand, logger)) { + if (executeRemote(clientSession, initCommand, logger, collectInitScriptLogs, listener)) { log( Level.FINE, computer, diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly index 1ebbd7fca..6e4b1cda5 100644 --- a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly @@ -251,6 +251,10 @@ THE SOFTWARE. + + + + diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-collectInitScriptLogs.html b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-collectInitScriptLogs.html new file mode 100644 index 000000000..d7d28f7d5 --- /dev/null +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/help-collectInitScriptLogs.html @@ -0,0 +1,6 @@ +
+ When checked, the output (stdout and stderr) from the init script execution will be collected and displayed in the Jenkins build logs. + This can be useful for debugging init script issues, but may increase log verbosity. +

+ Note: This only affects the logging of init script output. The init script will still execute regardless of this setting. +
\ No newline at end of file diff --git a/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java b/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java index ea856030d..179b8d158 100644 --- a/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java +++ b/src/test/java/hudson/plugins/ec2/ConfigurationAsCodeTest.java @@ -415,4 +415,37 @@ void testWindowsSSHConfigAsCodeWithAltEndpointAndJavaPathExport(JenkinsConfigure String expected = Util.toStringFromYamlFile(this, "WindowsSSHDataExport-withAltEndpointAndJavaPath.yml"); assertEquals(expected, exported); } + + @Test + @ConfiguredWithCode("UnixDataWithCollectInitScriptLogs.yml") + void testUnixDataWithInitLogs(JenkinsConfiguredWithCodeRule j) throws Exception { + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + CNode clouds = Util.getJenkinsRoot(context).get("clouds"); + String exported = Util.toYamlString(clouds); + String expected = Util.toStringFromYamlFile(this, "UnixDataExportWithCollectInitScriptLogs.yml"); + assertEquals(expected, exported); + } + + @Test + @ConfiguredWithCode("MacDataWithCollectInitScriptLogs.yml") + void testMacDataWithInitLogs(JenkinsConfiguredWithCodeRule j) throws Exception { + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + CNode clouds = Util.getJenkinsRoot(context).get("clouds"); + String exported = Util.toYamlString(clouds); + String expected = Util.toStringFromYamlFile(this, "MacDataExportWithCollectInitScriptLogs.yml"); + assertEquals(expected, exported); + } + + @Test + @ConfiguredWithCode("WindowsSSHDataWithCollectInitScriptLogs.yml") + void testWindowsDataWithInitLogs(JenkinsConfiguredWithCodeRule j) throws Exception { + ConfiguratorRegistry registry = ConfiguratorRegistry.get(); + ConfigurationContext context = new ConfigurationContext(registry); + CNode clouds = Util.getJenkinsRoot(context).get("clouds"); + String exported = Util.toYamlString(clouds); + String expected = Util.toStringFromYamlFile(this, "WindowsSSHDataExportWithCollectInitScriptLogs.yml"); + assertEquals(expected, exported); + } } diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java index 7644f5a1d..e9ee303fd 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java @@ -157,7 +157,8 @@ void testConfigRoundtrip() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -167,6 +168,10 @@ void testConfigRoundtrip() throws Exception { r.submit(getConfigForm(ac)); SlaveTemplate received = ((EC2Cloud) r.jenkins.clouds.iterator().next()).getTemplate(description); + + // Assert that collectInitScriptLogs defaults to false + assertFalse(received.getCollectInitScriptLogs(), "collectInitScriptLogs should default to false"); + r.assertEqualBeans( orig, received, @@ -227,7 +232,8 @@ void testConfigRoundtripWithCustomSSHHostKeyVerificationStrategy() throws Except EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -305,7 +311,8 @@ void testConfigWithSpotBidPrice() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -377,7 +384,8 @@ void testSpotConfigWithoutBidPrice() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -441,7 +449,8 @@ void testWindowsConfigRoundTrip() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -505,7 +514,8 @@ void testUnixConfigRoundTrip() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -574,7 +584,8 @@ void testMinimumNumberOfInstancesActiveRangeConfig() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); slaveTemplate.setMinimumNumberOfInstancesTimeRangeConfig(minimumNumberOfInstancesTimeRangeConfig); List templates = new ArrayList<>(); @@ -667,7 +678,8 @@ void provisionSetsNetworkInterface(AssociateIPStrategy associateIpStrategy) thro EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); SlaveTemplate noSubnet = new SlaveTemplate( TEST_AMI, @@ -714,7 +726,8 @@ void provisionSetsNetworkInterface(AssociateIPStrategy associateIpStrategy) thro EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); for (SlaveTemplate template : List.of(orig, noSubnet)) { Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -784,7 +797,8 @@ void provisionSetsNetworkInterface(AssociateIPStrategy associateIpStrategy) thro EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); SlaveTemplate spotNoSubnet = new SlaveTemplate( TEST_AMI, @@ -831,7 +845,8 @@ void provisionSetsNetworkInterface(AssociateIPStrategy associateIpStrategy) thro EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); for (SlaveTemplate template : List.of(spot, spotNoSubnet)) { Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -931,7 +946,8 @@ void provisionSpotFallsBackToOndemandWhenSpotQuotaExceeded() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -1059,7 +1075,8 @@ void testMacConfig() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -1120,7 +1137,8 @@ void testAgentName() { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); SlaveTemplate working = new SlaveTemplate( TEST_AMI, TEST_ZONE, @@ -1166,7 +1184,8 @@ void testAgentName() { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(broken); templates.add(working); @@ -1228,6 +1247,7 @@ void testMetadataV2Config() throws Exception { true, 2, true, + false, false); List templates = Collections.singletonList(orig); @@ -1290,6 +1310,7 @@ void provisionOnDemandWithUnsupportedInstanceMetadata() throws Exception { false, 2, false, + false, false); Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -1351,6 +1372,7 @@ void provisionOnDemandSetsMetadataV1Options() throws Exception { false, 2, true, + false, false); Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -1414,6 +1436,7 @@ void provisionOnDemandSetsMetadataV2Options() throws Exception { true, 2, true, + false, false); Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -1540,6 +1563,7 @@ void provisionOnDemandSetsMetadataDefaultOptionsWithEC2Exception() throws Except true, null, true, + false, false); Ec2Client mockedEC2 = setupTestForProvisioning(template); when(mockedEC2.runInstances(any(RunInstancesRequest.class))) @@ -1596,7 +1620,8 @@ void provisionOnDemandWithEnclaveEnabled() throws Exception { true, null, true, - true); + true, + false); Ec2Client mockedEC2 = setupTestForProvisioning(template); @@ -1659,7 +1684,8 @@ void testWindowsSSHConfigRoundTrip() throws Exception { EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED, EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED, - EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED); + EC2AbstractSlave.DEFAULT_ENCLAVE_ENABLED, + false); List templates = new ArrayList<>(); templates.add(orig); @@ -1671,6 +1697,203 @@ void testWindowsSSHConfigRoundTrip() throws Exception { r.assertEqualBeans(orig, received, "amiType"); } + @Test + void testInitScriptLogCapture() throws Exception { + String description = "init script log capture test"; + String initScript = "echo 'Starting initialization'\necho 'Setting up environment'\necho 'Init complete'"; + + SlaveTemplate template = new SlaveTemplate( + TEST_AMI, + TEST_ZONE, + TEST_SPOT_CFG, + TEST_SEC_GROUPS, + TEST_REMOTE_FS, + TEST_INSTANCE_TYPE.toString(), + TEST_EBSO, + TEST_LABEL, + Node.Mode.NORMAL, + description, + initScript, // Set init script with echo statements + "bbb", + "aaa", + "10", + "fff", + null, + "java", + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "", + true, + false, + "", + AssociateIPStrategy.SUBNET, + "", + true, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + true, + true, + 2, + true, + false, + true); // collectInitScriptLogs set to true + + List templates = Collections.singletonList(template); + + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + r.jenkins.clouds.add(ac); + + r.submit(r.createWebClient().goTo("configure").getFormByName("config")); + SlaveTemplate received = ((EC2Cloud) r.jenkins.clouds.iterator().next()).getTemplate(description); + + // Assert that collectInitScriptLogs is preserved correctly + assertTrue(received.getCollectInitScriptLogs(), "collectInitScriptLogs should be true"); + + // Assert other important fields are preserved + assertEquals(initScript, received.getInitScript(), "Init script should be preserved"); + r.assertEqualBeans( + template, + received, + "ami,zone,description,remoteFS,type,javaPath,jvmopts,stopOnTerminate,securityGroups,subnetId,initScript,collectInitScriptLogs"); + } + + @Test + void testCollectInitScriptLogsRoundTrip() throws Exception { + String description = "collect init script logs roundtrip test"; + + // Test with collectInitScriptLogs = true + SlaveTemplate origTrue = new SlaveTemplate( + TEST_AMI, + TEST_ZONE, + TEST_SPOT_CFG, + TEST_SEC_GROUPS, + TEST_REMOTE_FS, + TEST_INSTANCE_TYPE.toString(), + TEST_EBSO, + TEST_LABEL, + Node.Mode.NORMAL, + description + " true", + "echo 'test with logging enabled'", + "bbb", + "aaa", + "10", + "fff", + null, + "java", + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "", + true, + false, + "", + AssociateIPStrategy.SUBNET, + "", + true, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + true, + true, + 2, + true, + false, + true); // collectInitScriptLogs = true + + // Test with collectInitScriptLogs = false + SlaveTemplate origFalse = new SlaveTemplate( + TEST_AMI, + TEST_ZONE, + TEST_SPOT_CFG, + TEST_SEC_GROUPS, + TEST_REMOTE_FS, + TEST_INSTANCE_TYPE.toString(), + TEST_EBSO, + TEST_LABEL, + Node.Mode.NORMAL, + description + " false", + "echo 'test with logging disabled'", + "bbb", + "aaa", + "10", + "fff", + null, + "java", + "-Xmx1g", + false, + "subnet 456", + null, + null, + 0, + 0, + null, + "", + true, + false, + "", + AssociateIPStrategy.SUBNET, + "", + true, + false, + false, + ConnectionStrategy.PUBLIC_IP, + -1, + Collections.emptyList(), + null, + Tenancy.Default, + EbsEncryptRootVolume.DEFAULT, + true, + true, + 2, + true, + false, + false); // collectInitScriptLogs = false + + List templates = new ArrayList<>(); + templates.add(origTrue); + templates.add(origFalse); + + EC2Cloud ac = new EC2Cloud("us-east-1", false, "abc", "us-east-1", "ghi", null, "3", templates, null, null); + r.jenkins.clouds.add(ac); + + r.submit(r.createWebClient().goTo("configure").getFormByName("config")); + + SlaveTemplate receivedTrue = ((EC2Cloud) r.jenkins.clouds.iterator().next()).getTemplate(description + " true"); + SlaveTemplate receivedFalse = + ((EC2Cloud) r.jenkins.clouds.iterator().next()).getTemplate(description + " false"); + + // Assert that both configurations are preserved correctly + assertTrue(receivedTrue.getCollectInitScriptLogs(), "collectInitScriptLogs should be true for first template"); + assertFalse( + receivedFalse.getCollectInitScriptLogs(), "collectInitScriptLogs should be false for second template"); + + // Assert other fields are preserved + r.assertEqualBeans(origTrue, receivedTrue, "collectInitScriptLogs,initScript"); + r.assertEqualBeans(origFalse, receivedFalse, "collectInitScriptLogs,initScript"); + } + private HtmlForm getConfigForm(EC2Cloud ac) throws IOException, SAXException { return r.createWebClient().goTo(ac.getUrl() + "configure").getFormByName("config"); } diff --git a/src/test/java/hudson/plugins/ec2/ssh/EC2SSHLauncherTest.java b/src/test/java/hudson/plugins/ec2/ssh/EC2SSHLauncherTest.java index 7a7e148d5..c889af6cb 100644 --- a/src/test/java/hudson/plugins/ec2/ssh/EC2SSHLauncherTest.java +++ b/src/test/java/hudson/plugins/ec2/ssh/EC2SSHLauncherTest.java @@ -1,17 +1,28 @@ package hudson.plugins.ec2.ssh; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; import hudson.model.TaskListener; import hudson.plugins.ec2.HostKeyVerificationStrategyEnum; import hudson.plugins.ec2.MockEC2Computer; +import hudson.util.StreamTaskListener; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.EnumSet; import java.util.List; +import java.util.logging.Level; +import org.apache.sshd.client.channel.ChannelExec; +import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.future.OpenFuture; +import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.common.config.keys.PublicKeyEntry; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LogRecorder; import org.jvnet.hudson.test.junit.jupiter.WithJenkins; @WithJenkins @@ -19,6 +30,8 @@ class EC2SSHLauncherTest { private JenkinsRule r; + private final LogRecorder loggerRule = new LogRecorder(); + @BeforeEach void setUp(JenkinsRule rule) { r = rule; @@ -63,4 +76,160 @@ void testServerKeyVerifier() throws Exception { r.jenkins.removeNode(computer.getNode()); } } + + @Test + void testExecuteRemoteWithLogCapture() throws Exception { + // Create a mock computer and template + MockEC2Computer computer = MockEC2Computer.createComputer("test-computer"); + r.jenkins.addNode(computer.getNode()); + + // Set up the computer to enable init script log collection + computer.getSlaveTemplate().setCollectInitScriptLogs(true); + + // Create a test launcher + EC2UnixLauncher launcher = new EC2UnixLauncher(); + + // Create mock objects for SSH session and streams + ClientSession mockSession = mock(ClientSession.class); + ChannelExec mockChannel = mock(ChannelExec.class); + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + + // Mock the channel creation and execution + when(mockSession.createExecChannel(anyString())).thenReturn(mockChannel); + when(mockChannel.open()).thenReturn(mock(OpenFuture.class)); + when(mockChannel.waitFor(any(), anyLong())).thenReturn(EnumSet.of(ClientChannelEvent.CLOSED)); + when(mockChannel.getExitStatus()).thenReturn(0); + + // Set up channel to capture output + doAnswer(invocation -> { + OutputStream out = invocation.getArgument(0); + out.write("Test init script output\n".getBytes()); + out.flush(); + return null; + }) + .when(mockChannel) + .setOut(any(OutputStream.class)); + + doAnswer(invocation -> { + OutputStream err = invocation.getArgument(0); + err.write("Test error output\n".getBytes()); + err.flush(); + return null; + }) + .when(mockChannel) + .setErr(any(OutputStream.class)); + + // Create a TaskListener to capture the output + ByteArrayOutputStream logOutput = new ByteArrayOutputStream(); + TaskListener listener = new StreamTaskListener(logOutput); + + try { + // Test the executeRemote method with log collection enabled + loggerRule.capture(3).record("hudson.plugins.ec2.ssh.EC2SSHLauncher", Level.ALL); + boolean result = launcher.executeRemote( + mockSession, + "echo 'Hello from init script'", + System.out, + true, // collectOutput = true + listener); + + // Verify the execution was successful + assertTrue(result); + + // Verify that output was captured and logged + assertTrue( + loggerRule.getMessages().stream().anyMatch(message -> message.contains("Hello from init script"))); + + // Verify that the channel was properly configured for output capture + verify(mockChannel).setOut(any(OutputStream.class)); + verify(mockChannel).setErr(any(OutputStream.class)); + + } finally { + r.jenkins.removeNode(computer.getNode()); + } + } + + @Test + void testExecuteRemoteWithoutLogCapture() throws Exception { + // Create a mock computer and template + MockEC2Computer computer = MockEC2Computer.createComputer("test-computer-no-logs"); + r.jenkins.addNode(computer.getNode()); + + // Set up the computer to disable init script log collection + computer.getSlaveTemplate().setCollectInitScriptLogs(false); + + // Create a test launcher + EC2UnixLauncher launcher = new EC2UnixLauncher(); + + // Create mock objects for SSH session + ClientSession mockSession = mock(ClientSession.class); + ChannelExec mockChannel = mock(ChannelExec.class); + + // Mock the channel creation and execution + when(mockSession.createExecChannel(anyString())).thenReturn(mockChannel); + when(mockChannel.open()).thenReturn(mock(OpenFuture.class)); + when(mockChannel.waitFor(any(), anyLong())).thenReturn(EnumSet.of(ClientChannelEvent.CLOSED)); + when(mockChannel.getExitStatus()).thenReturn(0); + + // Create a TaskListener + ByteArrayOutputStream logOutput = new ByteArrayOutputStream(); + TaskListener listener = new StreamTaskListener(logOutput); + + try { + // Test the executeRemote method with log collection disabled + loggerRule.capture(3).record("hudson.plugins.ec2.ssh.EC2SSHLauncher", Level.ALL); + boolean result = launcher.executeRemote( + mockSession, + "echo 'Hello from init script'", + System.out, + false, // collectOutput = false + listener); + + // Verify the execution was successful + assertTrue(result); + + assertFalse( + loggerRule.getMessages().stream().anyMatch(message -> message.contains("Hello from init script"))); + + // Verify that output streams were not set up for capture + verify(mockChannel, never()).setOut(any(OutputStream.class)); + verify(mockChannel, never()).setErr(any(OutputStream.class)); + + } finally { + r.jenkins.removeNode(computer.getNode()); + } + } + + @Test + void testExecuteRemoteBackwardCompatibility() throws Exception { + // Test the original 3-parameter executeRemote method still works + MockEC2Computer computer = MockEC2Computer.createComputer("test-backward-compat"); + r.jenkins.addNode(computer.getNode()); + + EC2UnixLauncher launcher = new EC2UnixLauncher(); + + // Create mock objects + ClientSession mockSession = mock(ClientSession.class); + ChannelExec mockChannel = mock(ChannelExec.class); + + when(mockSession.createExecChannel(anyString())).thenReturn(mockChannel); + when(mockChannel.open()).thenReturn(mock(OpenFuture.class)); + when(mockChannel.waitFor(any(), anyLong())).thenReturn(EnumSet.of(ClientChannelEvent.CLOSED)); + when(mockChannel.getExitStatus()).thenReturn(0); + + try { + // Test the original executeRemote method (should default to no log capture) + boolean result = launcher.executeRemote(mockSession, "echo 'Hello'", System.out); + + // Verify the execution was successful + assertTrue(result); + + // Verify that this calls the new method with collectOutput=false + verify(mockChannel, never()).setOut(any(OutputStream.class)); + + } finally { + r.jenkins.removeNode(computer.getNode()); + } + } } diff --git a/src/test/resources/hudson/plugins/ec2/MacDataExport.yml b/src/test/resources/hudson/plugins/ec2/MacDataExport.yml index 3ef0af360..ac8079531 100644 --- a/src/test/resources/hudson/plugins/ec2/MacDataExport.yml +++ b/src/test/resources/hudson/plugins/ec2/MacDataExport.yml @@ -12,6 +12,7 @@ slaveCommandSuffix: "-fakeFlag" sshPort: "22" associateIPStrategy: DEFAULT + collectInitScriptLogs: false connectBySSHProcess: false connectionStrategy: PRIVATE_IP deleteRootOnTermination: false diff --git a/src/test/resources/hudson/plugins/ec2/MacDataExportWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/MacDataExportWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..feb573f17 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/MacDataExportWithCollectInitScriptLogs.yml @@ -0,0 +1,41 @@ +- amazonEC2: + name: "production" + region: "us-east-1" + sshKeysCredentialsId: "random credentials id" + templates: + - ami: "ami-12345" + amiType: + macData: + bootDelay: "180" + rootCommandPrefix: "sudo" + slaveCommandPrefix: "sudo -u jenkins" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22" + associateIPStrategy: DEFAULT + collectInitScriptLogs: true + connectBySSHProcess: false + connectionStrategy: PRIVATE_IP + deleteRootOnTermination: false + ebsEncryptRootVolume: DEFAULT + ebsOptimized: false + enclaveEnabled: false + hostKeyVerificationStrategy: CHECK_NEW_SOFT + javaPath: "java" + labelString: "mac metal" + maxTotalUses: -1 + metadataEndpointEnabled: true + metadataHopsLimit: 1 + metadataSupported: true + metadataTokensRequired: true + minimumNumberOfInstances: 0 + minimumNumberOfSpareInstances: 0 + mode: NORMAL + monitoring: false + numExecutors: 1 + remoteFS: "/Users/ec2-user" + stopOnTerminate: false + t2Unlimited: false + tenancy: Host + type: "mac1.metal" + useEphemeralDevices: false + useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/MacDataWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/MacDataWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..0e3b5a8e0 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/MacDataWithCollectInitScriptLogs.yml @@ -0,0 +1,23 @@ +--- +jenkins: + clouds: + - amazonEC2: + name: "production" + useInstanceProfileForCredentials: true + sshKeysCredentialsId: "random credentials id" + templates: + - description: + ami: "ami-12345" + labelString: "mac metal" + type: "mac1.metal" + remoteFS: "/Users/ec2-user" + mode: "NORMAL" + tenancy: Host + collectInitScriptLogs: true + amiType: + macData: + rootCommandPrefix: "sudo" + slaveCommandPrefix: "sudo -u jenkins" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22" + bootDelay: "180" diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml b/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml index 5fc99c7ae..c051feefb 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml @@ -12,6 +12,7 @@ slaveCommandSuffix: "-fakeFlag" sshPort: "22" associateIPStrategy: DEFAULT + collectInitScriptLogs: false connectBySSHProcess: false connectionStrategy: PRIVATE_IP deleteRootOnTermination: false diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml b/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml index 09134c6a2..a4a9ab7e8 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml @@ -12,6 +12,7 @@ slaveCommandSuffix: "-fakeFlag" sshPort: "22" associateIPStrategy: DEFAULT + collectInitScriptLogs: false connectBySSHProcess: false connectionStrategy: PRIVATE_IP deleteRootOnTermination: false diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataExportWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/UnixDataExportWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..15931b7f8 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/UnixDataExportWithCollectInitScriptLogs.yml @@ -0,0 +1,46 @@ +- amazonEC2: + name: "production" + region: "us-east-1" + sshKeysCredentialsId: "random credentials id" + templates: + - ami: "ami-12345" + amiType: + unixData: + bootDelay: "180" + rootCommandPrefix: "sudo" + slaveCommandPrefix: "sudo -u jenkins" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22" + associateIPStrategy: DEFAULT + collectInitScriptLogs: true + connectBySSHProcess: false + connectionStrategy: PRIVATE_IP + deleteRootOnTermination: false + ebsEncryptRootVolume: DEFAULT + ebsOptimized: false + enclaveEnabled: false + hostKeyVerificationStrategy: CHECK_NEW_SOFT + javaPath: "java" + labelString: "linux ubuntu" + maxTotalUses: -1 + metadataEndpointEnabled: true + metadataHopsLimit: 1 + metadataSupported: true + metadataTokensRequired: true + minimumNumberOfInstances: 0 + minimumNumberOfSpareInstances: 0 + mode: NORMAL + monitoring: false + numExecutors: 1 + remoteFS: "/home/ec2-user" + spotConfig: + fallbackToOndemand: true + spotBlockReservationDuration: 3 + spotMaxBidPrice: "0.15" + useBidPrice: true + stopOnTerminate: false + t2Unlimited: false + tenancy: Default + type: "t2.micro" + useEphemeralDevices: false + useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/UnixDataWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..bdcf480e1 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/UnixDataWithCollectInitScriptLogs.yml @@ -0,0 +1,27 @@ +--- +jenkins: + clouds: + - amazonEC2: + name: "production" + useInstanceProfileForCredentials: true + sshKeysCredentialsId: "random credentials id" + templates: + - description: + ami: "ami-12345" + labelString: "linux ubuntu" + type: "t2.micro" + remoteFS: "/home/ec2-user" + mode: "NORMAL" + spotConfig: + fallbackToOndemand: true + spotBlockReservationDuration: 3 + spotMaxBidPrice: "0.15" + useBidPrice: true + collectInitScriptLogs: true + amiType: + unixData: + bootDelay: "180" + rootCommandPrefix: "sudo" + slaveCommandPrefix: "sudo -u jenkins" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22" diff --git a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport-withAltEndpointAndJavaPath.yml b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport-withAltEndpointAndJavaPath.yml index 671dd0469..4b8268b7b 100644 --- a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport-withAltEndpointAndJavaPath.yml +++ b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport-withAltEndpointAndJavaPath.yml @@ -12,6 +12,7 @@ slaveCommandSuffix: "-fakeFlag" sshPort: "22" associateIPStrategy: DEFAULT + collectInitScriptLogs: false connectBySSHProcess: false connectionStrategy: PRIVATE_IP deleteRootOnTermination: false diff --git a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport.yml b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport.yml index 8a3019f9f..3a0cee59e 100644 --- a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport.yml +++ b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExport.yml @@ -12,6 +12,7 @@ slaveCommandSuffix: "-fakeFlag" sshPort: "22" associateIPStrategy: DEFAULT + collectInitScriptLogs: false connectBySSHProcess: false connectionStrategy: PRIVATE_IP deleteRootOnTermination: false diff --git a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExportWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExportWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..e81515b02 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataExportWithCollectInitScriptLogs.yml @@ -0,0 +1,46 @@ +- amazonEC2: + name: "production" + region: "us-east-1" + sshKeysCredentialsId: "random credentials id" + templates: + - ami: "ami-12345" + amiType: + windowsSSHData: + bootDelay: "180" + rootCommandPrefix: "CMD /C" + slaveCommandPrefix: "CMD /C" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22" + associateIPStrategy: DEFAULT + collectInitScriptLogs: true + connectBySSHProcess: false + connectionStrategy: PRIVATE_IP + deleteRootOnTermination: false + ebsEncryptRootVolume: DEFAULT + ebsOptimized: false + enclaveEnabled: false + hostKeyVerificationStrategy: CHECK_NEW_SOFT + javaPath: "java" + labelString: "windows server" + maxTotalUses: -1 + metadataEndpointEnabled: true + metadataHopsLimit: 1 + metadataSupported: true + metadataTokensRequired: true + minimumNumberOfInstances: 0 + minimumNumberOfSpareInstances: 0 + mode: NORMAL + monitoring: false + numExecutors: 1 + remoteFS: "C:\\Users\\ec2-user" + spotConfig: + fallbackToOndemand: true + spotBlockReservationDuration: 3 + spotMaxBidPrice: "0.15" + useBidPrice: true + stopOnTerminate: false + t2Unlimited: false + tenancy: Default + type: "t2.micro" + useEphemeralDevices: false + useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/WindowsSSHDataWithCollectInitScriptLogs.yml b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataWithCollectInitScriptLogs.yml new file mode 100644 index 000000000..d50a6f499 --- /dev/null +++ b/src/test/resources/hudson/plugins/ec2/WindowsSSHDataWithCollectInitScriptLogs.yml @@ -0,0 +1,27 @@ +--- +jenkins: + clouds: + - amazonEC2: + name: "production" + useInstanceProfileForCredentials: true + sshKeysCredentialsId: "random credentials id" + templates: + - description: + ami: "ami-12345" + labelString: "windows server" + type: "t2.micro" + remoteFS: "C:\\Users\\ec2-user" + mode: "NORMAL" + spotConfig: + fallbackToOndemand: true + spotBlockReservationDuration: 3 + spotMaxBidPrice: "0.15" + useBidPrice: true + collectInitScriptLogs: true + amiType: + windowsSSHData: + bootDelay: "180" + rootCommandPrefix: "CMD /C" + slaveCommandPrefix: "CMD /C" + slaveCommandSuffix: "-fakeFlag" + sshPort: "22"