From 98fc9a96b65375210d2dd3f9285d924d91582f3f Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Tue, 26 Nov 2024 13:18:16 -0800 Subject: [PATCH] Format repository with Spotless --- pom.xml | 402 ++-- .../java/hudson/plugins/ec2/AMITypeData.java | 5 +- .../hudson/plugins/ec2/AmazonEC2Cloud.java | 105 +- .../java/hudson/plugins/ec2/CloudHelper.java | 47 +- .../plugins/ec2/ConnectionStrategy.java | 3 +- .../hudson/plugins/ec2/EC2AbstractSlave.java | 708 ++++--- .../java/hudson/plugins/ec2/EC2Cloud.java | 510 +++-- .../java/hudson/plugins/ec2/EC2Computer.java | 22 +- .../plugins/ec2/EC2ComputerLauncher.java | 24 +- .../plugins/ec2/EC2ComputerListener.java | 2 +- .../java/hudson/plugins/ec2/EC2Filter.java | 28 +- .../plugins/ec2/EC2HostAddressProvider.java | 7 +- .../hudson/plugins/ec2/EC2OndemandSlave.java | 415 +++- .../hudson/plugins/ec2/EC2PrivateKey.java | 33 +- .../java/hudson/plugins/ec2/EC2Readiness.java | 1 + .../plugins/ec2/EC2RetentionStrategy.java | 102 +- .../hudson/plugins/ec2/EC2SlaveMonitor.java | 23 +- .../java/hudson/plugins/ec2/EC2SpotSlave.java | 188 +- src/main/java/hudson/plugins/ec2/EC2Step.java | 27 +- src/main/java/hudson/plugins/ec2/EC2Tag.java | 26 +- .../plugins/ec2/EbsEncryptRootVolume.java | 1 - .../java/hudson/plugins/ec2/Eucalyptus.java | 96 +- .../ec2/HostKeyVerificationStrategyEnum.java | 11 +- .../hudson/plugins/ec2/InstanceState.java | 7 +- .../plugins/ec2/InstanceTypeConverter.java | 9 +- src/main/java/hudson/plugins/ec2/MacData.java | 41 +- .../ec2/NoDelayProvisionerStrategy.java | 40 +- .../java/hudson/plugins/ec2/PluginImpl.java | 17 +- .../hudson/plugins/ec2/SlaveTemplate.java | 1760 +++++++++++++---- .../hudson/plugins/ec2/SpotConfiguration.java | 45 +- src/main/java/hudson/plugins/ec2/Tenancy.java | 4 +- .../java/hudson/plugins/ec2/UnixData.java | 47 +- .../java/hudson/plugins/ec2/WindowsData.java | 57 +- .../plugins/ec2/ebs/ZPoolExpandNotice.java | 4 +- .../hudson/plugins/ec2/ebs/ZPoolMonitor.java | 14 +- .../hudson/plugins/ec2/ebs/package-info.java | 2 +- .../plugins/ec2/ssh/EC2MacLauncher.java | 183 +- .../plugins/ec2/ssh/EC2UnixLauncher.java | 207 +- .../plugins/ec2/ssh/HostKeyVerifierImpl.java | 11 +- .../ec2/ssh/verifiers/AcceptNewStrategy.java | 25 +- .../ssh/verifiers/CheckNewHardStrategy.java | 59 +- .../ssh/verifiers/CheckNewSoftStrategy.java | 62 +- .../plugins/ec2/ssh/verifiers/HostKey.java | 67 +- .../ec2/ssh/verifiers/HostKeyHelper.java | 63 +- .../NonVerifyingKeyVerificationStrategy.java | 13 +- ...tKeyVerificationAdministrativeMonitor.java | 51 +- .../SshHostKeyVerificationStrategy.java | 64 +- .../plugins/ec2/util/AmazonEC2Factory.java | 5 +- .../ec2/util/AmazonEC2FactoryImpl.java | 7 +- .../plugins/ec2/util/DeviceMappingParser.java | 6 +- .../plugins/ec2/util/EC2AgentConfig.java | 20 +- .../plugins/ec2/util/EC2AgentFactory.java | 4 +- .../plugins/ec2/util/EC2AgentFactoryImpl.java | 55 +- .../ec2/util/MinimumInstanceChecker.java | 136 +- ...nimumNumberOfInstancesTimeRangeConfig.java | 35 +- .../ec2/util/ResettableCountDownLatch.java | 5 +- .../plugins/ec2/win/EC2WindowsLauncher.java | 91 +- .../SelfSignedCertificateAllowedMonitor.java | 31 +- .../hudson/plugins/ec2/win/WinConnection.java | 73 +- .../win/winrm/NegotiateNTLMSchemaFactory.java | 6 +- .../hudson/plugins/ec2/win/winrm/WinRM.java | 4 +- .../plugins/ec2/win/winrm/WinRMClient.java | 90 +- .../ec2/win/winrm/WinRMConnectException.java | 1 - .../winrm/WinRMConnectionManagerFactory.java | 21 +- .../plugins/ec2/win/winrm/WindowsProcess.java | 10 +- .../winrm/request/AbstractWinRMRequest.java | 15 +- .../win/winrm/request/DeleteShellRequest.java | 6 +- .../winrm/request/ExecuteCommandRequest.java | 9 +- .../win/winrm/request/GetOutputRequest.java | 12 +- .../win/winrm/request/OpenShellRequest.java | 11 +- .../ec2/win/winrm/request/RequestFactory.java | 1 - .../win/winrm/request/SendInputRequest.java | 14 +- .../ec2/win/winrm/request/SignalRequest.java | 14 +- .../plugins/ec2/win/winrm/soap/Header.java | 68 +- .../ec2/win/winrm/soap/HeaderBuilder.java | 6 +- .../ec2/win/winrm/soap/MessageBuilder.java | 3 +- .../ec2/win/winrm/soap/Namespaces.java | 22 +- .../plugins/ec2/AmazonEC2CloudTest.java | 93 +- .../plugins/ec2/AmazonEC2CloudUnitTest.java | 131 +- .../hudson/plugins/ec2/CloudHelperTest.java | 31 +- .../plugins/ec2/ConfigurationAsCodeTest.java | 49 +- .../plugins/ec2/ConnectionStrategyTest.java | 24 +- .../plugins/ec2/EC2AbstractSlaveTest.java | 153 +- .../plugins/ec2/EC2CloudMigrationTest.java | 23 +- .../java/hudson/plugins/ec2/EC2CloudTest.java | 282 ++- .../hudson/plugins/ec2/EC2FilterTest.java | 12 +- .../ec2/EC2HostAddressProviderTest.java | 1 - .../plugins/ec2/EC2OndemandSlaveTest.java | 57 +- .../hudson/plugins/ec2/EC2PrivateKeyTest.java | 15 +- .../plugins/ec2/EC2RetentionStrategyTest.java | 792 ++++++-- .../plugins/ec2/EC2SlaveMonitorTest.java | 207 +- .../java/hudson/plugins/ec2/EC2StepTest.java | 56 +- .../hudson/plugins/ec2/EucalyptusTest.java | 20 +- .../plugins/ec2/FileBasedSSHKeyTest.java | 35 +- .../hudson/plugins/ec2/SlaveTemplateTest.java | 872 +++++++- .../plugins/ec2/SlaveTemplateUnitTest.java | 839 ++++++-- .../plugins/ec2/TemplateLabelsTest.java | 41 +- .../hudson/plugins/ec2/WinRMMessageTest.java | 9 +- .../ec2/ssh/HostKeyVerifierImplTest.java | 13 +- .../SshHostKeyVerificationStrategyTest.java | 308 +-- .../ec2/util/AmazonEC2FactoryMockImpl.java | 211 +- .../plugins/ec2/util/ConnectionRule.java | 103 +- .../ec2/util/DeviceMappingParserTest.java | 45 +- .../ec2/util/EC2AgentFactoryMockImpl.java | 221 ++- .../plugins/ec2/util/PrivateKeyHelper.java | 3 +- .../plugins/ec2/util/SSHCredentialHelper.java | 31 +- .../ec2/util/TestSSHUserPrivateKey.java | 16 +- .../plugins/ec2/win/WinConnectionTest.java | 14 +- 108 files changed, 7984 insertions(+), 3142 deletions(-) diff --git a/pom.xml b/pom.xml index 161392eae..e400bde7a 100644 --- a/pom.xml +++ b/pom.xml @@ -22,216 +22,216 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - - - 4.0.0 - - - org.jenkins-ci.plugins - plugin - 4.88 - - + 4.0.0 + + org.jenkins-ci.plugins + plugin + 4.88 + + - ec2 - ${changelist} - hpi + ec2 + ${changelist} + hpi + Amazon EC2 plugin + This is a Jenkins plugin to support ephemeral Jenkins agents on Amazon EC2 or other EC2-compatible clouds + https://github.com/jenkinsci/${project.artifactId}-plugin - Amazon EC2 plugin - This is a Jenkins plugin to support ephemeral Jenkins agents on Amazon EC2 or other EC2-compatible clouds - https://github.com/jenkinsci/${project.artifactId}-plugin - - - The MIT License - https://opensource.org/licenses/MIT - repo - - + + + The MIT License + https://opensource.org/licenses/MIT + repo + + - - - - thoulen - F Manfred Furuholen - fabrizio.manfredi@gmail.com - - - julienduchesne - Julien Duchesne - julienduchesne@live.com - - - raihaan - Raihaan Shouhell - raihaanhimself@gmail.com - - + + + + thoulen + F Manfred Furuholen + fabrizio.manfredi@gmail.com + + + julienduchesne + Julien Duchesne + julienduchesne@live.com + + + raihaan + Raihaan Shouhell + raihaanhimself@gmail.com + + - - scm:git:https://github.com/${gitHubRepo}.git - scm:git:git@github.com:${gitHubRepo}.git - https://github.com/${gitHubRepo} - ${scmTag} - + + scm:git:https://github.com/${gitHubRepo}.git + scm:git:git@github.com:${gitHubRepo}.git + ${scmTag} + https://github.com/${gitHubRepo} + - - 999999-SNAPSHOT - 2.452.4 - jenkinsci/${project.artifactId}-plugin - 1626 - + + 999999-SNAPSHOT + 2.452.4 + jenkinsci/${project.artifactId}-plugin + 1626 + false + + - - com.hierynomus - smbj - 0.13.0 - - - org.bouncycastle - bcprov-jdk18on - - - org.slf4j - slf4j-api - - - - - org.jenkins-ci.plugins - credentials - - - org.jenkins-ci.plugins - aws-credentials - - - org.jenkins-ci.plugins - ssh-credentials - - - org.jenkins-ci.plugins - bouncycastle-api - - - org.jenkins-ci.plugins.aws-java-sdk - aws-java-sdk-ec2 - 1.12.696-451.v0651a_da_9ca_ec - - - org.jenkins-ci.plugins.aws-java-sdk - aws-java-sdk-minimal - 1.12.767-467.vb_e93f0c614b_6 - - - org.jenkins-ci.plugins - node-iterator-api - 55.v3b_77d4032326 - - - org.jenkins-ci.plugins - apache-httpcomponents-client-4-api - - - org.jenkins-ci.plugins - command-launcher - - - org.jenkins-ci.plugins - trilead-api - - - org.jenkins-ci.plugins.workflow - workflow-step-api - - - org.mockito - mockito-core - test - - - junit - junit - test - - - org.jenkins-ci.plugins.workflow - workflow-cps - test - - - org.jenkins-ci.plugins.workflow - workflow-durable-task-step - test - - - org.jenkins-ci.plugins.workflow - workflow-job - test - - - org.jenkins-ci.plugins.workflow - workflow-api - test - - - io.jenkins - configuration-as-code - test - - - io.jenkins.configuration-as-code - test-harness - test - - - org.testcontainers - testcontainers - 1.20.3 - test - - - org.kohsuke - libzfs - 0.8 - + + io.jenkins.tools.bom + bom-2.452.x + 3654.v237e4a_f2d8da_ + pom + import + - - - - io.jenkins.tools.bom - bom-2.452.x - 3654.v237e4a_f2d8da_ - import - pom - - - + + + + + com.hierynomus + smbj + 0.13.0 + + + org.bouncycastle + bcprov-jdk18on + + + org.slf4j + slf4j-api + + + + + org.jenkins-ci.plugins + apache-httpcomponents-client-4-api + + + org.jenkins-ci.plugins + aws-credentials + + + org.jenkins-ci.plugins + bouncycastle-api + + + org.jenkins-ci.plugins + command-launcher + + + org.jenkins-ci.plugins + credentials + + + org.jenkins-ci.plugins + node-iterator-api + 55.v3b_77d4032326 + + + org.jenkins-ci.plugins + ssh-credentials + + + org.jenkins-ci.plugins + trilead-api + + + org.jenkins-ci.plugins.aws-java-sdk + aws-java-sdk-ec2 + 1.12.696-451.v0651a_da_9ca_ec + + + org.jenkins-ci.plugins.aws-java-sdk + aws-java-sdk-minimal + 1.12.767-467.vb_e93f0c614b_6 + + + org.jenkins-ci.plugins.workflow + workflow-step-api + + + org.kohsuke + libzfs + 0.8 + + + io.jenkins + configuration-as-code + test + + + io.jenkins.configuration-as-code + test-harness + test + + + junit + junit + test + + + org.jenkins-ci.plugins.workflow + workflow-api + test + + + org.jenkins-ci.plugins.workflow + workflow-cps + test + + + org.jenkins-ci.plugins.workflow + workflow-durable-task-step + test + + + org.jenkins-ci.plugins.workflow + workflow-job + test + + + org.mockito + mockito-core + test + + + org.testcontainers + testcontainers + 1.20.3 + test + + + + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - - - - repo.jenkins-ci.org - https://repo.jenkins-ci.org/public/ - - + + + repo.jenkins-ci.org + https://repo.jenkins-ci.org/public/ + + - - - - org.jenkins-ci.tools - maven-hpi-plugin - true - - 1.45 - - - - + + + + org.jenkins-ci.tools + maven-hpi-plugin + true + + 1.45 + + + + diff --git a/src/main/java/hudson/plugins/ec2/AMITypeData.java b/src/main/java/hudson/plugins/ec2/AMITypeData.java index 6d27b95c8..997e43e10 100644 --- a/src/main/java/hudson/plugins/ec2/AMITypeData.java +++ b/src/main/java/hudson/plugins/ec2/AMITypeData.java @@ -1,7 +1,6 @@ package hudson.plugins.ec2; import hudson.model.AbstractDescribableImpl; - import java.util.concurrent.TimeUnit; public abstract class AMITypeData extends AbstractDescribableImpl { @@ -14,13 +13,13 @@ public abstract class AMITypeData extends AbstractDescribableImpl { public abstract String getBootDelay(); public int getBootDelayInMillis() { - if (getBootDelay() == null) + if (getBootDelay() == null) { return 0; + } try { return (int) TimeUnit.SECONDS.toMillis(Integer.parseInt(getBootDelay())); } catch (NumberFormatException nfe) { return 0; } } - } diff --git a/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java b/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java index 0441987a4..950fc104b 100644 --- a/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java +++ b/src/main/java/hudson/plugins/ec2/AmazonEC2Cloud.java @@ -24,6 +24,11 @@ package hudson.plugins.ec2; import com.amazonaws.SdkClientException; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.model.DescribeRegionsResult; +import com.amazonaws.services.ec2.model.Region; +import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; import hudson.Util; import hudson.model.Failure; @@ -31,7 +36,6 @@ import hudson.plugins.ec2.util.AmazonEC2Factory; import hudson.util.FormValidation; import hudson.util.ListBoxModel; - import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; @@ -39,21 +43,13 @@ import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; -import edu.umd.cs.findbugs.annotations.Nullable; import javax.servlet.ServletException; - import jenkins.model.Jenkins; - import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; - -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeRegionsResult; -import com.amazonaws.services.ec2.model.Region; import org.kohsuke.stapler.verb.POST; /** @@ -62,8 +58,8 @@ * @author Kohsuke Kawaguchi */ public class AmazonEC2Cloud extends EC2Cloud { - private final static Logger LOGGER = Logger.getLogger(AmazonEC2Cloud.class.getName()); - + private static final Logger LOGGER = Logger.getLogger(AmazonEC2Cloud.class.getName()); + /** * Represents the region. Can be null for backward compatibility reasons. */ @@ -74,14 +70,50 @@ public class AmazonEC2Cloud extends EC2Cloud { private boolean noDelayProvisioning; @DataBoundConstructor - public AmazonEC2Cloud(String name, boolean useInstanceProfileForCredentials, String credentialsId, String region, String privateKey, String sshKeysCredentialsId, String instanceCapStr, List templates, String roleArn, String roleSessionName) { - super(name, useInstanceProfileForCredentials, credentialsId, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName); + public AmazonEC2Cloud( + String name, + boolean useInstanceProfileForCredentials, + String credentialsId, + String region, + String privateKey, + String sshKeysCredentialsId, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { + super( + name, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + sshKeysCredentialsId, + instanceCapStr, + templates, + roleArn, + roleSessionName); this.region = region; } @Deprecated - public AmazonEC2Cloud(String name, boolean useInstanceProfileForCredentials, String credentialsId, String region, String privateKey, String instanceCapStr, List templates, String roleArn, String roleSessionName) { - super(name, useInstanceProfileForCredentials, credentialsId, privateKey, instanceCapStr, templates, roleArn, roleSessionName); + public AmazonEC2Cloud( + String name, + boolean useInstanceProfileForCredentials, + String credentialsId, + String region, + String privateKey, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { + super( + name, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + instanceCapStr, + templates, + roleArn, + roleSessionName); this.region = region; } @@ -94,12 +126,14 @@ public String getCloudName() { } public String getRegion() { - if (region == null) + if (region == null) { region = DEFAULT_EC2_HOST; // Backward compatibility + } // Handles pre 1.14 region names that used the old AwsRegion enum, note we don't change // the region here to keep the meta-data compatible in the case of a downgrade (is that right?) - if (region.indexOf('_') > 0) + if (region.indexOf('_') > 0) { return region.replace('_', '-').toLowerCase(Locale.ENGLISH); + } return region; } @@ -145,7 +179,12 @@ public void setAltEC2Endpoint(String altEC2Endpoint) { @Override protected AWSCredentialsProvider createCredentialsProvider() { - return createCredentialsProvider(isUseInstanceProfileForCredentials(), getCredentialsId(), getRoleArn(), getRoleSessionName(), getRegion()); + return createCredentialsProvider( + isUseInstanceProfileForCredentials(), + getCredentialsId(), + getRoleArn(), + getRoleSessionName(), + getRegion()); } @Extension @@ -180,20 +219,20 @@ public FormValidation doCheckAltEC2Endpoint(@QueryParameter String value) { } return FormValidation.ok(); } - + @RequirePOST public ListBoxModel doFillRegionItems( @QueryParameter String altEC2Endpoint, @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId) - throws IOException, ServletException { ListBoxModel model = new ListBoxModel(); if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { try { - AWSCredentialsProvider credentialsProvider = createCredentialsProvider(useInstanceProfileForCredentials, - credentialsId); - AmazonEC2 client = AmazonEC2Factory.getInstance().connect(credentialsProvider, determineEC2EndpointURL(altEC2Endpoint)); + AWSCredentialsProvider credentialsProvider = + createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); + AmazonEC2 client = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, determineEC2EndpointURL(altEC2Endpoint)); DescribeRegionsResult regions = client.describeRegions(); List regionList = regions.getRegions(); for (Region r : regionList) { @@ -209,15 +248,18 @@ public ListBoxModel doFillRegionItems( // Will use the alternate EC2 endpoint if provided by the UI (via a @QueryParameter field), or use the default // value if not specified. - //VisibleForTesting + // VisibleForTesting URL determineEC2EndpointURL(@Nullable String altEC2Endpoint) throws MalformedURLException { if (Util.fixEmpty(altEC2Endpoint) == null) { return new URL(DEFAULT_EC2_ENDPOINT); } try { - return new URL(altEC2Endpoint); + return new URL(altEC2Endpoint); } catch (MalformedURLException e) { - LOGGER.log(Level.WARNING, "The alternate EC2 endpoint is malformed ({0}). Using the default endpoint ({1})", new Object[]{altEC2Endpoint, DEFAULT_EC2_ENDPOINT}); + LOGGER.log( + Level.WARNING, + "The alternate EC2 endpoint is malformed ({0}). Using the default endpoint ({1})", + new Object[] {altEC2Endpoint, DEFAULT_EC2_ENDPOINT}); return new URL(DEFAULT_EC2_ENDPOINT); } } @@ -231,14 +273,21 @@ public FormValidation doTestConnection( @QueryParameter String sshKeysCredentialsId, @QueryParameter String roleArn, @QueryParameter String roleSessionName) - throws IOException, ServletException { if (Util.fixEmpty(region) == null) { region = DEFAULT_EC2_HOST; } - return super.doTestConnection(context, getEc2EndpointUrl(region), useInstanceProfileForCredentials, credentialsId, sshKeysCredentialsId, roleArn, roleSessionName, region); + return super.doTestConnection( + context, + getEc2EndpointUrl(region), + useInstanceProfileForCredentials, + credentialsId, + sshKeysCredentialsId, + roleArn, + roleSessionName, + region); } } } diff --git a/src/main/java/hudson/plugins/ec2/CloudHelper.java b/src/main/java/hudson/plugins/ec2/CloudHelper.java index 82344f194..7a453d0ac 100644 --- a/src/main/java/hudson/plugins/ec2/CloudHelper.java +++ b/src/main/java/hudson/plugins/ec2/CloudHelper.java @@ -1,5 +1,7 @@ package hudson.plugins.ec2; +import static hudson.plugins.ec2.EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE; + import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.ec2.AmazonEC2; @@ -10,20 +12,18 @@ import com.amazonaws.services.ec2.model.Image; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Reservation; -import java.util.ArrayList; -import org.apache.commons.lang.StringUtils; - import edu.umd.cs.findbugs.annotations.CheckForNull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Logger; - -import static hudson.plugins.ec2.EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE; +import org.apache.commons.lang.StringUtils; final class CloudHelper { private static final Logger LOGGER = Logger.getLogger(CloudHelper.class.getName()); - static Instance getInstanceWithRetry(String instanceId, EC2Cloud cloud) throws AmazonClientException, InterruptedException { + static Instance getInstanceWithRetry(String instanceId, EC2Cloud cloud) + throws AmazonClientException, InterruptedException { // Sometimes even after a successful RunInstances, DescribeInstances // returns an error for a few seconds. We do a few retries instead of // failing instantly. See [JENKINS-15319]. @@ -31,7 +31,8 @@ static Instance getInstanceWithRetry(String instanceId, EC2Cloud cloud) throws A try { return getInstance(instanceId, cloud); } catch (AmazonServiceException e) { - if (e.getErrorCode().equals("InvalidInstanceID.NotFound") || EC2_REQUEST_EXPIRED_ERROR_CODE.equals(e.getErrorCode())) { + if (e.getErrorCode().equals("InvalidInstanceID.NotFound") + || EC2_REQUEST_EXPIRED_ERROR_CODE.equals(e.getErrorCode())) { // retry in 5 seconds. Thread.sleep(5000); continue; @@ -45,31 +46,35 @@ static Instance getInstanceWithRetry(String instanceId, EC2Cloud cloud) throws A @CheckForNull static Instance getInstance(String instanceId, EC2Cloud cloud) throws AmazonClientException { - if (StringUtils.isEmpty(instanceId) || cloud == null) + if (StringUtils.isEmpty(instanceId) || cloud == null) { return null; + } DescribeInstancesRequest request = new DescribeInstancesRequest(); request.setInstanceIds(Collections.singletonList(instanceId)); - List reservations = cloud.connect().describeInstances(request).getReservations(); + List reservations = + cloud.connect().describeInstances(request).getReservations(); if (reservations.size() != 1) { - String message = "Unexpected number of reservations reported by EC2 for instance id '" + instanceId + "', expected 1 result, found " + reservations + "."; - if (reservations.size() == 0) { - message += " Instance seems to be dead."; - } - LOGGER.info(message); - throw new AmazonClientException(message); + String message = "Unexpected number of reservations reported by EC2 for instance id '" + instanceId + + "', expected 1 result, found " + reservations + "."; + if (reservations.size() == 0) { + message += " Instance seems to be dead."; + } + LOGGER.info(message); + throw new AmazonClientException(message); } Reservation reservation = reservations.get(0); List instances = reservation.getInstances(); if (instances.size() != 1) { - String message = "Unexpected number of instances reported by EC2 for instance id '" + instanceId + "', expected 1 result, found " + instances + "."; - if (instances.size() == 0) { - message += " Instance seems to be dead."; - } - LOGGER.info(message); - throw new AmazonClientException(message); + String message = "Unexpected number of instances reported by EC2 for instance id '" + instanceId + + "', expected 1 result, found " + instances + "."; + if (instances.size() == 0) { + message += " Instance seems to be dead."; + } + LOGGER.info(message); + throw new AmazonClientException(message); } return instances.get(0); } diff --git a/src/main/java/hudson/plugins/ec2/ConnectionStrategy.java b/src/main/java/hudson/plugins/ec2/ConnectionStrategy.java index f182b712d..bb49716a2 100644 --- a/src/main/java/hudson/plugins/ec2/ConnectionStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ConnectionStrategy.java @@ -23,7 +23,8 @@ public String getDisplayText() { * @param associatePublicIp whether or not to associate to a public ip. * @return an {@link ConnectionStrategy} based on provided parameters. */ - public static ConnectionStrategy backwardsCompatible(boolean usePrivateDnsName, boolean connectUsingPublicIp, boolean associatePublicIp) { + public static ConnectionStrategy backwardsCompatible( + boolean usePrivateDnsName, boolean connectUsingPublicIp, boolean associatePublicIp) { if (usePrivateDnsName && !connectUsingPublicIp) { return PRIVATE_DNS; } else if (connectUsingPublicIp || associatePublicIp) { diff --git a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java index 949124021..e70319f68 100644 --- a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java @@ -23,6 +23,20 @@ */ package hudson.plugins.ec2; +import com.amazonaws.AmazonClientException; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.model.AvailabilityZone; +import com.amazonaws.services.ec2.model.CreateTagsRequest; +import com.amazonaws.services.ec2.model.DeleteTagsRequest; +import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; +import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.InstanceBlockDeviceMapping; +import com.amazonaws.services.ec2.model.InstanceStateName; +import com.amazonaws.services.ec2.model.InstanceType; +import com.amazonaws.services.ec2.model.StopInstancesRequest; +import com.amazonaws.services.ec2.model.Tag; +import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import hudson.Util; import hudson.model.Computer; import hudson.model.Descriptor; @@ -31,11 +45,11 @@ import hudson.model.Slave; import hudson.plugins.ec2.util.AmazonEC2Factory; import hudson.plugins.ec2.util.ResettableCountDownLatch; -import hudson.slaves.NodeProperty; import hudson.slaves.ComputerLauncher; +import hudson.slaves.NodeProperty; import hudson.slaves.RetentionStrategy; import hudson.util.ListBoxModel; - +import hudson.util.Secret; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; @@ -45,29 +59,11 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; - -import hudson.util.Secret; import jenkins.model.Jenkins; import net.sf.json.JSONObject; - import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.AvailabilityZone; -import com.amazonaws.services.ec2.model.CreateTagsRequest; -import com.amazonaws.services.ec2.model.DeleteTagsRequest; -import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceBlockDeviceMapping; -import com.amazonaws.services.ec2.model.InstanceStateName; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.StopInstancesRequest; -import com.amazonaws.services.ec2.model.Tag; -import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import org.kohsuke.stapler.verb.POST; /** @@ -91,6 +87,7 @@ public abstract class EC2AbstractSlave extends Slave { * Comes from {@link SlaveTemplate#initScript}. */ public final String initScript; + public final String tmpDir; public final String remoteAdmin; // e.g. 'ubuntu' @@ -134,8 +131,8 @@ public abstract class EC2AbstractSlave extends Slave { * The time (in milliseconds) after which we will always re-fetch externally changeable EC2 data when we are asked * for it */ - protected static final long MIN_FETCH_TIME = Long.getLong("hudson.plugins.ec2.EC2AbstractSlave.MIN_FETCH_TIME", - TimeUnit.SECONDS.toMillis(20)); + protected static final long MIN_FETCH_TIME = + Long.getLong("hudson.plugins.ec2.EC2AbstractSlave.MIN_FETCH_TIME", TimeUnit.SECONDS.toMillis(20)); protected final int launchTimeout; @@ -157,7 +154,35 @@ public abstract class EC2AbstractSlave extends Slave { public static final String TEST_ZONE = "testZone"; - public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported) + public EC2AbstractSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit, + Boolean metadataSupported) throws FormException, IOException { super(name, remoteFS, launcher); setNumExecutors(numExecutors); @@ -191,28 +216,224 @@ public EC2AbstractSlave(String name, String instanceId, String templateDescripti } @Deprecated - public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit) + public EC2AbstractSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + launcher, + retentionStrategy, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + tenancy, + metadataEndpointEnabled, + metadataTokensRequired, + metadataHopsLimit, + DEFAULT_METADATA_SUPPORTED); } @Deprecated - public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy) + public EC2AbstractSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + launcher, + retentionStrategy, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + tenancy, + DEFAULT_METADATA_ENDPOINT_ENABLED, + DEFAULT_METADATA_TOKENS_REQUIRED, + DEFAULT_METADATA_HOPS_LIMIT); } @Deprecated - public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List tags, String cloudName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses) + public EC2AbstractSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + List tags, + String cloudName, + boolean useDedicatedTenancy, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, Tenancy.backwardsCompatible(useDedicatedTenancy)); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + launcher, + retentionStrategy, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + Tenancy.backwardsCompatible(useDedicatedTenancy)); } @Deprecated - public EC2AbstractSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String labelString, ComputerLauncher launcher, RetentionStrategy retentionStrategy, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, List tags, String cloudName, boolean usePrivateDnsName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType) + public EC2AbstractSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String labelString, + ComputerLauncher launcher, + RetentionStrategy retentionStrategy, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + List tags, + String cloudName, + boolean usePrivateDnsName, + boolean useDedicatedTenancy, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, launcher, retentionStrategy, initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, useDedicatedTenancy, launchTimeout, amiType, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), -1); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + launcher, + retentionStrategy, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + tags, + cloudName, + useDedicatedTenancy, + launchTimeout, + amiType, + ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), + -1); } @Override @@ -228,7 +449,8 @@ protected Object readResolve() { } if (o.amiType == null) { - o.amiType = new UnixData(o.rootCommandPrefix, o.slaveCommandPrefix, o.slaveCommandSuffix, Integer.toString(o.sshPort), null); + o.amiType = new UnixData( + o.rootCommandPrefix, o.slaveCommandPrefix, o.slaveCommandSuffix, Integer.toString(o.sshPort), null); } if (o.maxTotalUses == 0) { @@ -263,174 +485,174 @@ public EC2Cloud getCloud() { /** * See http://aws.amazon.com/ec2/instance-types/ */ - /* package */static int toNumExecutors(InstanceType it) { + /* package */ static int toNumExecutors(InstanceType it) { switch (it) { - case T1Micro: - return 1; - case M1Small: - return 1; - case M1Medium: - return 2; - case M3Medium: - return 2; - case T3Nano: - return 2; - case T3aNano: - return 2; - case T3Micro: - return 2; - case T3aMicro: - return 2; - case T3Small: - return 2; - case T3aSmall: - return 2; - case T3Medium: - return 2; - case T3aMedium: - return 2; - case A1Large: - return 2; - case T3Large: - return 3; - case T3aLarge: - return 3; - case M1Large: - return 4; - case M3Large: - return 4; - case M4Large: - return 4; - case M5Large: - return 4; - case M5aLarge: - return 4; - case T3Xlarge: - return 5; - case T3aXlarge: - return 5; - case A1Xlarge: - return 5; - case C1Medium: - return 5; - case M2Xlarge: - return 6; - case C3Large: - return 7; - case C4Large: - return 7; - case C5Large: - return 7; - case C5dLarge: - return 7; - case M1Xlarge: - return 8; - case T32xlarge: - return 10; - case T3a2xlarge: - return 10; - case A12xlarge: - return 10; - case M22xlarge: - return 13; - case M3Xlarge: - return 13; - case M4Xlarge: - return 13; - case M5Xlarge: - return 13; - case M5aXlarge: - return 13; - case A14xlarge: - return 14; - case C3Xlarge: - return 14; - case C4Xlarge: - return 14; - case C5Xlarge: - return 14; - case C5dXlarge: - return 14; - case C1Xlarge: - return 20; - case M24xlarge: - return 26; - case M32xlarge: - return 26; - case M42xlarge: - return 26; - case M52xlarge: - return 26; - case M5a2xlarge: - return 26; - case G22xlarge: - return 26; - case C32xlarge: - return 28; - case C42xlarge: - return 28; - case C52xlarge: - return 28; - case C5d2xlarge: - return 28; - case Cc14xlarge: - return 33; - case Cg14xlarge: - return 33; - case Hi14xlarge: - return 35; - case Hs18xlarge: - return 35; - case C34xlarge: - return 55; - case C44xlarge: - return 55; - case C54xlarge: - return 55; - case C5d4xlarge: - return 55; - case M44xlarge: - return 55; - case M54xlarge: - return 55; - case M5a4xlarge: - return 55; - case Cc28xlarge: - return 88; - case Cr18xlarge: - return 88; - case C38xlarge: - return 108; - case C48xlarge: - return 108; - case C59xlarge: - return 108; - case C5d9xlarge: - return 108; - case M410xlarge: - return 120; - case M512xlarge: - return 120; - case M5a12xlarge: - return 120; - case M416xlarge: - return 160; - case C518xlarge: - return 216; - case C5d18xlarge: - return 216; - case M524xlarge: - return 240; - case M5a24xlarge: - return 240; - case Dl124xlarge: - return 250; - case Mac1Metal: - return 1; - // We don't have a suggestion, but we don't want to fail completely - // surely? - default: - return 1; + case T1Micro: + return 1; + case M1Small: + return 1; + case M1Medium: + return 2; + case M3Medium: + return 2; + case T3Nano: + return 2; + case T3aNano: + return 2; + case T3Micro: + return 2; + case T3aMicro: + return 2; + case T3Small: + return 2; + case T3aSmall: + return 2; + case T3Medium: + return 2; + case T3aMedium: + return 2; + case A1Large: + return 2; + case T3Large: + return 3; + case T3aLarge: + return 3; + case M1Large: + return 4; + case M3Large: + return 4; + case M4Large: + return 4; + case M5Large: + return 4; + case M5aLarge: + return 4; + case T3Xlarge: + return 5; + case T3aXlarge: + return 5; + case A1Xlarge: + return 5; + case C1Medium: + return 5; + case M2Xlarge: + return 6; + case C3Large: + return 7; + case C4Large: + return 7; + case C5Large: + return 7; + case C5dLarge: + return 7; + case M1Xlarge: + return 8; + case T32xlarge: + return 10; + case T3a2xlarge: + return 10; + case A12xlarge: + return 10; + case M22xlarge: + return 13; + case M3Xlarge: + return 13; + case M4Xlarge: + return 13; + case M5Xlarge: + return 13; + case M5aXlarge: + return 13; + case A14xlarge: + return 14; + case C3Xlarge: + return 14; + case C4Xlarge: + return 14; + case C5Xlarge: + return 14; + case C5dXlarge: + return 14; + case C1Xlarge: + return 20; + case M24xlarge: + return 26; + case M32xlarge: + return 26; + case M42xlarge: + return 26; + case M52xlarge: + return 26; + case M5a2xlarge: + return 26; + case G22xlarge: + return 26; + case C32xlarge: + return 28; + case C42xlarge: + return 28; + case C52xlarge: + return 28; + case C5d2xlarge: + return 28; + case Cc14xlarge: + return 33; + case Cg14xlarge: + return 33; + case Hi14xlarge: + return 35; + case Hs18xlarge: + return 35; + case C34xlarge: + return 55; + case C44xlarge: + return 55; + case C54xlarge: + return 55; + case C5d4xlarge: + return 55; + case M44xlarge: + return 55; + case M54xlarge: + return 55; + case M5a4xlarge: + return 55; + case Cc28xlarge: + return 88; + case Cr18xlarge: + return 88; + case C38xlarge: + return 108; + case C48xlarge: + return 108; + case C59xlarge: + return 108; + case C5d9xlarge: + return 108; + case M410xlarge: + return 120; + case M512xlarge: + return 120; + case M5a12xlarge: + return 120; + case M416xlarge: + return 160; + case C518xlarge: + return 216; + case C5d18xlarge: + return 216; + case M524xlarge: + return 240; + case M5a24xlarge: + return 240; + case Dl124xlarge: + return 250; + case Mac1Metal: + return 1; + // We don't have a suggestion, but we don't want to fail completely + // surely? + default: + return 1; } } @@ -482,13 +704,13 @@ void stop() { } catch (AmazonClientException e) { LOGGER.log(Level.WARNING, "Failed to stop EC2 instance: " + getInstanceId(), e); } - } boolean terminateInstance() { try { AmazonEC2 ec2 = getCloud().connect(); - TerminateInstancesRequest request = new TerminateInstancesRequest(Collections.singletonList(getInstanceId())); + TerminateInstancesRequest request = + new TerminateInstancesRequest(Collections.singletonList(getInstanceId())); LOGGER.fine("Sending terminate request for " + getInstanceId()); ec2.terminateInstances(request); LOGGER.info("EC2 instance terminate request sent for " + getInstanceId()); @@ -532,7 +754,7 @@ void idleTimeout() { } } - void launchTimeout(){ + void launchTimeout() { LOGGER.info("EC2 instance failed to launch: " + getInstanceId()); terminate(); } @@ -543,29 +765,39 @@ public long getLaunchTimeoutInMillis() { } public String getRemoteAdmin() { - if (remoteAdmin == null || remoteAdmin.length() == 0) + if (remoteAdmin == null || remoteAdmin.length() == 0) { return amiType.isWindows() ? "Administrator" : "root"; + } return remoteAdmin; } String getRootCommandPrefix() { - String commandPrefix = (amiType.isUnix() ? ((UnixData) amiType).getRootCommandPrefix() : (amiType.isMac() ? ((MacData) amiType).getRootCommandPrefix() : "")); - if (commandPrefix == null || commandPrefix.length() == 0) + String commandPrefix = (amiType.isUnix() + ? ((UnixData) amiType).getRootCommandPrefix() + : (amiType.isMac() ? ((MacData) amiType).getRootCommandPrefix() : "")); + if (commandPrefix == null || commandPrefix.length() == 0) { return ""; + } return commandPrefix + " "; } String getSlaveCommandPrefix() { - String commandPrefix = (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandPrefix() :(amiType.isMac() ? ((MacData) amiType).getSlaveCommandPrefix() : "")); - if (commandPrefix == null || commandPrefix.length() == 0) + String commandPrefix = (amiType.isUnix() + ? ((UnixData) amiType).getSlaveCommandPrefix() + : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandPrefix() : "")); + if (commandPrefix == null || commandPrefix.length() == 0) { return ""; + } return commandPrefix + " "; } String getSlaveCommandSuffix() { - String commandSuffix = (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandSuffix() :(amiType.isMac() ? ((MacData) amiType).getSlaveCommandSuffix() : "")); - if (commandSuffix == null || commandSuffix.length() == 0) + String commandSuffix = (amiType.isUnix() + ? ((UnixData) amiType).getSlaveCommandSuffix() + : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandSuffix() : "")); + if (commandSuffix == null || commandSuffix.length() == 0) { return ""; + } return " " + commandSuffix; } @@ -578,9 +810,12 @@ String getJvmopts() { } public int getSshPort() { - String sshPort = (amiType.isUnix() ? ((UnixData) amiType).getSshPort() :(amiType.isMac() ? ((MacData) amiType).getSshPort() : "22")); - if (sshPort == null || sshPort.length() == 0) + String sshPort = (amiType.isUnix() + ? ((UnixData) amiType).getSshPort() + : (amiType.isMac() ? ((MacData) amiType).getSshPort() : "22")); + if (sshPort == null || sshPort.length() == 0) { return 22; + } int port = 0; try { @@ -603,10 +838,12 @@ public void onConnected() { protected boolean isAlive(boolean force) { fetchLiveInstanceData(force); - if (lastFetchInstance == null) + if (lastFetchInstance == null) { return false; - if (lastFetchInstance.getState().getName().equals(InstanceStateName.Terminated.toString())) + } + if (lastFetchInstance.getState().getName().equals(InstanceStateName.Terminated.toString())) { return false; + } return true; } @@ -639,16 +876,15 @@ private void fetchLiveInstanceData(boolean force) throws AmazonClientException { i = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); } catch (InterruptedException e) { // We'll just retry next time we test for idleness. - LOGGER.fine("InterruptedException while get " + getInstanceId() - + " Exception: " + e); + LOGGER.fine("InterruptedException while get " + getInstanceId() + " Exception: " + e); return; } - lastFetchTime = now; lastFetchInstance = i; - if (i == null) + if (i == null) { return; + } publicDNS = i.getPublicDnsName(); privateDNS = i.getPrivateIpAddress(); @@ -659,7 +895,7 @@ private void fetchLiveInstanceData(boolean force) throws AmazonClientException { * Only fetch tags from live instance if tags are set. This check is required to mitigate a race condition * when fetchLiveInstanceData() is called before pushLiveInstancedata(). */ - if(!i.getTags().isEmpty()) { + if (!i.getTags().isEmpty()) { tags = new LinkedList(); for (Tag t : i.getTags()) { tags.add(new EC2Tag(t.getKey(), t.getValue())); @@ -676,12 +912,10 @@ protected void clearLiveInstancedata() throws AmazonClientException { inst = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); } catch (InterruptedException e) { // We'll just retry next time we test for idleness. - LOGGER.fine("InterruptedException while get " + getInstanceId() - + " Exception: " + e); + LOGGER.fine("InterruptedException while get " + getInstanceId() + " Exception: " + e); return; } - /* Now that we have our instance, we can clear the tags on it */ if (!tags.isEmpty()) { HashSet instTags = new HashSet(); @@ -707,11 +941,9 @@ protected void pushLiveInstancedata() throws AmazonClientException { inst = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); } catch (InterruptedException e) { // We'll just retry next time we test for idleness. - LOGGER.fine("InterruptedException while get " + getInstanceId() - + " Exception: " + e); + LOGGER.fine("InterruptedException while get " + getInstanceId() + " Exception: " + e); } - /* Now that we have our instance, we can set tags on it */ if (inst != null && tags != null && !tags.isEmpty()) { HashSet instTags = new HashSet(); @@ -733,7 +965,7 @@ protected void pushLiveInstancedata() throws AmazonClientException { private List getResourcesToTag(Instance inst) { List resources = new ArrayList<>(); resources.add(inst.getInstanceId()); - for(InstanceBlockDeviceMapping blockDeviceMapping : inst.getBlockDeviceMappings()) { + for (InstanceBlockDeviceMapping blockDeviceMapping : inst.getBlockDeviceMappings()) { resources.add(blockDeviceMapping.getEbs().getVolumeId()); } return resources; @@ -782,11 +1014,11 @@ public int getBootDelay() { } public Boolean getMetadataSupported() { - return metadataSupported; + return metadataSupported; } public Boolean getMetadataEndpointEnabled() { - return metadataEndpointEnabled; + return metadataEndpointEnabled; } public Boolean getMetadataTokensRequired() { @@ -809,7 +1041,8 @@ public static ListBoxModel fillZoneItems(AWSCredentialsProvider credentialsProvi ListBoxModel model = new ListBoxModel(); if (!StringUtils.isEmpty(region)) { - AmazonEC2 client = AmazonEC2Factory.getInstance().connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + AmazonEC2 client = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); DescribeAvailabilityZonesResult zones = client.describeAvailabilityZones(); List zoneList = zones.getAvailabilityZones(); model.add("", ""); @@ -823,9 +1056,9 @@ public static ListBoxModel fillZoneItems(AWSCredentialsProvider credentialsProvi /* * Used to determine if the agent is On Demand or Spot */ - abstract public String getEc2Type(); + public abstract String getEc2Type(); - public static abstract class DescriptorImpl extends SlaveDescriptor { + public abstract static class DescriptorImpl extends SlaveDescriptor { @Override public abstract String getDisplayName(); @@ -836,15 +1069,17 @@ public boolean isInstantiable() { } @POST - public ListBoxModel doFillZoneItems(@QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, - @QueryParameter String region, - @QueryParameter String roleArn, - @QueryParameter String roleSessionName) { + public ListBoxModel doFillZoneItems( + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String region, + @QueryParameter String roleArn, + @QueryParameter String roleSessionName) { if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { return new ListBoxModel(); } - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); + AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); return fillZoneItems(credentialsProvider, region); } @@ -852,5 +1087,4 @@ public List> getAMITypeDescriptors() { return Jenkins.get().getDescriptorList(AMITypeData.class); } } - } diff --git a/src/main/java/hudson/plugins/ec2/EC2Cloud.java b/src/main/java/hudson/plugins/ec2/EC2Cloud.java index 138a94179..59d06d0c6 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Cloud.java +++ b/src/main/java/hudson/plugins/ec2/EC2Cloud.java @@ -18,6 +18,9 @@ */ package hudson.plugins.ec2; +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.ClientConfiguration; @@ -47,6 +50,7 @@ import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.domains.DomainRequirement; +import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.ProxyConfiguration; @@ -67,17 +71,6 @@ import hudson.util.ListBoxModel; import hudson.util.Secret; import hudson.util.StreamTaskListener; -import jenkins.model.Jenkins; -import jenkins.model.JenkinsLocationConfiguration; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import javax.servlet.ServletException; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintStream; @@ -95,10 +88,15 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; - -import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; -import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - +import javax.servlet.ServletException; +import jenkins.model.Jenkins; +import jenkins.model.JenkinsLocationConfiguration; +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; @@ -144,15 +142,19 @@ public abstract class EC2Cloud extends Cloud { */ @CheckForNull private String credentialsId; + @CheckForNull @Deprecated private transient String accessId; + @CheckForNull @Deprecated private transient Secret secretKey; + @CheckForNull @Deprecated private transient EC2PrivateKey privateKey; + @CheckForNull private String sshKeysCredentialsId; @@ -167,8 +169,16 @@ public abstract class EC2Cloud extends Cloud { private transient volatile AmazonEC2 connection; - protected EC2Cloud(String name, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String sshKeysCredentialsId, - String instanceCapStr, List templates, String roleArn, String roleSessionName) { + protected EC2Cloud( + String name, + boolean useInstanceProfileForCredentials, + String credentialsId, + String privateKey, + String sshKeysCredentialsId, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { super(name); this.useInstanceProfileForCredentials = useInstanceProfileForCredentials; this.roleArn = roleArn; @@ -192,13 +202,29 @@ protected EC2Cloud(String name, boolean useInstanceProfileForCredentials, String } @Deprecated - protected EC2Cloud(String id, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, - String instanceCapStr, List templates, String roleArn, String roleSessionName) { - this(id, useInstanceProfileForCredentials, credentialsId, privateKey, null, instanceCapStr, templates, roleArn, roleSessionName); + protected EC2Cloud( + String id, + boolean useInstanceProfileForCredentials, + String credentialsId, + String privateKey, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { + this( + id, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + null, + instanceCapStr, + templates, + roleArn, + roleSessionName); } @CheckForNull - public EC2PrivateKey resolvePrivateKey(){ + public EC2PrivateKey resolvePrivateKey() { if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) { LOGGER.fine(() -> "(resolvePrivateKey) secret key file configured, will load from disk"); return EC2PrivateKey.fetchFromDisk(); @@ -218,18 +244,23 @@ public EC2PrivateKey resolvePrivateKey(){ public void addTemplate(SlaveTemplate newTemplate) throws Exception { String newTemplateDescription = newTemplate.description; - if (getTemplate(newTemplateDescription) != null) throw new Exception( - String.format("A SlaveTemplate with description %s already exists", newTemplateDescription)); + if (getTemplate(newTemplateDescription) != null) { + throw new Exception( + String.format("A SlaveTemplate with description %s already exists", newTemplateDescription)); + } List templatesHolder = new ArrayList<>(templates); templatesHolder.add(newTemplate); templates = templatesHolder; } - public void updateTemplate(SlaveTemplate newTemplate, String oldTemplateDescription) throws Exception{ - Optional optionalOldTemplate = templates.stream().filter(template -> - Objects.equals(template.description, oldTemplateDescription)).findFirst(); - if (!optionalOldTemplate.isPresent()) - throw new Exception(String.format("A SlaveTemplate with description %s does not exist", oldTemplateDescription)); + public void updateTemplate(SlaveTemplate newTemplate, String oldTemplateDescription) throws Exception { + Optional optionalOldTemplate = templates.stream() + .filter(template -> Objects.equals(template.description, oldTemplateDescription)) + .findFirst(); + if (!optionalOldTemplate.isPresent()) { + throw new Exception( + String.format("A SlaveTemplate with description %s does not exist", oldTemplateDescription)); + } int oldTemplateIndex = templates.indexOf(optionalOldTemplate.get()); List templatesHolder = new ArrayList<>(templates); templatesHolder.set(oldTemplateIndex, newTemplate); @@ -238,28 +269,33 @@ public void updateTemplate(SlaveTemplate newTemplate, String oldTemplateDescript private void migratePrivateSshKeyToCredential(String privateKey) { // GET matching private key credential from Credential API if exists - Optional keyCredential = SystemCredentialsProvider.getInstance().getCredentials() - .stream() - .filter((cred) -> cred instanceof SSHUserPrivateKey) - .filter((cred) -> ((SSHUserPrivateKey)cred).getPrivateKey().trim().equals(privateKey.trim())) - .map(cred -> (SSHUserPrivateKey)cred) + Optional keyCredential = SystemCredentialsProvider.getInstance().getCredentials().stream() + .filter(SSHUserPrivateKey.class::isInstance) + .filter(cred -> + ((SSHUserPrivateKey) cred).getPrivateKey().trim().equals(privateKey.trim())) + .map(cred -> (SSHUserPrivateKey) cred) .findFirst(); - if (keyCredential.isPresent()){ + if (keyCredential.isPresent()) { // SET this.sshKeysCredentialsId with the found credential sshKeysCredentialsId = keyCredential.get().getId(); } else { // CREATE new credential String credsId = UUID.randomUUID().toString(); - SSHUserPrivateKey sshKeyCredentials = new BasicSSHUserPrivateKey(CredentialsScope.SYSTEM, credsId, "key", + SSHUserPrivateKey sshKeyCredentials = new BasicSSHUserPrivateKey( + CredentialsScope.SYSTEM, + credsId, + "key", new BasicSSHUserPrivateKey.PrivateKeySource() { @NonNull @Override public List getPrivateKeys() { return Collections.singletonList(privateKey.trim()); } - }, "", "EC2 Cloud Private Key - " + getDisplayName()); + }, + "", + "EC2 Cloud Private Key - " + getDisplayName()); addNewGlobalCredential(sshKeyCredentials); @@ -270,13 +306,15 @@ public List getPrivateKeys() { protected Object readResolve() { this.slaveCountingLock = new ReentrantLock(); - for (SlaveTemplate t : templates) + for (SlaveTemplate t : templates) { t.parent = this; + } - if (this.sshKeysCredentialsId == null && this.privateKey != null ){ + if (this.sshKeysCredentialsId == null && this.privateKey != null) { migratePrivateSshKeyToCredential(this.privateKey.getPrivateKey()); } - this.privateKey = null; // This enforces it not to be persisted and that CasC will never output privateKey on export + this.privateKey = + null; // This enforces it not to be persisted and that CasC will never output privateKey on export if (this.accessId != null && this.secretKey != null && credentialsId == null) { String secretKeyEncryptedValue = this.secretKey.getEncryptedValue(); @@ -285,12 +323,12 @@ protected Object readResolve() { SystemCredentialsProvider systemCredentialsProvider = SystemCredentialsProvider.getInstance(); // ITERATE ON EXISTING CREDS AND DON'T CREATE IF EXIST - for (Credentials credentials: systemCredentialsProvider.getCredentials()) { + for (Credentials credentials : systemCredentialsProvider.getCredentials()) { if (credentials instanceof AmazonWebServicesCredentials) { AmazonWebServicesCredentials awsCreds = (AmazonWebServicesCredentials) credentials; AWSCredentials awsCredentials = awsCreds.getCredentials(); - if (accessId.equals(awsCredentials.getAWSAccessKeyId()) && - Secret.toString(this.secretKey).equals(awsCredentials.getAWSSecretKey())) { + if (accessId.equals(awsCredentials.getAWSAccessKeyId()) + && Secret.toString(this.secretKey).equals(awsCredentials.getAWSSecretKey())) { this.credentialsId = awsCreds.getId(); this.accessId = null; @@ -303,34 +341,39 @@ protected Object readResolve() { // CREATE String credsId = UUID.randomUUID().toString(); addNewGlobalCredential(new AWSCredentialsImpl( - CredentialsScope.SYSTEM, credsId, this.accessId, secretKeyEncryptedValue, + CredentialsScope.SYSTEM, + credsId, + this.accessId, + secretKeyEncryptedValue, "EC2 Cloud - " + getDisplayName())); this.credentialsId = credsId; this.accessId = null; this.secretKey = null; - // PROBLEM, GLOBAL STORE NOT FOUND - LOGGER.log(Level.WARNING, "EC2 Plugin could not migrate credentials to the Jenkins Global Credentials Store, EC2 Plugin for cloud {0} must be manually reconfigured", getDisplayName()); + LOGGER.log( + Level.WARNING, + "EC2 Plugin could not migrate credentials to the Jenkins Global Credentials Store, EC2 Plugin for cloud {0} must be manually reconfigured", + getDisplayName()); } return this; } - private void addNewGlobalCredential(Credentials credentials){ - for (CredentialsStore credentialsStore: CredentialsProvider.lookupStores(Jenkins.get())) { + private void addNewGlobalCredential(Credentials credentials) { + for (CredentialsStore credentialsStore : CredentialsProvider.lookupStores(Jenkins.get())) { - if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { + if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { try { credentialsStore.addCredentials(Domain.global(), credentials); } catch (IOException e) { this.credentialsId = null; - LOGGER.log(Level.WARNING, "Exception converting legacy configuration to the new credentials API", e); + LOGGER.log( + Level.WARNING, "Exception converting legacy configuration to the new credentials API", e); } } - } } @@ -361,10 +404,11 @@ public EC2PrivateKey getPrivateKey() { } public String getInstanceCapStr() { - if (instanceCap == Integer.MAX_VALUE) + if (instanceCap == Integer.MAX_VALUE) { return ""; - else + } else { return String.valueOf(instanceCap); + } } public int getInstanceCap() { @@ -474,17 +518,20 @@ public HttpResponse doProvision(@QueryParameter String template) throws ServletE } try { List nodes = getNewOrExistingAvailableSlave(t, 1, true); - if (nodes == null || nodes.isEmpty()) - throw HttpResponses.error(SC_BAD_REQUEST, "Cloud or AMI instance cap would be exceeded for: " + template); + if (nodes == null || nodes.isEmpty()) { + throw HttpResponses.error( + SC_BAD_REQUEST, "Cloud or AMI instance cap would be exceeded for: " + template); + } - //Reconnect a stopped instance, the ADD is invoking the connect only for the node creation + // Reconnect a stopped instance, the ADD is invoking the connect only for the node creation Computer c = nodes.get(0).toComputer(); - if (nodes.get(0).getStopOnTerminate() && c != null) { + if (nodes.get(0).getStopOnTerminate() && c != null) { c.connect(false); } jenkinsInstance.addNode(nodes.get(0)); - return HttpResponses.redirectViaContextPath("/computer/" + nodes.get(0).getNodeName()); + return HttpResponses.redirectViaContextPath( + "/computer/" + nodes.get(0).getNodeName()); } catch (AmazonClientException e) { throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR, e); } @@ -500,13 +547,19 @@ private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientExc String jenkinsServerUrl = JenkinsLocationConfiguration.get().getUrl(); if (jenkinsServerUrl == null) { - LOGGER.log(Level.WARNING, "No Jenkins server URL specified, it is strongly recommended to open /configure and set the server URL. " + - "Not having has disabled the per-controller instance cap counting (cf. https://github.com/jenkinsci/ec2-plugin/pull/310)"); + LOGGER.log( + Level.WARNING, + "No Jenkins server URL specified, it is strongly recommended to open /configure and set the server URL. " + + "Not having has disabled the per-controller instance cap counting (cf. https://github.com/jenkinsci/ec2-plugin/pull/310)"); } - LOGGER.log(Level.FINE, "Counting current agents: " - + (template != null ? (" AMI: " + template.getAmi() + " TemplateDesc: " + template.description) : " All AMIS") - + " Jenkins Server: " + jenkinsServerUrl); + LOGGER.log( + Level.FINE, + "Counting current agents: " + + (template != null + ? (" AMI: " + template.getAmi() + " TemplateDesc: " + template.description) + : " All AMIS") + + " Jenkins Server: " + jenkinsServerUrl); int n = 0; Set instanceIds = new HashSet<>(); String description = template != null ? template.description : null; @@ -521,14 +574,17 @@ private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientExc for (Reservation r : result.getReservations()) { for (Instance i : r.getInstances()) { if (isEc2ProvisionedAmiSlave(i.getTags(), description)) { - LOGGER.log(Level.FINE, "Existing instance found: " + i.getInstanceId() + " AMI: " + i.getImageId() - + (template != null ? (" Template: " + description) : "") + " Jenkins Server: " + jenkinsServerUrl); + LOGGER.log( + Level.FINE, + "Existing instance found: " + i.getInstanceId() + " AMI: " + i.getImageId() + + (template != null ? (" Template: " + description) : "") + " Jenkins Server: " + + jenkinsServerUrl); n++; instanceIds.add(i.getInstanceId()); } } } - } while(result.getNextToken() != null); + } while (result.getNextToken() != null); n += countCurrentEC2SpotSlaves(template, jenkinsServerUrl, instanceIds); @@ -541,7 +597,8 @@ private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientExc * * @param template If left null, then all spot instances are counted. */ - private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServerUrl, Set instanceIds) throws AmazonClientException { + private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServerUrl, Set instanceIds) + throws AmazonClientException { int n = 0; String description = template != null ? template.description : null; List sirs = null; @@ -550,7 +607,8 @@ private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServ filters.add(new Filter("launch.image-id").withValues(template.getAmi())); } - DescribeSpotInstanceRequestsRequest dsir = new DescribeSpotInstanceRequestsRequest().withFilters(filters).withMaxResults(100); + DescribeSpotInstanceRequestsRequest dsir = + new DescribeSpotInstanceRequestsRequest().withFilters(filters).withMaxResults(100); Set sirSet = new HashSet<>(); DescribeSpotInstanceRequestsResult sirResp = null; @@ -569,51 +627,66 @@ private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServ for (SpotInstanceRequest sir : sirs) { sirSet.add(sir); if (sir.getState().equals("open") || sir.getState().equals("active")) { - if (sir.getInstanceId() != null && instanceIds.contains(sir.getInstanceId())) + if (sir.getInstanceId() != null && instanceIds.contains(sir.getInstanceId())) { continue; + } if (isEc2ProvisionedAmiSlave(sir.getTags(), description)) { - LOGGER.log(Level.FINE, "Spot instance request found: " + sir.getSpotInstanceRequestId() + " AMI: " - + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); + LOGGER.log( + Level.FINE, + "Spot instance request found: " + sir.getSpotInstanceRequestId() + " AMI: " + + sir.getInstanceId() + " state: " + sir.getState() + " status: " + + sir.getStatus()); n++; - if (sir.getInstanceId() != null) + if (sir.getInstanceId() != null) { instanceIds.add(sir.getInstanceId()); + } } } else { // Cancelled or otherwise dead for (Node node : Jenkins.get().getNodes()) { try { - if (!(node instanceof EC2SpotSlave)) + if (!(node instanceof EC2SpotSlave)) { continue; + } EC2SpotSlave ec2Slave = (EC2SpotSlave) node; if (ec2Slave.getSpotInstanceRequestId().equals(sir.getSpotInstanceRequestId())) { - LOGGER.log(Level.INFO, "Removing dead request: " + sir.getSpotInstanceRequestId() + " AMI: " - + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); + LOGGER.log( + Level.INFO, + "Removing dead request: " + sir.getSpotInstanceRequestId() + " AMI: " + + sir.getInstanceId() + " state: " + sir.getState() + " status: " + + sir.getStatus()); Jenkins.get().removeNode(node); break; } } catch (IOException e) { - LOGGER.log(Level.WARNING, "Failed to remove node for dead request: " + sir.getSpotInstanceRequestId() - + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus(), + LOGGER.log( + Level.WARNING, + "Failed to remove node for dead request: " + sir.getSpotInstanceRequestId() + + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + + " status: " + sir.getStatus(), e); } } } } } - } while(sirResp.getNextToken() != null); + } while (sirResp.getNextToken() != null); n += countJenkinsNodeSpotInstancesWithoutRequests(template, sirSet, instanceIds); return n; } // Count nodes where the spot request does not yet exist (sometimes it takes time for the request to appear // in the EC2 API) - private int countJenkinsNodeSpotInstancesWithoutRequests(SlaveTemplate template, Set sirSet, Set instanceIds) throws AmazonClientException { + private int countJenkinsNodeSpotInstancesWithoutRequests( + SlaveTemplate template, Set sirSet, Set instanceIds) + throws AmazonClientException { int n = 0; for (Node node : Jenkins.get().getNodes()) { - if (!(node instanceof EC2SpotSlave)) + if (!(node instanceof EC2SpotSlave)) { continue; + } EC2SpotSlave ec2Slave = (EC2SpotSlave) node; SpotInstanceRequest sir = ec2Slave.getSpotRequest(); @@ -623,8 +696,9 @@ private int countJenkinsNodeSpotInstancesWithoutRequests(SlaveTemplate template, continue; } - if (sirSet.contains(sir)) + if (sirSet.contains(sir)) { continue; + } sirSet.add(sir); @@ -632,17 +706,25 @@ private int countJenkinsNodeSpotInstancesWithoutRequests(SlaveTemplate template, if (template != null) { List instanceTags = sir.getTags(); for (Tag tag : instanceTags) { - if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) && StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2_SLAVE_TYPE_SPOT, template.description)) && sir.getLaunchSpecification().getImageId().equals(template.getAmi())) { + if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + && StringUtils.equals( + tag.getValue(), getSlaveTypeTagValue(EC2_SLAVE_TYPE_SPOT, template.description)) + && sir.getLaunchSpecification().getImageId().equals(template.getAmi())) { - if (sir.getInstanceId() != null && instanceIds.contains(sir.getInstanceId())) + if (sir.getInstanceId() != null && instanceIds.contains(sir.getInstanceId())) { continue; + } - LOGGER.log(Level.FINE, "Spot instance request found (from node): " + sir.getSpotInstanceRequestId() + " AMI: " - + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); + LOGGER.log( + Level.FINE, + "Spot instance request found (from node): " + sir.getSpotInstanceRequestId() + + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + + sir.getStatus()); n++; - if (sir.getInstanceId() != null) + if (sir.getInstanceId() != null) { instanceIds.add(sir.getInstanceId()); + } } } } @@ -651,7 +733,6 @@ private int countJenkinsNodeSpotInstancesWithoutRequests(SlaveTemplate template, return n; } - private List getGenericFilters(String jenkinsServerUrl, SlaveTemplate template) { List filters = new ArrayList<>(); filters.add(new Filter("tag-key").withValues(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)); @@ -684,8 +765,10 @@ private boolean isEc2ProvisionedAmiSlave(List tags, String description) { || StringUtils.equals(tag.getValue(), EC2Cloud.EC2_SLAVE_TYPE_SPOT)) { // To handle cases where description is null and also upgrade cases for existing agent nodes. return true; - } else if (StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_DEMAND, description)) - || StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_SPOT, description))) { + } else if (StringUtils.equals( + tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_DEMAND, description)) + || StringUtils.equals( + tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_SPOT, description))) { return true; } else { return false; @@ -704,8 +787,10 @@ private int getPossibleNewSlavesCount(SlaveTemplate template) throws AmazonClien int availableTotalSlaves = instanceCap - estimatedTotalSlaves; int availableAmiSlaves = template.getInstanceCap() - estimatedAmiSlaves; - LOGGER.log(Level.FINE, "Available Total Agents: " + availableTotalSlaves + " Available AMI agents: " + availableAmiSlaves - + " AMI: " + template.getAmi() + " TemplateDesc: " + template.description); + LOGGER.log( + Level.FINE, + "Available Total Agents: " + availableTotalSlaves + " Available AMI agents: " + availableAmiSlaves + + " AMI: " + template.getAmi() + " TemplateDesc: " + template.description); return Math.min(availableAmiSlaves, availableTotalSlaves); } @@ -714,7 +799,8 @@ private int getPossibleNewSlavesCount(SlaveTemplate template) throws AmazonClien * Obtains a agent whose AMI matches the AMI of the given template, and that also has requiredLabel (if requiredLabel is non-null) * forceCreateNew specifies that the creation of a new agent is required. Otherwise, an existing matching agent may be re-used */ - private List getNewOrExistingAvailableSlave(SlaveTemplate t, int number, boolean forceCreateNew) throws IOException { + private List getNewOrExistingAvailableSlave(SlaveTemplate t, int number, boolean forceCreateNew) + throws IOException { try { slaveCountingLock.lock(); int possibleSlavesCount = getPossibleNewSlavesCount(t); @@ -724,19 +810,26 @@ private List getNewOrExistingAvailableSlave(SlaveTemplate t, i } EnumSet provisionOptions; - if (forceCreateNew) + if (forceCreateNew) { provisionOptions = EnumSet.of(SlaveTemplate.ProvisionOptions.FORCE_CREATE); - else + } else { provisionOptions = EnumSet.of(SlaveTemplate.ProvisionOptions.ALLOW_CREATE); + } if (number > possibleSlavesCount) { - LOGGER.log(Level.INFO, String.format("%d nodes were requested for the template %s, " + - "but because of instance cap only %d can be provisioned", number, t, possibleSlavesCount)); + LOGGER.log( + Level.INFO, + String.format( + "%d nodes were requested for the template %s, " + + "but because of instance cap only %d can be provisioned", + number, t, possibleSlavesCount)); number = possibleSlavesCount; } return t.provision(number, provisionOptions); - } finally { slaveCountingLock.unlock(); } + } finally { + slaveCountingLock.unlock(); + } } @Override @@ -755,7 +848,10 @@ public Collection provision(final Label label, int excessWorkload) for (SlaveTemplate t : matchingTemplates) { try { - LOGGER.log(Level.INFO, "{0}. Attempting to provision agent needed by excess workload of " + excessWorkload + " units", t); + LOGGER.log( + Level.INFO, + "{0}. Attempting to provision agent needed by excess workload of " + excessWorkload + " units", + t); int number = Math.max(excessWorkload / t.getNumExecutors(), 1); final List slaves = getNewOrExistingAvailableSlave(t, number, false); @@ -775,7 +871,9 @@ public Collection provision(final Label label, int excessWorkload) } LOGGER.log(Level.INFO, "{0}. Attempting provision finished, excess workload: " + excessWorkload, t); - if (excessWorkload == 0) break; + if (excessWorkload == 0) { + break; + } } catch (AmazonServiceException e) { LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); if (e.getErrorCode().equals("RequestExpired")) { @@ -791,12 +889,14 @@ public Collection provision(final Label label, int excessWorkload) LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); } } - LOGGER.log(Level.INFO, "We have now {0} computers, waiting for {1} more", - new Object[]{jenkinsInstance.getComputers().length, plannedNodes.size()}); + LOGGER.log(Level.INFO, "We have now {0} computers, waiting for {1} more", new Object[] { + jenkinsInstance.getComputers().length, plannedNodes.size() + }); return plannedNodes; } - private static void attachSlavesToJenkins(Jenkins jenkins, List slaves, SlaveTemplate t) throws IOException { + private static void attachSlavesToJenkins(Jenkins jenkins, List slaves, SlaveTemplate t) + throws IOException { for (final EC2AbstractSlave slave : slaves) { if (slave == null) { LOGGER.warning("Can't raise node for " + t); @@ -823,7 +923,7 @@ public void provision(SlaveTemplate t, int number) { } try { - LOGGER.log(Level.INFO, "{0}. Attempting to provision {1} agent(s)", new Object[]{t, number}); + LOGGER.log(Level.INFO, "{0}. Attempting to provision {1} agent(s)", new Object[] {t, number}); final List slaves = getNewOrExistingAvailableSlave(t, number, false); if (slaves == null || slaves.isEmpty()) { @@ -834,8 +934,9 @@ public void provision(SlaveTemplate t, int number) { attachSlavesToJenkins(jenkinsInstance, slaves, t); LOGGER.log(Level.INFO, "{0}. Attempting provision finished", t); - LOGGER.log(Level.INFO, "We have now {0} computers, waiting for {1} more", - new Object[]{Jenkins.get().getComputers().length, number}); + LOGGER.log(Level.INFO, "We have now {0} computers, waiting for {1} more", new Object[] { + Jenkins.get().getComputers().length, number + }); } catch (AmazonClientException | IOException e) { LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); } @@ -848,10 +949,11 @@ public void provision(SlaveTemplate t, int number) { * @param template The corresponding SlaveTemplate of the nodes that are to be re-attached * @param requestedNum The requested number of nodes to re-attach. We don't go above this in the case its value corresponds to an instance cap. */ - void attemptReattachOrphanOrStoppedNodes(Jenkins jenkinsInstance, SlaveTemplate template, int requestedNum) throws IOException { + void attemptReattachOrphanOrStoppedNodes(Jenkins jenkinsInstance, SlaveTemplate template, int requestedNum) + throws IOException { LOGGER.info("Attempting to wake & re-attach orphan/stopped nodes"); AmazonEC2 ec2 = this.connect(); - DescribeInstancesResult diResult = template.getDescribeInstanceResult(ec2,true); + DescribeInstancesResult diResult = template.getDescribeInstanceResult(ec2, true); List orphansOrStopped = template.findOrphansOrStopped(diResult, requestedNum); template.wakeOrphansOrStoppedUp(ec2, orphansOrStopped); /* If the number of possible nodes to re-attach is greater than the number of nodes requested, will only attempt to re-attach up to the number requested */ @@ -865,16 +967,22 @@ void attemptReattachOrphanOrStoppedNodes(Jenkins jenkinsInstance, SlaveTemplate } private PlannedNode createPlannedNode(final SlaveTemplate t, final EC2AbstractSlave slave) { - return new PlannedNode(t.getDisplayName(), + return new PlannedNode( + t.getDisplayName(), Computer.threadPoolForRemoting.submit(new Callable() { - int retryCount = 0; + int retryCount = 0; private static final int DESCRIBE_LIMIT = 2; + + @Override public Node call() throws Exception { while (true) { String instanceId = slave.getInstanceId(); if (slave instanceof EC2SpotSlave) { if (((EC2SpotSlave) slave).isSpotRequestDead()) { - LOGGER.log(Level.WARNING, "{0} Spot request died, can't do anything. Terminate provisioning", t); + LOGGER.log( + Level.WARNING, + "{0} Spot request died, can't do anything. Terminate provisioning", + t); return null; } @@ -887,47 +995,56 @@ public Node call() throws Exception { Instance instance = CloudHelper.getInstanceWithRetry(instanceId, slave.getCloud()); if (instance == null) { - LOGGER.log(Level.WARNING, "{0} Can't find instance with instance id `{1}` in cloud {2}. Terminate provisioning ", - new Object[]{t, instanceId, slave.cloudName}); + LOGGER.log( + Level.WARNING, + "{0} Can't find instance with instance id `{1}` in cloud {2}. Terminate provisioning ", + new Object[] {t, instanceId, slave.cloudName}); return null; } - InstanceStateName state = InstanceStateName.fromValue(instance.getState().getName()); - if (state.equals(InstanceStateName.Running)) { - //Spot instance are not reconnected automatically, + InstanceStateName state = InstanceStateName.fromValue( + instance.getState().getName()); + if (state.equals(InstanceStateName.Running)) { + // Spot instance are not reconnected automatically, // but could be new orphans that has the option enable Computer c = slave.toComputer(); - if (slave.getStopOnTerminate() && (c != null )) { + if (slave.getStopOnTerminate() && (c != null)) { c.connect(false); } - long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - instance.getLaunchTime().getTime()); - LOGGER.log(Level.INFO, "{0} Node {1} moved to RUNNING state in {2} seconds and is ready to be connected by Jenkins", - new Object[]{t, slave.getNodeName(), startTime}); + long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() + - instance.getLaunchTime().getTime()); + LOGGER.log( + Level.INFO, + "{0} Node {1} moved to RUNNING state in {2} seconds and is ready to be connected by Jenkins", + new Object[] {t, slave.getNodeName(), startTime}); return slave; } if (!state.equals(InstanceStateName.Pending)) { - if (retryCount >= DESCRIBE_LIMIT){ - LOGGER.log(Level.WARNING,"Instance {0} did not move to running after {1} attempts, terminating provisioning", - new Object[]{instanceId, retryCount}); + if (retryCount >= DESCRIBE_LIMIT) { + LOGGER.log( + Level.WARNING, + "Instance {0} did not move to running after {1} attempts, terminating provisioning", + new Object[] {instanceId, retryCount}); return null; } - LOGGER.log(Level.INFO, "Attempt {0}: {1}. Node {2} is neither pending, neither running, it''s {3}. Will try again after 5s", - new Object[]{retryCount, t, slave.getNodeName(), state}); + LOGGER.log( + Level.INFO, + "Attempt {0}: {1}. Node {2} is neither pending, neither running, it''s {3}. Will try again after 5s", + new Object[] {retryCount, t, slave.getNodeName(), state}); retryCount++; } Thread.sleep(5000); } } - }) - , t.getNumExecutors()); + }), + t.getNumExecutors()); } - @Override public boolean canProvision(Label label) { return !getTemplates(label).isEmpty(); @@ -941,15 +1058,17 @@ public static String getSlaveTypeTagValue(String slaveType, String templateDescr return templateDescription != null ? slaveType + "_" + templateDescription : slaveType; } - public static AWSCredentialsProvider createCredentialsProvider(final boolean useInstanceProfileForCredentials, final String credentialsId) { + public static AWSCredentialsProvider createCredentialsProvider( + final boolean useInstanceProfileForCredentials, final String credentialsId) { if (useInstanceProfileForCredentials) { return new InstanceProfileCredentialsProvider(false); } else if (StringUtils.isBlank(credentialsId)) { return new DefaultAWSCredentialsProviderChain(); } else { AmazonWebServicesCredentials credentials = getCredentials(credentialsId); - if (credentials != null) + if (credentials != null) { return new AWSStaticCredentialsProvider(credentials.getCredentials()); + } } return new DefaultAWSCredentialsProviderChain(); } @@ -982,13 +1101,13 @@ private static AmazonWebServicesCredentials getCredentials(@CheckForNull String return null; } return (AmazonWebServicesCredentials) CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials(AmazonWebServicesCredentials.class, Jenkins.get(), - ACL.SYSTEM, Collections.emptyList()), + CredentialsProvider.lookupCredentials( + AmazonWebServicesCredentials.class, Jenkins.get(), ACL.SYSTEM, Collections.emptyList()), CredentialsMatchers.withId(credentialsId)); } private AmazonEC2 reconnectToEc2() throws IOException { - synchronized(this) { + synchronized (this) { connection = AmazonEC2Factory.getInstance().connect(createCredentialsProvider(), getEc2EndpointUrl()); return connection; } @@ -1001,8 +1120,7 @@ public AmazonEC2 connect() throws AmazonClientException { try { if (connection != null) { return connection; - } - else { + } else { return reconnectToEc2(); } } catch (IOException e) { @@ -1047,8 +1165,9 @@ public static String getAwsPartitionHostForService(String region, String service * Convert a configured hostname like 'us-east-1' to a FQDN or ip address */ public static String convertHostName(String ec2HostName) { - if (ec2HostName == null || ec2HostName.length() == 0) + if (ec2HostName == null || ec2HostName.length() == 0) { ec2HostName = DEFAULT_EC2_HOST; + } if (!ec2HostName.contains(".")) { ec2HostName = getAwsPartitionHostForService(ec2HostName, "ec2"); } @@ -1059,8 +1178,9 @@ public static String convertHostName(String ec2HostName) { * Convert a user entered string into a port number "" -> -1 to indicate default based on SSL setting */ public static Integer convertPort(String ec2Port) { - if (ec2Port == null || ec2Port.length() == 0) + if (ec2Port == null || ec2Port.length() == 0) { return -1; + } return Integer.parseInt(ec2Port); } @@ -1075,7 +1195,7 @@ public URL buildPresignedURL(String path) throws AmazonClientException { long expires = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(60); GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(path, credentials.getAWSSecretKey()); request.setExpiration(new Date(expires)); - AmazonS3 s3 = AmazonS3ClientBuilder.standard().withCredentials(provider).build(); + AmazonS3 s3 = AmazonS3ClientBuilder.standard().withCredentials(provider).build(); return s3.generatePresignedUrl(request); } @@ -1089,24 +1209,27 @@ public static URL checkEndPoint(String url) throws FormValidation { } @CheckForNull - private static SSHUserPrivateKey getSshCredential(String id, ItemGroup context){ + private static SSHUserPrivateKey getSshCredential(String id, ItemGroup context) { SSHUserPrivateKey credential = CredentialsMatchers.firstOrNull( - CredentialsProvider.lookupCredentials( - SSHUserPrivateKey.class, // (1) - context, - null, - Collections.emptyList()), - CredentialsMatchers.withId(id)); - - if (credential == null){ - LOGGER.log(Level.WARNING, "EC2 Plugin could not find the specified credentials ({0}) in the Jenkins Global Credentials Store, EC2 Plugin for cloud must be manually reconfigured", new String[]{id}); + CredentialsProvider.lookupCredentials( + SSHUserPrivateKey.class, // (1) + context, + null, + Collections.emptyList()), + CredentialsMatchers.withId(id)); + + if (credential == null) { + LOGGER.log( + Level.WARNING, + "EC2 Plugin could not find the specified credentials ({0}) in the Jenkins Global Credentials Store, EC2 Plugin for cloud must be manually reconfigured", + new String[] {id}); } return credential; } - public static abstract class DescriptorImpl extends Descriptor { + public abstract static class DescriptorImpl extends Descriptor { public InstanceType[] getInstanceTypes() { return InstanceType.values(); @@ -1126,20 +1249,31 @@ public FormValidation doCheckUseInstanceProfileForCredentials(@QueryParameter bo } @POST - public ListBoxModel doFillSshKeysCredentialsIdItems(@AncestorInPath ItemGroup context, @QueryParameter String sshKeysCredentialsId) { + public ListBoxModel doFillSshKeysCredentialsIdItems( + @AncestorInPath ItemGroup context, @QueryParameter String sshKeysCredentialsId) { AbstractIdCredentialsListBoxModel result = new StandardListBoxModel(); if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - result = result - .includeEmptyValue() - .includeMatchingAs(Jenkins.getAuthentication(), context, SSHUserPrivateKey.class, Collections.emptyList(), CredentialsMatchers.always()) - .includeMatchingAs(ACL.SYSTEM, context, SSHUserPrivateKey.class, Collections.emptyList(), CredentialsMatchers.always()) + result = result.includeEmptyValue() + .includeMatchingAs( + Jenkins.getAuthentication(), + context, + SSHUserPrivateKey.class, + Collections.emptyList(), + CredentialsMatchers.always()) + .includeMatchingAs( + ACL.SYSTEM, + context, + SSHUserPrivateKey.class, + Collections.emptyList(), + CredentialsMatchers.always()) .includeCurrentValue(sshKeysCredentialsId); } return result; } @RequirePOST - public FormValidation doCheckSshKeysCredentialsId(@AncestorInPath ItemGroup context, @QueryParameter String value) throws IOException, ServletException { + public FormValidation doCheckSshKeysCredentialsId( + @AncestorInPath ItemGroup context, @QueryParameter String value) throws IOException, ServletException { if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { // Don't do anything if the user is only reading the configuration return FormValidation.ok(); @@ -1163,31 +1297,36 @@ public FormValidation doCheckSshKeysCredentialsId(@AncestorInPath ItemGroup cont } else { EC2PrivateKey k = EC2PrivateKey.fetchFromDisk(); if (k == null) { - validations.add(FormValidation.error("Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH))); + validations.add(FormValidation.error( + "Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH))); if (!StringUtils.isEmpty(value)) { - validations.add(FormValidation.warning("Private key file path defined, selected credential will be ignored")); + validations.add(FormValidation.warning( + "Private key file path defined, selected credential will be ignored")); } return FormValidation.aggregate(validations); } privateKey = k.getPrivateKey(); } - boolean hasStart = false, hasEnd = false; BufferedReader br = new BufferedReader(new StringReader(privateKey)); String line; while ((line = br.readLine()) != null) { - if (line.equals("-----BEGIN RSA PRIVATE KEY-----") || - line.equals("-----BEGIN OPENSSH PRIVATE KEY-----")) + if (line.equals("-----BEGIN RSA PRIVATE KEY-----") + || line.equals("-----BEGIN OPENSSH PRIVATE KEY-----")) { hasStart = true; - if (line.equals("-----END RSA PRIVATE KEY-----") || - line.equals("-----END OPENSSH PRIVATE KEY-----")) + } + if (line.equals("-----END RSA PRIVATE KEY-----") || line.equals("-----END OPENSSH PRIVATE KEY-----")) { hasEnd = true; + } } - if (!hasStart) + if (!hasStart) { validations.add(FormValidation.error("This doesn't look like a private key at all")); - if (!hasEnd) - validations.add(FormValidation.error("The private key is missing the trailing 'END RSA PRIVATE KEY' marker. Copy&paste error?")); + } + if (!hasEnd) { + validations.add(FormValidation.error( + "The private key is missing the trailing 'END RSA PRIVATE KEY' marker. Copy&paste error?")); + } if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) { if (!StringUtils.isEmpty(value)) { @@ -1217,7 +1356,15 @@ public FormValidation doCheckSshKeysCredentialsId(@AncestorInPath ItemGroup cont * @throws ServletException */ @POST - protected FormValidation doTestConnection(@AncestorInPath ItemGroup context, URL ec2endpoint, boolean useInstanceProfileForCredentials, String credentialsId, String sshKeysCredentialsId, String roleArn, String roleSessionName, String region) + protected FormValidation doTestConnection( + @AncestorInPath ItemGroup context, + URL ec2endpoint, + boolean useInstanceProfileForCredentials, + String credentialsId, + String sshKeysCredentialsId, + String roleArn, + String roleSessionName, + String region) throws IOException, ServletException { if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { return FormValidation.ok(); @@ -1233,14 +1380,17 @@ protected FormValidation doTestConnection(@AncestorInPath ItemGroup context, URL if (sshCredential != null) { privateKey = sshCredential.getPrivateKey(); } else { - return FormValidation.error("Failed to find credential \"" + sshKeysCredentialsId + "\" in store."); + return FormValidation.error( + "Failed to find credential \"" + sshKeysCredentialsId + "\" in store."); } } else { EC2PrivateKey k = EC2PrivateKey.fetchFromDisk(); if (k == null) { - validations.add(FormValidation.error("Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH))); + validations.add(FormValidation.error( + "Failed to find private key file " + System.getProperty(SSH_PRIVATE_KEY_FILEPATH))); if (!StringUtils.isEmpty(sshKeysCredentialsId)) { - validations.add(FormValidation.warning("Private key file path defined, selected credential will be ignored")); + validations.add(FormValidation.warning( + "Private key file path defined, selected credential will be ignored")); } return FormValidation.aggregate(validations); } @@ -1248,23 +1398,25 @@ protected FormValidation doTestConnection(@AncestorInPath ItemGroup context, URL } LOGGER.fine(() -> "private key found ok"); - AWSCredentialsProvider credentialsProvider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); + AWSCredentialsProvider credentialsProvider = createCredentialsProvider( + useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, ec2endpoint); ec2.describeInstances(); - if (privateKey.trim().length() > 0) { // check if this key exists EC2PrivateKey pk = new EC2PrivateKey(privateKey); - if (pk.find(ec2) == null) - validations.add(FormValidation - .error("The EC2 key pair private key isn't registered to this EC2 region (fingerprint is " + if (pk.find(ec2) == null) { + validations.add(FormValidation.error( + "The EC2 key pair private key isn't registered to this EC2 region (fingerprint is " + pk.getFingerprint() + ")")); + } } if (!System.getProperty(SSH_PRIVATE_KEY_FILEPATH, "").isEmpty()) { if (!StringUtils.isEmpty(sshKeysCredentialsId)) { - validations.add(FormValidation.warning("Using private key file instead of selected credential")); + validations.add( + FormValidation.warning("Using private key file instead of selected credential")); } else { validations.add(FormValidation.ok("Using private key file")); } @@ -1284,7 +1436,12 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath ItemGroup context) } return new StandardListBoxModel() .includeEmptyValue() - .includeMatchingAs(ACL.SYSTEM, context, AmazonWebServicesCredentials.class, Collections.emptyList(), CredentialsMatchers.always()); + .includeMatchingAs( + ACL.SYSTEM, + context, + AmazonWebServicesCredentials.class, + Collections.emptyList(), + CredentialsMatchers.always()); } } @@ -1295,8 +1452,9 @@ public static void log(Logger logger, Level level, TaskListener listener, String public static void log(Logger logger, Level level, TaskListener listener, String message, Throwable exception) { logger.log(level, message, exception); if (listener != null) { - if (exception != null) + if (exception != null) { message += " Exception: " + exception; + } LogRecord lr = new LogRecord(level, message); lr.setLoggerName(LOGGER.getName()); PrintStream printStream = listener.getLogger(); @@ -1320,7 +1478,7 @@ protected void doRun() throws IOException { EC2Cloud ec2_cloud = (EC2Cloud) cloud; LOGGER.finer(() -> "Checking EC2 Connection on: " + ec2_cloud.getDisplayName()); try { - if(ec2_cloud.connection != null) { + if (ec2_cloud.connection != null) { List filters = new ArrayList<>(); filters.add(new Filter("tag-key").withValues("bogus-EC2ConnectionKeepalive")); DescribeInstancesRequest dir = new DescribeInstancesRequest().withFilters(filters); diff --git a/src/main/java/hudson/plugins/ec2/EC2Computer.java b/src/main/java/hudson/plugins/ec2/EC2Computer.java index 489b2f938..03e7b2494 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Computer.java +++ b/src/main/java/hudson/plugins/ec2/EC2Computer.java @@ -23,6 +23,8 @@ */ package hudson.plugins.ec2; +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.*; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Util; @@ -32,12 +34,8 @@ import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; - - import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpResponse; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; import org.kohsuke.stapler.verb.POST; /** @@ -119,11 +117,12 @@ public String getDecodedConsoleOutput() throws AmazonClientException { } } - private GetConsoleOutputResult getDecodedConsoleOutputResponse() throws AmazonClientException, InterruptedException { + private GetConsoleOutputResult getDecodedConsoleOutputResponse() + throws AmazonClientException, InterruptedException { AmazonEC2 ec2 = getCloud().connect(); GetConsoleOutputRequest request = new GetConsoleOutputRequest(getInstanceId()); if (checkIfNitro()) { - //Can only be used if instance has hypervisor Nitro + // Can only be used if instance has hypervisor Nitro request.setLatest(true); } return ec2.getConsoleOutput(request); @@ -136,7 +135,8 @@ private boolean checkIfNitro() throws AmazonClientException, InterruptedExceptio try { if (isNitro == null) { DescribeInstanceTypesRequest request = new DescribeInstanceTypesRequest(); - request.setInstanceTypes(Collections.singletonList(describeInstance().getInstanceType())); + request.setInstanceTypes( + Collections.singletonList(describeInstance().getInstanceType())); AmazonEC2 ec2 = getCloud().connect(); DescribeInstanceTypesResult result = ec2.describeInstanceTypes(request); if (result.getInstanceTypes().size() == 1) { @@ -145,7 +145,6 @@ private boolean checkIfNitro() throws AmazonClientException, InterruptedExceptio } else { isNitro = false; } - } return isNitro; } catch (AmazonClientException e) { @@ -165,8 +164,9 @@ private boolean checkIfNitro() throws AmazonClientException, InterruptedExceptio * The cache can be flushed using {@link #updateInstanceDescription()} */ public Instance describeInstance() throws AmazonClientException, InterruptedException { - if (ec2InstanceDescription == null) + if (ec2InstanceDescription == null) { ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); + } return ec2InstanceDescription; } @@ -219,8 +219,9 @@ public long getLaunchTime() throws InterruptedException { public HttpResponse doDoDelete() throws IOException { checkPermission(DELETE); EC2AbstractSlave node = getNode(); - if (node != null) + if (node != null) { node.terminate(); + } return new HttpRedirect(".."); } @@ -261,5 +262,4 @@ public void onConnected() { node.onConnected(); } } - } diff --git a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java index 4d95a7b01..b268ed941 100644 --- a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java +++ b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java @@ -23,16 +23,14 @@ */ package hudson.plugins.ec2; +import com.amazonaws.AmazonClientException; import hudson.model.TaskListener; import hudson.slaves.ComputerLauncher; import hudson.slaves.SlaveComputer; - import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import com.amazonaws.AmazonClientException; - /** * {@link ComputerLauncher} for EC2 that wraps the real user-specified {@link ComputerLauncher}. * @@ -48,8 +46,13 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) { launchScript(computer, listener); } catch (AmazonClientException | IOException e) { e.printStackTrace(listener.error(e.getMessage())); - if (slaveComputer.getNode() instanceof EC2AbstractSlave) { - LOGGER.log(Level.FINE, String.format("Terminating the ec2 agent %s due a problem launching or connecting to it", slaveComputer.getName()), e); + if (slaveComputer.getNode() instanceof EC2AbstractSlave) { + LOGGER.log( + Level.FINE, + String.format( + "Terminating the ec2 agent %s due a problem launching or connecting to it", + slaveComputer.getName()), + e); EC2AbstractSlave ec2AbstractSlave = (EC2AbstractSlave) slaveComputer.getNode(); if (ec2AbstractSlave != null) { ec2AbstractSlave.terminate(); @@ -58,15 +61,19 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) { } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(listener.error(e.getMessage())); - if (slaveComputer.getNode() instanceof EC2AbstractSlave) { - LOGGER.log(Level.FINE, String.format("Terminating the ec2 agent %s due a problem launching or connecting to it", slaveComputer.getName()), e); + if (slaveComputer.getNode() instanceof EC2AbstractSlave) { + LOGGER.log( + Level.FINE, + String.format( + "Terminating the ec2 agent %s due a problem launching or connecting to it", + slaveComputer.getName()), + e); EC2AbstractSlave ec2AbstractSlave = (EC2AbstractSlave) slaveComputer.getNode(); if (ec2AbstractSlave != null) { ec2AbstractSlave.terminate(); } } } - } /** @@ -74,5 +81,4 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) { */ protected abstract void launchScript(EC2Computer computer, TaskListener listener) throws AmazonClientException, IOException, InterruptedException; - } diff --git a/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java b/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java index db79e2380..d199bd994 100644 --- a/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java +++ b/src/main/java/hudson/plugins/ec2/EC2ComputerListener.java @@ -1,8 +1,8 @@ package hudson.plugins.ec2; import hudson.Extension; -import hudson.model.TaskListener; import hudson.model.Computer; +import hudson.model.TaskListener; import hudson.slaves.ComputerListener; @Extension diff --git a/src/main/java/hudson/plugins/ec2/EC2Filter.java b/src/main/java/hudson/plugins/ec2/EC2Filter.java index b432d27b0..fd7d707cb 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Filter.java +++ b/src/main/java/hudson/plugins/ec2/EC2Filter.java @@ -23,21 +23,18 @@ */ package hudson.plugins.ec2; -import hudson.model.Descriptor; -import hudson.model.AbstractDescribableImpl; +import com.amazonaws.services.ec2.model.Filter; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.Util; -import org.kohsuke.stapler.DataBoundConstructor; - +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; - -import com.amazonaws.services.ec2.model.Filter; +import org.kohsuke.stapler.DataBoundConstructor; public class EC2Filter extends AbstractDescribableImpl { @NonNull @@ -65,10 +62,12 @@ public String toString() { @Override public boolean equals(Object o) { - if (o == null) + if (o == null) { return false; - if (this.getClass() != o.getClass()) + } + if (this.getClass() != o.getClass()) { return false; + } EC2Filter other = (EC2Filter) o; return name.equals(other.name) && getValuesList().equals(other.getValuesList()); @@ -91,8 +90,7 @@ public String getValues() { @NonNull private List getValuesList() { - return Stream.of(Util.tokenize(values)) - .collect(Collectors.toList()); + return Stream.of(Util.tokenize(values)).collect(Collectors.toList()); } /* Helper method to convert EC2Filter to Filter */ @@ -104,9 +102,7 @@ public Filter toFilter() { /* Helper method to convert list of EC2Filter to list of Filter */ @NonNull public static List toFilterList(@CheckForNull List filters) { - return Util.fixNull(filters).stream() - .map(EC2Filter::toFilter) - .collect(Collectors.toList()); + return Util.fixNull(filters).stream().map(EC2Filter::toFilter).collect(Collectors.toList()); } @Extension diff --git a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java index 0be9d394c..596a4eefc 100644 --- a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java +++ b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java @@ -1,11 +1,10 @@ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.Instance; -import org.apache.commons.lang.StringUtils; +import static hudson.plugins.ec2.ConnectionStrategy.*; +import com.amazonaws.services.ec2.model.Instance; import java.util.Optional; - -import static hudson.plugins.ec2.ConnectionStrategy.*; +import org.apache.commons.lang.StringUtils; public class EC2HostAddressProvider { public static String unix(Instance instance, ConnectionStrategy strategy) { diff --git a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java index d648d8d10..fe759f14a 100644 --- a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java @@ -1,31 +1,26 @@ package hudson.plugins.ec2; +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.model.*; import hudson.Extension; -import hudson.model.Descriptor.FormException; import hudson.model.Computer; +import hudson.model.Descriptor.FormException; import hudson.model.Node; +import hudson.plugins.ec2.ssh.EC2MacLauncher; import hudson.plugins.ec2.ssh.EC2UnixLauncher; import hudson.plugins.ec2.win.EC2WindowsLauncher; -import hudson.plugins.ec2.ssh.EC2MacLauncher; import hudson.slaves.NodeProperty; - import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.logging.Level; import java.util.logging.Logger; - import jenkins.model.Jenkins; import net.sf.json.JSONObject; - import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.*; - /** * Agent running on EC2. * @@ -35,46 +30,373 @@ public class EC2OndemandSlave extends EC2AbstractSlave { private static final Logger LOGGER = Logger.getLogger(EC2OndemandSlave.class.getName()); @Deprecated - public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType) + public EC2OndemandSlave( + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, false, launchTimeout, amiType); + this( + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + false, + launchTimeout, + amiType); } @Deprecated - public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType) + public EC2OndemandSlave( + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + boolean useDedicatedTenancy, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, false, useDedicatedTenancy, launchTimeout, amiType); + this( + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + false, + useDedicatedTenancy, + launchTimeout, + amiType); } @Deprecated - public EC2OndemandSlave(String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean usePrivateDnsName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType) + public EC2OndemandSlave( + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + boolean usePrivateDnsName, + boolean useDedicatedTenancy, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(templateDescription + " (" + instanceId + ")", instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, Collections.emptyList(), remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, useDedicatedTenancy, launchTimeout, amiType, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), -1); + this( + templateDescription + " (" + instanceId + ")", + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + Collections.emptyList(), + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + useDedicatedTenancy, + launchTimeout, + amiType, + ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), + -1); } @Deprecated - public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, boolean useDedicatedTenancy, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses) + public EC2OndemandSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + boolean useDedicatedTenancy, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, Tenancy.backwardsCompatible(useDedicatedTenancy)); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + Tenancy.backwardsCompatible(useDedicatedTenancy)); } @Deprecated - public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy) + public EC2OndemandSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + tenancy, + DEFAULT_METADATA_ENDPOINT_ENABLED, + DEFAULT_METADATA_TOKENS_REQUIRED, + DEFAULT_METADATA_HOPS_LIMIT); } @Deprecated - public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit) + public EC2OndemandSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit) throws FormException, IOException { - this(name, instanceId, templateDescription, remoteFS, numExecutors, labelString, mode, initScript, tmpDir, nodeProperties, remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, stopOnTerminate, idleTerminationMinutes, publicDNS, privateDNS, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED); + this( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + labelString, + mode, + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + publicDNS, + privateDNS, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + tenancy, + metadataEndpointEnabled, + metadataTokensRequired, + metadataHopsLimit, + DEFAULT_METADATA_SUPPORTED); } @DataBoundConstructor - public EC2OndemandSlave(String name, String instanceId, String templateDescription, String remoteFS, int numExecutors, String labelString, Mode mode, String initScript, String tmpDir, List> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, boolean stopOnTerminate, String idleTerminationMinutes, String publicDNS, String privateDNS, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses, Tenancy tenancy, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported) + public EC2OndemandSlave( + String name, + String instanceId, + String templateDescription, + String remoteFS, + int numExecutors, + String labelString, + Mode mode, + String initScript, + String tmpDir, + List> nodeProperties, + String remoteAdmin, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String idleTerminationMinutes, + String publicDNS, + String privateDNS, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + Tenancy tenancy, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit, + Boolean metadataSupported) throws FormException, IOException { - super(name, instanceId, templateDescription, remoteFS, numExecutors, mode, labelString, (amiType.isWindows() ? new EC2WindowsLauncher() : (amiType.isMac() ? new EC2MacLauncher(): - new EC2UnixLauncher())), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, stopOnTerminate, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses, tenancy, metadataEndpointEnabled, metadataTokensRequired, metadataHopsLimit, metadataSupported); + super( + name, + instanceId, + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + (amiType.isWindows() + ? new EC2WindowsLauncher() + : (amiType.isMac() ? new EC2MacLauncher() : new EC2UnixLauncher())), + new EC2RetentionStrategy(idleTerminationMinutes), + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + javaPath, + jvmopts, + stopOnTerminate, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + tenancy, + metadataEndpointEnabled, + metadataTokensRequired, + metadataHopsLimit, + metadataSupported); this.publicDNS = publicDNS; this.privateDNS = privateDNS; } @@ -83,26 +405,51 @@ public EC2OndemandSlave(String name, String instanceId, String templateDescripti * Constructor for debugging. */ public EC2OndemandSlave(String instanceId) throws FormException, IOException { - this(instanceId, instanceId, "debug", "/tmp/hudson", 1, "debug", Mode.NORMAL, "", "/tmp", Collections.emptyList(), null, null, false, null, "Fake public", "Fake private", null, null, false, 0, new UnixData(null, null, null, null, null), ConnectionStrategy.PRIVATE_IP, -1); + this( + instanceId, + instanceId, + "debug", + "/tmp/hudson", + 1, + "debug", + Mode.NORMAL, + "", + "/tmp", + Collections.emptyList(), + null, + null, + false, + null, + "Fake public", + "Fake private", + null, + null, + false, + 0, + new UnixData(null, null, null, null, null), + ConnectionStrategy.PRIVATE_IP, + -1); } /** * Terminates the instance in EC2. */ + @Override public void terminate() { if (terminateScheduled.getCount() == 0) { - synchronized(terminateScheduled) { + synchronized (terminateScheduled) { if (terminateScheduled.getCount() == 0) { Computer.threadPoolForRemoting.submit(() -> { try { if (!isAlive(true)) { /* - * The node has been killed externally, so we've nothing to do here - */ + * The node has been killed externally, so we've nothing to do here + */ LOGGER.info("EC2 instance already terminated: " + getInstanceId()); } else { AmazonEC2 ec2 = getCloud().connect(); - TerminateInstancesRequest request = new TerminateInstancesRequest(Collections.singletonList(getInstanceId())); + TerminateInstancesRequest request = + new TerminateInstancesRequest(Collections.singletonList(getInstanceId())); ec2.terminateInstances(request); LOGGER.info("Terminated EC2 instance (terminated): " + getInstanceId()); } @@ -111,7 +458,7 @@ public void terminate() { } catch (AmazonClientException | IOException e) { LOGGER.log(Level.WARNING, "Failed to terminate EC2 instance: " + getInstanceId(), e); } finally { - synchronized(terminateScheduled) { + synchronized (terminateScheduled) { terminateScheduled.countDown(); } } @@ -133,8 +480,10 @@ public Node reconfigure(final StaplerRequest req, JSONObject form) throws FormEx try { Jenkins.get().removeNode(this); } catch (IOException ioe) { - LOGGER.log(Level.WARNING, "Attempt to reconfigure EC2 instance which has been externally terminated: " - + getInstanceId(), ioe); + LOGGER.log( + Level.WARNING, + "Attempt to reconfigure EC2 instance which has been externally terminated: " + getInstanceId(), + ioe); } return null; diff --git a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java index bc5bba2ea..496414905 100644 --- a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java +++ b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java @@ -23,33 +23,30 @@ */ package hudson.plugins.ec2; +import static hudson.plugins.ec2.EC2Cloud.SSH_PRIVATE_KEY_FILEPATH; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.model.KeyPairInfo; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.util.Secret; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.security.UnrecoverableKeyException; import java.util.Base64; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.KeyPairInfo; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import hudson.util.Secret; -import jenkins.bouncycastle.api.PEMEncodable; -import javax.crypto.Cipher; -import java.nio.charset.Charset; import java.util.logging.Level; import java.util.logging.Logger; - +import javax.crypto.Cipher; +import jenkins.bouncycastle.api.PEMEncodable; import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import static hudson.plugins.ec2.EC2Cloud.SSH_PRIVATE_KEY_FILEPATH; - /** * RSA private key (the one that you generate with ec2-add-keypair.) * @@ -110,8 +107,9 @@ public boolean isPrivateKey() throws IOException { BufferedReader br = new BufferedReader(new StringReader(privateKey.getPlainText())); String line; while ((line = br.readLine()) != null) { - if (line.equals("-----BEGIN RSA PRIVATE KEY-----")) + if (line.equals("-----BEGIN RSA PRIVATE KEY-----")) { return true; + } } return false; } @@ -144,7 +142,9 @@ public com.amazonaws.services.ec2.model.KeyPair find(AmazonEC2 ec2) throws IOExc public String decryptWindowsPassword(String encodedPassword) throws AmazonClientException { try { Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding"); - cipher.init(Cipher.DECRYPT_MODE, PEMEncodable.decode(privateKey.getPlainText()).toPrivateKey()); + cipher.init( + Cipher.DECRYPT_MODE, + PEMEncodable.decode(privateKey.getPlainText()).toPrivateKey()); byte[] cipherText = Base64.getDecoder().decode(StringUtils.deleteWhitespace(encodedPassword)); byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, Charset.forName("ASCII")); @@ -179,8 +179,9 @@ public int hashCode() { @Override public boolean equals(Object that) { - if (that != null && this.getClass() == that.getClass()) + if (that != null && this.getClass() == that.getClass()) { return this.privateKey.equals(((EC2PrivateKey) that).privateKey); + } return false; } diff --git a/src/main/java/hudson/plugins/ec2/EC2Readiness.java b/src/main/java/hudson/plugins/ec2/EC2Readiness.java index 3f3f112e3..ef21dfc4f 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Readiness.java +++ b/src/main/java/hudson/plugins/ec2/EC2Readiness.java @@ -4,5 +4,6 @@ public interface EC2Readiness { public boolean isReady(); + public String getEc2ReadinessStatus() throws AmazonClientException; } diff --git a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java index 356e7bb47..21b4df61d 100644 --- a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java +++ b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java @@ -24,25 +24,21 @@ package hudson.plugins.ec2; import com.amazonaws.AmazonClientException; - - import hudson.init.InitMilestone; import hudson.model.Descriptor; import hudson.model.Executor; import hudson.model.ExecutorListener; +import hudson.model.Label; import hudson.model.Queue; import hudson.plugins.ec2.util.MinimumInstanceChecker; -import hudson.model.Label; import hudson.slaves.RetentionStrategy; -import jenkins.model.Jenkins; - import java.time.Clock; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; - +import jenkins.model.Jenkins; import org.kohsuke.stapler.DataBoundConstructor; /** @@ -87,7 +83,6 @@ public EC2RetentionStrategy(String idleTerminationMinutes) { } } - EC2RetentionStrategy(String idleTerminationMinutes, Clock clock, long nextCheckAfter) { this(idleTerminationMinutes); this.clock = clock; @@ -121,21 +116,23 @@ public long check(EC2Computer c) { private long internalCheck(EC2Computer computer) { /* - * If we've been told never to terminate, or node is null(deleted), no checks to perform - */ + * If we've been told never to terminate, or node is null(deleted), no checks to perform + */ if (idleTerminationMinutes == 0 || computer.getNode() == null) { return CHECK_INTERVAL_MINUTES; } /* - * If we have equal or less number of agents than the template's minimum instance count, don't perform check. - */ + * If we have equal or less number of agents than the template's minimum instance count, don't perform check. + */ SlaveTemplate slaveTemplate = computer.getSlaveTemplate(); if (slaveTemplate != null) { long numberOfCurrentInstancesForTemplate = MinimumInstanceChecker.countCurrentNumberOfAgents(slaveTemplate); - if (numberOfCurrentInstancesForTemplate > 0 && numberOfCurrentInstancesForTemplate <= slaveTemplate.getMinimumNumberOfInstances()) { - //Check if we're in an active time-range for keeping minimum number of instances - if (MinimumInstanceChecker.minimumInstancesActive(slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { + if (numberOfCurrentInstancesForTemplate > 0 + && numberOfCurrentInstancesForTemplate <= slaveTemplate.getMinimumNumberOfInstances()) { + // Check if we're in an active time-range for keeping minimum number of instances + if (MinimumInstanceChecker.minimumInstancesActive( + slaveTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { return CHECK_INTERVAL_MINUTES; } } @@ -147,7 +144,8 @@ private long internalCheck(EC2Computer computer) { InstanceState state; try { - state = computer.getState(); //Get State before Uptime because getState will refresh the cached EC2 info + state = computer.getState(); // Get State before Uptime because getState will refresh the cached EC2 + // info uptime = computer.getUptime(); launchedAtMs = computer.getLaunchTime(); } catch (AmazonClientException | InterruptedException e) { @@ -157,25 +155,31 @@ private long internalCheck(EC2Computer computer) { return CHECK_INTERVAL_MINUTES; } - //Don't bother checking anything else if the instance is already in the desired state: + // Don't bother checking anything else if the instance is already in the desired state: // * Already Terminated // * We use stop-on-terminate and the instance is currently stopped or stopping if (InstanceState.TERMINATED.equals(state) - || (slaveTemplate != null && slaveTemplate.stopOnTerminate) && (InstanceState.STOPPED.equals(state) || InstanceState.STOPPING.equals(state))) { + || (slaveTemplate != null && slaveTemplate.stopOnTerminate) + && (InstanceState.STOPPED.equals(state) || InstanceState.STOPPING.equals(state))) { if (computer.isOnline()) { - LOGGER.info("External Stop of " + computer.getName() + " detected - disconnecting. instance status" + state.toString()); + LOGGER.info("External Stop of " + computer.getName() + " detected - disconnecting. instance status" + + state.toString()); computer.disconnect(null); } return CHECK_INTERVAL_MINUTES; } - //on rare occasions, AWS may return fault instance which shows running in AWS console but can not be connected. - //need terminate such fault instance. + // on rare occasions, AWS may return fault instance which shows running in AWS console but can not be + // connected. + // need terminate such fault instance. // An instance may also fail running user data scripts and // need to be cleaned up. - if (computer.isOffline()){ + if (computer.isOffline()) { if (computer.isConnecting()) { - LOGGER.log(Level.FINE, "Computer {0} connecting and still offline, will check if the launch timeout has expired", computer.getInstanceId()); + LOGGER.log( + Level.FINE, + "Computer {0} connecting and still offline, will check if the launch timeout has expired", + computer.getInstanceId()); EC2AbstractSlave node = computer.getNode(); if (Objects.isNull(node)) { @@ -185,29 +189,32 @@ private long internalCheck(EC2Computer computer) { if (launchTimeout > 0 && uptime > launchTimeout) { // Computer is offline and startup time has expired LOGGER.info("Startup timeout of " + computer.getName() + " after " - + uptime + - " milliseconds (timeout: " + launchTimeout + " milliseconds), instance status: " + state.toString()); + + uptime + " milliseconds (timeout: " + + launchTimeout + " milliseconds), instance status: " + state.toString()); node.launchTimeout(); } return CHECK_INTERVAL_MINUTES; } else { - LOGGER.log(Level.FINE, "Computer {0} offline but not connecting, will check if it should be terminated because of the idle time configured", computer.getInstanceId()); + LOGGER.log( + Level.FINE, + "Computer {0} offline but not connecting, will check if it should be terminated because of the idle time configured", + computer.getInstanceId()); } } - final long idleMilliseconds = this.clock.millis() - Math.max(computer.getIdleStartMilliseconds(), launchedAtMs); - + final long idleMilliseconds = + this.clock.millis() - Math.max(computer.getIdleStartMilliseconds(), launchedAtMs); if (idleTerminationMinutes > 0) { // TODO: really think about the right strategy here, see // JENKINS-23792 - if (idleMilliseconds > TimeUnit.MINUTES.toMillis(idleTerminationMinutes) && - !itemsInQueueForThisSlave(computer)){ + if (idleMilliseconds > TimeUnit.MINUTES.toMillis(idleTerminationMinutes) + && !itemsInQueueForThisSlave(computer)) { LOGGER.info("Idle timeout of " + computer.getName() + " after " - + TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) + - " idle minutes, instance status"+state.toString()); + + TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) + " idle minutes, instance status" + + state.toString()); EC2AbstractSlave slaveNode = computer.getNode(); if (slaveNode != null) { slaveNode.idleTimeout(); @@ -215,14 +222,17 @@ private long internalCheck(EC2Computer computer) { } } else { final int oneHourSeconds = (int) TimeUnit.SECONDS.convert(1, TimeUnit.HOURS); - // AWS bills by the hour for EC2 Instances, so calculate the remaining seconds left in the "billing hour" - // Note: Since October 2017, this isn't true for Linux instances, but the logic hasn't yet been updated for this + // AWS bills by the hour for EC2 Instances, so calculate the remaining seconds left in the "billing + // hour" + // Note: Since October 2017, this isn't true for Linux instances, but the logic hasn't yet been updated + // for this final int freeSecondsLeft = oneHourSeconds - (int) (TimeUnit.SECONDS.convert(uptime, TimeUnit.MILLISECONDS) % oneHourSeconds); // if we have less "free" (aka already paid for) time left than // our idle time, stop/terminate the instance // See JENKINS-23821 - if (freeSecondsLeft <= TimeUnit.MINUTES.toSeconds(Math.abs(idleTerminationMinutes)) && !itemsInQueueForThisSlave(computer)) { + if (freeSecondsLeft <= TimeUnit.MINUTES.toSeconds(Math.abs(idleTerminationMinutes)) + && !itemsInQueueForThisSlave(computer)) { LOGGER.info("Idle timeout of " + computer.getName() + " after " + TimeUnit.MILLISECONDS.toMinutes(idleMilliseconds) + " idle minutes, with " + TimeUnit.SECONDS.toMinutes(freeSecondsLeft) @@ -250,7 +260,9 @@ private boolean itemsInQueueForThisSlave(EC2Computer c) { * doesn't have a node it will return null. In this case we want to * return false because there's no slave to prevent a timeout of. */ - if (selfNode == null) return false; + if (selfNode == null) { + return false; + } final Label selfLabel = selfNode.getSelfLabel(); Queue.Item[] items = Jenkins.getInstance().getQueue().getItems(); for (int i = 0; i < items.length; i++) { @@ -274,7 +286,7 @@ private boolean itemsInQueueForThisSlave(EC2Computer c) { */ @Override public void start(EC2Computer c) { - //Jenkins is in the process of starting up + // Jenkins is in the process of starting up if (Jenkins.get().getInitLevel() != InitMilestone.COMPLETED) { InstanceState state = null; try { @@ -286,10 +298,9 @@ public void start(EC2Computer c) { LOGGER.info("Ignoring start request for " + c.getName() + " during Jenkins startup due to EC2 instance state of " + state); return; - } + } } - LOGGER.info("Start requested for " + c.getName()); c.connect(false); } @@ -310,6 +321,7 @@ protected Object readResolve() { return this; } + @Override public void taskAccepted(Executor executor, Queue.Task task) { EC2Computer computer = (EC2Computer) executor.getOwner(); if (computer != null) { @@ -317,7 +329,8 @@ public void taskAccepted(Executor executor, Queue.Task task) { if (slaveNode != null) { int maxTotalUses = slaveNode.maxTotalUses; if (maxTotalUses <= -1) { - LOGGER.fine("maxTotalUses set to unlimited (" + slaveNode.maxTotalUses + ") for agent " + slaveNode.instanceId); + LOGGER.fine("maxTotalUses set to unlimited (" + slaveNode.maxTotalUses + ") for agent " + + slaveNode.instanceId); return; } else if (maxTotalUses <= 1) { LOGGER.info("maxTotalUses drained - suspending agent " + slaveNode.instanceId); @@ -330,10 +343,12 @@ public void taskAccepted(Executor executor, Queue.Task task) { } } + @Override public void taskCompleted(Executor executor, Queue.Task task, long durationMS) { postJobAction(executor); } + @Override public void taskCompletedWithProblems(Executor executor, Queue.Task task, long durationMS, Throwable problems) { postJobAction(executor); } @@ -343,13 +358,16 @@ private void postJobAction(Executor executor) { if (computer != null) { EC2AbstractSlave slaveNode = computer.getNode(); if (slaveNode != null) { - // At this point, if agent is in suspended state and has 1 last executer running, it is safe to terminate. + // At this point, if agent is in suspended state and has 1 last executer running, it is safe to + // terminate. if (computer.countBusy() <= 1 && !computer.isAcceptingTasks()) { - LOGGER.info("Agent " + slaveNode.instanceId + " is terminated due to maxTotalUses (" + slaveNode.maxTotalUses + ")"); + LOGGER.info("Agent " + slaveNode.instanceId + " is terminated due to maxTotalUses (" + + slaveNode.maxTotalUses + ")"); slaveNode.terminate(); } else { if (slaveNode.maxTotalUses == 1) { - LOGGER.info("Agent " + slaveNode.instanceId + " is still in use by more than one (" + computer.countBusy() + ") executers."); + LOGGER.info("Agent " + slaveNode.instanceId + " is still in use by more than one (" + + computer.countBusy() + ") executers."); } } } diff --git a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java index daa622a39..5209c89b6 100644 --- a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java +++ b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java @@ -1,23 +1,20 @@ package hudson.plugins.ec2; +import static hudson.plugins.ec2.EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE; + +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.model.AmazonEC2Exception; import hudson.Extension; import hudson.model.AsyncPeriodicWork; -import hudson.model.TaskListener; import hudson.model.Node; - +import hudson.model.TaskListener; +import hudson.plugins.ec2.util.MinimumInstanceChecker; import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; - -import hudson.plugins.ec2.util.MinimumInstanceChecker; import jenkins.model.Jenkins; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.AmazonEC2Exception; - -import static hudson.plugins.ec2.EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE; - /** * @author Bruno Meneguello */ @@ -54,9 +51,10 @@ private void removeDeadNodes() { ec2Slave.terminate(); } } catch (AmazonClientException e) { - if (e instanceof AmazonEC2Exception && - EC2_REQUEST_EXPIRED_ERROR_CODE.equals(((AmazonEC2Exception) e).getErrorCode())) { - LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId() + " due to unknown state."); + if (e instanceof AmazonEC2Exception + && EC2_REQUEST_EXPIRED_ERROR_CODE.equals(((AmazonEC2Exception) e).getErrorCode())) { + LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId() + + " due to unknown state."); } else { LOGGER.info("EC2 instance is dead and failed to terminate: " + ec2Slave.getInstanceId()); removeNode(ec2Slave); @@ -73,5 +71,4 @@ private void removeNode(EC2AbstractSlave ec2Slave) { LOGGER.log(Level.WARNING, "Failed to remove node: " + ec2Slave.getInstanceId()); } } - } diff --git a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java index 04c4b861b..2f1372b1f 100644 --- a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java @@ -1,16 +1,5 @@ package hudson.plugins.ec2; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jenkins.model.Jenkins; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.stapler.DataBoundConstructor; - import com.amazonaws.AmazonClientException; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest; @@ -19,15 +8,21 @@ import com.amazonaws.services.ec2.model.SpotInstanceRequest; import com.amazonaws.services.ec2.model.SpotInstanceState; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; - +import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Extension; import hudson.model.Computer; import hudson.model.Descriptor.FormException; import hudson.plugins.ec2.ssh.EC2UnixLauncher; import hudson.plugins.ec2.win.EC2WindowsLauncher; import hudson.slaves.NodeProperty; - -import edu.umd.cs.findbugs.annotations.CheckForNull; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import jenkins.model.Jenkins; +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.DataBoundConstructor; public class EC2SpotSlave extends EC2AbstractSlave implements EC2Readiness { private static final Logger LOGGER = Logger.getLogger(EC2SpotSlave.class.getName()); @@ -35,23 +30,140 @@ public class EC2SpotSlave extends EC2AbstractSlave implements EC2Readiness { private final String spotInstanceRequestId; @Deprecated - public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType) + public EC2SpotSlave( + String name, + String spotInstanceRequestId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String initScript, + String tmpDir, + String labelString, + String remoteAdmin, + String jvmopts, + String idleTerminationMinutes, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(name, spotInstanceRequestId, templateDescription, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, remoteAdmin, jvmopts, idleTerminationMinutes, tags, cloudName, false, launchTimeout, amiType); + this( + name, + spotInstanceRequestId, + templateDescription, + remoteFS, + numExecutors, + mode, + initScript, + tmpDir, + labelString, + remoteAdmin, + jvmopts, + idleTerminationMinutes, + tags, + cloudName, + false, + launchTimeout, + amiType); } @Deprecated - public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, String remoteAdmin, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, boolean usePrivateDnsName, int launchTimeout, AMITypeData amiType) + public EC2SpotSlave( + String name, + String spotInstanceRequestId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String initScript, + String tmpDir, + String labelString, + String remoteAdmin, + String jvmopts, + String idleTerminationMinutes, + List tags, + String cloudName, + boolean usePrivateDnsName, + int launchTimeout, + AMITypeData amiType) throws FormException, IOException { - this(templateDescription + " (" + name + ")", spotInstanceRequestId, templateDescription, remoteFS, numExecutors, mode, initScript, tmpDir, labelString, Collections.emptyList(), remoteAdmin, DEFAULT_JAVA_PATH, jvmopts, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), -1); + this( + templateDescription + " (" + name + ")", + spotInstanceRequestId, + templateDescription, + remoteFS, + numExecutors, + mode, + initScript, + tmpDir, + labelString, + Collections.emptyList(), + remoteAdmin, + DEFAULT_JAVA_PATH, + jvmopts, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + ConnectionStrategy.backwardsCompatible(usePrivateDnsName, false, false), + -1); } @DataBoundConstructor - public EC2SpotSlave(String name, String spotInstanceRequestId, String templateDescription, String remoteFS, int numExecutors, Mode mode, String initScript, String tmpDir, String labelString, List> nodeProperties, String remoteAdmin, String javaPath, String jvmopts, String idleTerminationMinutes, List tags, String cloudName, int launchTimeout, AMITypeData amiType, ConnectionStrategy connectionStrategy, int maxTotalUses) + public EC2SpotSlave( + String name, + String spotInstanceRequestId, + String templateDescription, + String remoteFS, + int numExecutors, + Mode mode, + String initScript, + String tmpDir, + String labelString, + List> nodeProperties, + String remoteAdmin, + String javaPath, + String jvmopts, + String idleTerminationMinutes, + List tags, + String cloudName, + int launchTimeout, + AMITypeData amiType, + ConnectionStrategy connectionStrategy, + int maxTotalUses) throws FormException, IOException { - super(name, "", templateDescription, remoteFS, numExecutors, mode, labelString, amiType.isWindows() ? new EC2WindowsLauncher() : - new EC2UnixLauncher(), new EC2RetentionStrategy(idleTerminationMinutes), initScript, tmpDir, nodeProperties, remoteAdmin, javaPath, jvmopts, false, idleTerminationMinutes, tags, cloudName, launchTimeout, amiType, connectionStrategy, maxTotalUses,null, DEFAULT_METADATA_ENDPOINT_ENABLED, DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT, DEFAULT_METADATA_SUPPORTED); + super( + name, + "", + templateDescription, + remoteFS, + numExecutors, + mode, + labelString, + amiType.isWindows() ? new EC2WindowsLauncher() : new EC2UnixLauncher(), + new EC2RetentionStrategy(idleTerminationMinutes), + initScript, + tmpDir, + nodeProperties, + remoteAdmin, + javaPath, + jvmopts, + false, + idleTerminationMinutes, + tags, + cloudName, + launchTimeout, + amiType, + connectionStrategy, + maxTotalUses, + null, + DEFAULT_METADATA_ENDPOINT_ENABLED, + DEFAULT_METADATA_TOKENS_REQUIRED, + DEFAULT_METADATA_HOPS_LIMIT, + DEFAULT_METADATA_SUPPORTED); this.name = name; this.spotInstanceRequestId = spotInstanceRequestId; @@ -68,7 +180,7 @@ protected boolean isAlive(boolean force) { @Override public void terminate() { if (terminateScheduled.getCount() == 0) { - synchronized(terminateScheduled) { + synchronized (terminateScheduled) { if (terminateScheduled.getCount() == 0) { Computer.threadPoolForRemoting.submit(() -> { try { @@ -77,7 +189,8 @@ public void terminate() { String instanceId = getInstanceId(); List requestIds = Collections.singletonList(spotInstanceRequestId); - CancelSpotInstanceRequestsRequest cancelRequest = new CancelSpotInstanceRequestsRequest(requestIds); + CancelSpotInstanceRequestsRequest cancelRequest = + new CancelSpotInstanceRequestsRequest(requestIds); try { ec2.cancelSpotInstanceRequests(cancelRequest); LOGGER.info("Cancelled Spot request: " + spotInstanceRequestId); @@ -90,22 +203,26 @@ public void terminate() { if (instanceId != null && !instanceId.equals("")) { if (!super.isAlive(true)) { /* - * The node has been killed externally, so we've nothing to do here - */ + * The node has been killed externally, so we've nothing to do here + */ LOGGER.info("EC2 instance already terminated: " + instanceId); } else { - TerminateInstancesRequest request = new TerminateInstancesRequest(Collections.singletonList(instanceId)); + TerminateInstancesRequest request = + new TerminateInstancesRequest(Collections.singletonList(instanceId)); try { ec2.terminateInstances(request); LOGGER.info("Terminated EC2 instance (terminated): " + instanceId); } catch (AmazonClientException e) { // Spot request is no longer valid - LOGGER.log(Level.WARNING, "Failed to terminate the Spot instance: " + instanceId, e); + LOGGER.log( + Level.WARNING, + "Failed to terminate the Spot instance: " + instanceId, + e); } } } } catch (Exception e) { - LOGGER.log(Level.WARNING,"Failed to remove agent: ", e); + LOGGER.log(Level.WARNING, "Failed to remove agent: ", e); } finally { // Remove the instance even if deletion failed, otherwise it will hang around forever in // the nodes page. One way for this to occur is that an instance was terminated @@ -116,7 +233,7 @@ public void terminate() { } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to remove agent: " + name, e); } - synchronized(terminateScheduled) { + synchronized (terminateScheduled) { terminateScheduled.countDown(); } } @@ -140,7 +257,8 @@ SpotInstanceRequest getSpotRequest() { return null; } - DescribeSpotInstanceRequestsRequest dsirRequest = new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(this.spotInstanceRequestId); + DescribeSpotInstanceRequestsRequest dsirRequest = + new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(this.spotInstanceRequestId); try { DescribeSpotInstanceRequestsResult dsirResult = ec2.describeSpotInstanceRequests(dsirRequest); List siRequests = dsirResult.getSpotInstanceRequests(); @@ -148,7 +266,9 @@ SpotInstanceRequest getSpotRequest() { return siRequests.get(0); } catch (AmazonClientException e) { // Spot request is no longer valid - LOGGER.log(Level.WARNING, "Failed to fetch spot instance request for requestId: " + this.spotInstanceRequestId); + LOGGER.log( + Level.WARNING, + "Failed to fetch spot instance request for requestId: " + this.spotInstanceRequestId); } return null; @@ -177,8 +297,9 @@ public String getSpotInstanceRequestId() { public String getInstanceId() { if (StringUtils.isEmpty(instanceId)) { SpotInstanceRequest sr = getSpotRequest(); - if (sr != null) + if (sr != null) { instanceId = sr.getInstanceId(); + } } return instanceId; } @@ -204,7 +325,8 @@ public String getEc2Type() { SpotInstanceRequest spotRequest = getSpotRequest(); if (spotRequest != null) { String spotMaxBidPrice = spotRequest.getSpotPrice(); - return Messages.EC2SpotSlave_Spot1() + spotMaxBidPrice.substring(0, spotMaxBidPrice.length() - 3) + return Messages.EC2SpotSlave_Spot1() + + spotMaxBidPrice.substring(0, spotMaxBidPrice.length() - 3) + Messages.EC2SpotSlave_Spot2(); } return null; diff --git a/src/main/java/hudson/plugins/ec2/EC2Step.java b/src/main/java/hudson/plugins/ec2/EC2Step.java index bd8967760..9407462cf 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Step.java +++ b/src/main/java/hudson/plugins/ec2/EC2Step.java @@ -29,14 +29,13 @@ import hudson.model.TaskListener; import hudson.slaves.Cloud; import hudson.util.ListBoxModel; +import java.util.*; import jenkins.model.Jenkins; import org.jenkinsci.plugins.workflow.steps.*; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; -import java.util.*; - /** * Returns the instance provisioned. * @@ -60,6 +59,7 @@ public EC2Step(String cloud, String template) { this.cloud = cloud; this.template = template; } + public String getCloud() { return cloud; } @@ -70,7 +70,7 @@ public String getTemplate() { @Override public StepExecution start(StepContext context) throws Exception { - return new EC2Step.Execution( this, context); + return new EC2Step.Execution(this, context); } @Extension @@ -91,7 +91,8 @@ public ListBoxModel doFillCloudItems() { Jenkins.get().checkPermission(Jenkins.SYSTEM_READ); ListBoxModel r = new ListBoxModel(); r.add("", ""); - Jenkins.get().clouds + Jenkins.get() + .clouds .getAll(AmazonEC2Cloud.class) .forEach(c -> r.add(c.getDisplayName(), c.getDisplayName())); return r; @@ -106,26 +107,26 @@ public ListBoxModel doFillTemplateItems(@QueryParameter String cloudName) { AmazonEC2Cloud ec2Cloud = (AmazonEC2Cloud) cloud; for (SlaveTemplate template : ec2Cloud.getTemplates()) { for (String labelList : template.labels.split(" ")) { - r.add(labelList + " (AMI: " + template.getAmi() + ", REGION: " + ec2Cloud.getRegion() + ", TYPE: " + template.type.name() + ")", labelList); + r.add( + labelList + " (AMI: " + template.getAmi() + ", REGION: " + ec2Cloud.getRegion() + + ", TYPE: " + template.type.name() + ")", + labelList); } } } return r; } - @Override public Set> getRequiredContext() { return Collections.singleton(TaskListener.class); } - } public static class Execution extends SynchronousNonBlockingStepExecution { private final String cloud; private final String template; - Execution(EC2Step step, StepContext context) { super(context); this.cloud = step.cloud; @@ -145,16 +146,19 @@ protected Instance run() throws Exception { List instances = t.provision(1, opt); if (instances == null) { - throw new IllegalArgumentException("Error in AWS Cloud. Please review AWS template defined in Jenkins configuration."); + throw new IllegalArgumentException( + "Error in AWS Cloud. Please review AWS template defined in Jenkins configuration."); } EC2AbstractSlave slave = instances.get(0); return CloudHelper.getInstanceWithRetry(slave.getInstanceId(), (AmazonEC2Cloud) cl); } else { - throw new IllegalArgumentException("Error in AWS Cloud. Please review AWS template defined in Jenkins configuration."); + throw new IllegalArgumentException( + "Error in AWS Cloud. Please review AWS template defined in Jenkins configuration."); } } else { - throw new IllegalArgumentException("Error in AWS Cloud. Please review EC2 settings in Jenkins configuration."); + throw new IllegalArgumentException( + "Error in AWS Cloud. Please review EC2 settings in Jenkins configuration."); } } @@ -172,5 +176,4 @@ public Cloud getByDisplayName(Jenkins.CloudList clouds, String name) { return c; } } - } diff --git a/src/main/java/hudson/plugins/ec2/EC2Tag.java b/src/main/java/hudson/plugins/ec2/EC2Tag.java index ccdaef430..cfab3bdaa 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Tag.java +++ b/src/main/java/hudson/plugins/ec2/EC2Tag.java @@ -23,17 +23,14 @@ */ package hudson.plugins.ec2; -import hudson.model.Descriptor; -import hudson.model.AbstractDescribableImpl; +import com.amazonaws.services.ec2.model.Tag; import hudson.Extension; -import org.kohsuke.stapler.DataBoundConstructor; - -import java.util.List; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; import java.util.LinkedList; +import java.util.List; import java.util.Objects; - - -import com.amazonaws.services.ec2.model.Tag; +import org.kohsuke.stapler.DataBoundConstructor; public class EC2Tag extends AbstractDescribableImpl { private final String name; @@ -43,6 +40,7 @@ public class EC2Tag extends AbstractDescribableImpl { * Tag name for the specific jenkins agent type tag, used to identify the EC2 instances provisioned by this plugin. */ public static final String TAG_NAME_JENKINS_SLAVE_TYPE = "jenkins_slave_type"; + public static final String TAG_NAME_JENKINS_SERVER_URL = "jenkins_server_url"; @DataBoundConstructor @@ -72,16 +70,20 @@ public String toString() { @Override public boolean equals(Object o) { - if (o == null) + if (o == null) { return false; - if (this.getClass() != o.getClass()) + } + if (this.getClass() != o.getClass()) { return false; + } EC2Tag other = (EC2Tag) o; - if ((name == null && other.name != null) || (name != null && !name.equals(other.name))) + if ((name == null && other.name != null) || (name != null && !name.equals(other.name))) { return false; - if ((value == null && other.value != null) || (value != null && !value.equals(other.value))) + } + if ((value == null && other.value != null) || (value != null && !value.equals(other.value))) { return false; + } return true; } diff --git a/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java b/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java index 47ca5d15a..79d169b49 100644 --- a/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java +++ b/src/main/java/hudson/plugins/ec2/EbsEncryptRootVolume.java @@ -23,5 +23,4 @@ public String getDisplayText() { public Boolean getValue() { return value; } - } diff --git a/src/main/java/hudson/plugins/ec2/Eucalyptus.java b/src/main/java/hudson/plugins/ec2/Eucalyptus.java index 68c6fa612..788f4e5f0 100644 --- a/src/main/java/hudson/plugins/ec2/Eucalyptus.java +++ b/src/main/java/hudson/plugins/ec2/Eucalyptus.java @@ -26,13 +26,10 @@ import hudson.Extension; import hudson.model.ItemGroup; import hudson.util.FormValidation; - import java.io.IOException; import java.net.URL; import java.util.List; - import javax.servlet.ServletException; - import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; @@ -48,22 +45,83 @@ public class Eucalyptus extends EC2Cloud { private final URL s3endpoint; @DataBoundConstructor - public Eucalyptus(String name, URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String sshKeysCredentialsId, String instanceCapStr, List templates, String roleArn, String roleSessionName) { - super(name, useInstanceProfileForCredentials, credentialsId, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName); + public Eucalyptus( + String name, + URL ec2EndpointUrl, + URL s3EndpointUrl, + boolean useInstanceProfileForCredentials, + String credentialsId, + String privateKey, + String sshKeysCredentialsId, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) { + super( + name, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + sshKeysCredentialsId, + instanceCapStr, + templates, + roleArn, + roleSessionName); this.ec2endpoint = ec2EndpointUrl; this.s3endpoint = s3EndpointUrl; } @Deprecated - public Eucalyptus(URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String sshKeysCredentialsId, String instanceCapStr, List templates, String roleArn, String roleSessionName) + public Eucalyptus( + URL ec2EndpointUrl, + URL s3EndpointUrl, + boolean useInstanceProfileForCredentials, + String credentialsId, + String privateKey, + String sshKeysCredentialsId, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) throws IOException { - this("eucalyptus", ec2EndpointUrl, s3EndpointUrl, useInstanceProfileForCredentials, credentialsId, privateKey, sshKeysCredentialsId, instanceCapStr, templates, roleArn, roleSessionName); + this( + "eucalyptus", + ec2EndpointUrl, + s3EndpointUrl, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + sshKeysCredentialsId, + instanceCapStr, + templates, + roleArn, + roleSessionName); } @Deprecated - public Eucalyptus(URL ec2EndpointUrl, URL s3EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String instanceCapStr, List templates, String roleArn, String roleSessionName) + public Eucalyptus( + URL ec2EndpointUrl, + URL s3EndpointUrl, + boolean useInstanceProfileForCredentials, + String credentialsId, + String privateKey, + String instanceCapStr, + List templates, + String roleArn, + String roleSessionName) throws IOException { - this("eucalyptus", ec2EndpointUrl, s3EndpointUrl, useInstanceProfileForCredentials, credentialsId, privateKey, null, instanceCapStr, templates, roleArn, roleSessionName); + this( + "eucalyptus", + ec2EndpointUrl, + s3EndpointUrl, + useInstanceProfileForCredentials, + credentialsId, + privateKey, + null, + instanceCapStr, + templates, + roleArn, + roleSessionName); } @Override @@ -85,9 +143,25 @@ public String getDisplayName() { @Override @RequirePOST - public FormValidation doTestConnection(@AncestorInPath ItemGroup context, @QueryParameter URL ec2endpoint, @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId, @QueryParameter String sshKeysCredentialsId, @QueryParameter String roleArn, @QueryParameter String roleSessionName, @QueryParameter String region) + public FormValidation doTestConnection( + @AncestorInPath ItemGroup context, + @QueryParameter URL ec2endpoint, + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String sshKeysCredentialsId, + @QueryParameter String roleArn, + @QueryParameter String roleSessionName, + @QueryParameter String region) throws IOException, ServletException { - return super.doTestConnection(context, ec2endpoint, useInstanceProfileForCredentials, credentialsId, sshKeysCredentialsId, roleArn, roleSessionName, region); + return super.doTestConnection( + context, + ec2endpoint, + useInstanceProfileForCredentials, + credentialsId, + sshKeysCredentialsId, + roleArn, + roleSessionName, + region); } } } diff --git a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java index 69b8af71a..df4df25dd 100644 --- a/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java +++ b/src/main/java/hudson/plugins/ec2/HostKeyVerificationStrategyEnum.java @@ -35,12 +35,15 @@ public enum HostKeyVerificationStrategyEnum { CHECK_NEW_SOFT("check-new-soft", "accept-new", new CheckNewSoftStrategy()), ACCEPT_NEW("accept-new", "accept-new", new AcceptNewStrategy()), OFF("off", "no", new NonVerifyingKeyVerificationStrategy()); - + private final String displayText; private final SshHostKeyVerificationStrategy strategy; private final String sshCommandEquivalentFlag; - - HostKeyVerificationStrategyEnum(@NonNull String displayText, @NonNull String sshCommandEquivalentFlag, @NonNull SshHostKeyVerificationStrategy strategy) { + + HostKeyVerificationStrategyEnum( + @NonNull String displayText, + @NonNull String sshCommandEquivalentFlag, + @NonNull SshHostKeyVerificationStrategy strategy) { this.displayText = displayText; this.sshCommandEquivalentFlag = sshCommandEquivalentFlag; this.strategy = strategy; @@ -50,7 +53,7 @@ public enum HostKeyVerificationStrategyEnum { public SshHostKeyVerificationStrategy getStrategy() { return strategy; } - + public boolean equalsDisplayText(String other) { return this.displayText.equals(other); } diff --git a/src/main/java/hudson/plugins/ec2/InstanceState.java b/src/main/java/hudson/plugins/ec2/InstanceState.java index 4384bdca2..afaf150e4 100644 --- a/src/main/java/hudson/plugins/ec2/InstanceState.java +++ b/src/main/java/hudson/plugins/ec2/InstanceState.java @@ -29,7 +29,12 @@ * @author Kohsuke Kawaguchi */ public enum InstanceState { - PENDING, RUNNING, SHUTTING_DOWN, TERMINATED, STOPPING, STOPPED; + PENDING, + RUNNING, + SHUTTING_DOWN, + TERMINATED, + STOPPING, + STOPPED; public String getCode() { return name().toLowerCase().replace('_', '-'); diff --git a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java index c659aa263..12b13b172 100644 --- a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java +++ b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java @@ -23,15 +23,14 @@ */ package hudson.plugins.ec2; -import java.util.HashMap; -import java.util.Map; - import com.amazonaws.services.ec2.model.InstanceType; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import java.util.HashMap; +import java.util.Map; /* * Note this is used only to handle the metadata for older versions of the ec2-plugin. The current @@ -55,15 +54,18 @@ public class InstanceTypeConverter implements Converter { TYPICAL_INSTANCE_TYPES.put("XLARGE_CLUSTER_COMPUTE", InstanceType.Cc14xlarge); } + @Override public boolean canConvert(Class type) { return InstanceType.class == type; } + @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { InstanceType instanceType = (InstanceType) source; writer.setValue(instanceType.name()); } + @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { InstanceType instanceType = null; @@ -77,5 +79,4 @@ public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext co return instanceType; } - } diff --git a/src/main/java/hudson/plugins/ec2/MacData.java b/src/main/java/hudson/plugins/ec2/MacData.java index 7fc77a5d9..e8c5bdefe 100644 --- a/src/main/java/hudson/plugins/ec2/MacData.java +++ b/src/main/java/hudson/plugins/ec2/MacData.java @@ -14,7 +14,12 @@ public class MacData extends AMITypeData { private final String bootDelay; @DataBoundConstructor - public MacData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) { + public MacData( + String rootCommandPrefix, + String slaveCommandPrefix, + String slaveCommandSuffix, + String sshPort, + String bootDelay) { this.rootCommandPrefix = rootCommandPrefix; this.slaveCommandPrefix = slaveCommandPrefix; this.slaveCommandSuffix = slaveCommandSuffix; @@ -44,6 +49,7 @@ public boolean isMac() { return true; } + @Override public String getBootDelay() { return bootDelay; } @@ -85,33 +91,44 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (this.getClass() != obj.getClass()) + } + if (this.getClass() != obj.getClass()) { return false; + } final MacData other = (MacData) obj; if (StringUtils.isEmpty(rootCommandPrefix)) { - if (!StringUtils.isEmpty(other.rootCommandPrefix)) + if (!StringUtils.isEmpty(other.rootCommandPrefix)) { return false; - } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) + } + } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) { return false; + } if (StringUtils.isEmpty(slaveCommandPrefix)) { - if (!StringUtils.isEmpty(other.slaveCommandPrefix)) + if (!StringUtils.isEmpty(other.slaveCommandPrefix)) { return false; - } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) + } + } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) { return false; + } if (StringUtils.isEmpty(slaveCommandSuffix)) { - if (!StringUtils.isEmpty(other.slaveCommandSuffix)) + if (!StringUtils.isEmpty(other.slaveCommandSuffix)) { return false; - } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) + } + } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) { return false; + } if (StringUtils.isEmpty(sshPort)) { - if (!StringUtils.isEmpty(other.sshPort)) + if (!StringUtils.isEmpty(other.sshPort)) { return false; - } else if (!sshPort.equals(other.sshPort)) + } + } else if (!sshPort.equals(other.sshPort)) { return false; + } return true; } } diff --git a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java index a4af7a43a..056ba5ab3 100644 --- a/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java +++ b/src/main/java/hudson/plugins/ec2/NoDelayProvisionerStrategy.java @@ -5,11 +5,10 @@ import hudson.model.LoadStatistics; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner; -import jenkins.model.Jenkins; - import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.model.Jenkins; /** * Implementation of {@link NodeProvisioner.Strategy} which will provision a new node immediately as @@ -28,27 +27,37 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra final Label label = strategyState.getLabel(); LoadStatistics.LoadStatisticsSnapshot snapshot = strategyState.getSnapshot(); - int availableCapacity = - snapshot.getAvailableExecutors() // live executors - + snapshot.getConnectingExecutors() // executors present but not yet connected - + strategyState.getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds - + strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_ + int availableCapacity = snapshot.getAvailableExecutors() // live executors + + snapshot.getConnectingExecutors() // executors present but not yet connected + + strategyState + .getPlannedCapacitySnapshot() // capacity added by previous strategies from previous rounds + + strategyState.getAdditionalPlannedCapacity(); // capacity added by previous strategies _this round_ int currentDemand = snapshot.getQueueLength(); - LOGGER.log(Level.FINE, "Available capacity={0}, currentDemand={1}", - new Object[]{availableCapacity, currentDemand}); + LOGGER.log( + Level.FINE, "Available capacity={0}, currentDemand={1}", new Object[] {availableCapacity, currentDemand + }); if (availableCapacity < currentDemand) { Jenkins jenkinsInstance = Jenkins.get(); for (Cloud cloud : jenkinsInstance.clouds) { - if (!(cloud instanceof AmazonEC2Cloud)) continue; - if (!cloud.canProvision(label)) continue; + if (!(cloud instanceof AmazonEC2Cloud)) { + continue; + } + if (!cloud.canProvision(label)) { + continue; + } AmazonEC2Cloud ec2 = (AmazonEC2Cloud) cloud; - if (!ec2.isNoDelayProvisioning()) continue; + if (!ec2.isNoDelayProvisioning()) { + continue; + } - Collection plannedNodes = cloud.provision(label, currentDemand - availableCapacity); + Collection plannedNodes = + cloud.provision(label, currentDemand - availableCapacity); LOGGER.log(Level.FINE, "Planned {0} new nodes", plannedNodes.size()); strategyState.recordPendingLaunches(plannedNodes); availableCapacity += plannedNodes.size(); - LOGGER.log(Level.FINE, "After provisioning, available capacity={0}, currentDemand={1}", new Object[]{availableCapacity, currentDemand}); + LOGGER.log(Level.FINE, "After provisioning, available capacity={0}, currentDemand={1}", new Object[] { + availableCapacity, currentDemand + }); break; } } @@ -60,5 +69,4 @@ public NodeProvisioner.StrategyDecision apply(NodeProvisioner.StrategyState stra return NodeProvisioner.StrategyDecision.CONSULT_REMAINING_STRATEGIES; } } - -} \ No newline at end of file +} diff --git a/src/main/java/hudson/plugins/ec2/PluginImpl.java b/src/main/java/hudson/plugins/ec2/PluginImpl.java index 656cfff58..6f4dc0928 100644 --- a/src/main/java/hudson/plugins/ec2/PluginImpl.java +++ b/src/main/java/hudson/plugins/ec2/PluginImpl.java @@ -28,12 +28,9 @@ import hudson.model.Describable; import hudson.model.Descriptor; import hudson.plugins.ec2.util.MinimumInstanceChecker; -import jenkins.model.Jenkins; - import java.io.IOException; import java.util.logging.Logger; - -import java.io.IOException; +import jenkins.model.Jenkins; /** * Added to handle backwards compatibility of xstream class name mapping. @@ -41,17 +38,18 @@ @Extension public class PluginImpl extends Plugin implements Describable { private static final Logger LOGGER = Logger.getLogger(PluginImpl.class.getName()); - + // Whether the SshHostKeyVerificationAdministrativeMonitor should show messages when we have templates using // accept-new or check-new-soft strategies - private long dismissInsecureMessages; + private long dismissInsecureMessages; public void saveDismissInsecureMessages(long dismissInsecureMessages) { this.dismissInsecureMessages = dismissInsecureMessages; try { save(); - } catch(IOException io) { - LOGGER.warning("There was a problem saving that you want to dismiss all messages related to insecure EC2 templates"); + } catch (IOException io) { + LOGGER.warning( + "There was a problem saving that you want to dismiss all messages related to insecure EC2 templates"); } } @@ -59,6 +57,7 @@ public long getDismissInsecureMessages() { return dismissInsecureMessages; } + @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl) Jenkins.get().getDescriptorOrDie(getClass()); } @@ -84,7 +83,7 @@ public void postInitialize() throws IOException { Jenkins.XSTREAM.registerConverter(new InstanceTypeConverter()); load(); - + MinimumInstanceChecker.checkForMinimumInstances(); } } diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index 1c9de51d4..4d51368f5 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -17,11 +17,12 @@ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package hudson.plugins.ec2; -import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED; + +import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_JAVA_PATH; import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_ENDPOINT_ENABLED; -import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED; import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT; -import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_JAVA_PATH; +import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED; +import static hudson.plugins.ec2.EC2AbstractSlave.DEFAULT_METADATA_TOKENS_REQUIRED; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; @@ -70,6 +71,7 @@ import com.amazonaws.services.ec2.model.Subnet; import com.amazonaws.services.ec2.model.Tag; import com.amazonaws.services.ec2.model.TagSpecification; +import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.Util; @@ -98,22 +100,6 @@ import hudson.util.FormValidation; import hudson.util.ListBoxModel; import hudson.util.Secret; -import jenkins.model.Jenkins; -import jenkins.model.JenkinsLocationConfiguration; -import jenkins.slaves.iterators.api.NodeIterator; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.Stapler; -import org.kohsuke.stapler.interceptor.RequirePOST; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import org.kohsuke.stapler.verb.POST; - -import javax.servlet.ServletException; import java.io.IOException; import java.io.PrintStream; import java.net.URL; @@ -136,6 +122,19 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.servlet.ServletException; +import jenkins.model.Jenkins; +import jenkins.model.JenkinsLocationConfiguration; +import jenkins.slaves.iterators.api.NodeIterator; +import org.apache.commons.lang.StringUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.Stapler; +import org.kohsuke.stapler.interceptor.RequirePOST; +import org.kohsuke.stapler.verb.POST; /** * Template of {@link EC2AbstractSlave} to launch. @@ -243,9 +242,9 @@ public class SlaveTemplate implements Describable { private Integer metadataHopsLimit; - private transient/* almost final */ Set labelSet; + private transient /* almost final */ Set labelSet; - private transient/* almost final */Set securityGroupSet; + private transient /* almost final */ Set securityGroupSet; /* FIXME: Ideally these would be List, but Jenkins currently * doesn't offer a usable way to represent those in forms. Instead @@ -287,23 +286,61 @@ public class SlaveTemplate implements Describable { public transient boolean useDedicatedTenancy; @DataBoundConstructor - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String javaPath, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume, - Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit, Boolean metadataSupported) { - - if(StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)){ - LOGGER.log(Level.FINE, "As remoteAdmin, jvmopts or tmpDir is not blank, we must ensure the user has ADMINISTER rights."); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties, + HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, + Tenancy tenancy, + EbsEncryptRootVolume ebsEncryptRootVolume, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit, + Boolean metadataSupported) { + + if (StringUtils.isNotBlank(remoteAdmin) || StringUtils.isNotBlank(jvmopts) || StringUtils.isNotBlank(tmpDir)) { + LOGGER.log( + Level.FINE, + "As remoteAdmin, jvmopts or tmpDir is not blank, we must ensure the user has ADMINISTER rights."); // Can be null during tests Jenkins j = Jenkins.getInstanceOrNull(); - if (j != null) + if (j != null) { j.checkPermission(Jenkins.ADMINISTER); + } } this.ami = ami; @@ -367,243 +404,1010 @@ public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, Stri this.customDeviceMapping = customDeviceMapping; this.t2Unlimited = t2Unlimited; - this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; + this.hostKeyVerificationStrategy = hostKeyVerificationStrategy != null + ? hostKeyVerificationStrategy + : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; this.tenancy = tenancy != null ? tenancy : Tenancy.Default; this.ebsEncryptRootVolume = ebsEncryptRootVolume != null ? ebsEncryptRootVolume : EbsEncryptRootVolume.DEFAULT; this.metadataSupported = metadataSupported != null ? metadataSupported : DEFAULT_METADATA_SUPPORTED; - this.metadataEndpointEnabled = metadataEndpointEnabled != null ? metadataEndpointEnabled : DEFAULT_METADATA_ENDPOINT_ENABLED; - this.metadataTokensRequired = metadataTokensRequired != null ? metadataTokensRequired : DEFAULT_METADATA_TOKENS_REQUIRED; + this.metadataEndpointEnabled = + metadataEndpointEnabled != null ? metadataEndpointEnabled : DEFAULT_METADATA_ENDPOINT_ENABLED; + this.metadataTokensRequired = + metadataTokensRequired != null ? metadataTokensRequired : DEFAULT_METADATA_TOKENS_REQUIRED; this.metadataHopsLimit = metadataHopsLimit != null ? metadataHopsLimit : DEFAULT_METADATA_HOPS_LIMIT; readResolve(); // initialize } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String javaPath, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume, - Boolean metadataSupported, Boolean metadataEndpointEnabled, Boolean metadataTokensRequired, Integer metadataHopsLimit) { - this(ami, zone, spotConfig, securityGroups, remoteFS, - type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, DEFAULT_JAVA_PATH, jvmopts, - stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, - minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, launchTimeoutStr, associatePublicIp, - customDeviceMapping, connectBySSHProcess, monitoring, - t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, hostKeyVerificationStrategy, tenancy, null, metadataEndpointEnabled, - metadataTokensRequired, metadataHopsLimit, DEFAULT_METADATA_SUPPORTED); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String javaPath, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties, + HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, + Tenancy tenancy, + EbsEncryptRootVolume ebsEncryptRootVolume, + Boolean metadataSupported, + Boolean metadataEndpointEnabled, + Boolean metadataTokensRequired, + Integer metadataHopsLimit) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + hostKeyVerificationStrategy, + tenancy, + null, + metadataEndpointEnabled, + metadataTokensRequired, + metadataHopsLimit, + DEFAULT_METADATA_SUPPORTED); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy, EbsEncryptRootVolume ebsEncryptRootVolume) { - this(ami, zone, spotConfig, securityGroups, remoteFS, - type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, DEFAULT_JAVA_PATH, jvmopts, - stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, - minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, launchTimeoutStr, associatePublicIp, - customDeviceMapping, connectBySSHProcess, monitoring, - t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, hostKeyVerificationStrategy, tenancy, null, DEFAULT_METADATA_ENDPOINT_ENABLED, - DEFAULT_METADATA_TOKENS_REQUIRED, DEFAULT_METADATA_HOPS_LIMIT, DEFAULT_METADATA_SUPPORTED); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties, + HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, + Tenancy tenancy, + EbsEncryptRootVolume ebsEncryptRootVolume) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + DEFAULT_JAVA_PATH, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + hostKeyVerificationStrategy, + tenancy, + null, + DEFAULT_METADATA_ENDPOINT_ENABLED, + DEFAULT_METADATA_TOKENS_REQUIRED, + DEFAULT_METADATA_HOPS_LIMIT, + DEFAULT_METADATA_SUPPORTED); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, Tenancy tenancy) { - this(ami, zone, spotConfig, securityGroups, remoteFS, - type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, - stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, - minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, launchTimeoutStr, associatePublicIp, - customDeviceMapping, connectBySSHProcess, monitoring, - t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, hostKeyVerificationStrategy, tenancy, null); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties, + HostKeyVerificationStrategyEnum hostKeyVerificationStrategy, + Tenancy tenancy) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + hostKeyVerificationStrategy, + tenancy, + null); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, - List> nodeProperties, HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) { - this(ami, zone, spotConfig, securityGroups, remoteFS, - type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, - stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, - minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, launchTimeoutStr, associatePublicIp, - customDeviceMapping, connectBySSHProcess, monitoring, - t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, hostKeyVerificationStrategy, Tenancy.backwardsCompatible(useDedicatedTenancy)); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties, + HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + hostKeyVerificationStrategy, + Tenancy.backwardsCompatible(useDedicatedTenancy)); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - int minimumNumberOfSpareInstances, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses, + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + int minimumNumberOfSpareInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, List> nodeProperties) { - this(ami, zone, spotConfig, securityGroups, remoteFS, - type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, - stopOnTerminate, subnetId, tags, idleTerminationMinutes, minimumNumberOfInstances, - minimumNumberOfSpareInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, - customDeviceMapping, connectBySSHProcess, monitoring, - t2Unlimited, connectionStrategy, maxTotalUses, - nodeProperties, null); + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + null); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses,List> nodeProperties ) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, minimumNumberOfInstances, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, - connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, nodeProperties); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses, + List> nodeProperties) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + 0, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, int minimumNumberOfInstances, - String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, minimumNumberOfInstances, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, - useEphemeralDevices, useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, - connectBySSHProcess, monitoring, t2Unlimited, connectionStrategy, maxTotalUses, Collections.emptyList()); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + int minimumNumberOfInstances, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + Collections.emptyList()); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean monitoring, - boolean t2Unlimited, ConnectionStrategy connectionStrategy, int maxTotalUses) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, 0, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - monitoring, t2Unlimited, connectionStrategy, maxTotalUses); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean monitoring, + boolean t2Unlimited, + ConnectionStrategy connectionStrategy, + int maxTotalUses) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + 0, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp, boolean monitoring, + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + boolean usePrivateDnsName, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean connectUsingPublicIp, + boolean monitoring, boolean t2Unlimited) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - monitoring, t2Unlimited, ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp), -1); + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp), + -1); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean deleteRootOnTermination, - boolean useEphemeralDevices, boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, - String customDeviceMapping, boolean connectBySSHProcess, boolean connectUsingPublicIp) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, deleteRootOnTermination, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, - connectUsingPublicIp, false, false); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + boolean usePrivateDnsName, + String instanceCapStr, + String iamInstanceProfile, + boolean deleteRootOnTermination, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, + boolean connectBySSHProcess, + boolean connectUsingPublicIp) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + usePrivateDnsName, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + connectUsingPublicIp, + false, + false); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, - boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping, + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + boolean usePrivateDnsName, + String instanceCapStr, + String iamInstanceProfile, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping, boolean connectBySSHProcess) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, false, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, connectBySSHProcess, false); + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + usePrivateDnsName, + instanceCapStr, + iamInstanceProfile, + false, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + false); } @Deprecated - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, String initScript, - String tmpDir, String userData, String numExecutors, String remoteAdmin, AMITypeData amiType, String jvmopts, - boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, - boolean useDedicatedTenancy, String launchTimeoutStr, boolean associatePublicIp, String customDeviceMapping) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, amiType, jvmopts, stopOnTerminate, subnetId, tags, - idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, useEphemeralDevices, - useDedicatedTenancy, launchTimeoutStr, associatePublicIp, customDeviceMapping, false); + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + AMITypeData amiType, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + boolean usePrivateDnsName, + String instanceCapStr, + String iamInstanceProfile, + boolean useEphemeralDevices, + boolean useDedicatedTenancy, + String launchTimeoutStr, + boolean associatePublicIp, + String customDeviceMapping) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + usePrivateDnsName, + instanceCapStr, + iamInstanceProfile, + useEphemeralDevices, + useDedicatedTenancy, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + false); } /** * Backward compatible constructor for reloading previous version data */ - public SlaveTemplate(String ami, String zone, SpotConfiguration spotConfig, String securityGroups, String remoteFS, - String sshPort, InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, String description, - String initScript, String tmpDir, String userData, String numExecutors, String remoteAdmin, String rootCommandPrefix, - String slaveCommandPrefix, String slaveCommandSuffix, String jvmopts, boolean stopOnTerminate, String subnetId, List tags, String idleTerminationMinutes, - boolean usePrivateDnsName, String instanceCapStr, String iamInstanceProfile, boolean useEphemeralDevices, + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + String sshPort, + InstanceType type, + boolean ebsOptimized, + String labelString, + Node.Mode mode, + String description, + String initScript, + String tmpDir, + String userData, + String numExecutors, + String remoteAdmin, + String rootCommandPrefix, + String slaveCommandPrefix, + String slaveCommandSuffix, + String jvmopts, + boolean stopOnTerminate, + String subnetId, + List tags, + String idleTerminationMinutes, + boolean usePrivateDnsName, + String instanceCapStr, + String iamInstanceProfile, + boolean useEphemeralDevices, String launchTimeoutStr) { - this(ami, zone, spotConfig, securityGroups, remoteFS, type, ebsOptimized, labelString, mode, description, initScript, - tmpDir, userData, numExecutors, remoteAdmin, new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null), - jvmopts, stopOnTerminate, subnetId, tags, idleTerminationMinutes, usePrivateDnsName, instanceCapStr, iamInstanceProfile, - useEphemeralDevices, false, launchTimeoutStr, false, null); + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + type, + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null), + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + usePrivateDnsName, + instanceCapStr, + iamInstanceProfile, + useEphemeralDevices, + false, + launchTimeoutStr, + false, + null); } public boolean isConnectBySSHProcess() { @@ -686,22 +1490,28 @@ public String getRemoteAdmin() { } public String getRootCommandPrefix() { - return (amiType.isUnix() ? ((UnixData) amiType).getRootCommandPrefix() : (amiType.isMac() ? ((MacData) amiType).getRootCommandPrefix():"")); + return (amiType.isUnix() + ? ((UnixData) amiType).getRootCommandPrefix() + : (amiType.isMac() ? ((MacData) amiType).getRootCommandPrefix() : "")); } public String getSlaveCommandPrefix() { - return (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandPrefix() : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandPrefix() : "")); + return (amiType.isUnix() + ? ((UnixData) amiType).getSlaveCommandPrefix() + : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandPrefix() : "")); } public String getSlaveCommandSuffix() { - return (amiType.isUnix() ? ((UnixData) amiType).getSlaveCommandSuffix() : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandSuffix() : "")); + return (amiType.isUnix() + ? ((UnixData) amiType).getSlaveCommandSuffix() + : (amiType.isMac() ? ((MacData) amiType).getSlaveCommandSuffix() : "")); } public String chooseSubnetId() { if (StringUtils.isBlank(subnetId)) { return null; } else { - String[] subnetIdList= getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS); + String[] subnetIdList = getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS); // Round-robin subnet selection. currentSubnetId = subnetIdList[nextSubnet]; @@ -735,14 +1545,16 @@ public boolean getAssociatePublicIp() { @DataBoundSetter public void setConnectUsingPublicIp(boolean connectUsingPublicIp) { this.connectUsingPublicIp = connectUsingPublicIp; - this.connectionStrategy = ConnectionStrategy.backwardsCompatible(this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp); + this.connectionStrategy = ConnectionStrategy.backwardsCompatible( + this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp); } @Deprecated @DataBoundSetter public void setUsePrivateDnsName(boolean usePrivateDnsName) { this.usePrivateDnsName = usePrivateDnsName; - this.connectionStrategy = ConnectionStrategy.backwardsCompatible(this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp); + this.connectionStrategy = ConnectionStrategy.backwardsCompatible( + this.usePrivateDnsName, this.connectUsingPublicIp, this.associatePublicIp); } @Deprecated @@ -756,8 +1568,9 @@ public boolean isConnectUsingPublicIp() { } public List getTags() { - if (null == tags) + if (null == tags) { return null; + } return Collections.unmodifiableList(tags); } @@ -801,7 +1614,8 @@ public MinimumNumberOfInstancesTimeRangeConfig getMinimumNumberOfInstancesTimeRa } @DataBoundSetter - public void setMinimumNumberOfInstancesTimeRangeConfig(MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { + public void setMinimumNumberOfInstancesTimeRangeConfig( + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { this.minimumNumberOfInstancesTimeRangeConfig = minimumNumberOfInstancesTimeRangeConfig; } @@ -810,8 +1624,9 @@ public int getInstanceCap() { } public int getSpotBlockReservationDuration() { - if (spotConfig == null) + if (spotConfig == null) { return 0; + } return spotConfig.getSpotBlockReservationDuration(); } @@ -820,8 +1635,9 @@ public String getSpotBlockReservationDurationStr() { return ""; } else { int dur = getSpotBlockReservationDuration(); - if (dur == 0) + if (dur == 0) { return ""; + } return String.valueOf(getSpotBlockReservationDuration()); } } @@ -835,8 +1651,9 @@ public String getInstanceCapStr() { } public String getSpotMaxBidPrice() { - if (spotConfig == null) + if (spotConfig == null) { return null; + } return SpotConfiguration.normalizeBid(spotConfig.getSpotMaxBidPrice()); } @@ -846,12 +1663,16 @@ public String getIamInstanceProfile() { @DataBoundSetter public void setHostKeyVerificationStrategy(HostKeyVerificationStrategyEnum hostKeyVerificationStrategy) { - this.hostKeyVerificationStrategy = (hostKeyVerificationStrategy != null) ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; + this.hostKeyVerificationStrategy = (hostKeyVerificationStrategy != null) + ? hostKeyVerificationStrategy + : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; } @NonNull public HostKeyVerificationStrategyEnum getHostKeyVerificationStrategy() { - return hostKeyVerificationStrategy != null ? hostKeyVerificationStrategy : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; + return hostKeyVerificationStrategy != null + ? hostKeyVerificationStrategy + : HostKeyVerificationStrategyEnum.CHECK_NEW_SOFT; } @CheckForNull @@ -886,10 +1707,7 @@ public void setAmiFilters(List amiFilters) { @Override public String toString() { - return "SlaveTemplate{" + - "description='" + description + '\'' + - ", labels='" + labels + '\'' + - '}'; + return "SlaveTemplate{" + "description='" + description + '\'' + ", labels='" + labels + '\'' + '}'; } public int getMaxTotalUses() { @@ -897,7 +1715,7 @@ public int getMaxTotalUses() { } public Boolean getMetadataSupported() { - return metadataSupported; + return metadataSupported; } public Boolean getMetadataEndpointEnabled() { @@ -920,7 +1738,10 @@ public DescribableList, NodePropertyDescriptor> getNodePropertie return Objects.requireNonNull(nodeProperties); } - public enum ProvisionOptions { ALLOW_CREATE, FORCE_CREATE } + public enum ProvisionOptions { + ALLOW_CREATE, + FORCE_CREATE + } /** * Provisions a new EC2 agent or starts a previously stopped on-demand instance. @@ -928,11 +1749,14 @@ public enum ProvisionOptions { ALLOW_CREATE, FORCE_CREATE } * @return always non-null. This needs to be then added to {@link Hudson#addNode(Node)}. */ @NonNull - public List provision(int number, EnumSet provisionOptions) throws AmazonClientException, IOException { + public List provision(int number, EnumSet provisionOptions) + throws AmazonClientException, IOException { final Image image = getImage(); if (this.spotConfig != null) { - if (provisionOptions.contains(ProvisionOptions.ALLOW_CREATE) || provisionOptions.contains(ProvisionOptions.FORCE_CREATE)) + if (provisionOptions.contains(ProvisionOptions.ALLOW_CREATE) + || provisionOptions.contains(ProvisionOptions.FORCE_CREATE)) { return provisionSpot(image, number, provisionOptions); + } return Collections.emptyList(); } return provisionOndemand(image, number, provisionOptions); @@ -943,11 +1767,10 @@ public List provision(int number, EnumSet pr */ private boolean checkInstance(Instance instance) { for (EC2AbstractSlave node : NodeIterator.nodes(EC2AbstractSlave.class)) { - if ( (node.getInstanceId().equals(instance.getInstanceId())) && - (! (instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopped.toString()) - )) - ){ - logInstanceCheck(instance, ". false - found existing corresponding Jenkins agent: " + node.getInstanceId()); + if ((node.getInstanceId().equals(instance.getInstanceId())) + && (!(instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopped.toString())))) { + logInstanceCheck( + instance, ". false - found existing corresponding Jenkins agent: " + node.getInstanceId()); return false; } } @@ -960,10 +1783,9 @@ private void logInstanceCheck(Instance instance, String message) { } private boolean isSameIamInstanceProfile(Instance instance) { - return StringUtils.isBlank(getIamInstanceProfile()) || - (instance.getIamInstanceProfile() != null && - instance.getIamInstanceProfile().getArn().equals(getIamInstanceProfile())); - + return StringUtils.isBlank(getIamInstanceProfile()) + || (instance.getIamInstanceProfile() != null + && instance.getIamInstanceProfile().getArn().equals(getIamInstanceProfile())); } private boolean isTerminatingOrShuttindDown(String instanceStateName) { @@ -975,22 +1797,25 @@ private void logProvisionInfo(String message) { LOGGER.info(this + ". " + message); } - HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2) throws IOException { + HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2) + throws IOException { return makeRunInstancesRequestAndFilters(image, number, ec2, true); } @Deprecated - HashMap> makeRunInstancesRequestAndFilters(int number, AmazonEC2 ec2) throws IOException { + HashMap> makeRunInstancesRequestAndFilters(int number, AmazonEC2 ec2) + throws IOException { return makeRunInstancesRequestAndFilters(getImage(), number, ec2); } - HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException { + HashMap> makeRunInstancesRequestAndFilters( + Image image, int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException { String imageId = image.getImageId(); RunInstancesRequest riRequest = new RunInstancesRequest(imageId, 1, number).withInstanceType(type); riRequest.setEbsOptimized(ebsOptimized); riRequest.setMonitoring(monitoring); - if (t2Unlimited){ + if (t2Unlimited) { CreditSpecificationRequest creditRequest = new CreditSpecificationRequest(); creditRequest.setCpuCredits("unlimited"); riRequest.setCreditSpecification(creditRequest); @@ -998,10 +1823,10 @@ HashMap> makeRunInstancesRequestAndFilters(Ima setupBlockDeviceMappings(image, riRequest.getBlockDeviceMappings()); - if(stopOnTerminate){ + if (stopOnTerminate) { riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Stop); logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Stop"); - }else{ + } else { riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Terminate); logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Terminate"); } @@ -1011,7 +1836,7 @@ HashMap> makeRunInstancesRequestAndFilters(Ima diFilters.add(new Filter("instance-type").withValues(type.toString())); KeyPair keyPair = getKeyPair(ec2); - if (keyPair == null){ + if (keyPair == null) { logProvisionInfo("Could not retrieve a valid key pair."); return null; } @@ -1019,7 +1844,6 @@ HashMap> makeRunInstancesRequestAndFilters(Ima riRequest.setKeyName(keyPair.getKeyName()); diFilters.add(new Filter("key-name").withValues(keyPair.getKeyName())); - if (StringUtils.isNotBlank(getZone())) { Placement placement = new Placement(getZone()); if (getTenancyAttribute().equals(Tenancy.Dedicated)) { @@ -1029,12 +1853,12 @@ HashMap> makeRunInstancesRequestAndFilters(Ima diFilters.add(new Filter("availability-zone").withValues(getZone())); } - if(getTenancyAttribute().equals(Tenancy.Host)){ + if (getTenancyAttribute().equals(Tenancy.Host)) { Placement placement = new Placement(); placement.setTenancy("host"); riRequest.setPlacement(placement); diFilters.add(new Filter("tenancy").withValues(placement.getTenancy())); - }else if(getTenancyAttribute().equals(Tenancy.Default)){ + } else if (getTenancyAttribute().equals(Tenancy.Default)) { Placement placement = new Placement(); placement.setTenancy("default"); riRequest.setPlacement(placement); @@ -1071,10 +1895,10 @@ HashMap> makeRunInstancesRequestAndFilters(Ima } } } else { - List groupIds = getSecurityGroupsBy("group-name", securityGroupSet, ec2) - .getSecurityGroups() - .stream().map(SecurityGroup::getGroupId) - .collect(Collectors.toList()); + List groupIds = + getSecurityGroupsBy("group-name", securityGroupSet, ec2).getSecurityGroups().stream() + .map(SecurityGroup::getGroupId) + .collect(Collectors.toList()); if (getAssociatePublicIp()) { net.setGroups(groupIds); } else { @@ -1111,10 +1935,14 @@ HashMap> makeRunInstancesRequestAndFilters(Ima if (metadataSupported) { InstanceMetadataOptionsRequest instanceMetadataOptionsRequest = new InstanceMetadataOptionsRequest(); - instanceMetadataOptionsRequest.setHttpEndpoint(metadataEndpointEnabled ? InstanceMetadataEndpointState.Enabled.toString() : InstanceMetadataEndpointState.Disabled.toString()); - instanceMetadataOptionsRequest.setHttpPutResponseHopLimit(metadataHopsLimit == null ? EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT : metadataHopsLimit); + instanceMetadataOptionsRequest.setHttpEndpoint( + metadataEndpointEnabled + ? InstanceMetadataEndpointState.Enabled.toString() + : InstanceMetadataEndpointState.Disabled.toString()); + instanceMetadataOptionsRequest.setHttpPutResponseHopLimit( + metadataHopsLimit == null ? EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT : metadataHopsLimit); instanceMetadataOptionsRequest.setHttpTokens( - metadataTokensRequired ? HttpTokensState.Required.toString() : HttpTokensState.Optional.toString()); + metadataTokensRequired ? HttpTokensState.Required.toString() : HttpTokensState.Optional.toString()); riRequest.setMetadataOptions(instanceMetadataOptionsRequest); } @@ -1124,29 +1952,37 @@ HashMap> makeRunInstancesRequestAndFilters(Ima } @Deprecated - HashMap> makeRunInstancesRequestAndFilters(int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException { + HashMap> makeRunInstancesRequestAndFilters( + int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException { return makeRunInstancesRequestAndFilters(getImage(), number, ec2, rotateSubnet); } /** * Provisions an On-demand EC2 agent by launching a new instance or starting a previously-stopped instance. */ - private List provisionOndemand(Image image, int number, EnumSet provisionOptions) - throws IOException { + private List provisionOndemand( + Image image, int number, EnumSet provisionOptions) throws IOException { return provisionOndemand(image, number, provisionOptions, false, false); } /** * Provisions an On-demand EC2 agent by launching a new instance or starting a previously-stopped instance. */ - private List provisionOndemand(Image image, int number, EnumSet provisionOptions, boolean spotWithoutBidPrice, boolean fallbackSpotToOndemand) + private List provisionOndemand( + Image image, + int number, + EnumSet provisionOptions, + boolean spotWithoutBidPrice, + boolean fallbackSpotToOndemand) throws IOException { AmazonEC2 ec2 = getParent().connect(); logProvisionInfo("Considering launching"); - HashMap> runInstancesRequestFilterMap = makeRunInstancesRequestAndFilters(image, number, ec2); - Map.Entry> entry = runInstancesRequestFilterMap.entrySet().iterator().next(); + HashMap> runInstancesRequestFilterMap = + makeRunInstancesRequestAndFilters(image, number, ec2); + Map.Entry> entry = + runInstancesRequestFilterMap.entrySet().iterator().next(); RunInstancesRequest riRequest = entry.getKey(); List diFilters = entry.getValue(); @@ -1157,8 +1993,9 @@ private List provisionOndemand(Image image, int number, EnumSe DescribeInstancesResult diResult = ec2.describeInstances(diRequest); List orphansOrStopped = findOrphansOrStopped(diResult, number); - if (orphansOrStopped.isEmpty() && !provisionOptions.contains(ProvisionOptions.FORCE_CREATE) && - !provisionOptions.contains(ProvisionOptions.ALLOW_CREATE)) { + if (orphansOrStopped.isEmpty() + && !provisionOptions.contains(ProvisionOptions.FORCE_CREATE) + && !provisionOptions.contains(ProvisionOptions.ALLOW_CREATE)) { logProvisionInfo("No existing instance found - but cannot create new instance"); return null; } @@ -1173,9 +2010,11 @@ private List provisionOndemand(Image image, int number, EnumSe List newInstances; if (spotWithoutBidPrice) { - InstanceMarketOptionsRequest instanceMarketOptionsRequest = new InstanceMarketOptionsRequest().withMarketType(MarketType.Spot); + InstanceMarketOptionsRequest instanceMarketOptionsRequest = + new InstanceMarketOptionsRequest().withMarketType(MarketType.Spot); if (getSpotBlockReservationDuration() != 0) { - SpotMarketOptions spotOptions = new SpotMarketOptions().withBlockDurationMinutes(getSpotBlockReservationDuration() * 60); + SpotMarketOptions spotOptions = + new SpotMarketOptions().withBlockDurationMinutes(getSpotBlockReservationDuration() * 60); instanceMarketOptionsRequest.setSpotOptions(spotOptions); } riRequest.setInstanceMarketOptions(instanceMarketOptionsRequest); @@ -1183,7 +2022,8 @@ private List provisionOndemand(Image image, int number, EnumSe newInstances = ec2.runInstances(riRequest).getReservation().getInstances(); } catch (AmazonEC2Exception e) { if (fallbackSpotToOndemand && e.getErrorCode().equals("InsufficientInstanceCapacity")) { - logProvisionInfo("There is no spot capacity available matching your request, falling back to on-demand instance."); + logProvisionInfo( + "There is no spot capacity available matching your request, falling back to on-demand instance."); riRequest.setInstanceMarketOptions(new InstanceMarketOptionsRequest()); newInstances = ec2.runInstances(riRequest).getReservation().getInstances(); } else { @@ -1206,14 +2046,15 @@ private List provisionOndemand(Image image, int number, EnumSe void wakeOrphansOrStoppedUp(AmazonEC2 ec2, List orphansOrStopped) { List instances = new ArrayList<>(); - for(Instance instance : orphansOrStopped) { + for (Instance instance : orphansOrStopped) { if (instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopping.toString()) || instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopped.toString())) { logProvisionInfo("Found stopped instances - will start it: " + instance); instances.add(instance.getInstanceId()); } else { // Should be pending or running at this point, just let it come up - logProvisionInfo("Found existing pending or running: " + instance.getState().getName() + " instance: " + instance); + logProvisionInfo("Found existing pending or running: " + + instance.getState().getName() + " instance: " + instance); } } @@ -1222,7 +2063,6 @@ void wakeOrphansOrStoppedUp(AmazonEC2 ec2, List orphansOrStopped) { StartInstancesResult siResult = ec2.startInstances(siRequest); logProvisionInfo("Result of starting stopped instances:" + siResult); } - } List toSlaves(List newInstances) throws IOException { @@ -1245,7 +2085,9 @@ List findOrphansOrStopped(DescribeInstancesResult diResult, int number for (Reservation reservation : diResult.getReservations()) { for (Instance instance : reservation.getInstances()) { if (!isSameIamInstanceProfile(instance)) { - logInstanceCheck(instance, ". false - IAM Instance profile does not match: " + instance.getIamInstanceProfile()); + logInstanceCheck( + instance, + ". false - IAM Instance profile does not match: " + instance.getIamInstanceProfile()); continue; } @@ -1301,10 +2143,11 @@ private void setupRootDevice(Image image, List deviceMapping } newMapping.getEbs().setEncrypted(ebsEncryptRootVolume.getValue()); - String message = String.format("EBS default encryption value set to: %s (%s)", ebsEncryptRootVolume.getDisplayText(), ebsEncryptRootVolume.getValue()); + String message = String.format( + "EBS default encryption value set to: %s (%s)", + ebsEncryptRootVolume.getDisplayText(), ebsEncryptRootVolume.getValue()); logProvisionInfo(message); deviceMappings.add(0, newMapping); - } private List getNewEphemeralDeviceMapping(Image image) { @@ -1317,19 +2160,20 @@ private List getNewEphemeralDeviceMapping(Image image) { occupiedDevices.add(mapping.getDeviceName()); } - final List available = new ArrayList<>( - Arrays.asList("ephemeral0", "ephemeral1", "ephemeral2", "ephemeral3")); + final List available = + new ArrayList<>(Arrays.asList("ephemeral0", "ephemeral1", "ephemeral2", "ephemeral3")); final List newDeviceMapping = new ArrayList<>(4); for (char suffix = 'b'; suffix <= 'z' && !available.isEmpty(); suffix++) { final String deviceName = String.format("/dev/xvd%s", suffix); - if (occupiedDevices.contains(deviceName)) + if (occupiedDevices.contains(deviceName)) { continue; + } - final BlockDeviceMapping newMapping = new BlockDeviceMapping().withDeviceName(deviceName).withVirtualName( - available.get(0)); + final BlockDeviceMapping newMapping = + new BlockDeviceMapping().withDeviceName(deviceName).withVirtualName(available.get(0)); newDeviceMapping.add(newMapping); available.remove(0); @@ -1345,15 +2189,13 @@ private void setupEphemeralDeviceMapping(Image image, List d @NonNull private static List makeImageAttributeList(@CheckForNull String attr) { - return Stream.of(Util.tokenize(Util.fixNull(attr))) - .collect(Collectors.toList()); + return Stream.of(Util.tokenize(Util.fixNull(attr))).collect(Collectors.toList()); } @NonNull private DescribeImagesRequest makeDescribeImagesRequest() throws AmazonClientException { - List imageIds = Util.fixEmptyAndTrim(ami) == null ? - Collections.emptyList() : - Collections.singletonList(ami); + List imageIds = + Util.fixEmptyAndTrim(ami) == null ? Collections.emptyList() : Collections.singletonList(ami); List owners = makeImageAttributeList(amiOwners); List users = makeImageAttributeList(amiUsers); List filters = EC2Filter.toFilterList(amiFilters); @@ -1361,17 +2203,16 @@ private DescribeImagesRequest makeDescribeImagesRequest() throws AmazonClientExc // Raise an exception if there were no search attributes. // This is legal but not what anyone wants - it will // launch random recently created public AMIs. - int numAttrs = Stream.of(imageIds, owners, users, filters) - .collect(Collectors.summingInt(List::size)); + int numAttrs = Stream.of(imageIds, owners, users, filters).collect(Collectors.summingInt(List::size)); if (numAttrs == 0) { throw new AmazonClientException("Neither AMI ID nor AMI search attributes provided"); } return new DescribeImagesRequest() - .withImageIds(imageIds) - .withOwners(owners) - .withExecutableUsers(users) - .withFilters(filters); + .withImageIds(imageIds) + .withOwners(owners) + .withExecutableUsers(users) + .withFilters(filters); } @NonNull @@ -1389,7 +2230,6 @@ private Image getImage() throws AmazonClientException { return images.get(0); } - private void setupCustomDeviceMapping(List deviceMappings) { if (StringUtils.isNotBlank(customDeviceMapping)) { deviceMappings.addAll(DeviceMappingParser.parse(customDeviceMapping)); @@ -1452,10 +2292,10 @@ private List provisionSpot(Image image, int number, EnumSet groupIds = getSecurityGroupsBy("group-name", securityGroupSet, ec2) - .getSecurityGroups() - .stream().map(SecurityGroup::getGroupId) - .collect(Collectors.toList()); + List groupIds = + getSecurityGroupsBy("group-name", securityGroupSet, ec2).getSecurityGroups().stream() + .map(SecurityGroup::getGroupId) + .collect(Collectors.toList()); net.setGroups(groupIds); } } @@ -1473,7 +2313,8 @@ private List provisionSpot(Image image, int number, EnumSet instTags = buildTags(EC2Cloud.EC2_SLAVE_TYPE_SPOT); if (StringUtils.isNotBlank(getIamInstanceProfile())) { - launchSpecification.setIamInstanceProfile(new IamInstanceProfileSpecification().withArn(getIamInstanceProfile())); + launchSpecification.setIamInstanceProfile( + new IamInstanceProfileSpecification().withArn(getIamInstanceProfile())); } setupBlockDeviceMappings(image, launchSpecification.getBlockDeviceMappings()); @@ -1490,7 +2331,8 @@ private List provisionSpot(Image image, int number, EnumSet provisionSpot(Image image, int number, EnumSet slaves = new ArrayList<>(reqInstances.size()); - for(SpotInstanceRequest spotInstReq : reqInstances) { + for (SpotInstanceRequest spotInstReq : reqInstances) { if (spotInstReq == null) { throw new AmazonClientException("Spot instance request is null"); } @@ -1514,22 +2356,31 @@ private List provisionSpot(Image image, int number, EnumSet spotRequestBadCodes = Arrays.asList("capacity-not-available", "capacity-oversubscribed", "price-too-low"); + List spotRequestBadCodes = + Arrays.asList("capacity-not-available", "capacity-oversubscribed", "price-too-low"); if (spotRequestBadCodes.contains(spotInstReq.getStatus().getCode())) { - LOGGER.info("There is no spot capacity available matching your request, falling back to on-demand instance."); - List requestsToCancel = reqInstances.stream().map(SpotInstanceRequest::getSpotInstanceRequestId).collect(Collectors.toList()); - CancelSpotInstanceRequestsRequest cancelRequest = new CancelSpotInstanceRequestsRequest(requestsToCancel); + LOGGER.info( + "There is no spot capacity available matching your request, falling back to on-demand instance."); + List requestsToCancel = reqInstances.stream() + .map(SpotInstanceRequest::getSpotInstanceRequestId) + .collect(Collectors.toList()); + CancelSpotInstanceRequestsRequest cancelRequest = + new CancelSpotInstanceRequestsRequest(requestsToCancel); ec2.cancelSpotInstanceRequests(cancelRequest); return provisionOndemand(image, number, provisionOptions); } } // Now that we have our Spot request, we can set tags on it - updateRemoteTags(ec2, instTags, "InvalidSpotInstanceRequestID.NotFound", spotInstReq.getSpotInstanceRequestId()); + updateRemoteTags( + ec2, instTags, "InvalidSpotInstanceRequestID.NotFound", spotInstReq.getSpotInstanceRequestId()); // That was a remote request - we should also update our local instance data spotInstReq.setTags(instTags); @@ -1543,7 +2394,7 @@ private List provisionSpot(Image image, int number, EnumSet buildTags(String slaveType) { } } if (!hasCustomTypeTag) { - instTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, EC2Cloud.getSlaveTypeTagValue( - slaveType, description))); + instTags.add( + new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, EC2Cloud.getSlaveTypeTagValue(slaveType, description))); } JenkinsLocationConfiguration jenkinsLocation = JenkinsLocationConfiguration.get(); if (!hasJenkinsServerUrlTag && jenkinsLocation.getUrl() != null) { @@ -1587,61 +2438,61 @@ private HashSet buildTags(String slaveType) { protected EC2OndemandSlave newOndemandSlave(Instance inst) throws FormException, IOException { EC2AgentConfig.OnDemand config = new EC2AgentConfig.OnDemandBuilder() - .withName(getSlaveName(inst.getInstanceId())) - .withInstanceId(inst.getInstanceId()) - .withDescription(description) - .withRemoteFS(remoteFS) - .withNumExecutors(getNumExecutors()) - .withLabelString(labels) - .withMode(mode) - .withInitScript(initScript) - .withTmpDir(tmpDir) - .withNodeProperties(nodeProperties.toList()) - .withRemoteAdmin(remoteAdmin) - .withJavaPath(javaPath) - .withJvmopts(jvmopts) - .withStopOnTerminate(stopOnTerminate) - .withIdleTerminationMinutes(idleTerminationMinutes) - .withPublicDNS(inst.getPublicDnsName()) - .withPrivateDNS(inst.getPrivateDnsName()) - .withTags(EC2Tag.fromAmazonTags(inst.getTags())) - .withCloudName(parent.name) - .withLaunchTimeout(getLaunchTimeout()) - .withAmiType(amiType) - .withConnectionStrategy(connectionStrategy) - .withMaxTotalUses(maxTotalUses) - .withTenancyAttribute(tenancy) - .withMetadataSupported(metadataSupported) - .withMetadataEndpointEnabled(metadataEndpointEnabled) - .withMetadataTokensRequired(metadataTokensRequired) - .withMetadataHopsLimit(metadataHopsLimit) - .build(); + .withName(getSlaveName(inst.getInstanceId())) + .withInstanceId(inst.getInstanceId()) + .withDescription(description) + .withRemoteFS(remoteFS) + .withNumExecutors(getNumExecutors()) + .withLabelString(labels) + .withMode(mode) + .withInitScript(initScript) + .withTmpDir(tmpDir) + .withNodeProperties(nodeProperties.toList()) + .withRemoteAdmin(remoteAdmin) + .withJavaPath(javaPath) + .withJvmopts(jvmopts) + .withStopOnTerminate(stopOnTerminate) + .withIdleTerminationMinutes(idleTerminationMinutes) + .withPublicDNS(inst.getPublicDnsName()) + .withPrivateDNS(inst.getPrivateDnsName()) + .withTags(EC2Tag.fromAmazonTags(inst.getTags())) + .withCloudName(parent.name) + .withLaunchTimeout(getLaunchTimeout()) + .withAmiType(amiType) + .withConnectionStrategy(connectionStrategy) + .withMaxTotalUses(maxTotalUses) + .withTenancyAttribute(tenancy) + .withMetadataSupported(metadataSupported) + .withMetadataEndpointEnabled(metadataEndpointEnabled) + .withMetadataTokensRequired(metadataTokensRequired) + .withMetadataHopsLimit(metadataHopsLimit) + .build(); return EC2AgentFactory.getInstance().createOnDemandAgent(config); } protected EC2SpotSlave newSpotSlave(SpotInstanceRequest sir) throws FormException, IOException { EC2AgentConfig.Spot config = new EC2AgentConfig.SpotBuilder() - .withName(getSlaveName(sir.getSpotInstanceRequestId())) - .withSpotInstanceRequestId(sir.getSpotInstanceRequestId()) - .withDescription(description) - .withRemoteFS(remoteFS) - .withNumExecutors(getNumExecutors()) - .withMode(mode) - .withInitScript(initScript) - .withTmpDir(tmpDir) - .withLabelString(labels) - .withNodeProperties(nodeProperties.toList()) - .withRemoteAdmin(remoteAdmin) - .withJavaPath(javaPath) - .withJvmopts(jvmopts) - .withIdleTerminationMinutes(idleTerminationMinutes) - .withTags(EC2Tag.fromAmazonTags(sir.getTags())) - .withCloudName(parent.name) - .withLaunchTimeout(getLaunchTimeout()) - .withAmiType(amiType) - .withConnectionStrategy(connectionStrategy) - .withMaxTotalUses(maxTotalUses) - .build(); + .withName(getSlaveName(sir.getSpotInstanceRequestId())) + .withSpotInstanceRequestId(sir.getSpotInstanceRequestId()) + .withDescription(description) + .withRemoteFS(remoteFS) + .withNumExecutors(getNumExecutors()) + .withMode(mode) + .withInitScript(initScript) + .withTmpDir(tmpDir) + .withLabelString(labels) + .withNodeProperties(nodeProperties.toList()) + .withRemoteAdmin(remoteAdmin) + .withJavaPath(javaPath) + .withJvmopts(jvmopts) + .withIdleTerminationMinutes(idleTerminationMinutes) + .withTags(EC2Tag.fromAmazonTags(sir.getTags())) + .withCloudName(parent.name) + .withLaunchTimeout(getLaunchTimeout()) + .withAmiType(amiType) + .withConnectionStrategy(connectionStrategy) + .withMaxTotalUses(maxTotalUses) + .build(); return EC2AgentFactory.getInstance().createSpotAgent(config); } @@ -1652,7 +2503,8 @@ protected EC2SpotSlave newSpotSlave(SpotInstanceRequest sir) throws FormExceptio private KeyPair getKeyPair(AmazonEC2 ec2) throws IOException, AmazonClientException { EC2PrivateKey ec2PrivateKey = getParent().resolvePrivateKey(); if (ec2PrivateKey == null) { - throw new AmazonClientException("No keypair credential found. Please configure a credential in the Jenkins configuration."); + throw new AmazonClientException( + "No keypair credential found. Please configure a credential in the Jenkins configuration."); } KeyPair keyPair = ec2PrivateKey.find(ec2); if (keyPair == null) { @@ -1694,8 +2546,11 @@ private void updateRemoteTags(AmazonEC2 ec2, Collection instTags, String ca * Get a list of security group ids for the agent */ private List getEc2SecurityGroups(AmazonEC2 ec2) throws AmazonClientException { - LOGGER.log(Level.FINE, () -> String.format("Get security group %s for EC2Cloud %s with currentSubnetId %s", - securityGroupSet, this.getParent().name, getCurrentSubnetId())); + LOGGER.log( + Level.FINE, + () -> String.format( + "Get security group %s for EC2Cloud %s with currentSubnetId %s", + securityGroupSet, this.getParent().name, getCurrentSubnetId())); List groupIds = new ArrayList<>(); DescribeSecurityGroupsResult groupResult = getSecurityGroupsBy("group-name", securityGroupSet, ec2); if (groupResult.getSecurityGroups().size() == 0) { @@ -1703,8 +2558,11 @@ private List getEc2SecurityGroups(AmazonEC2 ec2) throws AmazonClientExce } for (SecurityGroup group : groupResult.getSecurityGroups()) { - LOGGER.log(Level.FINE, () -> String.format("Checking security group %s (vpc-id = %s, subnet-id = %s)", - group.getGroupId(), group.getVpcId(), getCurrentSubnetId())); + LOGGER.log( + Level.FINE, + () -> String.format( + "Checking security group %s (vpc-id = %s, subnet-id = %s)", + group.getGroupId(), group.getVpcId(), getCurrentSubnetId())); if (group.getVpcId() != null && !group.getVpcId().isEmpty()) { List filters = new ArrayList<>(); filters.add(new Filter("vpc-id").withValues(group.getVpcId())); @@ -1730,7 +2588,8 @@ private List getEc2SecurityGroups(AmazonEC2 ec2) throws AmazonClientExce return groupIds; } - private DescribeSecurityGroupsResult getSecurityGroupsBy(String filterName, Set filterValues, AmazonEC2 ec2) { + private DescribeSecurityGroupsResult getSecurityGroupsBy( + String filterName, Set filterValues, AmazonEC2 ec2) { DescribeSecurityGroupsRequest groupReq = new DescribeSecurityGroupsRequest(); groupReq.withFilters(new Filter(filterName).withValues(filterValues)); return ec2.describeSecurityGroups(groupReq); @@ -1748,11 +2607,15 @@ public EC2AbstractSlave attach(String instanceId, TaskListener listener) throws LOGGER.info("Attaching to " + instanceId); DescribeInstancesRequest request = new DescribeInstancesRequest(); request.setInstanceIds(Collections.singletonList(instanceId)); - Instance inst = ec2.describeInstances(request).getReservations().get(0).getInstances().get(0); + Instance inst = ec2.describeInstances(request) + .getReservations() + .get(0) + .getInstances() + .get(0); return newOndemandSlave(inst); } catch (FormException e) { throw new AssertionError(); // we should have discovered all - // configuration issues upfront + // configuration issues upfront } } @@ -1761,9 +2624,9 @@ public EC2AbstractSlave attach(String instanceId, TaskListener listener) throws */ protected Object readResolve() { Jenkins j = Jenkins.getInstanceOrNull(); - if (j != null) { - j.checkPermission(Jenkins.ADMINISTER); - } + if (j != null) { + j.checkPermission(Jenkins.ADMINISTER); + } securityGroupSet = parseSecurityGroups(); @@ -1783,9 +2646,10 @@ protected Object readResolve() { amiType = new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null); } - // 1.43 new parameters - if (connectionStrategy == null ) { - connectionStrategy = ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp); + // 1.43 new parameters + if (connectionStrategy == null) { + connectionStrategy = + ConnectionStrategy.backwardsCompatible(usePrivateDnsName, connectUsingPublicIp, associatePublicIp); } if (maxTotalUses == 0) { @@ -1828,6 +2692,7 @@ protected Object readResolve() { return this; } + @Override public Descriptor getDescriptor() { return Jenkins.get().getDescriptor(getClass()); } @@ -1871,8 +2736,10 @@ public boolean isUseHTTPS() { * @return DescribeInstancesResult of DescribeInstanceRequst constructed from this SlaveTemplate's configs */ DescribeInstancesResult getDescribeInstanceResult(AmazonEC2 ec2, boolean allSubnets) throws IOException { - HashMap> runInstancesRequestFilterMap = makeRunInstancesRequestAndFilters(getImage(), 1, ec2, false); - Map.Entry> entry = runInstancesRequestFilterMap.entrySet().iterator().next(); + HashMap> runInstancesRequestFilterMap = + makeRunInstancesRequestAndFilters(getImage(), 1, ec2, false); + Map.Entry> entry = + runInstancesRequestFilterMap.entrySet().iterator().next(); List diFilters = entry.getValue(); if (allSubnets) { @@ -1929,17 +2796,20 @@ public List> getAMITypeDescriptors() { @Override public String getHelpFile(String fieldName) { String p = super.getHelpFile(fieldName); - if (p != null) + if (p != null) { return p; + } Descriptor slaveDescriptor = Jenkins.get().getDescriptor(EC2OndemandSlave.class); if (slaveDescriptor != null) { p = slaveDescriptor.getHelpFile(fieldName); - if (p != null) + if (p != null) { return p; + } } slaveDescriptor = Jenkins.get().getDescriptor(EC2SpotSlave.class); - if (slaveDescriptor != null) + if (slaveDescriptor != null) { return slaveDescriptor.getHelpFile(fieldName); + } return null; } @@ -1958,15 +2828,22 @@ public FormValidation doCheckDescription(@QueryParameter String value) { * Check that the AMI requested is available in the cloud and can be used. */ @RequirePOST - public FormValidation doValidateAmi(@QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, @QueryParameter String ec2endpoint, - @QueryParameter String region, final @QueryParameter String ami, @QueryParameter String roleArn, - @QueryParameter String roleSessionName) throws IOException { + public FormValidation doValidateAmi( + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String ec2endpoint, + @QueryParameter String region, + final @QueryParameter String ami, + @QueryParameter String roleArn, + @QueryParameter String roleSessionName) + throws IOException { checkPermission(EC2Cloud.PROVISION); - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); + AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); AmazonEC2 ec2; if (region != null) { - ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + ec2 = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); } else { ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, new URL(ec2endpoint)); } @@ -2003,12 +2880,14 @@ public FormValidation doCheckLabelString(@QueryParameter String value, @QueryPar @POST public FormValidation doCheckIdleTerminationMinutes(@QueryParameter String value) { - if (value == null || value.trim().isEmpty()) + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); - if (val >= -59) + if (val >= -59) { return FormValidation.ok(); + } } catch (NumberFormatException nfe) { } return FormValidation.error("Idle Termination time must be a greater than -59 (or null)"); @@ -2018,17 +2897,20 @@ public FormValidation doCheckIdleTerminationMinutes(@QueryParameter String value public FormValidation doCheckMaxTotalUses(@QueryParameter String value) { try { int val = Integer.parseInt(value); - if (val >= -1) + if (val >= -1) { return FormValidation.ok(); + } } catch (NumberFormatException nfe) { } return FormValidation.error("Maximum Total Uses must be greater or equal to -1"); } @POST - public FormValidation doCheckMinimumNumberOfInstances(@QueryParameter String value, @QueryParameter String instanceCapStr) { - if (value == null || value.trim().isEmpty()) + public FormValidation doCheckMinimumNumberOfInstances( + @QueryParameter String value, @QueryParameter String instanceCapStr) { + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); if (val >= 0) { @@ -2039,9 +2921,8 @@ public FormValidation doCheckMinimumNumberOfInstances(@QueryParameter String val instanceCap = Integer.MAX_VALUE; } if (val > instanceCap) { - return FormValidation - .error("Minimum number of instances must not be larger than AMI Instance Cap %d", - instanceCap); + return FormValidation.error( + "Minimum number of instances must not be larger than AMI Instance Cap %d", instanceCap); } return FormValidation.ok(); } @@ -2072,23 +2953,27 @@ public FormValidation doCheckMinimumNoInstancesActiveTimeRangeTo(@QueryParameter // For some reason, all days will validate against this method so no need to repeat for each day. @POST - public FormValidation doCheckMonday(@QueryParameter boolean monday, - @QueryParameter boolean tuesday, - @QueryParameter boolean wednesday, - @QueryParameter boolean thursday, - @QueryParameter boolean friday, - @QueryParameter boolean saturday, - @QueryParameter boolean sunday) { + public FormValidation doCheckMonday( + @QueryParameter boolean monday, + @QueryParameter boolean tuesday, + @QueryParameter boolean wednesday, + @QueryParameter boolean thursday, + @QueryParameter boolean friday, + @QueryParameter boolean saturday, + @QueryParameter boolean sunday) { if (!(monday || tuesday || wednesday || thursday || friday || saturday || sunday)) { - return FormValidation.warning("At least one day should be checked or minimum number of instances won't be active"); + return FormValidation.warning( + "At least one day should be checked or minimum number of instances won't be active"); } return FormValidation.ok(); } @POST - public FormValidation doCheckMinimumNumberOfSpareInstances(@QueryParameter String value, @QueryParameter String instanceCapStr) { - if (value == null || value.trim().isEmpty()) + public FormValidation doCheckMinimumNumberOfSpareInstances( + @QueryParameter String value, @QueryParameter String instanceCapStr) { + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); if (val >= 0) { @@ -2099,9 +2984,9 @@ public FormValidation doCheckMinimumNumberOfSpareInstances(@QueryParameter Strin instanceCap = Integer.MAX_VALUE; } if (val > instanceCap) { - return FormValidation - .error("Minimum number of spare instances must not be larger than AMI Instance Cap %d", - instanceCap); + return FormValidation.error( + "Minimum number of spare instances must not be larger than AMI Instance Cap %d", + instanceCap); } return FormValidation.ok(); } @@ -2112,12 +2997,14 @@ public FormValidation doCheckMinimumNumberOfSpareInstances(@QueryParameter Strin @POST public FormValidation doCheckInstanceCapStr(@QueryParameter String value) { - if (value == null || value.trim().isEmpty()) + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); - if (val > 0) + if (val > 0) { return FormValidation.ok(); + } } catch (NumberFormatException nfe) { } return FormValidation.error("InstanceCap must be a non-negative integer (or null)"); @@ -2128,12 +3015,14 @@ public FormValidation doCheckInstanceCapStr(@QueryParameter String value) { */ @POST public FormValidation doCheckSpotBlockReservationDurationStr(@QueryParameter String value) { - if (value == null || value.trim().isEmpty()) + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); - if (val >= 0 && val <= 6) + if (val >= 0 && val <= 6) { return FormValidation.ok(); + } } catch (NumberFormatException nfe) { } return FormValidation.error("Spot Block Reservation Duration must be an integer between 0 & 6"); @@ -2141,24 +3030,30 @@ public FormValidation doCheckSpotBlockReservationDurationStr(@QueryParameter Str @POST public FormValidation doCheckLaunchTimeoutStr(@QueryParameter String value) { - if (value == null || value.trim().isEmpty()) + if (value == null || value.trim().isEmpty()) { return FormValidation.ok(); + } try { int val = Integer.parseInt(value); - if (val >= 0) + if (val >= 0) { return FormValidation.ok(); + } } catch (NumberFormatException nfe) { } return FormValidation.error("Launch Timeout must be a non-negative integer (or null)"); } @RequirePOST - public ListBoxModel doFillZoneItems(@QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, @QueryParameter String region, @QueryParameter String roleArn, + public ListBoxModel doFillZoneItems( + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String region, + @QueryParameter String roleArn, @QueryParameter String roleSessionName) throws IOException, ServletException { checkPermission(EC2Cloud.PROVISION); - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); + AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); return EC2AbstractSlave.fillZoneItems(credentialsProvider, region); } @@ -2229,10 +3124,12 @@ public ListBoxModel doFillHostKeyVerificationStrategyItems(@QueryParameter Strin @POST public FormValidation doCheckHostKeyVerificationStrategy(@QueryParameter String hostKeyVerificationStrategy) { Stream stream = Stream.of(HostKeyVerificationStrategyEnum.values()); - Stream filteredStream = stream.filter(v -> v.name().equals(hostKeyVerificationStrategy)); + Stream filteredStream = + stream.filter(v -> v.name().equals(hostKeyVerificationStrategy)); Optional matched = filteredStream.findFirst(); Optional okResult = matched.map(s -> FormValidation.ok()); - return okResult.orElse(FormValidation.error(String.format("Could not find selected host key verification (%s)", hostKeyVerificationStrategy))); + return okResult.orElse(FormValidation.error( + String.format("Could not find selected host key verification (%s)", hostKeyVerificationStrategy))); } @POST @@ -2247,12 +3144,13 @@ public ListBoxModel doFillTenancyItems(@QueryParameter String tenancy) { }) .collect(Collectors.toCollection(ListBoxModel::new)); } + public String getDefaultEbsEncryptRootVolume() { return EbsEncryptRootVolume.DEFAULT.getDisplayText(); } @POST - public ListBoxModel doFillEbsEncryptRootVolumeItems(@QueryParameter String ebsEncryptRootVolume ) { + public ListBoxModel doFillEbsEncryptRootVolumeItems(@QueryParameter String ebsEncryptRootVolume) { return Stream.of(EbsEncryptRootVolume.values()) .map(v -> { if (v.name().equals(ebsEncryptRootVolume)) { @@ -2267,10 +3165,12 @@ public ListBoxModel doFillEbsEncryptRootVolumeItems(@QueryParameter String ebsEn @POST public FormValidation doEbsEncryptRootVolume(@QueryParameter String ebsEncryptRootVolume) { Stream stream = Stream.of(EbsEncryptRootVolume.values()); - Stream filteredStream = stream.filter(v -> v.name().equals(ebsEncryptRootVolume)); + Stream filteredStream = + stream.filter(v -> v.name().equals(ebsEncryptRootVolume)); Optional matched = filteredStream.findFirst(); Optional okResult = matched.map(s -> FormValidation.ok()); - return okResult.orElse(FormValidation.error(String.format("Could not find selected option (%s)", ebsEncryptRootVolume))); + return okResult.orElse( + FormValidation.error(String.format("Could not find selected option (%s)", ebsEncryptRootVolume))); } } } diff --git a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java index 5b278d45f..5a1a22ba0 100644 --- a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java +++ b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java @@ -1,5 +1,7 @@ package hudson.plugins.ec2; +import static hudson.Functions.checkPermission; + import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.ec2.AmazonEC2; @@ -24,16 +26,18 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -import static hudson.Functions.checkPermission; - -public final class SpotConfiguration extends AbstractDescribableImpl { +public final class SpotConfiguration extends AbstractDescribableImpl { public final boolean useBidPrice; private String spotMaxBidPrice; private boolean fallbackToOndemand; private int spotBlockReservationDuration; @Deprecated - public SpotConfiguration(boolean useBidPrice, String spotMaxBidPrice, boolean fallbackToOndemand, String spotBlockReservationDurationStr) { + public SpotConfiguration( + boolean useBidPrice, + String spotMaxBidPrice, + boolean fallbackToOndemand, + String spotBlockReservationDurationStr) { this.useBidPrice = useBidPrice; this.spotMaxBidPrice = spotMaxBidPrice; this.fallbackToOndemand = fallbackToOndemand; @@ -77,7 +81,8 @@ public void setSpotBlockReservationDuration(int spotBlockReservationDuration) { this.spotBlockReservationDuration = spotBlockReservationDuration; } - @Override public boolean equals(Object obj) { + @Override + public boolean equals(Object obj) { if (obj == null || (this.getClass() != obj.getClass())) { return false; } @@ -92,11 +97,14 @@ public void setSpotBlockReservationDuration(int spotBlockReservationDuration) { blockReservationIsEqual = false; } - return this.useBidPrice == config.useBidPrice && this.fallbackToOndemand == config.fallbackToOndemand - && normalizedBidsAreEqual && blockReservationIsEqual; + return this.useBidPrice == config.useBidPrice + && this.fallbackToOndemand == config.fallbackToOndemand + && normalizedBidsAreEqual + && blockReservationIsEqual; } - @Override public int hashCode() { + @Override + public int hashCode() { return Objects.hash(useBidPrice, spotMaxBidPrice, fallbackToOndemand); } @@ -119,7 +127,6 @@ public static String normalizeBid(String bid) { } catch (NumberFormatException ex) { return null; } - } @Extension @@ -133,10 +140,16 @@ public String getDisplayName() { * Check the current Spot price of the selected instance type for the selected region */ @RequirePOST - public FormValidation doCurrentSpotPrice(@QueryParameter boolean useInstanceProfileForCredentials, - @QueryParameter String credentialsId, @QueryParameter String region, - @QueryParameter String type, @QueryParameter String zone, @QueryParameter String roleArn, - @QueryParameter String roleSessionName, @QueryParameter String ami) throws IOException, ServletException { + public FormValidation doCurrentSpotPrice( + @QueryParameter boolean useInstanceProfileForCredentials, + @QueryParameter String credentialsId, + @QueryParameter String region, + @QueryParameter String type, + @QueryParameter String zone, + @QueryParameter String roleArn, + @QueryParameter String roleSessionName, + @QueryParameter String ami) + throws IOException, ServletException { checkPermission(EC2Cloud.PROVISION); @@ -145,8 +158,10 @@ public FormValidation doCurrentSpotPrice(@QueryParameter boolean useInstanceProf // Connect to the EC2 cloud with the access id, secret key, and // region queried from the created cloud - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider(useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); + AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); + AmazonEC2 ec2 = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, AmazonEC2Cloud.getEc2EndpointUrl(region)); if (ec2 != null) { diff --git a/src/main/java/hudson/plugins/ec2/Tenancy.java b/src/main/java/hudson/plugins/ec2/Tenancy.java index 030d2b7dc..93b995f5e 100644 --- a/src/main/java/hudson/plugins/ec2/Tenancy.java +++ b/src/main/java/hudson/plugins/ec2/Tenancy.java @@ -11,6 +11,7 @@ private Tenancy(String value) { this.value = value; } + @Override public String toString() { return this.value; } @@ -20,7 +21,7 @@ public static Tenancy fromValue(String value) { Tenancy[] var1 = values(); int var2 = var1.length; - for(int var3 = 0; var3 < var2; ++var3) { + for (int var3 = 0; var3 < var2; ++var3) { Tenancy enumEntry = var1[var3]; if (enumEntry.toString().equals(value)) { return enumEntry; @@ -46,4 +47,3 @@ public static Tenancy backwardsCompatible(boolean useDedicatedTenancy) { } } } - diff --git a/src/main/java/hudson/plugins/ec2/UnixData.java b/src/main/java/hudson/plugins/ec2/UnixData.java index dba888018..36bd8575b 100644 --- a/src/main/java/hudson/plugins/ec2/UnixData.java +++ b/src/main/java/hudson/plugins/ec2/UnixData.java @@ -14,7 +14,12 @@ public class UnixData extends AMITypeData { private final String bootDelay; @DataBoundConstructor - public UnixData(String rootCommandPrefix, String slaveCommandPrefix, String slaveCommandSuffix, String sshPort, String bootDelay) { + public UnixData( + String rootCommandPrefix, + String slaveCommandPrefix, + String slaveCommandSuffix, + String sshPort, + String bootDelay) { this.rootCommandPrefix = rootCommandPrefix; this.slaveCommandPrefix = slaveCommandPrefix; this.slaveCommandSuffix = slaveCommandSuffix; @@ -71,6 +76,7 @@ public String getSshPort() { return sshPort == null || sshPort.isEmpty() ? "22" : sshPort; } + @Override public String getBootDelay() { return bootDelay; } @@ -89,38 +95,51 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (this.getClass() != obj.getClass()) + } + if (this.getClass() != obj.getClass()) { return false; + } final UnixData other = (UnixData) obj; if (StringUtils.isEmpty(rootCommandPrefix)) { - if (!StringUtils.isEmpty(other.rootCommandPrefix)) + if (!StringUtils.isEmpty(other.rootCommandPrefix)) { return false; - } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) + } + } else if (!rootCommandPrefix.equals(other.rootCommandPrefix)) { return false; + } if (StringUtils.isEmpty(slaveCommandPrefix)) { - if (!StringUtils.isEmpty(other.slaveCommandPrefix)) + if (!StringUtils.isEmpty(other.slaveCommandPrefix)) { return false; - } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) + } + } else if (!slaveCommandPrefix.equals(other.slaveCommandPrefix)) { return false; + } if (StringUtils.isEmpty(slaveCommandSuffix)) { - if (!StringUtils.isEmpty(other.slaveCommandSuffix)) + if (!StringUtils.isEmpty(other.slaveCommandSuffix)) { return false; - } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) + } + } else if (!slaveCommandSuffix.equals(other.slaveCommandSuffix)) { return false; + } if (StringUtils.isEmpty(sshPort)) { - if (!StringUtils.isEmpty(other.sshPort)) + if (!StringUtils.isEmpty(other.sshPort)) { return false; - } else if (!sshPort.equals(other.sshPort)) + } + } else if (!sshPort.equals(other.sshPort)) { return false; + } if (bootDelay == null) { - if (other.bootDelay != null) + if (other.bootDelay != null) { return false; - } else if (!bootDelay.equals(other.bootDelay)) + } + } else if (!bootDelay.equals(other.bootDelay)) { return false; + } return true; } } diff --git a/src/main/java/hudson/plugins/ec2/WindowsData.java b/src/main/java/hudson/plugins/ec2/WindowsData.java index 4eba6b25f..fbea948e6 100644 --- a/src/main/java/hudson/plugins/ec2/WindowsData.java +++ b/src/main/java/hudson/plugins/ec2/WindowsData.java @@ -1,12 +1,10 @@ package hudson.plugins.ec2; -import java.util.concurrent.TimeUnit; -import java.util.Objects; - import hudson.Extension; import hudson.model.Descriptor; - import hudson.util.Secret; +import java.util.Objects; +import java.util.concurrent.TimeUnit; import org.kohsuke.stapler.DataBoundConstructor; public class WindowsData extends AMITypeData { @@ -15,14 +13,20 @@ public class WindowsData extends AMITypeData { private final boolean useHTTPS; private final String bootDelay; private final boolean specifyPassword; - private final Boolean allowSelfSignedCertificate; //Boolean to allow nulls when the saved template doesn't have the field + private final Boolean + allowSelfSignedCertificate; // Boolean to allow nulls when the saved template doesn't have the field @DataBoundConstructor - public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean specifyPassword, boolean allowSelfSignedCertificate) { + public WindowsData( + String password, + boolean useHTTPS, + String bootDelay, + boolean specifyPassword, + boolean allowSelfSignedCertificate) { this.password = Secret.fromString(password); this.useHTTPS = useHTTPS; this.bootDelay = bootDelay; - //Backwards compatibility + // Backwards compatibility if (!specifyPassword && !this.password.getPlainText().isEmpty()) { specifyPassword = true; } @@ -30,9 +34,9 @@ public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean this.allowSelfSignedCertificate = allowSelfSignedCertificate; } - + @Deprecated - public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean specifyPassword) { + public WindowsData(String password, boolean useHTTPS, String bootDelay, boolean specifyPassword) { this(password, useHTTPS, bootDelay, specifyPassword, true); } @@ -63,6 +67,7 @@ public boolean isUseHTTPS() { return useHTTPS; } + @Override public String getBootDelay() { return bootDelay; } @@ -71,6 +76,7 @@ public boolean isSpecifyPassword() { return specifyPassword; } + @Override public int getBootDelayInMillis() { try { return (int) TimeUnit.SECONDS.toMillis(Integer.parseInt(bootDelay)); @@ -79,10 +85,10 @@ public int getBootDelayInMillis() { } } - public boolean isAllowSelfSignedCertificate(){ + public boolean isAllowSelfSignedCertificate() { return allowSelfSignedCertificate == null || allowSelfSignedCertificate; } - + @Extension public static class DescriptorImpl extends Descriptor { @Override @@ -93,33 +99,42 @@ public String getDisplayName() { @Override public int hashCode() { - return Objects.hash(password,useHTTPS, bootDelay, specifyPassword); + return Objects.hash(password, useHTTPS, bootDelay, specifyPassword); } @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (this.getClass() != obj.getClass()) + } + if (this.getClass() != obj.getClass()) { return false; + } final WindowsData other = (WindowsData) obj; if (bootDelay == null) { - if (other.bootDelay != null) + if (other.bootDelay != null) { return false; - } else if (!bootDelay.equals(other.bootDelay)) + } + } else if (!bootDelay.equals(other.bootDelay)) { return false; + } if (password == null) { - if (other.password != null) + if (other.password != null) { return false; - } else if (!password.equals(other.password)) + } + } else if (!password.equals(other.password)) { return false; + } if (allowSelfSignedCertificate == null) { - if (other.allowSelfSignedCertificate != null) + if (other.allowSelfSignedCertificate != null) { return false; - } else if (!allowSelfSignedCertificate.equals(other.allowSelfSignedCertificate)) + } + } else if (!allowSelfSignedCertificate.equals(other.allowSelfSignedCertificate)) { return false; + } return useHTTPS == other.useHTTPS && specifyPassword == other.specifyPassword; } } diff --git a/src/main/java/hudson/plugins/ec2/ebs/ZPoolExpandNotice.java b/src/main/java/hudson/plugins/ec2/ebs/ZPoolExpandNotice.java index e5ab4a08a..a11eb5a9e 100644 --- a/src/main/java/hudson/plugins/ec2/ebs/ZPoolExpandNotice.java +++ b/src/main/java/hudson/plugins/ec2/ebs/ZPoolExpandNotice.java @@ -23,8 +23,8 @@ */ package hudson.plugins.ec2.ebs; -import hudson.model.AdministrativeMonitor; import hudson.Extension; +import hudson.model.AdministrativeMonitor; /** * {@link AdministrativeMonitor} that tells the user that ZFS pool is filling up and they need to add more storage. @@ -36,7 +36,7 @@ public class ZPoolExpandNotice extends AdministrativeMonitor { /** * Set by {@link ZPoolMonitor}. */ - /* package */boolean activated = false; + /* package */ boolean activated = false; public ZPoolExpandNotice() { super("zpool.ebs"); diff --git a/src/main/java/hudson/plugins/ec2/ebs/ZPoolMonitor.java b/src/main/java/hudson/plugins/ec2/ebs/ZPoolMonitor.java index 61e6abd68..68dbb79e5 100644 --- a/src/main/java/hudson/plugins/ec2/ebs/ZPoolMonitor.java +++ b/src/main/java/hudson/plugins/ec2/ebs/ZPoolMonitor.java @@ -23,18 +23,17 @@ */ package hudson.plugins.ec2.ebs; -import hudson.model.PeriodicWork; -import hudson.model.AdministrativeMonitor; import hudson.Extension; +import hudson.model.AdministrativeMonitor; +import hudson.model.PeriodicWork; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.TimeUnit; import jenkins.model.Jenkins; import org.jvnet.solaris.libzfs.LibZFS; import org.jvnet.solaris.libzfs.ZFSFileSystem; import org.jvnet.solaris.libzfs.ZFSPool; -import java.net.URL; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - /** * Once an hour, check if the main zpool is that hosts $HUDSON_HOME has still enough free space. * @@ -55,8 +54,9 @@ protected void doRun() { Jenkins jenkinsInstance = Jenkins.getInstanceOrNull(); ZFSFileSystem fs = null; try { - if (isInsideEC2() && jenkinsInstance != null) + if (isInsideEC2() && jenkinsInstance != null) { fs = new LibZFS().getFileSystemByMountPoint(jenkinsInstance.getRootDir()); + } } catch (LinkageError e) { // probably not running on OpenSolaris } diff --git a/src/main/java/hudson/plugins/ec2/ebs/package-info.java b/src/main/java/hudson/plugins/ec2/ebs/package-info.java index ccd1f7a4a..326cfe3a1 100644 --- a/src/main/java/hudson/plugins/ec2/ebs/package-info.java +++ b/src/main/java/hudson/plugins/ec2/ebs/package-info.java @@ -29,4 +29,4 @@ * This should eventually move to its own plugin, but for * now I'm putting this here. */ -package hudson.plugins.ec2.ebs; \ No newline at end of file +package hudson.plugins.ec2.ebs; diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java index f45b54309..996245d62 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java @@ -39,10 +39,6 @@ import hudson.remoting.Channel.Listener; import hudson.slaves.CommandLauncher; import hudson.slaves.ComputerLauncher; -import jenkins.model.Jenkins; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; - import java.io.*; import java.net.InetSocketAddress; import java.net.Proxy; @@ -50,6 +46,9 @@ import java.nio.file.Files; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.model.Jenkins; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; /** * {@link ComputerLauncher} that connects to a Unix agent on EC2 by using SSH. @@ -61,9 +60,9 @@ public class EC2MacLauncher extends EC2ComputerLauncher { private static final Logger LOGGER = Logger.getLogger(EC2MacLauncher.class.getName()); private static final String BOOTSTRAP_AUTH_SLEEP_MS = "jenkins.ec2.bootstrapAuthSleepMs"; - private static final String BOOTSTRAP_AUTH_TRIES= "jenkins.ec2.bootstrapAuthTries"; + private static final String BOOTSTRAP_AUTH_TRIES = "jenkins.ec2.bootstrapAuthTries"; private static final String READINESS_SLEEP_MS = "jenkins.ec2.readinessSleepMs"; - private static final String READINESS_TRIES= "jenkins.ec2.readinessTries"; + private static final String READINESS_TRIES = "jenkins.ec2.readinessTries"; private static int bootstrapAuthSleepMs = 30000; private static int bootstrapAuthTries = 30; @@ -71,19 +70,23 @@ public class EC2MacLauncher extends EC2ComputerLauncher { private static int readinessSleepMs = 1000; private static int readinessTries = 120; - static { + static { String prop = System.getProperty(BOOTSTRAP_AUTH_SLEEP_MS); - if (prop != null) + if (prop != null) { bootstrapAuthSleepMs = Integer.parseInt(prop); + } prop = System.getProperty(BOOTSTRAP_AUTH_TRIES); - if (prop != null) + if (prop != null) { bootstrapAuthTries = Integer.parseInt(prop); + } prop = System.getProperty(READINESS_TRIES); - if (prop != null) + if (prop != null) { readinessTries = Integer.parseInt(prop); + } prop = System.getProperty(READINESS_SLEEP_MS); - if (prop != null) + if (prop != null) { readinessSleepMs = Integer.parseInt(prop); + } } protected void log(Level level, EC2Computer computer, TaskListener listener, String message) { @@ -111,17 +114,17 @@ protected String buildUpCommand(EC2Computer computer, String command) { } @Override - protected void launchScript(EC2Computer computer, TaskListener listener) throws IOException, - AmazonClientException, InterruptedException { + protected void launchScript(EC2Computer computer, TaskListener listener) + throws IOException, AmazonClientException, InterruptedException { final Connection conn; Connection cleanupConn = null; // java's code path analysis for final - // doesn't work that well. + // doesn't work that well. boolean successful = false; PrintStream logger = listener.getLogger(); EC2AbstractSlave node = computer.getNode(); SlaveTemplate template = computer.getSlaveTemplate(); - if(node == null) { + if (node == null) { throw new IllegalStateException(); } @@ -138,12 +141,17 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws break; } - logInfo(computer, listener, "Node still not ready. Current status: " + readinessNode.getEc2ReadinessStatus()); + logInfo( + computer, + listener, + "Node still not ready. Current status: " + readinessNode.getEc2ReadinessStatus()); Thread.sleep(readinessSleepMs); } if (!readinessNode.isReady()) { - throw new AmazonClientException("Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + "s with status " + readinessNode.getEc2ReadinessStatus()); + throw new AmazonClientException( + "Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + + "s with status " + readinessNode.getEc2ReadinessStatus()); } } @@ -154,7 +162,10 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws if (isBootstrapped) { int bootDelay = node.getBootDelay(); if (bootDelay > 0) { - logInfo(computer, listener, "SSH service responded. Waiting " + bootDelay + "ms for service to stabilize"); + logInfo( + computer, + listener, + "SSH service responded. Waiting " + bootDelay + "ms for service to stabilize"); Thread.sleep(bootDelay); logInfo(computer, listener, "SSH service should have stabilized"); } @@ -162,7 +173,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logInfo(computer, listener, "connect fresh as root"); cleanupConn = connectToSsh(computer, listener, template); KeyPair key = computer.getCloud().getKeyPair(); - if (key == null || !cleanupConn.authenticateWithPublicKey(computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { + if (key == null + || !cleanupConn.authenticateWithPublicKey( + computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { logWarning(computer, listener, "Authentication failed"); return; // failed to connect as root. } @@ -179,18 +192,19 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logInfo(computer, listener, "Creating tmp directory (" + tmpDir + ") if it does not exist"); conn.exec("mkdir -p " + tmpDir, logger); - if (initScript != null && initScript.trim().length() > 0 + if (initScript != null + && initScript.trim().length() > 0 && conn.exec("test -e ~/.hudson-run-init", logger) != 0) { logInfo(computer, listener, "Executing init script"); scp.put(initScript.getBytes("UTF-8"), "init.sh", tmpDir, "0700"); Session sess = conn.openSession(); sess.requestDumbPTY(); // so that the remote side bundles stdout - // and stderr + // and stderr sess.execCommand(buildUpCommand(computer, tmpDir + "/init.sh")); sess.getStdin().close(); // nothing to write here sess.getStderr().close(); // we are not supposed to get anything - // from stderr + // from stderr IOUtils.copy(sess.getStdout(), logger); int exitStatus = waitCompletion(sess); @@ -205,12 +219,12 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws // Needs a tty to run sudo. sess = conn.openSession(); sess.requestDumbPTY(); // so that the remote side bundles stdout - // and stderr + // and stderr sess.execCommand(buildUpCommand(computer, "touch ~/.hudson-run-init")); sess.getStdin().close(); // nothing to write here sess.getStderr().close(); // we are not supposed to get anything - // from stderr + // from stderr IOUtils.copy(sess.getStdout(), logger); exitStatus = waitCompletion(sess); @@ -227,10 +241,21 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws Instance nodeInstance = computer.describeInstance(); if (nodeInstance.getInstanceType().equals("mac2.metal")) { LOGGER.info("Running Command for mac2.metal"); - executeRemote(computer, conn, javaPath + " -fullversion", "curl -L -O https://corretto.aws/downloads/latest/amazon-corretto-11-aarch64-macos-jdk.pkg; sudo installer -pkg amazon-corretto-11-aarch64-macos-jdk.pkg -target /", logger, listener); - } - else{ - executeRemote(computer, conn, javaPath + " -fullversion", "curl -L -O https://corretto.aws/downloads/latest/amazon-corretto-11-x64-macos-jdk.pkg; sudo installer -pkg amazon-corretto-11-x64-macos-jdk.pkg -target /", logger, listener); + executeRemote( + computer, + conn, + javaPath + " -fullversion", + "curl -L -O https://corretto.aws/downloads/latest/amazon-corretto-11-aarch64-macos-jdk.pkg; sudo installer -pkg amazon-corretto-11-aarch64-macos-jdk.pkg -target /", + logger, + listener); + } else { + executeRemote( + computer, + conn, + javaPath + " -fullversion", + "curl -L -O https://corretto.aws/downloads/latest/amazon-corretto-11-x64-macos-jdk.pkg; sudo installer -pkg amazon-corretto-11-x64-macos-jdk.pkg -target /", + logger, + listener); } } catch (InterruptedException ex) { LOGGER.warning(ex.getMessage()); @@ -245,8 +270,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws final String suffix = computer.getSlaveCommandSuffix(); final String remoteFS = node.getRemoteFS(); final String workDir = Util.fixEmptyAndTrim(remoteFS) != null ? remoteFS : tmpDir; - String launchString = prefix + " " + javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + "/remoting.jar -workDir " + workDir + suffix; - // launchString = launchString.trim(); + String launchString = prefix + " " + javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + + "/remoting.jar -workDir " + workDir + suffix; + // launchString = launchString.trim(); SlaveTemplate slaveTemplate = computer.getSlaveTemplate(); @@ -256,13 +282,23 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws try { // Obviously the controller must have an installed ssh client. // Depending on the strategy selected on the UI, we set the StrictHostKeyChecking flag - String sshClientLaunchString = String.format("ssh -o StrictHostKeyChecking=%s -i %s %s@%s -p %d %s", slaveTemplate.getHostKeyVerificationStrategy().getSshCommandEquivalentFlag(), identityKeyFile.getAbsolutePath(), node.remoteAdmin, getEC2HostAddress(computer, template), node.getSshPort(), launchString); - - logInfo(computer, listener, "Launching remoting agent (via SSH client process): " + sshClientLaunchString); + String sshClientLaunchString = String.format( + "ssh -o StrictHostKeyChecking=%s -i %s %s@%s -p %d %s", + slaveTemplate.getHostKeyVerificationStrategy().getSshCommandEquivalentFlag(), + identityKeyFile.getAbsolutePath(), + node.remoteAdmin, + getEC2HostAddress(computer, template), + node.getSshPort(), + launchString); + + logInfo( + computer, + listener, + "Launching remoting agent (via SSH client process): " + sshClientLaunchString); CommandLauncher commandLauncher = new CommandLauncher(sshClientLaunchString, null); commandLauncher.launch(computer, listener); } finally { - if(!identityKeyFile.delete()) { + if (!identityKeyFile.delete()) { LOGGER.log(Level.WARNING, "Failed to delete identity key file"); } } @@ -281,14 +317,21 @@ public void onClosed(Channel channel, IOException cause) { successful = true; } finally { - if (cleanupConn != null && !successful) + if (cleanupConn != null && !successful) { cleanupConn.close(); + } } } - private boolean executeRemote(EC2Computer computer, Connection conn, String checkCommand, String command, PrintStream logger, TaskListener listener) + private boolean executeRemote( + EC2Computer computer, + Connection conn, + String checkCommand, + String command, + PrintStream logger, + TaskListener listener) throws IOException, InterruptedException { - logInfo(computer, listener,"Verifying: " + checkCommand); + logInfo(computer, listener, "Verifying: " + checkCommand); if (conn.exec(checkCommand, logger) != 0) { logInfo(computer, listener, "Installing: " + command); if (conn.exec(command, logger) != 0) { @@ -302,7 +345,7 @@ private boolean executeRemote(EC2Computer computer, Connection conn, String chec private File createIdentityKeyFile(EC2Computer computer) throws IOException { EC2PrivateKey ec2PrivateKey = computer.getCloud().resolvePrivateKey(); String privateKey = ""; - if (ec2PrivateKey != null){ + if (ec2PrivateKey != null) { privateKey = ec2PrivateKey.getPrivateKey(); } @@ -329,8 +372,8 @@ private File createIdentityKeyFile(EC2Computer computer) throws IOException { } } - private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) throws IOException, - InterruptedException, AmazonClientException { + private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) + throws IOException, InterruptedException, AmazonClientException { logInfo(computer, listener, "bootstrap()"); Connection bootstrapConn = null; try { @@ -338,18 +381,22 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp boolean isAuthenticated = false; logInfo(computer, listener, "Getting keypair..."); KeyPair key = computer.getCloud().getKeyPair(); - if (key == null){ + if (key == null) { logWarning(computer, listener, "Could not retrieve a valid key pair."); return false; } - logInfo(computer, listener, - String.format("Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); + logInfo( + computer, + listener, + String.format( + "Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); while (tries-- > 0) { logInfo(computer, listener, "Authenticating as " + computer.getRemoteAdmin()); try { bootstrapConn = connectToSsh(computer, listener, template); - isAuthenticated = bootstrapConn.authenticateWithPublicKey(computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); - } catch(IOException e) { + isAuthenticated = bootstrapConn.authenticateWithPublicKey( + computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); + } catch (IOException e) { logException(computer, listener, "Exception trying to authenticate", e); bootstrapConn.close(); } @@ -371,8 +418,8 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp return true; } - private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) throws AmazonClientException, - InterruptedException { + private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) + throws AmazonClientException, InterruptedException { final EC2AbstractSlave node = computer.getNode(); final long timeout = node == null ? 0L : node.getLaunchTimeoutInMillis(); final long startTime = System.currentTimeMillis(); @@ -387,8 +434,8 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla String host = getEC2HostAddress(computer, template); if ((node instanceof EC2SpotSlave) && computer.getInstanceId() == null) { - // getInstanceId() on EC2SpotSlave can return null if the spot request doesn't yet know - // the instance id that it is starting. Continue to wait until the instanceId is set. + // getInstanceId() on EC2SpotSlave can return null if the spot request doesn't yet know + // the instance id that it is starting. Continue to wait until the instanceId is set. logInfo(computer, listener, "empty instanceId for Spot Slave."); throw new IOException("goto sleep"); } @@ -399,14 +446,19 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla } if ("0.0.0.0".equals(host)) { - logWarning(computer, listener, "Invalid host 0.0.0.0, your host is most likely waiting for an ip address."); + logWarning( + computer, + listener, + "Invalid host 0.0.0.0, your host is most likely waiting for an ip address."); throw new IOException("goto sleep"); } int port = computer.getSshPort(); Integer slaveConnectTimeout = Integer.getInteger("jenkins.ec2.slaveConnectTimeout", 10000); - logInfo(computer, listener, "Connecting to " + host + " on port " + port + ", with timeout " + slaveConnectTimeout - + "."); + logInfo( + computer, + listener, + "Connecting to " + host + " on port " + port + ", with timeout " + slaveConnectTimeout + "."); Connection conn = new Connection(host, port); ProxyConfiguration proxyConfig = Jenkins.get().proxy; Proxy proxy = proxyConfig == null ? Proxy.NO_PROXY : proxyConfig.createProxy(host); @@ -414,7 +466,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla InetSocketAddress address = (InetSocketAddress) proxy.address(); HTTPProxyData proxyData = null; if (null != proxyConfig.getUserName()) { - proxyData = new HTTPProxyData(address.getHostName(), address.getPort(), proxyConfig.getUserName(), proxyConfig.getPassword()); + proxyData = new HTTPProxyData( + address.getHostName(), + address.getPort(), + proxyConfig.getUserName(), + proxyConfig.getPassword()); } else { proxyData = new HTTPProxyData(address.getHostName(), address.getPort()); } @@ -422,7 +478,8 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla logInfo(computer, listener, "Using HTTP Proxy Configuration"); } - conn.connect(new ServerHostKeyVerifierImpl(computer, listener), slaveConnectTimeout, slaveConnectTimeout); + conn.connect( + new ServerHostKeyVerifierImpl(computer, listener), slaveConnectTimeout, slaveConnectTimeout); logInfo(computer, listener, "Connected via SSH."); return conn; // successfully connected } catch (IOException e) { @@ -431,8 +488,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla // If the computer was set offline because it's not trusted, we avoid persisting in connecting to it. // The computer is offline for a long period - if (computer.isOffline() && StringUtils.isNotBlank(computer.getOfflineCauseReason()) && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { - throw new AmazonClientException("The connection couldn't be established and the computer is now offline", e); + if (computer.isOffline() + && StringUtils.isNotBlank(computer.getOfflineCauseReason()) + && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { + throw new AmazonClientException( + "The connection couldn't be established and the computer is now offline", e); } else { logInfo(computer, listener, "Waiting for SSH to come up. Sleeping 5."); Thread.sleep(5000); @@ -455,9 +515,13 @@ public ServerHostKeyVerifierImpl(final EC2Computer computer, final TaskListener } @Override - public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { + public boolean verifyServerHostKey( + String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { SlaveTemplate template = computer.getSlaveTemplate(); - return template != null && template.getHostKeyVerificationStrategy().getStrategy().verify(computer, new HostKey(serverHostKeyAlgorithm, serverHostKey), listener); + return template != null + && template.getHostKeyVerificationStrategy() + .getStrategy() + .verify(computer, new HostKey(serverHostKeyAlgorithm, serverHostKey), listener); } } @@ -472,8 +536,9 @@ private int waitCompletion(Session session) throws InterruptedException { // to 1 sec. for (int i = 0; i < 10; i++) { Integer r = session.getExitStatus(); - if (r != null) + if (r != null) { return r; + } Thread.sleep(100); } return -1; diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index 663a356d6..04acbd606 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -23,9 +23,17 @@ */ package hudson.plugins.ec2.ssh; +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.KeyPair; +import com.trilead.ssh2.Connection; +import com.trilead.ssh2.HTTPProxyData; +import com.trilead.ssh2.SCPClient; +import com.trilead.ssh2.ServerHostKeyVerifier; +import com.trilead.ssh2.Session; import hudson.FilePath; -import hudson.Util; import hudson.ProxyConfiguration; +import hudson.Util; import hudson.model.Descriptor; import hudson.model.TaskListener; import hudson.plugins.ec2.*; @@ -36,7 +44,6 @@ import hudson.remoting.Channel.Listener; import hudson.slaves.CommandLauncher; import hudson.slaves.ComputerLauncher; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -49,19 +56,8 @@ import java.util.Base64; import java.util.logging.Level; import java.util.logging.Logger; - import jenkins.model.Jenkins; - import org.apache.commons.io.IOUtils; - -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.KeyPair; -import com.trilead.ssh2.Connection; -import com.trilead.ssh2.HTTPProxyData; -import com.trilead.ssh2.SCPClient; -import com.trilead.ssh2.ServerHostKeyVerifier; -import com.trilead.ssh2.Session; import org.apache.commons.lang.StringUtils; /** @@ -74,9 +70,9 @@ public class EC2UnixLauncher extends EC2ComputerLauncher { private static final Logger LOGGER = Logger.getLogger(EC2UnixLauncher.class.getName()); private static final String BOOTSTRAP_AUTH_SLEEP_MS = "jenkins.ec2.bootstrapAuthSleepMs"; - private static final String BOOTSTRAP_AUTH_TRIES= "jenkins.ec2.bootstrapAuthTries"; + private static final String BOOTSTRAP_AUTH_TRIES = "jenkins.ec2.bootstrapAuthTries"; private static final String READINESS_SLEEP_MS = "jenkins.ec2.readinessSleepMs"; - private static final String READINESS_TRIES= "jenkins.ec2.readinessTries"; + private static final String READINESS_TRIES = "jenkins.ec2.readinessTries"; private static int bootstrapAuthSleepMs = 30000; private static int bootstrapAuthTries = 30; @@ -84,19 +80,23 @@ public class EC2UnixLauncher extends EC2ComputerLauncher { private static int readinessSleepMs = 1000; private static int readinessTries = 120; - static { + static { String prop = System.getProperty(BOOTSTRAP_AUTH_SLEEP_MS); - if (prop != null) + if (prop != null) { bootstrapAuthSleepMs = Integer.parseInt(prop); + } prop = System.getProperty(BOOTSTRAP_AUTH_TRIES); - if (prop != null) + if (prop != null) { bootstrapAuthTries = Integer.parseInt(prop); + } prop = System.getProperty(READINESS_TRIES); - if (prop != null) + if (prop != null) { readinessTries = Integer.parseInt(prop); + } prop = System.getProperty(READINESS_SLEEP_MS); - if (prop != null) + if (prop != null) { readinessSleepMs = Integer.parseInt(prop); + } } protected void log(Level level, EC2Computer computer, TaskListener listener, String message) { @@ -124,17 +124,17 @@ protected String buildUpCommand(EC2Computer computer, String command) { } @Override - protected void launchScript(EC2Computer computer, TaskListener listener) throws IOException, - AmazonClientException, InterruptedException { + protected void launchScript(EC2Computer computer, TaskListener listener) + throws IOException, AmazonClientException, InterruptedException { final Connection conn; Connection cleanupConn = null; // java's code path analysis for final - // doesn't work that well. + // doesn't work that well. boolean successful = false; PrintStream logger = listener.getLogger(); EC2AbstractSlave node = computer.getNode(); SlaveTemplate template = computer.getSlaveTemplate(); - if(node == null) { + if (node == null) { throw new IllegalStateException(); } @@ -151,12 +151,17 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws break; } - logInfo(computer, listener, "Node still not ready. Current status: " + readinessNode.getEc2ReadinessStatus()); + logInfo( + computer, + listener, + "Node still not ready. Current status: " + readinessNode.getEc2ReadinessStatus()); Thread.sleep(readinessSleepMs); } if (!readinessNode.isReady()) { - throw new AmazonClientException("Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + "s with status " + readinessNode.getEc2ReadinessStatus()); + throw new AmazonClientException( + "Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + + "s with status " + readinessNode.getEc2ReadinessStatus()); } } @@ -167,7 +172,10 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws if (isBootstrapped) { int bootDelay = node.getBootDelay(); if (bootDelay > 0) { - logInfo(computer, listener, "SSH service responded. Waiting " + bootDelay + "ms for service to stabilize"); + logInfo( + computer, + listener, + "SSH service responded. Waiting " + bootDelay + "ms for service to stabilize"); Thread.sleep(bootDelay); logInfo(computer, listener, "SSH service should have stabilized"); } @@ -176,7 +184,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logInfo(computer, listener, "connect fresh as root"); cleanupConn = connectToSsh(computer, listener, template); KeyPair key = computer.getCloud().getKeyPair(); - if (key == null || !cleanupConn.authenticateWithPublicKey(computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { + if (key == null + || !cleanupConn.authenticateWithPublicKey( + computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { logWarning(computer, listener, "Authentication failed"); return; // failed to connect as root. } @@ -193,18 +203,19 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws logInfo(computer, listener, "Creating tmp directory (" + tmpDir + ") if it does not exist"); conn.exec("mkdir -p " + tmpDir, logger); - if (initScript != null && initScript.trim().length() > 0 + if (initScript != null + && initScript.trim().length() > 0 && conn.exec("test -e ~/.hudson-run-init", logger) != 0) { logInfo(computer, listener, "Executing init script"); scp.put(initScript.getBytes("UTF-8"), "init.sh", tmpDir, "0700"); Session sess = conn.openSession(); sess.requestDumbPTY(); // so that the remote side bundles stdout - // and stderr + // and stderr sess.execCommand(buildUpCommand(computer, tmpDir + "/init.sh")); sess.getStdin().close(); // nothing to write here sess.getStderr().close(); // we are not supposed to get anything - // from stderr + // from stderr IOUtils.copy(sess.getStdout(), logger); int exitStatus = waitCompletion(sess); @@ -219,12 +230,12 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws // Needs a tty to run sudo. sess = conn.openSession(); sess.requestDumbPTY(); // so that the remote side bundles stdout - // and stderr + // and stderr sess.execCommand(buildUpCommand(computer, "touch ~/.hudson-run-init")); sess.getStdin().close(); // nothing to write here sess.getStderr().close(); // we are not supposed to get anything - // from stderr + // from stderr IOUtils.copy(sess.getStdout(), logger); exitStatus = waitCompletion(sess); @@ -237,7 +248,13 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws // TODO: parse the version number. maven-enforcer-plugin might help final String javaPath = node.javaPath; - executeRemote(computer, conn, javaPath + " -fullversion", "sudo amazon-linux-extras install java-openjdk11 -y; sudo yum install -y fontconfig java-11-openjdk", logger, listener); + executeRemote( + computer, + conn, + javaPath + " -fullversion", + "sudo amazon-linux-extras install java-openjdk11 -y; sudo yum install -y fontconfig java-11-openjdk", + logger, + listener); executeRemote(computer, conn, "which scp", "sudo yum install -y openssh-clients", logger, listener); // Always copy so we get the most recent remoting.jar @@ -249,8 +266,9 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws final String suffix = computer.getSlaveCommandSuffix(); final String remoteFS = node.getRemoteFS(); final String workDir = Util.fixEmptyAndTrim(remoteFS) != null ? remoteFS : tmpDir; - String launchString = prefix + " " + javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + "/remoting.jar -workDir " + workDir + suffix; - // launchString = launchString.trim(); + String launchString = prefix + " " + javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + + "/remoting.jar -workDir " + workDir + suffix; + // launchString = launchString.trim(); if (template.isConnectBySSHProcess()) { File identityKeyFile = createIdentityKeyFile(computer); @@ -258,22 +276,35 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws File hostKeyFile = createHostKeyFile(computer, ec2HostAddress, listener); String userKnownHostsFileFlag = ""; if (hostKeyFile != null) { - userKnownHostsFileFlag = String.format(" -o \"UserKnownHostsFile=%s\"", hostKeyFile.getAbsolutePath()); + userKnownHostsFileFlag = + String.format(" -o \"UserKnownHostsFile=%s\"", hostKeyFile.getAbsolutePath()); } try { // Obviously the controller must have an installed ssh client. // Depending on the strategy selected on the UI, we set the StrictHostKeyChecking flag - String sshClientLaunchString = String.format("ssh -o StrictHostKeyChecking=%s%s%s -i %s %s@%s -p %d %s", template.getHostKeyVerificationStrategy().getSshCommandEquivalentFlag(), userKnownHostsFileFlag, getEC2HostKeyAlgorithmFlag(computer), identityKeyFile.getAbsolutePath(), node.remoteAdmin, ec2HostAddress, node.getSshPort(), launchString); - - logInfo(computer, listener, "Launching remoting agent (via SSH client process): " + sshClientLaunchString); + String sshClientLaunchString = String.format( + "ssh -o StrictHostKeyChecking=%s%s%s -i %s %s@%s -p %d %s", + template.getHostKeyVerificationStrategy().getSshCommandEquivalentFlag(), + userKnownHostsFileFlag, + getEC2HostKeyAlgorithmFlag(computer), + identityKeyFile.getAbsolutePath(), + node.remoteAdmin, + ec2HostAddress, + node.getSshPort(), + launchString); + + logInfo( + computer, + listener, + "Launching remoting agent (via SSH client process): " + sshClientLaunchString); CommandLauncher commandLauncher = new CommandLauncher(sshClientLaunchString, null); commandLauncher.launch(computer, listener); } finally { - if(!identityKeyFile.delete()) { + if (!identityKeyFile.delete()) { LOGGER.log(Level.WARNING, "Failed to delete identity key file"); } - if(hostKeyFile != null && !hostKeyFile.delete()) { + if (hostKeyFile != null && !hostKeyFile.delete()) { LOGGER.log(Level.WARNING, "Failed to delete host key file"); } } @@ -292,14 +323,21 @@ public void onClosed(Channel channel, IOException cause) { successful = true; } finally { - if (cleanupConn != null && (!successful || template.isConnectBySSHProcess())) + if (cleanupConn != null && (!successful || template.isConnectBySSHProcess())) { cleanupConn.close(); + } } } - private boolean executeRemote(EC2Computer computer, Connection conn, String checkCommand, String command, PrintStream logger, TaskListener listener) + private boolean executeRemote( + EC2Computer computer, + Connection conn, + String checkCommand, + String command, + PrintStream logger, + TaskListener listener) throws IOException, InterruptedException { - logInfo(computer, listener,"Verifying: " + checkCommand); + logInfo(computer, listener, "Verifying: " + checkCommand); if (conn.exec(checkCommand, logger) != 0) { logInfo(computer, listener, "Installing: " + command); if (conn.exec(command, logger) != 0) { @@ -313,7 +351,7 @@ private boolean executeRemote(EC2Computer computer, Connection conn, String chec private File createIdentityKeyFile(EC2Computer computer) throws IOException { EC2PrivateKey ec2PrivateKey = computer.getCloud().resolvePrivateKey(); String privateKey = ""; - if (ec2PrivateKey != null){ + if (ec2PrivateKey != null) { privateKey = ec2PrivateKey.getPrivateKey(); } @@ -340,17 +378,20 @@ private File createIdentityKeyFile(EC2Computer computer) throws IOException { } } - private File createHostKeyFile(EC2Computer computer, String ec2HostAddress, TaskListener listener) throws IOException { + private File createHostKeyFile(EC2Computer computer, String ec2HostAddress, TaskListener listener) + throws IOException { HostKey ec2HostKey = HostKeyHelper.getInstance().getHostKey(computer); - if (ec2HostKey == null){ + if (ec2HostKey == null) { return null; } File tempFile = Files.createTempFile("ec2_", "_known_hosts").toFile(); String knownHost = ""; - knownHost = String.format("%s %s %s", ec2HostAddress, ec2HostKey.getAlgorithm(), Base64.getEncoder().encodeToString(ec2HostKey.getKey())); + knownHost = String.format( + "%s %s %s", + ec2HostAddress, ec2HostKey.getAlgorithm(), Base64.getEncoder().encodeToString(ec2HostKey.getKey())); try (FileOutputStream fileOutputStream = new FileOutputStream(tempFile); - OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) { + OutputStreamWriter writer = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8)) { writer.write(knownHost); writer.flush(); FilePath filePath = new FilePath(tempFile); @@ -364,8 +405,8 @@ private File createHostKeyFile(EC2Computer computer, String ec2HostAddress, Task } } - private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) throws IOException, - InterruptedException, AmazonClientException { + private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) + throws IOException, InterruptedException, AmazonClientException { logInfo(computer, listener, "bootstrap()"); Connection bootstrapConn = null; try { @@ -373,18 +414,22 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp boolean isAuthenticated = false; logInfo(computer, listener, "Getting keypair..."); KeyPair key = computer.getCloud().getKeyPair(); - if (key == null){ + if (key == null) { logWarning(computer, listener, "Could not retrieve a valid key pair."); return false; } - logInfo(computer, listener, - String.format("Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); + logInfo( + computer, + listener, + String.format( + "Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); while (tries-- > 0) { logInfo(computer, listener, "Authenticating as " + computer.getRemoteAdmin()); try { bootstrapConn = connectToSsh(computer, listener, template); - isAuthenticated = bootstrapConn.authenticateWithPublicKey(computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); - } catch(IOException e) { + isAuthenticated = bootstrapConn.authenticateWithPublicKey( + computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); + } catch (IOException e) { logException(computer, listener, "Exception trying to authenticate", e); bootstrapConn.close(); } @@ -406,8 +451,8 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp return true; } - private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) throws AmazonClientException, - InterruptedException { + private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) + throws AmazonClientException, InterruptedException { final EC2AbstractSlave node = computer.getNode(); final long timeout = node == null ? 0L : node.getLaunchTimeoutInMillis(); final long startTime = System.currentTimeMillis(); @@ -422,8 +467,8 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla String host = getEC2HostAddress(computer, template); if ((node instanceof EC2SpotSlave) && computer.getInstanceId() == null) { - // getInstanceId() on EC2SpotSlave can return null if the spot request doesn't yet know - // the instance id that it is starting. Continue to wait until the instanceId is set. + // getInstanceId() on EC2SpotSlave can return null if the spot request doesn't yet know + // the instance id that it is starting. Continue to wait until the instanceId is set. logInfo(computer, listener, "empty instanceId for Spot Slave."); throw new IOException("goto sleep"); } @@ -434,14 +479,19 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla } if ("0.0.0.0".equals(host)) { - logWarning(computer, listener, "Invalid host 0.0.0.0, your host is most likely waiting for an ip address."); + logWarning( + computer, + listener, + "Invalid host 0.0.0.0, your host is most likely waiting for an ip address."); throw new IOException("goto sleep"); } int port = computer.getSshPort(); Integer slaveConnectTimeout = Integer.getInteger("jenkins.ec2.slaveConnectTimeout", 10000); - logInfo(computer, listener, "Connecting to " + host + " on port " + port + ", with timeout " + slaveConnectTimeout - + "."); + logInfo( + computer, + listener, + "Connecting to " + host + " on port " + port + ", with timeout " + slaveConnectTimeout + "."); Connection conn = new Connection(host, port); ProxyConfiguration proxyConfig = Jenkins.get().proxy; Proxy proxy = proxyConfig == null ? Proxy.NO_PROXY : proxyConfig.createProxy(host); @@ -449,7 +499,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla InetSocketAddress address = (InetSocketAddress) proxy.address(); HTTPProxyData proxyData = null; if (null != proxyConfig.getUserName()) { - proxyData = new HTTPProxyData(address.getHostName(), address.getPort(), proxyConfig.getUserName(), proxyConfig.getPassword()); + proxyData = new HTTPProxyData( + address.getHostName(), + address.getPort(), + proxyConfig.getUserName(), + proxyConfig.getPassword()); } else { proxyData = new HTTPProxyData(address.getHostName(), address.getPort()); } @@ -457,7 +511,8 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla logInfo(computer, listener, "Using HTTP Proxy Configuration"); } - conn.connect(new ServerHostKeyVerifierImpl(computer, listener), slaveConnectTimeout, slaveConnectTimeout); + conn.connect( + new ServerHostKeyVerifierImpl(computer, listener), slaveConnectTimeout, slaveConnectTimeout); logInfo(computer, listener, "Connected via SSH."); return conn; // successfully connected } catch (IOException e) { @@ -466,8 +521,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla // If the computer was set offline because it's not trusted, we avoid persisting in connecting to it. // The computer is offline for a long period - if (computer.isOffline() && StringUtils.isNotBlank(computer.getOfflineCauseReason()) && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { - throw new AmazonClientException("The connection couldn't be established and the computer is now offline", e); + if (computer.isOffline() + && StringUtils.isNotBlank(computer.getOfflineCauseReason()) + && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { + throw new AmazonClientException( + "The connection couldn't be established and the computer is now offline", e); } else { logInfo(computer, listener, "Waiting for SSH to come up. Sleeping 5."); Thread.sleep(5000); @@ -490,9 +548,13 @@ public ServerHostKeyVerifierImpl(final EC2Computer computer, final TaskListener } @Override - public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { + public boolean verifyServerHostKey( + String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { SlaveTemplate template = computer.getSlaveTemplate(); - return template != null && template.getHostKeyVerificationStrategy().getStrategy().verify(computer, new HostKey(serverHostKeyAlgorithm, serverHostKey), listener); + return template != null + && template.getHostKeyVerificationStrategy() + .getStrategy() + .verify(computer, new HostKey(serverHostKeyAlgorithm, serverHostKey), listener); } } @@ -504,7 +566,7 @@ private static String getEC2HostAddress(EC2Computer computer, SlaveTemplate temp private static String getEC2HostKeyAlgorithmFlag(EC2Computer computer) throws IOException { HostKey ec2HostKey = HostKeyHelper.getInstance().getHostKey(computer); - if (ec2HostKey != null){ + if (ec2HostKey != null) { return String.format(" -o \"HostKeyAlgorithms=%s\"", ec2HostKey.getAlgorithm()); } return ""; @@ -515,8 +577,9 @@ private int waitCompletion(Session session) throws InterruptedException { // to 1 sec. for (int i = 0; i < 10; i++) { Integer r = session.getExitStatus(); - if (r != null) + if (r != null) { return r; + } Thread.sleep(100); } return -1; diff --git a/src/main/java/hudson/plugins/ec2/ssh/HostKeyVerifierImpl.java b/src/main/java/hudson/plugins/ec2/ssh/HostKeyVerifierImpl.java index 884a2420b..ac44fceb1 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/HostKeyVerifierImpl.java +++ b/src/main/java/hudson/plugins/ec2/ssh/HostKeyVerifierImpl.java @@ -23,10 +23,9 @@ */ package hudson.plugins.ec2.ssh; -import java.util.logging.Logger; - import com.trilead.ssh2.ServerHostKeyVerifier; import java.security.MessageDigest; +import java.util.logging.Logger; public class HostKeyVerifierImpl implements ServerHostKeyVerifier { private static final Logger LOGGER = Logger.getLogger(HostKeyVerifierImpl.class.getName()); @@ -44,13 +43,15 @@ private String getFingerprint(byte[] serverHostKey) throws Exception { StringBuilder buf = new StringBuilder(); for (byte b : fingerprint) { - if (buf.length() > 0) + if (buf.length() > 0) { buf.append(':'); + } buf.append(String.format("%02x", b)); } return buf.toString(); } + @Override public boolean verifyServerHostKey(String hostname, int port, String serverHostKeyAlgorithm, byte[] serverHostKey) throws Exception { String fingerprint = getFingerprint(serverHostKey); @@ -59,10 +60,10 @@ public boolean verifyServerHostKey(String hostname, int port, String serverHostK boolean matches = console.contains(fingerprint); - if (!matches) + if (!matches) { LOGGER.severe("No matching fingerprint found in the console output: " + console); + } return matches; } - } diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/AcceptNewStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/AcceptNewStrategy.java index 76bf4a015..cb056c3be 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/AcceptNewStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/AcceptNewStrategy.java @@ -27,7 +27,6 @@ import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; import hudson.slaves.OfflineCause; - import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -41,19 +40,35 @@ */ public class AcceptNewStrategy extends SshHostKeyVerificationStrategy { private static final Logger LOGGER = Logger.getLogger(AcceptNewStrategy.class.getName()); - + @Override public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException { HostKey existingHostKey = HostKeyHelper.getInstance().getHostKey(computer); if (null == existingHostKey) { HostKeyHelper.getInstance().saveHostKey(computer, hostKey); - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key %s %s has been automatically trusted for connections to %s", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The SSH key %s %s has been automatically trusted for connections to %s", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); return true; } else if (existingHostKey.equals(hostKey)) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("Connection allowed after the host key has been verified")); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format("Connection allowed after the host key has been verified")); return true; } else { - EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.WARNING, + computer.getListener(), + String.format( + "The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", + hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); return false; diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewHardStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewHardStrategy.java index 4f637f7e5..b1c9f198d 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewHardStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewHardStrategy.java @@ -27,7 +27,6 @@ import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; import hudson.slaves.OfflineCause; - import java.io.IOException; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -43,22 +42,40 @@ */ public class CheckNewHardStrategy extends SshHostKeyVerificationStrategy { private static final Logger LOGGER = Logger.getLogger(CheckNewHardStrategy.class.getName()); - + @Override public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException { HostKey existingHostKey = HostKeyHelper.getInstance().getHostKey(computer); if (null == existingHostKey) { HostKey consoleHostKey = getHostKeyFromConsole(LOGGER, computer, hostKey.getAlgorithm()); - + if (hostKey.equals(consoleHostKey)) { HostKeyHelper.getInstance().saveHostKey(computer, hostKey); - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key %s %s has been successfully checked against the instance console for connections to %s", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The SSH key %s %s has been successfully checked against the instance console for connections to %s", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); return true; } else if (consoleHostKey == null) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The instance console is blank. Cannot check the key. The connection to %s is not allowed", computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The instance console is blank. Cannot check the key. The connection to %s is not allowed", + computer.getName())); return false; // waiting for next retry to have the console filled up } else if (consoleHostKey.getKey().length == 0) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key (%s %s) presented by the instance has not been found on the instance console. Cannot check the key. The connection to %s is not allowed", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The SSH key (%s %s) presented by the instance has not been found on the instance console. Cannot check the key. The connection to %s is not allowed", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); // it is the difference with the soft strategy, the key is not accepted boolean stop = false; try { @@ -68,21 +85,41 @@ public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listen } if (stop) { - computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); // avoid next try + computer.setTemporarilyOffline( + true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); // avoid next try } return false; } else { - EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s %s) presented by the instance is different from the one printed out on the instance console (%s %s). The connection to %s is closed to prevent a possible man-in-the-middle attack", - hostKey.getAlgorithm(), hostKey.getFingerprint(), consoleHostKey.getAlgorithm(), consoleHostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.WARNING, + computer.getListener(), + String.format( + "The SSH key (%s %s) presented by the instance is different from the one printed out on the instance console (%s %s). The connection to %s is closed to prevent a possible man-in-the-middle attack", + hostKey.getAlgorithm(), + hostKey.getFingerprint(), + consoleHostKey.getAlgorithm(), + consoleHostKey.getFingerprint(), + computer.getName())); // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); return false; } } else if (existingHostKey.equals(hostKey)) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("Connection allowed after the host key has been verified")); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format("Connection allowed after the host key has been verified")); return true; } else { - EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.WARNING, + computer.getListener(), + String.format( + "The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", + hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); return false; diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewSoftStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewSoftStrategy.java index c884659b1..cad6090ad 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewSoftStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/CheckNewSoftStrategy.java @@ -27,7 +27,6 @@ import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; import hudson.slaves.OfflineCause; - import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,38 +44,75 @@ */ public class CheckNewSoftStrategy extends SshHostKeyVerificationStrategy { private static final Logger LOGGER = Logger.getLogger(CheckNewSoftStrategy.class.getName()); - + @Override public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException { HostKey existingHostKey = HostKeyHelper.getInstance().getHostKey(computer); if (null == existingHostKey) { HostKey consoleHostKey = getHostKeyFromConsole(LOGGER, computer, hostKey.getAlgorithm()); - + if (hostKey.equals(consoleHostKey)) { HostKeyHelper.getInstance().saveHostKey(computer, hostKey); - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key %s %s has been successfully checked against the instance console for connections to %s", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The SSH key %s %s has been successfully checked against the instance console for connections to %s", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); return true; } else if (consoleHostKey == null) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The instance console is blank. Cannot check the key. The connection to %s is not allowed", computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The instance console is blank. Cannot check the key. The connection to %s is not allowed", + computer.getName())); return false; // waiting for next retry to have the console filled up } else if (consoleHostKey.getKey().length == 0) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("The SSH key (%s %s) presented by the instance has not been found on the instance console. Cannot check the key but the connection to %s is allowed", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); - // it is the difference with the the hard strategy, the key is accepted + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "The SSH key (%s %s) presented by the instance has not been found on the instance console. Cannot check the key but the connection to %s is allowed", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + // it is the difference with the the hard strategy, the key is accepted HostKeyHelper.getInstance().saveHostKey(computer, hostKey); return true; } else { - EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s %s) presented by the instance is different from the one printed out on the instance console (%s %s). The connection to %s is closed to prevent a possible man-in-the-middle attack", - hostKey.getAlgorithm(), hostKey.getFingerprint(), consoleHostKey.getAlgorithm(), consoleHostKey.getFingerprint(), computer.getName())); - // To avoid reconnecting continuously + EC2Cloud.log( + LOGGER, + Level.WARNING, + computer.getListener(), + String.format( + "The SSH key (%s %s) presented by the instance is different from the one printed out on the instance console (%s %s). The connection to %s is closed to prevent a possible man-in-the-middle attack", + hostKey.getAlgorithm(), + hostKey.getFingerprint(), + consoleHostKey.getAlgorithm(), + consoleHostKey.getFingerprint(), + computer.getName())); + // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); return false; } } else if (existingHostKey.equals(hostKey)) { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("Connection allowed after the host key has been verified")); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format("Connection allowed after the host key has been verified")); return true; } else { - EC2Cloud.log(LOGGER, Level.WARNING, computer.getListener(), String.format("The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); - // To avoid reconnecting continuously + EC2Cloud.log( + LOGGER, + Level.WARNING, + computer.getListener(), + String.format( + "The SSH key (%s) presented by the instance has changed since first saved (%s). The connection to %s is closed to prevent a possible man-in-the-middle attack", + hostKey.getFingerprint(), existingHostKey.getFingerprint(), computer.getName())); + // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSHKeyCheckFailed())); return false; } diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java index 1f01734fe..d88e04cb4 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKey.java @@ -1,34 +1,33 @@ /* - * The MIT License - * - * Original work from ssh-slaves-plugin Copyright (c) 2016, Michael Clarke - * Modified work Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. - * Modified work: - * - Just the since annotation +* The MIT License +* +* Original work from ssh-slaves-plugin Copyright (c) 2016, Michael Clarke +* Modified work Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. +* Modified work: +* - Just the since annotation - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ package hudson.plugins.ec2.ssh.verifiers; import com.trilead.ssh2.KnownHosts; import edu.umd.cs.findbugs.annotations.NonNull; - import java.io.Serializable; import java.util.Arrays; @@ -84,20 +83,26 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) + if (this == obj) { return true; - if (obj == null) + } + if (obj == null) { return false; - if (getClass() != obj.getClass()) + } + if (getClass() != obj.getClass()) { return false; + } HostKey other = (HostKey) obj; if (algorithm == null) { - if (other.algorithm != null) + if (other.algorithm != null) { return false; - } else if (!algorithm.equals(other.algorithm)) + } + } else if (!algorithm.equals(other.algorithm)) { return false; - if (!Arrays.equals(key, other.key)) + } + if (!Arrays.equals(key, other.key)) { return false; + } return true; } } diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java index f0eed8543..dfad6f888 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/HostKeyHelper.java @@ -1,53 +1,52 @@ /* - * The MIT License - * - * Original work from ssh-slaves-plugin Copyright (c) 2016, Michael Clarke - * Modified work Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. - * Modified work: - * - Just the since annotation +* The MIT License +* +* Original work from ssh-slaves-plugin Copyright (c) 2016, Michael Clarke +* Modified work Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. +* Modified work: +* - Just the since annotation - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ package hudson.plugins.ec2.ssh.verifiers; import hudson.XmlFile; import hudson.model.Computer; import hudson.model.Node; -import jenkins.model.Jenkins; - import java.io.File; import java.io.IOException; import java.util.Map; import java.util.WeakHashMap; +import jenkins.model.Jenkins; /** * Helper methods to allow loading and saving of host keys for a computer. Verifiers * don't have a reference to the Node or Computer that they're running for at the point * they're created, so can only load the existing key to run comparisons against at the - * point the verifier is invoked during the connection attempt. + * point the verifier is invoked during the connection attempt. * @author Michael Clarke, M Ramon Leon * @since TODO */ public final class HostKeyHelper { private static final HostKeyHelper INSTANCE = new HostKeyHelper(); - + private final Map cache = new WeakHashMap<>(); private HostKeyHelper() { @@ -58,7 +57,6 @@ public static HostKeyHelper getInstance() { return INSTANCE; } - /** * Retrieve the currently trusted host key for the requested computer, or null if * no key is currently trusted. @@ -81,7 +79,6 @@ public HostKey getHostKey(Computer host) throws IOException { return key; } - /** * Persists an SSH key to disk for the requested host. This effectively marks * the requested key as trusted for all future connections to the host, until @@ -95,18 +92,18 @@ public void saveHostKey(Computer host, HostKey hostKey) throws IOException { xmlHostKeyFile.write(hostKey); cache.put(host, hostKey); } - + private File getSshHostKeyFile(Node node) throws IOException { return new File(getNodeDirectory(node), "ssh-host-key.xml"); } - + private File getNodeDirectory(Node node) throws IOException { if (null == node) { throw new IOException("Could not load key for the requested node"); } return new File(getNodesDirectory(), node.getNodeName()); } - + private File getNodesDirectory() throws IOException { // jenkins.model.Nodes#getNodesDirectory() is private, so we have to duplicate it here. File nodesDir = new File(Jenkins.get().getRootDir(), "nodes"); diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/NonVerifyingKeyVerificationStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/NonVerifyingKeyVerificationStrategy.java index 609e33940..a5141390f 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/NonVerifyingKeyVerificationStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/NonVerifyingKeyVerificationStrategy.java @@ -2,7 +2,7 @@ * The MIT License * * Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -26,7 +26,6 @@ import hudson.model.TaskListener; import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; - import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,10 +37,16 @@ */ public class NonVerifyingKeyVerificationStrategy extends SshHostKeyVerificationStrategy { private static final Logger LOGGER = Logger.getLogger(NonVerifyingKeyVerificationStrategy.class.getName()); - + @Override public boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws IOException { - EC2Cloud.log(LOGGER, Level.INFO, computer.getListener(), String.format("No SSH key verification (%s %s) for connections to %s", hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); + EC2Cloud.log( + LOGGER, + Level.INFO, + computer.getListener(), + String.format( + "No SSH key verification (%s %s) for connections to %s", + hostKey.getAlgorithm(), hostKey.getFingerprint(), computer.getName())); return true; } } diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationAdministrativeMonitor.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationAdministrativeMonitor.java index 57e49f11f..cb0fe6d3b 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationAdministrativeMonitor.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationAdministrativeMonitor.java @@ -30,23 +30,22 @@ import hudson.plugins.ec2.PluginImpl; import hudson.plugins.ec2.SlaveTemplate; import hudson.slaves.Cloud; -import jenkins.model.Jenkins; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.HttpResponses; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.interceptor.RequirePOST; - import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import java.util.stream.Collectors; +import jenkins.model.Jenkins; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.HttpResponses; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; @Extension public class SshHostKeyVerificationAdministrativeMonitor extends AdministrativeMonitor { - private final static int MAX_TEMPLATES_FOUND = 5; - + private static final int MAX_TEMPLATES_FOUND = 5; + List veryInsecureTemplates = new ArrayList<>(MAX_TEMPLATES_FOUND); List insecureTemplates = new ArrayList<>(MAX_TEMPLATES_FOUND); @@ -73,9 +72,11 @@ public boolean showInsecureTemplates() { if (plugin == null) { return true; } - - Instant whenDismissed = Instant.ofEpochMilli(plugin.getDismissInsecureMessages()); // if not dismissed, it is EPOCH - return (whenDismissed.equals(Instant.EPOCH) || Instant.now().isBefore(whenDismissed)) && !insecureTemplates.isEmpty(); + + Instant whenDismissed = + Instant.ofEpochMilli(plugin.getDismissInsecureMessages()); // if not dismissed, it is EPOCH + return (whenDismissed.equals(Instant.EPOCH) || Instant.now().isBefore(whenDismissed)) + && !insecureTemplates.isEmpty(); } /** @@ -85,16 +86,16 @@ public boolean showInsecureTemplates() { @Override public boolean isActivated() { boolean maxTemplatesReached = false; - + ListIterator cloudIterator = Jenkins.get().clouds.listIterator(); - + // Let's clear the previously calculated wrong templates to populate the lists with them again veryInsecureTemplates.clear(); insecureTemplates.clear(); - + while (cloudIterator.hasNext() && !maxTemplatesReached) { Cloud cloud = cloudIterator.next(); - if (cloud instanceof EC2Cloud) { + if (cloud instanceof EC2Cloud) { maxTemplatesReached = gatherInsecureTemplate((EC2Cloud) cloud); } } @@ -115,29 +116,33 @@ private boolean gatherInsecureTemplate(EC2Cloud cloud) { } HostKeyVerificationStrategyEnum strategy = template.getHostKeyVerificationStrategy(); - if (veryInsecureTemplates.size() < MAX_TEMPLATES_FOUND && strategy.equals(HostKeyVerificationStrategyEnum.OFF)) { + if (veryInsecureTemplates.size() < MAX_TEMPLATES_FOUND + && strategy.equals(HostKeyVerificationStrategyEnum.OFF)) { veryInsecureTemplates.add(template.getDisplayName()); - } else if (insecureTemplates.size() < MAX_TEMPLATES_FOUND && (!strategy.equals(HostKeyVerificationStrategyEnum.CHECK_NEW_HARD))) { + } else if (insecureTemplates.size() < MAX_TEMPLATES_FOUND + && (!strategy.equals(HostKeyVerificationStrategyEnum.CHECK_NEW_HARD))) { // it is check-new-soft or accept-new insecureTemplates.add(template.getDisplayName()); } // stop collecting the status of the computers, we already have 5 each type - if (veryInsecureTemplates.size() >= MAX_TEMPLATES_FOUND || insecureTemplates.size() >= MAX_TEMPLATES_FOUND) { + if (veryInsecureTemplates.size() >= MAX_TEMPLATES_FOUND + || insecureTemplates.size() >= MAX_TEMPLATES_FOUND) { return true; } } - + return false; } - + @RequirePOST - public HttpResponse doAct(@QueryParameter String dismiss, @QueryParameter String dismissAllMessages) throws IOException { + public HttpResponse doAct(@QueryParameter String dismiss, @QueryParameter String dismissAllMessages) + throws IOException { Jenkins.get().checkPermission(Jenkins.ADMINISTER); if (dismiss != null) { PluginImpl.get().saveDismissInsecureMessages(System.currentTimeMillis()); - } - + } + if (dismissAllMessages != null) { disable(true); } diff --git a/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategy.java b/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategy.java index 12155fd60..8ba7b9552 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategy.java +++ b/src/main/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategy.java @@ -5,7 +5,7 @@ * Modified work Copyright (c) 2020-, M Ramon Leon, CloudBees, Inc. * Modified work: * - getHostKeyFromConsole method and called methods - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights @@ -35,16 +35,15 @@ import hudson.plugins.ec2.EC2Cloud; import hudson.plugins.ec2.EC2Computer; import hudson.plugins.ec2.InstanceState; -import jenkins.model.Jenkins; - import java.util.Base64; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.model.Jenkins; /** * A method for verifying the host key provided by the remote host during the * initiation of each connection. - * + * * @author Michael Clarke * @since TODO */ @@ -66,8 +65,8 @@ public SshHostKeyVerificationStrategyDescriptor getDescriptor() { */ public abstract boolean verify(EC2Computer computer, HostKey hostKey, TaskListener listener) throws Exception; - public static abstract class SshHostKeyVerificationStrategyDescriptor extends Descriptor { - } + public abstract static class SshHostKeyVerificationStrategyDescriptor + extends Descriptor {} /** * Get the host key printed out in the console. @@ -77,13 +76,21 @@ public static abstract class SshHostKeyVerificationStrategyDescriptor extends De * but an empty array as the key if the console is not blank and the key for such an algorithm couldn't be found. */ @Nullable - HostKey getHostKeyFromConsole(@NonNull final Logger logger, @NonNull final EC2Computer computer, @NonNull final String serverHostKeyAlgorithm) { + HostKey getHostKeyFromConsole( + @NonNull final Logger logger, + @NonNull final EC2Computer computer, + @NonNull final String serverHostKeyAlgorithm) { HostKey key; TaskListener listener = computer.getListener(); try { - if(!computer.getState().equals(InstanceState.RUNNING)) { - EC2Cloud.log(logger, Level.INFO, listener, "The instance " + computer.getName() + " is not running, waiting to validate the key against the console"); + if (!computer.getState().equals(InstanceState.RUNNING)) { + EC2Cloud.log( + logger, + Level.INFO, + listener, + "The instance " + computer.getName() + + " is not running, waiting to validate the key against the console"); } } catch (InterruptedException e) { return null; @@ -93,7 +100,7 @@ HostKey getHostKeyFromConsole(@NonNull final Logger logger, @NonNull final EC2Co if (line != null && line.length() > 0) { key = getKeyFromLine(logger, line, listener); } else if (line != null) { - key = new HostKey(serverHostKeyAlgorithm, new byte[]{}); + key = new HostKey(serverHostKeyAlgorithm, new byte[] {}); } else { key = null; } @@ -102,20 +109,28 @@ HostKey getHostKeyFromConsole(@NonNull final Logger logger, @NonNull final EC2Co } /** - * Get the line with the key for such an algorithm - * @param logger the logger to print the messages - * @param computer the computer + * Get the line with the key for such an algorithm + * @param logger the logger to print the messages + * @param computer the computer * @param serverHostKeyAlgorithm the algorithm to search for * @return the line where the key for the algorithm is on, null if the console is blank, "" if the console is not * blank and the line is not found. */ @CheckForNull - String getLineWithKey(@NonNull final Logger logger, @NonNull final EC2Computer computer, @NonNull final String serverHostKeyAlgorithm) { + String getLineWithKey( + @NonNull final Logger logger, + @NonNull final EC2Computer computer, + @NonNull final String serverHostKeyAlgorithm) { String line = null; String console = computer.getDecodedConsoleOutput(); if (console == null) { // The instance is running and the console is blank - EC2Cloud.log(logger, Level.INFO, computer.getListener(), "The instance " + computer.getName() + " has a blank console. Maybe the console is yet not available. If enough time has passed, consider changing the key verification strategy or the AMI used by one printing out the host key in the instance console"); + EC2Cloud.log( + logger, + Level.INFO, + computer.getListener(), + "The instance " + computer.getName() + + " has a blank console. Maybe the console is yet not available. If enough time has passed, consider changing the key verification strategy or the AMI used by one printing out the host key in the instance console"); return null; } @@ -126,7 +141,13 @@ String getLineWithKey(@NonNull final Logger logger, @NonNull final EC2Computer c line = console.substring(start, end); } else { // The instance printed on the console but the key was not printed with the expected format - EC2Cloud.log(logger, Level.INFO, computer.getListener(), String.format("The instance %s didn't print the host key. Expected a line starting with: \"%s\"", computer.getName(), serverHostKeyAlgorithm)); + EC2Cloud.log( + logger, + Level.INFO, + computer.getListener(), + String.format( + "The instance %s didn't print the host key. Expected a line starting with: \"%s\"", + computer.getName(), serverHostKeyAlgorithm)); return ""; } } catch (IllegalArgumentException ignored) { @@ -135,13 +156,20 @@ String getLineWithKey(@NonNull final Logger logger, @NonNull final EC2Computer c } @CheckForNull - HostKey getKeyFromLine(@NonNull final Logger logger, @NonNull final String line, @Nullable final TaskListener listener) { + HostKey getKeyFromLine( + @NonNull final Logger logger, @NonNull final String line, @Nullable final TaskListener listener) { String[] parts = line.split(" "); if (parts.length >= 2) { // The public SSH key in the console is Base64 encoded return new HostKey(parts[0], Base64.getDecoder().decode(parts[1])); } else { - EC2Cloud.log(logger, Level.INFO, listener, String.format("The line with the key doesn't have the required format. Found: \"%s\". Expected a line with this text: \"ALGORITHM THEHOSTKEY\", example: \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJbvbEIoY3tqKwkeRW/L1FnbCLLp8a1TwSOyZHKJqFFR \"", line)); + EC2Cloud.log( + logger, + Level.INFO, + listener, + String.format( + "The line with the key doesn't have the required format. Found: \"%s\". Expected a line with this text: \"ALGORITHM THEHOSTKEY\", example: \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJbvbEIoY3tqKwkeRW/L1FnbCLLp8a1TwSOyZHKJqFFR \"", + line)); return null; } } diff --git a/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java b/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java index 2961a1827..780470b39 100644 --- a/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java +++ b/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java @@ -1,11 +1,9 @@ package hudson.plugins.ec2.util; -import java.net.URL; - import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.ec2.AmazonEC2; - import hudson.ExtensionPoint; +import java.net.URL; import jenkins.model.Jenkins; public interface AmazonEC2Factory extends ExtensionPoint { @@ -23,5 +21,4 @@ static AmazonEC2Factory getInstance() { } AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL ec2Endpoint); - } diff --git a/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java b/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java index e685c4fbb..190b1f921 100644 --- a/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java +++ b/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java @@ -1,20 +1,19 @@ package hudson.plugins.ec2.util; -import java.net.URL; - import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2Client; - import hudson.Extension; import hudson.plugins.ec2.EC2Cloud; +import java.net.URL; @Extension public class AmazonEC2FactoryImpl implements AmazonEC2Factory { @Override public AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL ec2Endpoint) { - AmazonEC2 client = new AmazonEC2Client(credentialsProvider, EC2Cloud.createClientConfiguration(ec2Endpoint.getHost())); + AmazonEC2 client = + new AmazonEC2Client(credentialsProvider, EC2Cloud.createClientConfiguration(ec2Endpoint.getHost())); client.setEndpoint(ec2Endpoint.toString()); return client; } diff --git a/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java b/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java index 3ea5ac7b2..0b67eadb2 100644 --- a/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java +++ b/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java @@ -25,15 +25,13 @@ import com.amazonaws.services.ec2.model.BlockDeviceMapping; import com.amazonaws.services.ec2.model.EbsBlockDevice; -import org.apache.commons.lang.StringUtils; - import java.util.ArrayList; import java.util.List; +import org.apache.commons.lang.StringUtils; public class DeviceMappingParser { - private DeviceMappingParser() { - } + private DeviceMappingParser() {} public static List parse(String customDeviceMapping) { diff --git a/src/main/java/hudson/plugins/ec2/util/EC2AgentConfig.java b/src/main/java/hudson/plugins/ec2/util/EC2AgentConfig.java index 444c5b88a..527345dec 100644 --- a/src/main/java/hudson/plugins/ec2/util/EC2AgentConfig.java +++ b/src/main/java/hudson/plugins/ec2/util/EC2AgentConfig.java @@ -1,12 +1,11 @@ package hudson.plugins.ec2.util; -import hudson.plugins.ec2.Tenancy; import hudson.model.Node; import hudson.plugins.ec2.AMITypeData; import hudson.plugins.ec2.ConnectionStrategy; import hudson.plugins.ec2.EC2Tag; +import hudson.plugins.ec2.Tenancy; import hudson.slaves.NodeProperty; - import java.util.List; public abstract class EC2AgentConfig { @@ -60,8 +59,10 @@ public static class OnDemand extends EC2AgentConfig { final String publicDNS; final String privateDNS; final Tenancy tenancy; + @Deprecated final boolean useDedicatedTenancy; + final Boolean metadataSupported; final Boolean metadataEndpointEnabled; final Boolean metadataTokensRequired; @@ -92,7 +93,7 @@ private Spot(SpotBuilder builder) { } } - private static abstract class Builder, C extends EC2AgentConfig> { + private abstract static class Builder, C extends EC2AgentConfig> { private String name; private String description; @@ -225,8 +226,10 @@ public static class OnDemandBuilder extends Builder { private String publicDNS; private String privateDNS; private Tenancy tenancy; + @Deprecated private boolean useDedicatedTenancy; + private Boolean metadataSupported; private Boolean metadataEndpointEnabled; private Boolean metadataTokensRequired; @@ -279,16 +282,18 @@ public boolean isUseDedicatedTenancy() { return useDedicatedTenancy; } - public OnDemandBuilder withTenancyAttribute( Tenancy tenancy){ + public OnDemandBuilder withTenancyAttribute(Tenancy tenancy) { this.tenancy = tenancy; return this; } - public Tenancy getTenancyAttribute(){ return tenancy;} + public Tenancy getTenancyAttribute() { + return tenancy; + } public OnDemandBuilder withMetadataSupported(Boolean metadataSupported) { - this.metadataSupported = metadataSupported; - return this; + this.metadataSupported = metadataSupported; + return this; } public OnDemandBuilder withMetadataEndpointEnabled(Boolean metadataEndpointEnabled) { @@ -336,5 +341,4 @@ public Spot build() { return new Spot(this); } } - } diff --git a/src/main/java/hudson/plugins/ec2/util/EC2AgentFactory.java b/src/main/java/hudson/plugins/ec2/util/EC2AgentFactory.java index 59c79945c..aca9d136d 100644 --- a/src/main/java/hudson/plugins/ec2/util/EC2AgentFactory.java +++ b/src/main/java/hudson/plugins/ec2/util/EC2AgentFactory.java @@ -1,9 +1,8 @@ package hudson.plugins.ec2.util; -import java.io.IOException; - import hudson.model.Descriptor; import hudson.plugins.ec2.*; +import java.io.IOException; import jenkins.model.Jenkins; public interface EC2AgentFactory { @@ -23,5 +22,4 @@ static EC2AgentFactory getInstance() { EC2OndemandSlave createOnDemandAgent(EC2AgentConfig.OnDemand config) throws Descriptor.FormException, IOException; EC2SpotSlave createSpotAgent(EC2AgentConfig.Spot config) throws Descriptor.FormException, IOException; - } diff --git a/src/main/java/hudson/plugins/ec2/util/EC2AgentFactoryImpl.java b/src/main/java/hudson/plugins/ec2/util/EC2AgentFactoryImpl.java index d4d4ba372..3e1dd6d95 100644 --- a/src/main/java/hudson/plugins/ec2/util/EC2AgentFactoryImpl.java +++ b/src/main/java/hudson/plugins/ec2/util/EC2AgentFactoryImpl.java @@ -1,10 +1,9 @@ package hudson.plugins.ec2.util; -import java.io.IOException; - import hudson.Extension; import hudson.model.Descriptor; import hudson.plugins.ec2.*; +import java.io.IOException; @Extension public class EC2AgentFactoryImpl implements EC2AgentFactory { @@ -12,11 +11,59 @@ public class EC2AgentFactoryImpl implements EC2AgentFactory { @Override public EC2OndemandSlave createOnDemandAgent(EC2AgentConfig.OnDemand config) throws Descriptor.FormException, IOException { - return new EC2OndemandSlave(config.name, config.instanceId, config.description, config.remoteFS, config.numExecutors, config.labelString, config.mode, config.initScript, config.tmpDir, config.nodeProperties, config.remoteAdmin, config.javaPath, config.jvmopts, config.stopOnTerminate, config.idleTerminationMinutes, config.publicDNS, config.privateDNS, config.tags, config.cloudName, config.launchTimeout, config.amiType, config.connectionStrategy, config.maxTotalUses, config.tenancy, config.metadataEndpointEnabled, config.metadataTokensRequired, config.metadataHopsLimit, config.metadataSupported); + return new EC2OndemandSlave( + config.name, + config.instanceId, + config.description, + config.remoteFS, + config.numExecutors, + config.labelString, + config.mode, + config.initScript, + config.tmpDir, + config.nodeProperties, + config.remoteAdmin, + config.javaPath, + config.jvmopts, + config.stopOnTerminate, + config.idleTerminationMinutes, + config.publicDNS, + config.privateDNS, + config.tags, + config.cloudName, + config.launchTimeout, + config.amiType, + config.connectionStrategy, + config.maxTotalUses, + config.tenancy, + config.metadataEndpointEnabled, + config.metadataTokensRequired, + config.metadataHopsLimit, + config.metadataSupported); } @Override public EC2SpotSlave createSpotAgent(EC2AgentConfig.Spot config) throws Descriptor.FormException, IOException { - return new EC2SpotSlave(config.name, config.spotInstanceRequestId, config.description, config.remoteFS, config.numExecutors, config.mode, config.initScript, config.tmpDir, config.labelString, config.nodeProperties, config.remoteAdmin, config.javaPath, config.jvmopts, config.idleTerminationMinutes, config.tags, config.cloudName, config.launchTimeout, config.amiType, config.connectionStrategy, config.maxTotalUses); + return new EC2SpotSlave( + config.name, + config.spotInstanceRequestId, + config.description, + config.remoteFS, + config.numExecutors, + config.mode, + config.initScript, + config.tmpDir, + config.labelString, + config.nodeProperties, + config.remoteAdmin, + config.javaPath, + config.jvmopts, + config.idleTerminationMinutes, + config.tags, + config.cloudName, + config.launchTimeout, + config.amiType, + config.connectionStrategy, + config.maxTotalUses); } } diff --git a/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java b/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java index b573a1dfe..5841d3e5c 100644 --- a/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java +++ b/src/main/java/hudson/plugins/ec2/util/MinimumInstanceChecker.java @@ -1,23 +1,22 @@ package hudson.plugins.ec2.util; +import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import hudson.plugins.ec2.EC2Cloud; -import hudson.plugins.ec2.EC2Computer; -import hudson.plugins.ec2.SlaveTemplate; import hudson.model.Computer; import hudson.model.Label; import hudson.model.Queue; -import jenkins.model.Jenkins; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; - -import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.plugins.ec2.EC2Cloud; +import hudson.plugins.ec2.EC2Computer; +import hudson.plugins.ec2.SlaveTemplate; import java.time.Clock; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.Arrays; import java.util.Objects; import java.util.stream.Stream; +import jenkins.model.Jenkins; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; @Restricted(NoExternalUse.class) public class MinimumInstanceChecker { @@ -27,7 +26,7 @@ public class MinimumInstanceChecker { private static Stream agentsForTemplate(@NonNull SlaveTemplate agentTemplate) { return (Stream) Arrays.stream(Jenkins.get().getComputers()) - .filter(computer -> computer instanceof EC2Computer) + .filter(EC2Computer.class::isInstance) .filter(computer -> { SlaveTemplate computerTemplate = ((EC2Computer) computer).getSlaveTemplate(); return computerTemplate != null @@ -41,84 +40,79 @@ public static int countCurrentNumberOfAgents(@NonNull SlaveTemplate agentTemplat public static int countCurrentNumberOfSpareAgents(@NonNull SlaveTemplate agentTemplate) { return (int) agentsForTemplate(agentTemplate) - .filter(computer -> computer.countBusy() == 0) - .filter(computer -> computer.isOnline()) - .count(); + .filter(computer -> computer.countBusy() == 0) + .filter(Computer::isOnline) + .count(); } public static int countCurrentNumberOfProvisioningAgents(@NonNull SlaveTemplate agentTemplate) { return (int) agentsForTemplate(agentTemplate) - .filter(computer -> computer.countBusy() == 0) - .filter(computer -> computer.isOffline()) - .filter(computer -> computer.isConnecting()) - .count(); + .filter(computer -> computer.countBusy() == 0) + .filter(Computer::isOffline) + .filter(Computer::isConnecting) + .count(); } /* Get the number of queued builds that match an AMI (agentTemplate) */ public static int countQueueItemsForAgentTemplate(@NonNull SlaveTemplate agentTemplate) { - return (int) - Queue - .getInstance() - .getBuildableItems() - .stream() - .map((Queue.Item item) -> item.getAssignedLabel()) - .filter(Objects::nonNull) - .filter((Label label) -> label.matches(agentTemplate.getLabelSet())) - .count(); + return (int) Queue.getInstance().getBuildableItems().stream() + .map((Queue.Item item) -> item.getAssignedLabel()) + .filter(Objects::nonNull) + .filter((Label label) -> label.matches(agentTemplate.getLabelSet())) + .count(); } public static void checkForMinimumInstances() { Jenkins.get().clouds.stream() - .filter(cloud -> cloud instanceof EC2Cloud) - .map(cloud -> (EC2Cloud) cloud) - .forEach(cloud -> { - cloud.getTemplates().forEach(agentTemplate -> { - // Minimum instances now have a time range, check to see - // if we are within that time range and return early if not. - if (! minimumInstancesActive(agentTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { - return; - } - int requiredMinAgents = agentTemplate.getMinimumNumberOfInstances(); - int requiredMinSpareAgents = agentTemplate.getMinimumNumberOfSpareInstances(); - int currentNumberOfAgentsForTemplate = countCurrentNumberOfAgents(agentTemplate); - int currentNumberOfSpareAgentsForTemplate = countCurrentNumberOfSpareAgents(agentTemplate); - int currentNumberOfProvisioningAgentsForTemplate = countCurrentNumberOfProvisioningAgents(agentTemplate); - int currentBuildsWaitingForTemplate = countQueueItemsForAgentTemplate(agentTemplate); - int provisionForMinAgents = 0; - int provisionForMinSpareAgents = 0; - - // Check if we need to provision any agents because we - // don't have the minimum number of agents - provisionForMinAgents = requiredMinAgents - currentNumberOfAgentsForTemplate; - if (provisionForMinAgents < 0){ - provisionForMinAgents = 0; - } - - // Check if we need to provision any agents because we - // don't have the minimum number of spare agents. - // Don't double provision if minAgents and minSpareAgents are set. - provisionForMinSpareAgents = (requiredMinSpareAgents + currentBuildsWaitingForTemplate) - - ( - currentNumberOfSpareAgentsForTemplate + - provisionForMinAgents + - currentNumberOfProvisioningAgentsForTemplate - ); - if (provisionForMinSpareAgents < 0){ - provisionForMinSpareAgents = 0; - } - - int numberToProvision = provisionForMinAgents + provisionForMinSpareAgents; - if (numberToProvision > 0) { - cloud.provision(agentTemplate, numberToProvision); - } + .filter(EC2Cloud.class::isInstance) + .map(cloud -> (EC2Cloud) cloud) + .forEach(cloud -> { + cloud.getTemplates().forEach(agentTemplate -> { + // Minimum instances now have a time range, check to see + // if we are within that time range and return early if not. + if (!minimumInstancesActive(agentTemplate.getMinimumNumberOfInstancesTimeRangeConfig())) { + return; + } + int requiredMinAgents = agentTemplate.getMinimumNumberOfInstances(); + int requiredMinSpareAgents = agentTemplate.getMinimumNumberOfSpareInstances(); + int currentNumberOfAgentsForTemplate = countCurrentNumberOfAgents(agentTemplate); + int currentNumberOfSpareAgentsForTemplate = countCurrentNumberOfSpareAgents(agentTemplate); + int currentNumberOfProvisioningAgentsForTemplate = + countCurrentNumberOfProvisioningAgents(agentTemplate); + int currentBuildsWaitingForTemplate = countQueueItemsForAgentTemplate(agentTemplate); + int provisionForMinAgents = 0; + int provisionForMinSpareAgents = 0; + + // Check if we need to provision any agents because we + // don't have the minimum number of agents + provisionForMinAgents = requiredMinAgents - currentNumberOfAgentsForTemplate; + if (provisionForMinAgents < 0) { + provisionForMinAgents = 0; + } + + // Check if we need to provision any agents because we + // don't have the minimum number of spare agents. + // Don't double provision if minAgents and minSpareAgents are set. + provisionForMinSpareAgents = (requiredMinSpareAgents + currentBuildsWaitingForTemplate) + - (currentNumberOfSpareAgentsForTemplate + + provisionForMinAgents + + currentNumberOfProvisioningAgentsForTemplate); + if (provisionForMinSpareAgents < 0) { + provisionForMinSpareAgents = 0; + } + + int numberToProvision = provisionForMinAgents + provisionForMinSpareAgents; + if (numberToProvision > 0) { + cloud.provision(agentTemplate, numberToProvision); + } + }); }); - }); } public static boolean minimumInstancesActive( - MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { + MinimumNumberOfInstancesTimeRangeConfig minimumNumberOfInstancesTimeRangeConfig) { if (minimumNumberOfInstancesTimeRangeConfig == null) { return true; } @@ -126,7 +120,7 @@ public static boolean minimumInstancesActive( LocalTime toTime = minimumNumberOfInstancesTimeRangeConfig.getMinimumNoInstancesActiveTimeRangeToAsTime(); LocalDateTime now = LocalDateTime.now(clock); - LocalTime nowTime = LocalTime.from(now); //No date. Easier for comparison on time only. + LocalTime nowTime = LocalTime.from(now); // No date. Easier for comparison on time only. boolean passingMidnight = false; if (toTime.isBefore(fromTime)) { @@ -138,7 +132,7 @@ public static boolean minimumInstancesActive( String today = now.getDayOfWeek().name().toLowerCase(); return minimumNumberOfInstancesTimeRangeConfig.getDay(today); } else if (nowTime.isBefore(toTime)) { - //We've gone past midnight and want to check yesterday's setting. + // We've gone past midnight and want to check yesterday's setting. String yesterday = now.minusDays(1).getDayOfWeek().name().toLowerCase(); return minimumNumberOfInstancesTimeRangeConfig.getDay(yesterday); } diff --git a/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java b/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java index 5562245b7..9a4a58a66 100644 --- a/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java +++ b/src/main/java/hudson/plugins/ec2/util/MinimumNumberOfInstancesTimeRangeConfig.java @@ -1,15 +1,12 @@ package hudson.plugins.ec2.util; -import net.sf.json.JSONObject; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.DataBoundSetter; - import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.util.HashMap; import java.util.Locale; import java.util.Map; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; public class MinimumNumberOfInstancesTimeRangeConfig { @@ -28,10 +25,8 @@ public class MinimumNumberOfInstancesTimeRangeConfig { private Boolean saturday; private Boolean sunday; - @DataBoundConstructor - public MinimumNumberOfInstancesTimeRangeConfig() { - } + public MinimumNumberOfInstancesTimeRangeConfig() {} protected Object readResolve() { if (minimumNoInstancesActiveTimeRangeDays != null && !minimumNoInstancesActiveTimeRangeDays.isEmpty()) { @@ -157,14 +152,22 @@ public void setSunday(Boolean sunday) { public boolean getDay(String day) { switch (day.toLowerCase()) { - case "monday": return Boolean.TRUE.equals(this.monday); - case "tuesday": return Boolean.TRUE.equals(this.tuesday); - case "wednesday": return Boolean.TRUE.equals(this.wednesday); - case "thursday": return Boolean.TRUE.equals(this.thursday); - case "friday": return Boolean.TRUE.equals(this.friday); - case "saturday": return Boolean.TRUE.equals(this.saturday); - case "sunday": return Boolean.TRUE.equals(this.sunday); - default: throw new IllegalArgumentException("Can only get days"); + case "monday": + return Boolean.TRUE.equals(this.monday); + case "tuesday": + return Boolean.TRUE.equals(this.tuesday); + case "wednesday": + return Boolean.TRUE.equals(this.wednesday); + case "thursday": + return Boolean.TRUE.equals(this.thursday); + case "friday": + return Boolean.TRUE.equals(this.friday); + case "saturday": + return Boolean.TRUE.equals(this.saturday); + case "sunday": + return Boolean.TRUE.equals(this.sunday); + default: + throw new IllegalArgumentException("Can only get days"); } } } diff --git a/src/main/java/hudson/plugins/ec2/util/ResettableCountDownLatch.java b/src/main/java/hudson/plugins/ec2/util/ResettableCountDownLatch.java index e39c3adb6..aa97c36b4 100644 --- a/src/main/java/hudson/plugins/ec2/util/ResettableCountDownLatch.java +++ b/src/main/java/hudson/plugins/ec2/util/ResettableCountDownLatch.java @@ -3,9 +3,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; - -import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; @Restricted(NoExternalUse.class) public class ResettableCountDownLatch { @@ -44,4 +43,4 @@ public boolean await(long timeout, TimeUnit unit) throws InterruptedException { public long getCount() { return latchHolder.get().getCount(); } -} \ No newline at end of file +} diff --git a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java index 3795be6fd..85809c9e6 100644 --- a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java +++ b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java @@ -1,43 +1,38 @@ package hudson.plugins.ec2.win; +import com.amazonaws.AmazonClientException; +import com.amazonaws.services.ec2.model.GetPasswordDataRequest; +import com.amazonaws.services.ec2.model.GetPasswordDataResult; +import com.amazonaws.services.ec2.model.Instance; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Util; import hudson.model.Descriptor; import hudson.model.TaskListener; +import hudson.os.WindowsUtil; import hudson.plugins.ec2.*; import hudson.plugins.ec2.win.winrm.WindowsProcess; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.slaves.ComputerLauncher; -import hudson.Util; -import hudson.os.WindowsUtil; - +import hudson.slaves.OfflineCause; import java.io.EOFException; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.TimeUnit; - -import hudson.slaves.OfflineCause; -import edu.umd.cs.findbugs.annotations.NonNull; - +import javax.net.ssl.SSLException; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.GetPasswordDataRequest; -import com.amazonaws.services.ec2.model.GetPasswordDataResult; - -import javax.net.ssl.SSLException; - public class EC2WindowsLauncher extends EC2ComputerLauncher { private static final String AGENT_JAR = "remoting.jar"; final long sleepBetweenAttempts = TimeUnit.SECONDS.toMillis(10); @Override - protected void launchScript(EC2Computer computer, TaskListener listener) throws IOException, - AmazonClientException, InterruptedException { + protected void launchScript(EC2Computer computer, TaskListener listener) + throws IOException, AmazonClientException, InterruptedException { final PrintStream logger = listener.getLogger(); EC2AbstractSlave node = computer.getNode(); if (node == null) { @@ -50,10 +45,11 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws } final WinConnection connection = connectToWinRM(computer, node, template, logger); - + try { String initScript = node.initScript; - String tmpDir = (node.tmpDir != null && !node.tmpDir.equals("") ? WindowsUtil.quoteArgument(Util.ensureEndsWith(node.tmpDir,"\\")) + String tmpDir = (node.tmpDir != null && !node.tmpDir.equals("") + ? WindowsUtil.quoteArgument(Util.ensureEndsWith(node.tmpDir, "\\")) : "C:\\Windows\\Temp\\"); logger.println("Creating tmp directory if it does not exist"); @@ -66,7 +62,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws if (initScript != null && initScript.trim().length() > 0 && !connection.exists(tmpDir + ".jenkins-init")) { logger.println("Executing init script"); - try(OutputStream init = connection.putFile(tmpDir + "init.bat")) { + try (OutputStream init = connection.putFile(tmpDir + "init.bat")) { init.write(initScript.getBytes("utf-8")); } @@ -79,13 +75,13 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws return; } - try(OutputStream initGuard = connection.putFile(tmpDir + ".jenkins-init")) { + try (OutputStream initGuard = connection.putFile(tmpDir + ".jenkins-init")) { initGuard.write("init ran".getBytes(StandardCharsets.UTF_8)); } logger.println("init script ran successfully"); } - try(OutputStream agentJar = connection.putFile(tmpDir + AGENT_JAR)) { + try (OutputStream agentJar = connection.putFile(tmpDir + AGENT_JAR)) { agentJar.write(Jenkins.get().getJnlpJars(AGENT_JAR).readFully()); } @@ -95,7 +91,8 @@ protected void launchScript(EC2Computer computer, TaskListener listener) throws final String jvmopts = node.jvmopts; final String remoteFS = WindowsUtil.quoteArgument(node.getRemoteFS()); final String workDir = Util.fixEmptyAndTrim(remoteFS) != null ? remoteFS : tmpDir; - final String launchString = javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + AGENT_JAR + " -workDir " + workDir; + final String launchString = javaPath + " " + (jvmopts != null ? jvmopts : "") + " -jar " + tmpDir + + AGENT_JAR + " -workDir " + workDir; logger.println("Launching via WinRM:" + launchString); final WindowsProcess process = connection.execute(launchString, 86400); computer.setChannel(process.getStdout(), process.getStdin(), logger, new Listener() { @@ -106,11 +103,14 @@ public void onClosed(Channel channel, IOException cause) { } }); } catch (EOFException eof) { - // When we launch java with connection.execute(launchString... it keeps running, but if java is not installed - //the computer.setChannel fails with EOFException because the stream is already closed. It fails on - // setChannel - build - negotiate - is.read() == -1. Let's print a clear message to help diagnose the problem + // When we launch java with connection.execute(launchString... it keeps running, but if java is not + // installed + // the computer.setChannel fails with EOFException because the stream is already closed. It fails on + // setChannel - build - negotiate - is.read() == -1. Let's print a clear message to help diagnose the + // problem // In other case you see a EOFException which gives you few clues about the problem. - logger.println("The stream with the java process on the instance was closed. Maybe java is not installed there."); + logger.println( + "The stream with the java process on the instance was closed. Maybe java is not installed there."); eof.printStackTrace(logger); } catch (Throwable ioe) { logger.println("Ouch:"); @@ -121,8 +121,9 @@ public void onClosed(Channel channel, IOException cause) { } @NonNull - private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node, SlaveTemplate template, PrintStream logger) throws AmazonClientException, - InterruptedException { + private WinConnection connectToWinRM( + EC2Computer computer, EC2AbstractSlave node, SlaveTemplate template, PrintStream logger) + throws AmazonClientException, InterruptedException { final long minTimeout = 3000; long timeout = node.getLaunchTimeoutInMillis(); // timeout is less than 0 when jenkins is booting up. if (timeout < minTimeout) { @@ -139,8 +140,8 @@ private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node try { long waitTime = System.currentTimeMillis() - startTime; if (waitTime > timeout) { - throw new AmazonClientException("Timed out after " + (waitTime / 1000) - + " seconds of waiting for winrm to be connected"); + throw new AmazonClientException( + "Timed out after " + (waitTime / 1000) + " seconds of waiting for winrm to be connected"); } if (connection == null) { @@ -149,14 +150,17 @@ private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node // Check when host is null or we will keep trying and receiving a hostname cannot be null forever. if (host == null || "0.0.0.0".equals(host)) { - logger.println("Invalid host (null or 0.0.0.0). Your host is most likely waiting for an IP address."); + logger.println( + "Invalid host (null or 0.0.0.0). Your host is most likely waiting for an IP address."); throw new IOException("goto sleep"); } if (!node.isSpecifyPassword()) { GetPasswordDataResult result; try { - result = node.getCloud().connect().getPasswordData(new GetPasswordDataRequest(instance.getInstanceId())); + result = node.getCloud() + .connect() + .getPasswordData(new GetPasswordDataRequest(instance.getInstanceId())); } catch (Exception e) { logger.println("Unexpected Exception: " + e.toString()); Thread.sleep(sleepBetweenAttempts); @@ -169,20 +173,26 @@ private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node continue; } EC2PrivateKey ec2PrivateKey = node.getCloud().resolvePrivateKey(); - if (ec2PrivateKey == null){ - logger.println("Waiting for privateKey to be available. Consider checking the credentials in the cloud configuration. Sleeping 10s."); + if (ec2PrivateKey == null) { + logger.println( + "Waiting for privateKey to be available. Consider checking the credentials in the cloud configuration. Sleeping 10s."); Thread.sleep(sleepBetweenAttempts); continue; } String password = ec2PrivateKey.decryptWindowsPassword(passwordData); if (!node.getRemoteAdmin().equals("Administrator")) { - logger.println("WARNING: For password retrieval remote admin must be Administrator, ignoring user provided value"); + logger.println( + "WARNING: For password retrieval remote admin must be Administrator, ignoring user provided value"); } logger.println("Connecting to " + "(" + host + ") with WinRM as Administrator"); connection = new WinConnection(host, "Administrator", password, allowSelfSignedCertificate); - } else { //password Specified + } else { // password Specified logger.println("Connecting to " + "(" + host + ") with WinRM as " + node.getRemoteAdmin()); - connection = new WinConnection(host, node.getRemoteAdmin(), node.getAdminPassword().getPlainText(), allowSelfSignedCertificate); + connection = new WinConnection( + host, + node.getRemoteAdmin(), + node.getAdminPassword().getPlainText(), + allowSelfSignedCertificate); } connection.setUseHTTPS(node.isUseHTTPS()); } @@ -196,8 +206,8 @@ private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node if (!alreadyBooted || node.stopOnTerminate) { int bootDelay = node.getBootDelay(); if (bootDelay > 0) { - logger.println("WinRM service responded. Waiting " + bootDelay + "ms for WinRM service to stabilize on " - + node.getDisplayName()); + logger.println("WinRM service responded. Waiting " + bootDelay + + "ms for WinRM service to stabilize on " + node.getDisplayName()); Thread.sleep(bootDelay); logger.println("WinRM should now be ok on " + node.getDisplayName()); } @@ -215,7 +225,8 @@ private WinConnection connectToWinRM(EC2Computer computer, EC2AbstractSlave node if (e instanceof SSLException) { // To avoid reconnecting continuously computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSLException())); - // avoid waiting and trying again, this connection needs human intervention to change the certificate + // avoid waiting and trying again, this connection needs human intervention to change the + // certificate throw new AmazonClientException("The SSL connection failed while negotiating SSL", e); } logger.println("Waiting for WinRM to come up. Sleeping 10s."); diff --git a/src/main/java/hudson/plugins/ec2/win/SelfSignedCertificateAllowedMonitor.java b/src/main/java/hudson/plugins/ec2/win/SelfSignedCertificateAllowedMonitor.java index 9b6d16547..3ecebe0de 100644 --- a/src/main/java/hudson/plugins/ec2/win/SelfSignedCertificateAllowedMonitor.java +++ b/src/main/java/hudson/plugins/ec2/win/SelfSignedCertificateAllowedMonitor.java @@ -30,21 +30,20 @@ import hudson.plugins.ec2.SlaveTemplate; import hudson.plugins.ec2.WindowsData; import hudson.slaves.Cloud; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - @Extension public class SelfSignedCertificateAllowedMonitor extends AdministrativeMonitor { - private final static int MAX_TEMPLATES_FOUND = 5; - + private static final int MAX_TEMPLATES_FOUND = 5; + List insecureTemplates = new ArrayList<>(MAX_TEMPLATES_FOUND); @Override @@ -65,15 +64,15 @@ public String getSelfSignedCertAllowedTemplates() { @Override public boolean isActivated() { boolean maxTemplatesReached = false; - + ListIterator cloudIterator = Jenkins.get().clouds.listIterator(); - + // Let's clear the previously calculated wrong templates to populate the lists with them again insecureTemplates.clear(); - + while (cloudIterator.hasNext() && !maxTemplatesReached) { Cloud cloud = cloudIterator.next(); - if (cloud instanceof EC2Cloud) { + if (cloud instanceof EC2Cloud) { maxTemplatesReached = gatherInsecureTemplate((EC2Cloud) cloud); } } @@ -90,7 +89,9 @@ private boolean gatherInsecureTemplate(EC2Cloud cloud) { } AMITypeData amiTypeData = template.getAmiType(); - if (insecureTemplates.size() < MAX_TEMPLATES_FOUND && amiTypeData.isWindows() && ((WindowsData)amiTypeData).isAllowSelfSignedCertificate()) { + if (insecureTemplates.size() < MAX_TEMPLATES_FOUND + && amiTypeData.isWindows() + && ((WindowsData) amiTypeData).isAllowSelfSignedCertificate()) { // it is insecure insecureTemplates.add(template.getDisplayName()); } @@ -100,17 +101,17 @@ private boolean gatherInsecureTemplate(EC2Cloud cloud) { return true; } } - + return false; } - + @RequirePOST @SuppressWarnings("unused") // used by message.jelly public HttpResponse doAct(@QueryParameter String dismiss) throws IOException { Jenkins.get().checkPermission(Jenkins.ADMINISTER); if (dismiss != null) { disable(true); - } + } return HttpResponses.forwardToPreviousPage(); } } diff --git a/src/main/java/hudson/plugins/ec2/win/WinConnection.java b/src/main/java/hudson/plugins/ec2/win/WinConnection.java index c807dd68a..b82684a59 100644 --- a/src/main/java/hudson/plugins/ec2/win/WinConnection.java +++ b/src/main/java/hudson/plugins/ec2/win/WinConnection.java @@ -1,30 +1,25 @@ package hudson.plugins.ec2.win; +import com.hierynomus.msdtyp.AccessMask; +import com.hierynomus.mssmb2.SMB2CreateDisposition; +import com.hierynomus.mssmb2.SMB2ShareAccess; import com.hierynomus.protocol.transport.TransportException; -import com.hierynomus.security.bc.BCSecurityProvider; -import com.hierynomus.smbj.SmbConfig; +import com.hierynomus.smbj.SMBClient; +import com.hierynomus.smbj.auth.AuthenticationContext; +import com.hierynomus.smbj.connection.Connection; +import com.hierynomus.smbj.session.Session; +import com.hierynomus.smbj.share.DiskShare; import hudson.plugins.ec2.win.winrm.WinRM; import hudson.plugins.ec2.win.winrm.WindowsProcess; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.EnumSet; - -import com.hierynomus.smbj.auth.AuthenticationContext; -import com.hierynomus.smbj.SMBClient; -import com.hierynomus.smbj.share.DiskShare; -import com.hierynomus.smbj.connection.Connection; -import com.hierynomus.smbj.session.Session; -import com.hierynomus.msdtyp.AccessMask; -import com.hierynomus.mssmb2.SMB2ShareAccess; -import com.hierynomus.mssmb2.SMB2CreateDisposition; - -import javax.net.ssl.SSLException; import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.ssl.SSLException; public class WinConnection { private static final Logger LOGGER = Logger.getLogger(WinConnection.class.getName()); @@ -40,14 +35,14 @@ public class WinConnection { private Session session; private boolean useHTTPS; - private static final int TIMEOUT=8000; //8 seconds + private static final int TIMEOUT = 8000; // 8 seconds private boolean allowSelfSignedCertificate; @Deprecated public WinConnection(String host, String username, String password) { this(host, username, password, true); } - + public WinConnection(String host, String username, String password, boolean allowSelfSignedCertificate) { this.host = host; this.username = username; @@ -78,31 +73,31 @@ public WindowsProcess execute(String commandLine, int timeout) { } private DiskShare getSmbShare(String path) throws IOException { - if(this.connection == null) { + if (this.connection == null) { this.connection = smbclient.connect(host); } - if(this.session == null) { + if (this.session == null) { this.session = connection.authenticate(this.authentication); } return (DiskShare) session.connectShare(toAdministrativeShare(path)); } public OutputStream putFile(String path) throws IOException { - return getSmbShare(path).openFile(toFilePath(path), - EnumSet.of(AccessMask.GENERIC_READ, - AccessMask.GENERIC_WRITE), - null, - SMB2ShareAccess.ALL, - SMB2CreateDisposition.FILE_OVERWRITE_IF, - null).getOutputStream(); + return getSmbShare(path) + .openFile( + toFilePath(path), + EnumSet.of(AccessMask.GENERIC_READ, AccessMask.GENERIC_WRITE), + null, + SMB2ShareAccess.ALL, + SMB2CreateDisposition.FILE_OVERWRITE_IF, + null) + .getOutputStream(); } public InputStream getFile(String path) throws IOException { - return getSmbShare(path).openFile(toFilePath(path), - EnumSet.of(AccessMask.GENERIC_READ), - null, SMB2ShareAccess.ALL, - null, - null).getInputStream(); + return getSmbShare(path) + .openFile(toFilePath(path), EnumSet.of(AccessMask.GENERIC_READ), null, SMB2ShareAccess.ALL, null, null) + .getInputStream(); } public boolean exists(String path) throws IOException { @@ -115,7 +110,7 @@ private static String toAdministrativeShare(String path) { } private static String toFilePath(String path) { - //Strip drive and leading forward slash + // Strip drive and leading forward slash return path.substring(3); } @@ -127,14 +122,12 @@ public boolean ping() { return false; } } - + public boolean pingFailingIfSSHHandShakeError() throws IOException { LOGGER.log(Level.FINE, () -> "checking SMB connection to " + host); - try ( - Socket socket = new Socket(); - Connection connection = smbclient.connect(host); - Session session = connection.authenticate(authentication); - ) { + try (Socket socket = new Socket(); + Connection connection = smbclient.connect(host); + Session session = connection.authenticate(authentication); ) { socket.connect(new InetSocketAddress(host, 445), TIMEOUT); winrm().ping(); session.connectShare("IPC$"); @@ -154,21 +147,21 @@ public boolean pingFailingIfSSHHandShakeError() throws IOException { } public void close() { - if(this.session != null) { + if (this.session != null) { try { this.session.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to close session", e); } } - if(this.connection != null) { + if (this.connection != null) { try { this.connection.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to close connection", e); } } - if(this.smbclient != null) { + if (this.smbclient != null) { try { this.smbclient.close(); } catch (Exception e) { diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/NegotiateNTLMSchemaFactory.java b/src/main/java/hudson/plugins/ec2/win/winrm/NegotiateNTLMSchemaFactory.java index 9583553ab..749f331d7 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/NegotiateNTLMSchemaFactory.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/NegotiateNTLMSchemaFactory.java @@ -11,6 +11,7 @@ public class NegotiateNTLMSchemaFactory implements AuthSchemeProvider { + @Override public AuthScheme create(HttpContext context) { return new NegotiateNTLM(); } @@ -25,10 +26,11 @@ public String getSchemeName() { public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException { Credentials ntCredentials = credentials; if (!(credentials instanceof NTCredentials)) { - ntCredentials = new NTCredentials(credentials.getUserPrincipal().getName(), credentials.getPassword(), null, null); + ntCredentials = new NTCredentials( + credentials.getUserPrincipal().getName(), credentials.getPassword(), null, null); } Header header = super.authenticate(ntCredentials, request); - //need replace NTLM with Negotiate + // need replace NTLM with Negotiate CharArrayBuffer buffer = new CharArrayBuffer(512); buffer.append(header.getName()); buffer.append(": "); diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/WinRM.java b/src/main/java/hudson/plugins/ec2/win/winrm/WinRM.java index ed34b5063..c820fa8fe 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/WinRM.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/WinRM.java @@ -20,7 +20,7 @@ public class WinRM { public WinRM(String host, String username, String password) { this(host, username, password, true); } - + public WinRM(String host, String username, String password, boolean allowSelfSignedCertificate) { this.host = host; this.username = username; @@ -101,7 +101,7 @@ public void setTimeout(int timeout) { * # Convert the number of seconds to an ISO8601 duration format # @see * http://tools.ietf.org/html/rfc2445#section-4.3.6 # @param [Fixnum] seconds The amount of seconds for this * duration - * + * * @param seconds * @return */ diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMClient.java b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMClient.java index 2e76ebbf1..f9d1beae2 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMClient.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMClient.java @@ -14,7 +14,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; - import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.ParseException; @@ -22,10 +21,10 @@ import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.HttpClient; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Lookup; import org.apache.http.config.RegistryBuilder; @@ -66,7 +65,7 @@ public class WinRMClient { private boolean useHTTPS; private BasicCredentialsProvider credsProvider; private final boolean allowSelfSignedCertificate; - + @Deprecated public WinRMClient(URL url, String username, String password) { this(url, username, password, false); @@ -78,7 +77,7 @@ public WinRMClient(URL url, String username, String password, boolean allowSelfS this.password = password; this.factory = new RequestFactory(url); this.allowSelfSignedCertificate = allowSelfSignedCertificate; - + setupHTTPClient(); } @@ -93,7 +92,7 @@ public void executeCommand(String command) { LOGGER.log(Level.FINE, () -> "winrm execute on " + shellId + " command: " + command); Document request = factory.newExecuteCommandRequest(shellId, command).build(); commandId = first(sendRequest(request), "//" + Namespaces.NS_WIN_SHELL.getPrefix() + ":CommandId"); - LOGGER.log(Level.FINER, ()-> "winrm started execution on " + shellId + " commandId: " + commandId); + LOGGER.log(Level.FINER, () -> "winrm started execution on " + shellId + " commandId: " + commandId); } public void deleteShell() { @@ -105,7 +104,6 @@ public void deleteShell() { Document request = factory.newDeleteShellRequest(shellId).build(); sendRequest(request); - } public void signal() { @@ -122,13 +120,16 @@ public void signal() { public void sendInput(byte[] input) { LOGGER.log(Level.FINE, () -> "--> sending " + input.length); - Document request = factory.newSendInputRequest(input, shellId, commandId).build(); + Document request = + factory.newSendInputRequest(input, shellId, commandId).build(); sendRequest(request); } public boolean slurpOutput(FastPipedOutputStream stdout, FastPipedOutputStream stderr) throws IOException { LOGGER.log(Level.FINE, () -> "--> SlurpOutput"); - Map streams = new HashMap<>(); streams.put("stdout", stdout); streams.put("stderr", stderr); + Map streams = new HashMap<>(); + streams.put("stdout", stdout); + streams.put("stderr", stderr); Document request = factory.newGetOutputRequest(shellId, commandId).build(); Document response = sendRequest(request); @@ -141,16 +142,20 @@ public boolean slurpOutput(FastPipedOutputStream stdout, FastPipedOutputStream s for (Node node : xpath.selectNodes(response)) { if (node instanceof Element) { Element e = (Element) node; - FastPipedOutputStream stream = streams.get(e.attribute("Name").getText().toLowerCase()); + FastPipedOutputStream stream = + streams.get(e.attribute("Name").getText().toLowerCase()); final byte[] decode = Base64.getDecoder().decode(e.getText()); - LOGGER.log(Level.FINE, () -> "piping " + decode.length + " bytes from " - + e.attribute("Name").getText().toLowerCase()); + LOGGER.log( + Level.FINE, + () -> "piping " + decode.length + " bytes from " + + e.attribute("Name").getText().toLowerCase()); stream.write(decode); } } - XPath done = DocumentHelper.createXPath("//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']"); + XPath done = DocumentHelper.createXPath( + "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']"); done.setNamespaceContext(namespaceContext); final List nodes = done.selectNodes(response); if (nodes != null && nodes.isEmpty()) { @@ -178,46 +183,44 @@ private static String first(Document doc, String selector) { private void setupHTTPClient() { credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials(username, password)); + credsProvider.setCredentials( + new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), + new UsernamePasswordCredentials(username, password)); } private HttpClient buildHTTPClient() { HttpClientBuilder builder = HttpClientBuilder.create().setDefaultCredentialsProvider(credsProvider); - if(! (username.contains("\\")|| username.contains("/"))) { - //user is not a domain user + if (!(username.contains("\\") || username.contains("/"))) { + // user is not a domain user Lookup authSchemeRegistry = RegistryBuilder.create() - .register(AuthSchemes.BASIC, new BasicSchemeFactory()) - .register(AuthSchemes.SPNEGO,new NegotiateNTLMSchemaFactory()) - .build(); + .register(AuthSchemes.BASIC, new BasicSchemeFactory()) + .register(AuthSchemes.SPNEGO, new NegotiateNTLMSchemaFactory()) + .build(); builder.setDefaultAuthSchemeRegistry(authSchemeRegistry); } if (useHTTPS) { - WinRMConnectionManagerFactory.WinRMHttpConnectionManager connectionManager = - allowSelfSignedCertificate ? WinRMConnectionManagerFactory.SSL_ALLOW_SELF_SIGNED - : WinRMConnectionManagerFactory.SSL; + WinRMConnectionManagerFactory.WinRMHttpConnectionManager connectionManager = allowSelfSignedCertificate + ? WinRMConnectionManagerFactory.SSL_ALLOW_SELF_SIGNED + : WinRMConnectionManagerFactory.SSL; builder.setSSLSocketFactory(connectionManager.getSocketFactory()); builder.setConnectionManager(connectionManager.getConnectionManager()); } else { builder.setConnectionManager(WinRMConnectionManagerFactory.DEFAULT.getConnectionManager()); } RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(5000) - .setSocketTimeout(0) - .build(); + .setConnectTimeout(5000) + .setSocketTimeout(0) + .build(); builder.setDefaultRequestConfig(requestConfig); - SocketConfig socketConfig = SocketConfig.custom() - .setTcpNoDelay(true) - .build(); + SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build(); builder.setDefaultSocketConfig(socketConfig); // Add sleep between re-tries, by-default the call gets retried immediately. builder.setRetryHandler(new DefaultHttpRequestRetryHandler() { @Override public boolean retryRequest( - final IOException exception, - final int executionCount, - final HttpContext context) { + final IOException exception, final int executionCount, final HttpContext context) { boolean retryRequest = super.retryRequest(exception, executionCount, context); - if ( retryRequest ) { + if (retryRequest) { // sleep before retrying, increase the sleep time on each re-try int sleepTime = executionCount * 5; try { @@ -266,7 +269,10 @@ private Document sendRequest(Document request, int retry) { // check for possible timeout if (response.getStatusLine().getStatusCode() == 500 - && (responseEntity.getContentType() != null && entity.getContentType().getValue().startsWith(ContentType.APPLICATION_SOAP_XML.getMimeType()))) { + && (responseEntity.getContentType() != null + && entity.getContentType() + .getValue() + .startsWith(ContentType.APPLICATION_SOAP_XML.getMimeType()))) { String respStr = EntityUtils.toString(responseEntity); if (respStr.contains("TimedOut")) { return DocumentHelper.parseText(respStr); @@ -279,7 +285,8 @@ private Document sendRequest(Document request, int retry) { if (response.getStatusLine().getStatusCode() == 401) { // we need to force using new connections here // throw away our auth cache - LOGGER.log(Level.WARNING, "winrm returned 401 - shouldn't happen though - retrying in 2 minutes"); + LOGGER.log( + Level.WARNING, "winrm returned 401 - shouldn't happen though - retrying in 2 minutes"); try { Thread.sleep(TimeUnit.MINUTES.toMillis(2)); } catch (InterruptedException e) { @@ -289,12 +296,15 @@ private Document sendRequest(Document request, int retry) { LOGGER.log(Level.WARNING, "winrm returned 401 - retrying now"); return sendRequest(request, ++retry); } - LOGGER.log(Level.WARNING, "winrm service " + shellId + " unexpected HTTP Response (" - + response.getStatusLine().getReasonPhrase() + "): " - + EntityUtils.toString(response.getEntity())); - - throw new RuntimeException("Unexpected HTTP response " + response.getStatusLine().getStatusCode() - + " on " + url + ": " + response.getStatusLine().getReasonPhrase()); + LOGGER.log( + Level.WARNING, + "winrm service " + shellId + " unexpected HTTP Response (" + + response.getStatusLine().getReasonPhrase() + "): " + + EntityUtils.toString(response.getEntity())); + + throw new RuntimeException("Unexpected HTTP response " + + response.getStatusLine().getStatusCode() + " on " + url + ": " + + response.getStatusLine().getReasonPhrase()); } } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectException.java b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectException.java index 85db112f3..368f036e8 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectException.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectException.java @@ -5,5 +5,4 @@ public class WinRMConnectException extends RuntimeIOException { public WinRMConnectException(String message, Throwable cause) { super(message, cause); } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectionManagerFactory.java b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectionManagerFactory.java index 0721343c5..c25c0e7a5 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectionManagerFactory.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/WinRMConnectionManagerFactory.java @@ -1,5 +1,10 @@ package hudson.plugins.ec2.win.winrm; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; @@ -9,12 +14,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.logging.Level; -import java.util.logging.Logger; - public class WinRMConnectionManagerFactory { private static final Logger log = Logger.getLogger(WinRMClient.class.getName()); @@ -26,8 +25,8 @@ static class WinRMHttpConnectionManager { private final PoolingHttpClientConnectionManager connectionManager; private SSLConnectionSocketFactory socketFactory; - final static int DEFAULT_MAX_PER_ROUTE = 50; - final static int MAX_TOTAL = 2500; + static final int DEFAULT_MAX_PER_ROUTE = 50; + static final int MAX_TOTAL = 2500; WinRMHttpConnectionManager() { connectionManager = new PoolingHttpClientConnectionManager(); @@ -54,7 +53,9 @@ private Registry getSslSocketFactory(boolean allowSelfS try { if (allowSelfSignedCertificate) { this.socketFactory = new SSLConnectionSocketFactory( - new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build(), + new SSLContextBuilder() + .loadTrustMaterial(null, new TrustSelfSignedStrategy()) + .build(), NoopHostnameVerifier.INSTANCE); log.log(Level.FINE, "Allowing self-signed certificates"); } else { @@ -70,4 +71,4 @@ private Registry getSslSocketFactory(boolean allowSelfS .build(); } } -} \ No newline at end of file +} diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/WindowsProcess.java b/src/main/java/hudson/plugins/ec2/win/winrm/WindowsProcess.java index d255b700c..bbea1ef3f 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/WindowsProcess.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/WindowsProcess.java @@ -102,7 +102,7 @@ private void startStdoutCopyThread() { @Override public void run() { try { - for (;;) { + for (; ; ) { if (!client.slurpOutput(toCallersStdout, toCallersStderr)) { LOGGER.log(Level.FINE, () -> "no more output for " + command); break; @@ -127,13 +127,15 @@ private void startStdinCopyThread() { public void run() { try { byte[] buf = new byte[INPUT_BUFFER]; - for (;;) { + for (; ; ) { int n = toCallersStdin.read(buf); - if (n == -1) + if (n == -1) { break; - if (n == 0) + } + if (n == 0) { continue; + } byte[] bufToSend = new byte[n]; System.arraycopy(buf, 0, bufToSend, 0, n); diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/AbstractWinRMRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/AbstractWinRMRequest.java index 14bd5d1e4..0d9a1c6e1 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/AbstractWinRMRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/AbstractWinRMRequest.java @@ -1,13 +1,11 @@ package hudson.plugins.ec2.win.winrm.request; +import hudson.plugins.ec2.win.winrm.soap.HeaderBuilder; +import hudson.plugins.ec2.win.winrm.soap.MessageBuilder; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.UUID; - -import hudson.plugins.ec2.win.winrm.soap.HeaderBuilder; -import hudson.plugins.ec2.win.winrm.soap.MessageBuilder; - import org.dom4j.Document; import org.dom4j.Element; @@ -28,13 +26,19 @@ public AbstractWinRMRequest(URL url) { protected abstract void construct(); + @Override public Document build() { construct(); return message.build(); } protected HeaderBuilder defaultHeader() throws URISyntaxException { - return header.to(url.toURI()).replyTo(new URI("http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous")).maxEnvelopeSize(envelopSize).id(generateUUID()).locale(locale).timeout(timeout); + return header.to(url.toURI()) + .replyTo(new URI("http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous")) + .maxEnvelopeSize(envelopSize) + .id(generateUUID()) + .locale(locale) + .timeout(timeout); } protected void setBody(Element body) { @@ -69,5 +73,4 @@ public String getLocale() { public void setLocale(String locale) { this.locale = locale; } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/DeleteShellRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/DeleteShellRequest.java index 5bdb7f673..3646355c6 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/DeleteShellRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/DeleteShellRequest.java @@ -16,12 +16,14 @@ public DeleteShellRequest(URL url, String shellId) { @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete")).shellId(shellId).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")); + defaultHeader() + .action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Delete")) + .shellId(shellId) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")); setBody(null); } catch (URISyntaxException e) { throw new RuntimeException("Error while building request content", e); } } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/ExecuteCommandRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/ExecuteCommandRequest.java index df936b63d..9e3db9d0e 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/ExecuteCommandRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/ExecuteCommandRequest.java @@ -2,12 +2,10 @@ import hudson.plugins.ec2.win.winrm.soap.Namespaces; import hudson.plugins.ec2.win.winrm.soap.Option; - import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Collections; - import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; @@ -26,7 +24,11 @@ public ExecuteCommandRequest(URL url, String shellId, String command) { @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId).options(Collections.singletonList(new Option("WINRS_CONSOLEMODE_STDIN", "FALSE"))); + defaultHeader() + .action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")) + .shellId(shellId) + .options(Collections.singletonList(new Option("WINRS_CONSOLEMODE_STDIN", "FALSE"))); Element body = DocumentHelper.createElement(QName.get("CommandLine", Namespaces.NS_WIN_SHELL)); body.addElement(QName.get("Command", Namespaces.NS_WIN_SHELL)).addText("\"" + command + "\""); @@ -35,5 +37,4 @@ protected void construct() { throw new RuntimeException("Error while building request content", e); } } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/GetOutputRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/GetOutputRequest.java index 2b8e15a80..c4b5834a0 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/GetOutputRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/GetOutputRequest.java @@ -1,11 +1,9 @@ package hudson.plugins.ec2.win.winrm.request; import hudson.plugins.ec2.win.winrm.soap.Namespaces; - import java.net.URI; import java.net.URISyntaxException; import java.net.URL; - import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; @@ -23,14 +21,18 @@ public GetOutputRequest(URL url, String shellId, String commandId) { @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId); + defaultHeader() + .action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive")) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")) + .shellId(shellId); Element body = DocumentHelper.createElement(QName.get("Receive", Namespaces.NS_WIN_SHELL)); - body.addElement(QName.get("DesiredStream", Namespaces.NS_WIN_SHELL)).addAttribute("CommandId", commandId).addText("stdout stderr"); + body.addElement(QName.get("DesiredStream", Namespaces.NS_WIN_SHELL)) + .addAttribute("CommandId", commandId) + .addText("stdout stderr"); setBody(body); } catch (URISyntaxException e) { throw new RuntimeException("Error while building request content", e); } } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/OpenShellRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/OpenShellRequest.java index cdacfb908..92ae5fe05 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/OpenShellRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/OpenShellRequest.java @@ -2,12 +2,10 @@ import hudson.plugins.ec2.win.winrm.soap.Namespaces; import hudson.plugins.ec2.win.winrm.soap.Option; - import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; - import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; @@ -18,9 +16,14 @@ public OpenShellRequest(URL url) { super(url); } + @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).options(Arrays.asList(new Option("WINRS_NOPROFILE", "FALSE"), new Option("WINRS_CODEPAGE", "437"))); + defaultHeader() + .action(new URI("http://schemas.xmlsoap.org/ws/2004/09/transfer/Create")) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")) + .options( + Arrays.asList(new Option("WINRS_NOPROFILE", "FALSE"), new Option("WINRS_CODEPAGE", "437"))); Element body = DocumentHelper.createElement(QName.get("Shell", Namespaces.NS_WIN_SHELL)); body.addElement(QName.get("InputStreams", Namespaces.NS_WIN_SHELL)).addText("stdin"); @@ -29,7 +32,5 @@ protected void construct() { } catch (URISyntaxException e) { throw new RuntimeException("Error while building request content", e); } - } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/RequestFactory.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/RequestFactory.java index 0d01e5ad7..d77a5d5c9 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/RequestFactory.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/RequestFactory.java @@ -77,5 +77,4 @@ public String getLocale() { public void setLocale(String locale) { this.locale = locale; } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/SendInputRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/SendInputRequest.java index d483ae2f4..6a63dc869 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/SendInputRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/SendInputRequest.java @@ -1,12 +1,10 @@ package hudson.plugins.ec2.win.winrm.request; import hudson.plugins.ec2.win.winrm.soap.Namespaces; - import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Base64; - import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; @@ -26,17 +24,19 @@ public SendInputRequest(URL url, byte[] input, String shellId, String commandId) @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId); + defaultHeader() + .action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Send")) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")) + .shellId(shellId); Element body = DocumentHelper.createElement(QName.get("Send", Namespaces.NS_WIN_SHELL)); body.addElement(QName.get("Stream", Namespaces.NS_WIN_SHELL)) - .addAttribute("Name", "stdin") - .addAttribute("CommandId", commandId) - .addText(Base64.getEncoder().encodeToString(input)); + .addAttribute("Name", "stdin") + .addAttribute("CommandId", commandId) + .addText(Base64.getEncoder().encodeToString(input)); setBody(body); } catch (URISyntaxException e) { throw new RuntimeException("Error while building request content", e); } } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/request/SignalRequest.java b/src/main/java/hudson/plugins/ec2/win/winrm/request/SignalRequest.java index 7e7c21975..4267fd3c3 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/request/SignalRequest.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/request/SignalRequest.java @@ -1,11 +1,9 @@ package hudson.plugins.ec2.win.winrm.request; import hudson.plugins.ec2.win.winrm.soap.Namespaces; - import java.net.URI; import java.net.URISyntaxException; import java.net.URL; - import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.QName; @@ -23,15 +21,19 @@ public SignalRequest(URL url, String shellId, String commandId) { @Override protected void construct() { try { - defaultHeader().action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")).resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")).shellId(shellId); + defaultHeader() + .action(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Command")) + .resourceURI(new URI("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd")) + .shellId(shellId); - Element body = DocumentHelper.createElement(QName.get("Signal", Namespaces.NS_WIN_SHELL)).addAttribute("CommandId", commandId); + Element body = DocumentHelper.createElement(QName.get("Signal", Namespaces.NS_WIN_SHELL)) + .addAttribute("CommandId", commandId); - body.addElement(QName.get("Code", Namespaces.NS_WIN_SHELL)).addText("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate"); + body.addElement(QName.get("Code", Namespaces.NS_WIN_SHELL)) + .addText("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/signal/terminate"); setBody(body); } catch (URISyntaxException e) { throw new RuntimeException("Error while building request content", e); } } - } diff --git a/src/main/java/hudson/plugins/ec2/win/winrm/soap/Header.java b/src/main/java/hudson/plugins/ec2/win/winrm/soap/Header.java index d4292f862..c0ecdfa56 100644 --- a/src/main/java/hudson/plugins/ec2/win/winrm/soap/Header.java +++ b/src/main/java/hudson/plugins/ec2/win/winrm/soap/Header.java @@ -1,25 +1,34 @@ package hudson.plugins.ec2.win.winrm.soap; -import org.dom4j.Element; -import org.dom4j.QName; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.dom4j.Element; +import org.dom4j.QName; public class Header { private final String to; - private final String replyTo; - private final String maxEnvelopeSize; - private final String timeout; - private final String locale; - private final String id; - private final String action; - private final String shellId; - private final String resourceURI; + private final String replyTo; + private final String maxEnvelopeSize; + private final String timeout; + private final String locale; + private final String id; + private final String action; + private final String shellId; + private final String resourceURI; private final List