From 69826fed7baa77b288e08112f2eda80337a578bc Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 2 Jan 2025 19:31:27 -0800 Subject: [PATCH] [JENKINS-73640] Use AWS SDK for Java 2.x Co-authored-by: Denis Blanchette --- README.md | 2 +- pom.xml | 20 +- .../java/hudson/plugins/ec2/CloudHelper.java | 65 +- .../hudson/plugins/ec2/EC2AbstractSlave.java | 270 +++--- .../java/hudson/plugins/ec2/EC2Cloud.java | 488 +++++----- .../java/hudson/plugins/ec2/EC2Computer.java | 86 +- .../plugins/ec2/EC2ComputerLauncher.java | 6 +- .../java/hudson/plugins/ec2/EC2Filter.java | 4 +- .../plugins/ec2/EC2HostAddressProvider.java | 10 +- .../hudson/plugins/ec2/EC2OndemandSlave.java | 15 +- .../hudson/plugins/ec2/EC2PrivateKey.java | 41 +- .../java/hudson/plugins/ec2/EC2Readiness.java | 4 +- .../plugins/ec2/EC2RetentionStrategy.java | 15 +- .../hudson/plugins/ec2/EC2SlaveMonitor.java | 10 +- .../java/hudson/plugins/ec2/EC2SpotSlave.java | 61 +- src/main/java/hudson/plugins/ec2/EC2Step.java | 4 +- src/main/java/hudson/plugins/ec2/EC2Tag.java | 6 +- .../plugins/ec2/InstanceTypeConverter.java | 24 +- .../hudson/plugins/ec2/SlaveTemplate.java | 862 +++++++++++------- .../hudson/plugins/ec2/SpotConfiguration.java | 53 +- .../plugins/ec2/ssh/EC2MacLauncher.java | 39 +- .../plugins/ec2/ssh/EC2UnixLauncher.java | 36 +- .../plugins/ec2/util/AmazonEC2Factory.java | 9 +- .../ec2/util/AmazonEC2FactoryImpl.java | 26 +- .../plugins/ec2/util/DeviceMappingParser.java | 33 +- .../plugins/ec2/util/InstanceTypeCompat.java | 38 + .../java/hudson/plugins/ec2/util/KeyPair.java | 22 + .../plugins/ec2/win/EC2WindowsLauncher.java | 37 +- .../plugins/ec2/EC2Cloud/config-entries.jelly | 2 +- .../plugins/ec2/SlaveTemplate/config.jelly | 4 +- .../ec2/SpotConfiguration/config.jelly | 2 +- .../hudson/plugins/ec2/CloudHelperTest.java | 63 +- .../plugins/ec2/EC2AbstractSlaveTest.java | 4 +- .../java/hudson/plugins/ec2/EC2CloudTest.java | 6 +- .../hudson/plugins/ec2/EC2CloudUnitTest.java | 63 +- .../hudson/plugins/ec2/EC2FilterTest.java | 14 +- .../ec2/EC2HostAddressProviderTest.java | 34 +- .../hudson/plugins/ec2/EC2PrivateKeyTest.java | 6 +- .../plugins/ec2/EC2RetentionStrategyTest.java | 38 +- .../plugins/ec2/EC2SlaveMonitorTest.java | 6 +- .../java/hudson/plugins/ec2/EC2StepTest.java | 27 +- .../hudson/plugins/ec2/SlaveTemplateTest.java | 231 ++--- .../plugins/ec2/SlaveTemplateUnitTest.java | 166 ++-- .../plugins/ec2/TemplateLabelsTest.java | 4 +- .../SshHostKeyVerificationStrategyTest.java | 8 +- .../ec2/util/AmazonEC2FactoryMockImpl.java | 272 +++--- .../ec2/util/DeviceMappingParserTest.java | 76 +- .../ec2/util/EC2AgentFactoryMockImpl.java | 4 +- src/test/resources/hudson/plugins/ec2/Mac.yml | 2 +- .../resources/hudson/plugins/ec2/MacData.yml | 2 +- .../hudson/plugins/ec2/MacDataExport.yml | 2 +- .../Unix-withMinimumInstancesTimeRange.yml | 2 +- .../resources/hudson/plugins/ec2/Unix.yml | 2 +- .../UnixData-withAltEndpointAndJavaPath.yml | 2 +- .../resources/hudson/plugins/ec2/UnixData.yml | 2 +- ...xDataExport-withAltEndpointAndJavaPath.yml | 2 +- .../hudson/plugins/ec2/UnixDataExport.yml | 2 +- .../hudson/plugins/ec2/WindowsData.yml | 2 +- 58 files changed, 1868 insertions(+), 1468 deletions(-) create mode 100644 src/main/java/hudson/plugins/ec2/util/InstanceTypeCompat.java create mode 100644 src/main/java/hudson/plugins/ec2/util/KeyPair.java diff --git a/README.md b/README.md index b461c8638..7724cff76 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ console](https://wiki.jenkins.io/display/JENKINS/Jenkins+Script+Console), example: ```groovy -import com.amazonaws.services.ec2.model.InstanceType +import software.amazon.awssdk.services.ec2.model.InstanceType import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl import com.cloudbees.plugins.credentials.* import com.cloudbees.plugins.credentials.domains.Domain diff --git a/pom.xml b/pom.xml index 65ad566c4..3d2dfbd7c 100644 --- a/pom.xml +++ b/pom.xml @@ -85,7 +85,7 @@ THE SOFTWARE. io.jenkins.tools.bom bom-2.452.x - 3761.vd922730f0fd2 + 3875.v1df09947cde6 pom import @@ -108,6 +108,14 @@ THE SOFTWARE. + + io.jenkins.plugins.aws-java-sdk2 + aws-java-sdk2-core + + + io.jenkins.plugins.aws-java-sdk2 + aws-java-sdk2-ec2 + org.jenkins-ci.plugins apache-httpcomponents-client-4-api @@ -141,16 +149,6 @@ THE SOFTWARE. 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 diff --git a/src/main/java/hudson/plugins/ec2/CloudHelper.java b/src/main/java/hudson/plugins/ec2/CloudHelper.java index 86e631616..e109a4331 100644 --- a/src/main/java/hudson/plugins/ec2/CloudHelper.java +++ b/src/main/java/hudson/plugins/ec2/CloudHelper.java @@ -1,36 +1,36 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.AvailabilityZone; -import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; -import com.amazonaws.services.ec2.model.DescribeImagesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.Reservation; 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 org.apache.commons.lang.StringUtils; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.AvailabilityZone; +import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.Reservation; 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 SdkException, 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]. for (int i = 0; i < 5; i++) { try { return getInstance(instanceId, cloud); - } catch (AmazonServiceException e) { - if (e.getErrorCode().equals("InvalidInstanceID.NotFound") - || EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE.equals(e.getErrorCode())) { + } catch (AwsServiceException e) { + if (e.awsErrorDetails().errorCode().equals("InvalidInstanceID.NotFound") + || EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE.equals( + e.awsErrorDetails().errorCode())) { // retry in 5 seconds. Thread.sleep(5000); continue; @@ -43,16 +43,17 @@ static Instance getInstanceWithRetry(String instanceId, EC2Cloud cloud) } @CheckForNull - static Instance getInstance(String instanceId, EC2Cloud cloud) throws AmazonClientException { + static Instance getInstance(String instanceId, EC2Cloud cloud) throws SdkException { if (StringUtils.isEmpty(instanceId) || cloud == null) { return null; } - DescribeInstancesRequest request = new DescribeInstancesRequest(); - request.setInstanceIds(Collections.singletonList(instanceId)); + DescribeInstancesRequest request = DescribeInstancesRequest.builder() + .instanceIds(Collections.singletonList(instanceId)) + .build(); List reservations = - cloud.connect().describeInstances(request).getReservations(); + cloud.connect().describeInstances(request).reservations(); if (reservations.size() != 1) { String message = "Unexpected number of reservations reported by EC2 for instance id '" + instanceId + "', expected 1 result, found " + reservations + "."; @@ -60,11 +61,11 @@ static Instance getInstance(String instanceId, EC2Cloud cloud) throws AmazonClie message += " Instance seems to be dead."; } LOGGER.info(message); - throw new AmazonClientException(message); + throw SdkException.builder().message(message).build(); } Reservation reservation = reservations.get(0); - List instances = reservation.getInstances(); + List instances = reservation.instances(); if (instances.size() != 1) { String message = "Unexpected number of instances reported by EC2 for instance id '" + instanceId + "', expected 1 result, found " + instances + "."; @@ -72,21 +73,21 @@ static Instance getInstance(String instanceId, EC2Cloud cloud) throws AmazonClie message += " Instance seems to be dead."; } LOGGER.info(message); - throw new AmazonClientException(message); + throw SdkException.builder().message(message).build(); } return instances.get(0); } @CheckForNull - static Image getAmiImage(AmazonEC2 ec2, String ami) { + static Image getAmiImage(Ec2Client ec2, String ami) { List images = Collections.singletonList(ami); List owners = Collections.emptyList(); List users = Collections.emptyList(); - DescribeImagesRequest request = new DescribeImagesRequest(); - request.setImageIds(images); - request.setOwners(owners); - request.setExecutableUsers(users); - List img = ec2.describeImages(request).getImages(); + DescribeImagesRequest.Builder requestBuilder = DescribeImagesRequest.builder(); + requestBuilder.imageIds(images); + requestBuilder.owners(owners); + requestBuilder.executableUsers(users); + List img = ec2.describeImages(requestBuilder.build()).images(); if (img == null || img.isEmpty()) { // de-registered AMI causes an empty list to be // returned. so be defensive @@ -98,14 +99,14 @@ static Image getAmiImage(AmazonEC2 ec2, String ami) { } // Retrieve the availability zones for the region connected on - static ArrayList getAvailabilityZones(AmazonEC2 ec2) { + static ArrayList getAvailabilityZones(Ec2Client ec2) { ArrayList availabilityZones = new ArrayList<>(); - DescribeAvailabilityZonesResult zones = ec2.describeAvailabilityZones(); - List zoneList = zones.getAvailabilityZones(); + DescribeAvailabilityZonesResponse zones = ec2.describeAvailabilityZones(); + List zoneList = zones.availabilityZones(); for (AvailabilityZone z : zoneList) { - availabilityZones.add(z.getZoneName()); + availabilityZones.add(z.zoneName()); } return availabilityZones; diff --git a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java index 45dc4d9f9..64e0b7fbe 100644 --- a/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2AbstractSlave.java @@ -23,20 +23,6 @@ */ 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; @@ -51,6 +37,7 @@ import hudson.util.ListBoxModel; import hudson.util.Secret; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -65,6 +52,20 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.verb.POST; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.AvailabilityZone; +import software.amazon.awssdk.services.ec2.model.CreateTagsRequest; +import software.amazon.awssdk.services.ec2.model.DeleteTagsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceBlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.StopInstancesRequest; +import software.amazon.awssdk.services.ec2.model.Tag; +import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest; /** * Agent running on EC2. @@ -150,7 +151,7 @@ public abstract class EC2AbstractSlave extends Slave { public transient String slaveCommandSuffix; - private transient long createdTime; + private transient Instant createdTime; public static final String TEST_ZONE = "testZone"; @@ -487,167 +488,167 @@ public EC2Cloud getCloud() { */ /* package */ static int toNumExecutors(InstanceType it) { switch (it) { - case T1Micro: + case T1_MICRO: return 1; - case M1Small: + case M1_SMALL: return 1; - case M1Medium: + case M1_MEDIUM: return 2; - case M3Medium: + case M3_MEDIUM: return 2; - case T3Nano: + case T3_NANO: return 2; - case T3aNano: + case T3_A_NANO: return 2; - case T3Micro: + case T3_MICRO: return 2; - case T3aMicro: + case T3_A_MICRO: return 2; - case T3Small: + case T3_SMALL: return 2; - case T3aSmall: + case T3_A_SMALL: return 2; - case T3Medium: + case T3_MEDIUM: return 2; - case T3aMedium: + case T3_A_MEDIUM: return 2; - case A1Large: + case A1_LARGE: return 2; - case T3Large: + case T3_LARGE: return 3; - case T3aLarge: + case T3_A_LARGE: return 3; - case M1Large: + case M1_LARGE: return 4; - case M3Large: + case M3_LARGE: return 4; - case M4Large: + case M4_LARGE: return 4; - case M5Large: + case M5_LARGE: return 4; - case M5aLarge: + case M5_A_LARGE: return 4; - case T3Xlarge: + case T3_XLARGE: return 5; - case T3aXlarge: + case T3_A_XLARGE: return 5; - case A1Xlarge: + case A1_XLARGE: return 5; - case C1Medium: + case C1_MEDIUM: return 5; - case M2Xlarge: + case M2_XLARGE: return 6; - case C3Large: + case C3_LARGE: return 7; - case C4Large: + case C4_LARGE: return 7; - case C5Large: + case C5_LARGE: return 7; - case C5dLarge: + case C5_D_LARGE: return 7; - case M1Xlarge: + case M1_XLARGE: return 8; - case T32xlarge: + case T3_2_XLARGE: return 10; - case T3a2xlarge: + case T3_A_2_XLARGE: return 10; - case A12xlarge: + case A1_2_XLARGE: return 10; - case M22xlarge: + case M2_2_XLARGE: return 13; - case M3Xlarge: + case M3_XLARGE: return 13; - case M4Xlarge: + case M4_XLARGE: return 13; - case M5Xlarge: + case M5_XLARGE: return 13; - case M5aXlarge: + case M5_A_XLARGE: return 13; - case A14xlarge: + case A1_4_XLARGE: return 14; - case C3Xlarge: + case C3_XLARGE: return 14; - case C4Xlarge: + case C4_XLARGE: return 14; - case C5Xlarge: + case C5_XLARGE: return 14; - case C5dXlarge: + case C5_D_XLARGE: return 14; - case C1Xlarge: + case C1_XLARGE: return 20; - case M24xlarge: + case M2_4_XLARGE: return 26; - case M32xlarge: + case M3_2_XLARGE: return 26; - case M42xlarge: + case M4_2_XLARGE: return 26; - case M52xlarge: + case M5_2_XLARGE: return 26; - case M5a2xlarge: + case M5_A_2_XLARGE: return 26; - case G22xlarge: + case G2_2_XLARGE: return 26; - case C32xlarge: + case C3_2_XLARGE: return 28; - case C42xlarge: + case C4_2_XLARGE: return 28; - case C52xlarge: + case C5_2_XLARGE: return 28; - case C5d2xlarge: + case C5_D_2_XLARGE: return 28; - case Cc14xlarge: + case CC1_4_XLARGE: return 33; - case Cg14xlarge: + case CG1_4_XLARGE: return 33; - case Hi14xlarge: + case HI1_4_XLARGE: return 35; - case Hs18xlarge: + case HS1_8_XLARGE: return 35; - case C34xlarge: + case C3_4_XLARGE: return 55; - case C44xlarge: + case C4_4_XLARGE: return 55; - case C54xlarge: + case C5_4_XLARGE: return 55; - case C5d4xlarge: + case C5_D_4_XLARGE: return 55; - case M44xlarge: + case M4_4_XLARGE: return 55; - case M54xlarge: + case M5_4_XLARGE: return 55; - case M5a4xlarge: + case M5_A_4_XLARGE: return 55; - case Cc28xlarge: + case CC2_8_XLARGE: return 88; - case Cr18xlarge: + case CR1_8_XLARGE: return 88; - case C38xlarge: + case C3_8_XLARGE: return 108; - case C48xlarge: + case C4_8_XLARGE: return 108; - case C59xlarge: + case C5_9_XLARGE: return 108; - case C5d9xlarge: + case C5_D_9_XLARGE: return 108; - case M410xlarge: + case M4_10_XLARGE: return 120; - case M512xlarge: + case M5_12_XLARGE: return 120; - case M5a12xlarge: + case M5_A_12_XLARGE: return 120; - case M416xlarge: + case M4_16_XLARGE: return 160; - case C518xlarge: + case C5_18_XLARGE: return 216; - case C5d18xlarge: + case C5_D_18_XLARGE: return 216; - case M524xlarge: + case M5_24_XLARGE: return 240; - case M5a24xlarge: + case M5_A_24_XLARGE: return 240; - case Dl124xlarge: + case DL1_24_XLARGE: return 250; - case Mac1Metal: + case MAC1_METAL: return 1; // We don't have a suggestion, but we don't want to fail completely // surely? @@ -692,8 +693,10 @@ public static Instance getInstance(String instanceId, EC2Cloud cloud) { void stop() { try { - AmazonEC2 ec2 = getCloud().connect(); - StopInstancesRequest request = new StopInstancesRequest(Collections.singletonList(getInstanceId())); + Ec2Client ec2 = getCloud().connect(); + StopInstancesRequest request = StopInstancesRequest.builder() + .instanceIds(Collections.singletonList(getInstanceId())) + .build(); LOGGER.fine("Sending stop request for " + getInstanceId()); ec2.stopInstances(request); LOGGER.info("EC2 instance stop request sent for " + getInstanceId()); @@ -701,21 +704,22 @@ void stop() { if (computer != null) { computer.disconnect(null); } - } catch (AmazonClientException e) { + } catch (SdkException 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())); + Ec2Client ec2 = getCloud().connect(); + TerminateInstancesRequest request = TerminateInstancesRequest.builder() + .instanceIds(Collections.singletonList(getInstanceId())) + .build(); LOGGER.fine("Sending terminate request for " + getInstanceId()); ec2.terminateInstances(request); LOGGER.info("EC2 instance terminate request sent for " + getInstanceId()); return true; - } catch (AmazonClientException e) { + } catch (SdkException e) { LOGGER.log(Level.WARNING, "Failed to terminate EC2 instance: " + getInstanceId(), e); return false; } @@ -841,7 +845,7 @@ protected boolean isAlive(boolean force) { if (lastFetchInstance == null) { return false; } - if (lastFetchInstance.getState().getName().equals(InstanceStateName.Terminated.toString())) { + if (lastFetchInstance.state().name().equals(InstanceStateName.TERMINATED)) { return false; } return true; @@ -851,7 +855,7 @@ protected boolean isAlive(boolean force) { * Much of the EC2 data is beyond our direct control, therefore we need to refresh it from time to time to ensure we * reflect the reality of the instances. */ - private void fetchLiveInstanceData(boolean force) throws AmazonClientException { + private void fetchLiveInstanceData(boolean force) throws SdkException { /* * If we've grabbed the data recently, don't bother getting it again unless we are forced */ @@ -886,19 +890,19 @@ private void fetchLiveInstanceData(boolean force) throws AmazonClientException { return; } - publicDNS = i.getPublicDnsName(); - privateDNS = i.getPrivateIpAddress(); - createdTime = i.getLaunchTime().getTime(); - instanceType = i.getInstanceType(); + publicDNS = i.publicDnsName(); + privateDNS = i.privateIpAddress(); + createdTime = i.launchTime(); + instanceType = i.instanceType().name(); /* * 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.tags().isEmpty()) { tags = new LinkedList<>(); - for (Tag t : i.getTags()) { - tags.add(new EC2Tag(t.getKey(), t.getValue())); + for (Tag t : i.tags()) { + tags.add(new EC2Tag(t.key(), t.value())); } } } @@ -906,7 +910,7 @@ private void fetchLiveInstanceData(boolean force) throws AmazonClientException { /* * Clears all existing tag data so that we can force the instance into a known state */ - protected void clearLiveInstancedata() throws AmazonClientException { + protected void clearLiveInstancedata() throws SdkException { Instance inst = null; try { inst = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); @@ -921,12 +925,14 @@ protected void clearLiveInstancedata() throws AmazonClientException { HashSet instTags = new HashSet<>(); for (EC2Tag t : tags) { - instTags.add(new Tag(t.getName(), t.getValue())); + instTags.add(Tag.builder().key(t.getName()).value(t.getValue()).build()); } List resources = getResourcesToTag(inst); - DeleteTagsRequest tagRequest = new DeleteTagsRequest(); - tagRequest.withResources(resources).setTags(instTags); + DeleteTagsRequest tagRequest = DeleteTagsRequest.builder() + .resources(resources) + .tags(instTags) + .build(); getCloud().connect().deleteTags(tagRequest); } } @@ -935,7 +941,7 @@ protected void clearLiveInstancedata() throws AmazonClientException { * Sets tags on an instance and on the volumes attached to it. This will not clear existing tag data, so call * clearLiveInstancedata if needed */ - protected void pushLiveInstancedata() throws AmazonClientException { + protected void pushLiveInstancedata() throws SdkException { Instance inst = null; try { inst = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); @@ -949,12 +955,14 @@ protected void pushLiveInstancedata() throws AmazonClientException { HashSet instTags = new HashSet<>(); for (EC2Tag t : tags) { - instTags.add(new Tag(t.getName(), t.getValue())); + instTags.add(Tag.builder().key(t.getName()).value(t.getValue()).build()); } List resources = getResourcesToTag(inst); - CreateTagsRequest tagRequest = new CreateTagsRequest(); - tagRequest.withResources(resources).setTags(instTags); + CreateTagsRequest tagRequest = CreateTagsRequest.builder() + .resources(resources) + .tags(instTags) + .build(); getCloud().connect().createTags(tagRequest); } } @@ -964,9 +972,9 @@ protected void pushLiveInstancedata() throws AmazonClientException { */ private List getResourcesToTag(Instance inst) { List resources = new ArrayList<>(); - resources.add(inst.getInstanceId()); - for (InstanceBlockDeviceMapping blockDeviceMapping : inst.getBlockDeviceMappings()) { - resources.add(blockDeviceMapping.getEbs().getVolumeId()); + resources.add(inst.instanceId()); + for (InstanceBlockDeviceMapping blockDeviceMapping : inst.blockDeviceMappings()) { + resources.add(blockDeviceMapping.ebs().volumeId()); } return resources; } @@ -991,7 +999,7 @@ public List getTags() { return Collections.unmodifiableList(tags); } - public long getCreatedTime() { + public Instant getCreatedTime() { fetchLiveInstanceData(false); return createdTime; } @@ -1037,17 +1045,17 @@ public boolean isAllowSelfSignedCertificate() { return amiType.isWindows() && ((WindowsData) amiType).isAllowSelfSignedCertificate(); } - public static ListBoxModel fillZoneItems(AWSCredentialsProvider credentialsProvider, String region) { + public static ListBoxModel fillZoneItems(AwsCredentialsProvider credentialsProvider, String region) { ListBoxModel model = new ListBoxModel(); if (!StringUtils.isEmpty(region)) { - AmazonEC2 client = - AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); - DescribeAvailabilityZonesResult zones = client.describeAvailabilityZones(); - List zoneList = zones.getAvailabilityZones(); + Ec2Client client = + AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.parseRegion(region), null); + DescribeAvailabilityZonesResponse zones = client.describeAvailabilityZones(); + List zoneList = zones.availabilityZones(); model.add("", ""); for (AvailabilityZone z : zoneList) { - model.add(z.getZoneName(), z.getZoneName()); + model.add(z.zoneName(), z.zoneName()); } } return model; @@ -1078,7 +1086,7 @@ public ListBoxModel doFillZoneItems( if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { return new ListBoxModel(); } - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + AwsCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); return fillZoneItems(credentialsProvider, region); } diff --git a/src/main/java/hudson/plugins/ec2/EC2Cloud.java b/src/main/java/hudson/plugins/ec2/EC2Cloud.java index e0163c61c..8397fffb6 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Cloud.java +++ b/src/main/java/hudson/plugins/ec2/EC2Cloud.java @@ -18,36 +18,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.SdkClientException; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; -import com.amazonaws.auth.InstanceProfileCredentialsProvider; -import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.DescribeRegionsResult; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult; -import com.amazonaws.services.ec2.model.Filter; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceStateName; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.KeyPair; -import com.amazonaws.services.ec2.model.KeyPairInfo; -import com.amazonaws.services.ec2.model.Region; -import com.amazonaws.services.ec2.model.Reservation; -import com.amazonaws.services.ec2.model.SpotInstanceRequest; -import com.amazonaws.services.ec2.model.Tag; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; -import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl; import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials; import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; @@ -64,7 +34,6 @@ import com.google.common.annotations.VisibleForTesting; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; import hudson.ProxyConfiguration; import hudson.Util; @@ -77,6 +46,7 @@ import hudson.model.PeriodicWork; import hudson.model.TaskListener; import hudson.plugins.ec2.util.AmazonEC2Factory; +import hudson.plugins.ec2.util.KeyPair; import hudson.security.ACL; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner.PlannedNode; @@ -90,14 +60,15 @@ import java.io.PrintStream; import java.io.StringReader; import java.io.StringWriter; -import java.net.InetSocketAddress; import java.net.MalformedURLException; -import java.net.Proxy; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.EnumSet; import java.util.HashSet; import java.util.List; @@ -130,6 +101,40 @@ import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; +import software.amazon.awssdk.auth.credentials.InstanceProfileCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.auth.signer.Aws4Signer; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.core.retry.RetryPolicy; +import software.amazon.awssdk.http.SdkHttpClient; +import software.amazon.awssdk.http.apache.ApacheHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsResponse; +import software.amazon.awssdk.services.ec2.model.Filter; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.KeyPairInfo; +import software.amazon.awssdk.services.ec2.model.Reservation; +import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest; +import software.amazon.awssdk.services.ec2.model.SpotInstanceState; +import software.amazon.awssdk.services.ec2.model.Tag; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.services.sts.StsClientBuilder; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider; +import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; /** * Hudson's view of EC2. @@ -142,12 +147,6 @@ public class EC2Cloud extends Cloud { public static final String DEFAULT_EC2_HOST = "us-east-1"; - public static final String DEFAULT_EC2_ENDPOINT = "https://ec2.amazonaws.com"; - - public static final String AWS_URL_HOST = "amazonaws.com"; - - public static final String AWS_CN_URL_HOST = "amazonaws.com.cn"; - public static final String EC2_SLAVE_TYPE_SPOT = "spot"; public static final String EC2_SLAVE_TYPE_DEMAND = "demand"; @@ -207,7 +206,7 @@ public class EC2Cloud extends Cloud { private boolean noDelayProvisioning; - private transient volatile AmazonEC2 connection; + private transient volatile Ec2Client connection; @DataBoundConstructor public EC2Cloud( @@ -326,23 +325,31 @@ public String getRegion() { return region; } - public static URL getEc2EndpointUrl(String region) { - try { - return new URL("https://" + getAwsPartitionHostForService(region, "ec2")); - } catch (MalformedURLException e) { - throw new Error(e); // Impossible + @CheckForNull + @Restricted(NoExternalUse.class) + public static Region parseRegion(@CheckForNull final String input) { + final String regionId = Util.fixEmpty(input); + if (regionId == null) { + return null; } + return Region.regions().stream() + .filter(r -> r.id().equals(regionId)) + .findFirst() + .orElse(null); } - public URL getEc2EndpointUrl() { - return getEc2EndpointUrl(getRegion()); - } - - public URL getS3EndpointUrl() { + @CheckForNull + @Restricted(NoExternalUse.class) + public static URI parseEndpoint(@CheckForNull String input) { + final String endpoint = Util.fixEmpty(input); + if (endpoint == null) { + return null; + } try { - return new URL("https://" + getAwsPartitionHostForService(getRegion(), "s3") + "/"); - } catch (MalformedURLException e) { - throw new Error(e); // Impossible + return new URI(endpoint); + } catch (URISyntaxException e) { + LOGGER.log(Level.WARNING, "The alternate EC2 endpoint is malformed ({0}).", endpoint); + return null; } } @@ -448,9 +455,9 @@ protected Object readResolve() { 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())) { + AwsCredentials awsCredentials = awsCreds.resolveCredentials(); + if (accessId.equals(awsCredentials.accessKeyId()) + && Secret.toString(this.secretKey).equals(awsCredentials.secretAccessKey())) { this.credentialsId = awsCreds.getId(); this.accessId = null; @@ -593,7 +600,7 @@ public Collection getTemplates(Label label) { * Gets the {@link KeyPairInfo} used for the launch. */ @CheckForNull - public synchronized KeyPair getKeyPair() throws AmazonClientException, IOException { + public synchronized KeyPair getKeyPair() throws SdkException, IOException { if (usableKeyPair == null) { EC2PrivateKey ec2PrivateKey = this.resolvePrivateKey(); if (ec2PrivateKey != null) { @@ -608,7 +615,7 @@ public synchronized KeyPair getKeyPair() throws AmazonClientException, IOExcepti */ @RequirePOST public void doAttach(StaplerRequest req, StaplerResponse rsp, @QueryParameter String id) - throws ServletException, IOException, AmazonClientException { + throws ServletException, IOException, SdkException { checkPermission(PROVISION); SlaveTemplate t = getTemplates().get(0); @@ -655,7 +662,7 @@ public HttpResponse doProvision(@QueryParameter String template) throws ServletE return HttpResponses.redirectViaContextPath( "/computer/" + nodes.get(0).getNodeName()); - } catch (AmazonClientException e) { + } catch (SdkException e) { throw HttpResponses.error(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } @@ -666,7 +673,7 @@ public HttpResponse doProvision(@QueryParameter String template) throws ServletE * * @param template If left null, then all instances are counted. */ - private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientException { + private int countCurrentEC2Slaves(SlaveTemplate template) throws SdkException { String jenkinsServerUrl = JenkinsLocationConfiguration.get().getUrl(); if (jenkinsServerUrl == null) { @@ -688,26 +695,33 @@ private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientExc String description = template != null ? template.description : null; List filters = getGenericFilters(jenkinsServerUrl, template); - filters.add(new Filter("instance-state-name").withValues("running", "pending", "stopping")); - DescribeInstancesRequest dir = new DescribeInstancesRequest().withFilters(filters); - DescribeInstancesResult result = null; + filters.add(Filter.builder() + .name("instance-state-name") + .values("running", "pending", "stopping") + .build()); + DescribeInstancesRequest dir = + DescribeInstancesRequest.builder().filters(filters).build(); + DescribeInstancesResponse result = null; do { result = connect().describeInstances(dir); - dir.setNextToken(result.getNextToken()); - for (Reservation r : result.getReservations()) { - for (Instance i : r.getInstances()) { - if (isEc2ProvisionedAmiSlave(i.getTags(), description)) { + dir = DescribeInstancesRequest.builder() + .filters(filters) + .nextToken(result.nextToken()) + .build(); + for (Reservation r : result.reservations()) { + for (Instance i : r.instances()) { + if (isEc2ProvisionedAmiSlave(i.tags(), description)) { LOGGER.log( Level.FINE, - "Existing instance found: " + i.getInstanceId() + " AMI: " + i.getImageId() + "Existing instance found: " + i.instanceId() + " AMI: " + i.imageId() + (template != null ? (" Template: " + description) : "") + " Jenkins Server: " + jenkinsServerUrl); n++; - instanceIds.add(i.getInstanceId()); + instanceIds.add(i.instanceId()); } } } - } while (result.getNextToken() != null); + } while (result.nextToken() != null); n += countCurrentEC2SpotSlaves(template, jenkinsServerUrl, instanceIds); @@ -721,43 +735,52 @@ 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 { + throws SdkException { int n = 0; String description = template != null ? template.description : null; List sirs = null; List filters = getGenericFilters(jenkinsServerUrl, template); if (template != null) { - filters.add(new Filter("launch.image-id").withValues(template.getAmi())); + filters.add(Filter.builder() + .name("launch.image-id") + .values(template.getAmi()) + .build()); } - DescribeSpotInstanceRequestsRequest dsir = - new DescribeSpotInstanceRequestsRequest().withFilters(filters).withMaxResults(100); + DescribeSpotInstanceRequestsRequest dsir = DescribeSpotInstanceRequestsRequest.builder() + .filters(filters) + .maxResults(100) + .build(); Set sirSet = new HashSet<>(); - DescribeSpotInstanceRequestsResult sirResp = null; + DescribeSpotInstanceRequestsResponse sirResp = null; do { sirResp = connect().describeSpotInstanceRequests(dsir); - sirs = sirResp.getSpotInstanceRequests(); - dsir.setNextToken(sirResp.getNextToken()); + sirs = sirResp.spotInstanceRequests(); + dsir = DescribeSpotInstanceRequestsRequest.builder() + .filters(filters) + .maxResults(100) + .nextToken(sirResp.nextToken()) + .build(); if (sirs != null) { 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.state() == SpotInstanceState.OPEN || sir.state() == SpotInstanceState.ACTIVE) { + if (sir.instanceId() != null && instanceIds.contains(sir.instanceId())) { continue; } - if (isEc2ProvisionedAmiSlave(sir.getTags(), description)) { + if (isEc2ProvisionedAmiSlave(sir.tags(), description)) { LOGGER.log( Level.FINE, - "Spot instance request found: " + sir.getSpotInstanceRequestId() + " AMI: " - + sir.getInstanceId() + " state: " + sir.getState() + " status: " - + sir.getStatus()); + "Spot instance request found: " + sir.spotInstanceRequestId() + " AMI: " + + sir.instanceId() + " state: " + sir.state() + " status: " + + sir.status()); n++; - if (sir.getInstanceId() != null) { - instanceIds.add(sir.getInstanceId()); + if (sir.instanceId() != null) { + instanceIds.add(sir.instanceId()); } } } else { @@ -768,28 +791,28 @@ private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServ continue; } EC2SpotSlave ec2Slave = (EC2SpotSlave) node; - if (ec2Slave.getSpotInstanceRequestId().equals(sir.getSpotInstanceRequestId())) { + if (ec2Slave.getSpotInstanceRequestId().equals(sir.spotInstanceRequestId())) { LOGGER.log( Level.INFO, - "Removing dead request: " + sir.getSpotInstanceRequestId() + " AMI: " - + sir.getInstanceId() + " state: " + sir.getState() + " status: " - + sir.getStatus()); + "Removing dead request: " + sir.spotInstanceRequestId() + " AMI: " + + sir.instanceId() + " state: " + sir.state() + " status: " + + sir.status()); 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(), + "Failed to remove node for dead request: " + sir.spotInstanceRequestId() + + " AMI: " + sir.instanceId() + " state: " + sir.state() + + " status: " + sir.status(), e); } } } } } - } while (sirResp.getNextToken() != null); + } while (sirResp.nextToken() != null); n += countJenkinsNodeSpotInstancesWithoutRequests(template, sirSet, instanceIds); return n; } @@ -797,8 +820,7 @@ private int countCurrentEC2SpotSlaves(SlaveTemplate template, String jenkinsServ // 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 { + SlaveTemplate template, Set sirSet, Set instanceIds) throws SdkException { int n = 0; for (Node node : Jenkins.get().getNodes()) { if (!(node instanceof EC2SpotSlave)) { @@ -819,28 +841,28 @@ private int countJenkinsNodeSpotInstancesWithoutRequests( sirSet.add(sir); - if (sir.getState().equals("open") || sir.getState().equals("active")) { + if (sir.state() == SpotInstanceState.OPEN || sir.state() == SpotInstanceState.ACTIVE) { if (template != null) { - List instanceTags = sir.getTags(); + List instanceTags = sir.tags(); for (Tag tag : instanceTags) { - if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + if (StringUtils.equals(tag.key(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) && StringUtils.equals( - tag.getValue(), getSlaveTypeTagValue(EC2_SLAVE_TYPE_SPOT, template.description)) - && sir.getLaunchSpecification().getImageId().equals(template.getAmi())) { + tag.value(), getSlaveTypeTagValue(EC2_SLAVE_TYPE_SPOT, template.description)) + && sir.launchSpecification().imageId().equals(template.getAmi())) { - if (sir.getInstanceId() != null && instanceIds.contains(sir.getInstanceId())) { + if (sir.instanceId() != null && instanceIds.contains(sir.instanceId())) { continue; } LOGGER.log( Level.FINE, - "Spot instance request found (from node): " + sir.getSpotInstanceRequestId() - + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " - + sir.getStatus()); + "Spot instance request found (from node): " + sir.spotInstanceRequestId() + + " AMI: " + sir.instanceId() + " state: " + sir.state() + " status: " + + sir.status()); n++; - if (sir.getInstanceId() != null) { - instanceIds.add(sir.getInstanceId()); + if (sir.instanceId() != null) { + instanceIds.add(sir.instanceId()); } } } @@ -852,12 +874,21 @@ private int countJenkinsNodeSpotInstancesWithoutRequests( private List getGenericFilters(String jenkinsServerUrl, SlaveTemplate template) { List filters = new ArrayList<>(); - filters.add(new Filter("tag-key").withValues(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)); + filters.add(Filter.builder() + .name("tag-key") + .values(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .build()); if (jenkinsServerUrl != null) { // The instances must match the jenkins server url - filters.add(new Filter("tag:" + EC2Tag.TAG_NAME_JENKINS_SERVER_URL).withValues(jenkinsServerUrl)); + filters.add(Filter.builder() + .name("tag:" + EC2Tag.TAG_NAME_JENKINS_SERVER_URL) + .values(jenkinsServerUrl) + .build()); } else { - filters.add(new Filter("tag-key").withValues(EC2Tag.TAG_NAME_JENKINS_SERVER_URL)); + filters.add(Filter.builder() + .name("tag-key") + .values(EC2Tag.TAG_NAME_JENKINS_SERVER_URL) + .build()); } if (template != null) { @@ -865,7 +896,10 @@ private List getGenericFilters(String jenkinsServerUrl, SlaveTemplate te if (tags != null) { for (EC2Tag tag : tags) { if (tag.getName() != null && tag.getValue() != null) { - filters.add(new Filter("tag:" + tag.getName()).withValues(tag.getValue())); + filters.add(Filter.builder() + .name("tag:" + tag.getName()) + .values(tag.getValue()) + .build()); } } } @@ -875,17 +909,17 @@ private List getGenericFilters(String jenkinsServerUrl, SlaveTemplate te private boolean isEc2ProvisionedAmiSlave(List tags, String description) { for (Tag tag : tags) { - if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)) { + if (StringUtils.equals(tag.key(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)) { if (description == null) { return true; - } else if (StringUtils.equals(tag.getValue(), EC2Cloud.EC2_SLAVE_TYPE_DEMAND) - || StringUtils.equals(tag.getValue(), EC2Cloud.EC2_SLAVE_TYPE_SPOT)) { + } else if (StringUtils.equals(tag.value(), EC2Cloud.EC2_SLAVE_TYPE_DEMAND) + || StringUtils.equals(tag.value(), 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)) + tag.value(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_DEMAND, description)) || StringUtils.equals( - tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_SPOT, description))) { + tag.value(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_SPOT, description))) { return true; } else { return false; @@ -898,7 +932,7 @@ private boolean isEc2ProvisionedAmiSlave(List tags, String description) { /** * Returns the maximum number of possible agents that can be created. */ - private int getPossibleNewSlavesCount(SlaveTemplate template) throws AmazonClientException { + private int getPossibleNewSlavesCount(SlaveTemplate template) throws SdkException { int estimatedTotalSlaves = countCurrentEC2Slaves(null); int estimatedAmiSlaves = countCurrentEC2Slaves(template); @@ -991,10 +1025,10 @@ public Collection provision(final Label label, int excessWorkload) if (excessWorkload == 0) { break; } - } catch (AmazonServiceException e) { + } catch (AwsServiceException e) { LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); - if (e.getErrorCode().equals("RequestExpired") - || e.getErrorCode().equals("ExpiredToken")) { + if (e.awsErrorDetails().errorCode().equals("RequestExpired") + || e.awsErrorDetails().errorCode().equals("ExpiredToken")) { // A RequestExpired or ExpiredToken error can indicate that credentials have expired so reconnect LOGGER.log(Level.INFO, "Reconnecting to EC2 due to RequestExpired or ExpiredToken error"); try { @@ -1003,7 +1037,7 @@ public Collection provision(final Label label, int excessWorkload) LOGGER.log(Level.WARNING, "Failed to reconnect ec2", e2); } } - } catch (AmazonClientException | IOException e) { + } catch (SdkException | IOException e) { LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); } } @@ -1055,7 +1089,7 @@ public void provision(SlaveTemplate t, int 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) { + } catch (SdkException | IOException e) { LOGGER.log(Level.WARNING, t + ". Exception during provisioning", e); } } @@ -1070,8 +1104,8 @@ public void provision(SlaveTemplate t, int number) { 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); + Ec2Client ec2 = this.connect(); + DescribeInstancesResponse 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 */ @@ -1120,9 +1154,8 @@ public Node call() throws Exception { return null; } - InstanceStateName state = InstanceStateName.fromValue( - instance.getState().getName()); - if (state.equals(InstanceStateName.Running)) { + InstanceStateName state = instance.state().name(); + 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(); @@ -1130,16 +1163,15 @@ public Node call() throws Exception { c.connect(false); } - long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - - instance.getLaunchTime().getTime()); + long secondsSinceStart = Instant.now().until(instance.launchTime(), ChronoUnit.SECONDS); 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}); + new Object[] {t, slave.getNodeName(), secondsSinceStart}); return slave; } - if (!state.equals(InstanceStateName.Pending)) { + if (!state.equals(InstanceStateName.PENDING)) { if (retryCount >= DESCRIBE_LIMIT) { LOGGER.log( @@ -1168,7 +1200,7 @@ public boolean canProvision(Label label) { return !getTemplates(label).isEmpty(); } - protected AWSCredentialsProvider createCredentialsProvider() { + protected AwsCredentialsProvider createCredentialsProvider() { return createCredentialsProvider( isUseInstanceProfileForCredentials(), getCredentialsId(), @@ -1181,38 +1213,49 @@ public static String getSlaveTypeTagValue(String slaveType, String templateDescr return templateDescription != null ? slaveType + "_" + templateDescription : slaveType; } - public static AWSCredentialsProvider createCredentialsProvider( + public static AwsCredentialsProvider createCredentialsProvider( final boolean useInstanceProfileForCredentials, final String credentialsId) { if (useInstanceProfileForCredentials) { - return new InstanceProfileCredentialsProvider(false); + return InstanceProfileCredentialsProvider.create(); } else if (StringUtils.isBlank(credentialsId)) { - return new DefaultAWSCredentialsProviderChain(); + return DefaultCredentialsProvider.create(); } else { AmazonWebServicesCredentials credentials = getCredentials(credentialsId); if (credentials != null) { - return new AWSStaticCredentialsProvider(credentials.getCredentials()); + return StaticCredentialsProvider.create(credentials.resolveCredentials()); } } - return new DefaultAWSCredentialsProviderChain(); + return DefaultCredentialsProvider.create(); } - public static AWSCredentialsProvider createCredentialsProvider( + public static AwsCredentialsProvider createCredentialsProvider( final boolean useInstanceProfileForCredentials, final String credentialsId, final String roleArn, final String roleSessionName, final String region) { - AWSCredentialsProvider provider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); + AwsCredentialsProvider provider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); if (StringUtils.isNotEmpty(roleArn)) { - return new STSAssumeRoleSessionCredentialsProvider.Builder( - roleArn, StringUtils.defaultIfBlank(roleSessionName, "Jenkins")) - .withStsClient(AWSSecurityTokenServiceClientBuilder.standard() - .withCredentials(provider) - .withRegion(region) - .withClientConfiguration(createClientConfiguration(convertHostName(region))) - .build()) + AssumeRoleRequest assumeRoleRequest = AssumeRoleRequest.builder() + .roleArn(roleArn) + .roleSessionName(StringUtils.defaultIfBlank(roleSessionName, "Jenkins")) + .build(); + + StsClientBuilder stsClientBuilder = StsClient.builder() + .credentialsProvider(provider) + .httpClient(getHttpClient()) + .overrideConfiguration(createClientOverrideConfiguration()); + Region parsed = parseRegion(region); + if (parsed != null) { + stsClientBuilder.region(parsed); + } + StsClient stsClient = stsClientBuilder.build(); + + return StsAssumeRoleCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(assumeRoleRequest) .build(); } @@ -1230,17 +1273,18 @@ private static AmazonWebServicesCredentials getCredentials(@CheckForNull String CredentialsMatchers.withId(credentialsId)); } - private AmazonEC2 reconnectToEc2() throws IOException { + private Ec2Client reconnectToEc2() throws IOException { synchronized (this) { - connection = AmazonEC2Factory.getInstance().connect(createCredentialsProvider(), getEc2EndpointUrl()); + connection = AmazonEC2Factory.getInstance() + .connect(createCredentialsProvider(), parseRegion(getRegion()), parseEndpoint(getAltEC2Endpoint())); return connection; } } /** - * Connects to EC2 and returns {@link AmazonEC2}, which can then be used to communicate with EC2. + * Connects to EC2 and returns {@link Ec2Client}, which can then be used to communicate with EC2. */ - public AmazonEC2 connect() throws AmazonClientException { + public Ec2Client connect() throws SdkException { try { if (connection != null) { return connection; @@ -1248,79 +1292,36 @@ public AmazonEC2 connect() throws AmazonClientException { return reconnectToEc2(); } } catch (IOException e) { - throw new AmazonClientException("Failed to retrieve the endpoint", e); + throw SdkException.create("Failed to retrieve the endpoint", e); } } - public static ClientConfiguration createClientConfiguration(final String host) { - ClientConfiguration config = new ClientConfiguration(); - config.setMaxErrorRetry(16); // Default retry limit (3) is low and often - // cause problems. Raise it a bit. - // See: https://issues.jenkins-ci.org/browse/JENKINS-26800 - config.setSignerOverride("AWS4SignerType"); - ProxyConfiguration proxyConfig = Jenkins.get().proxy; - Proxy proxy = proxyConfig == null ? Proxy.NO_PROXY : proxyConfig.createProxy(host); - if (!proxy.equals(Proxy.NO_PROXY) && proxy.address() instanceof InetSocketAddress) { - InetSocketAddress address = (InetSocketAddress) proxy.address(); - config.setProxyHost(address.getHostName()); - config.setProxyPort(address.getPort()); - if (null != proxyConfig.getUserName()) { - config.setProxyUsername(proxyConfig.getUserName()); - config.setProxyPassword(proxyConfig.getPassword()); - } - } - return config; - } + public static SdkHttpClient getHttpClient() { + Jenkins instance = Jenkins.getInstanceOrNull(); - /*** - * Returns the DNS endpoint for a AWS service based on region provided - */ - public static String getAwsPartitionHostForService(String region, String service) { - String host; - if (region != null && region.startsWith("cn-")) { - host = service + "." + region + "." + AWS_CN_URL_HOST; - } else { - host = service + "." + region + "." + AWS_URL_HOST; - } - return host; - } - - /*** - * Convert a configured hostname like 'us-east-1' to a FQDN or ip address - */ - public static String convertHostName(String ec2HostName) { - if (ec2HostName == null || ec2HostName.isEmpty()) { - ec2HostName = DEFAULT_EC2_HOST; - } - if (!ec2HostName.contains(".")) { - ec2HostName = getAwsPartitionHostForService(ec2HostName, "ec2"); - } - return 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.isEmpty()) { - return -1; + ProxyConfiguration proxy = instance != null ? instance.proxy : null; + ApacheHttpClient.Builder builder = ApacheHttpClient.builder(); + if (proxy != null && proxy.name != null && !proxy.name.isEmpty()) { + software.amazon.awssdk.http.apache.ProxyConfiguration.Builder proxyConfiguration = + software.amazon.awssdk.http.apache.ProxyConfiguration.builder() + .endpoint(URI.create(String.format("http://%s:%s", proxy.name, proxy.port))); + if (proxy.getUserName() != null) { + proxyConfiguration.username(proxy.getUserName()); + proxyConfiguration.password(Secret.toString(proxy.getSecretPassword())); + } + builder.proxyConfiguration(proxyConfiguration.build()); } - return Integer.parseInt(ec2Port); + return builder.build(); } - /** - * Computes the presigned URL for the given S3 resource. - * - * @param path String like "/bucketName/folder/folder/abc.txt" that represents the resource to request. - */ - public URL buildPresignedURL(String path) throws AmazonClientException { - AWSCredentialsProvider provider = createCredentialsProvider(); - AWSCredentials credentials = provider.getCredentials(); - 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(); - return s3.generatePresignedUrl(request); + public static ClientOverrideConfiguration createClientOverrideConfiguration() { + // Default retry limit (3) is low and often cause problems. Raise it a bit. + // See: https://issues.jenkins-ci.org/browse/JENKINS-26800 + ClientOverrideConfiguration config = ClientOverrideConfiguration.builder() + .putAdvancedOption(SdkAdvancedClientOption.SIGNER, Aws4Signer.create()) + .retryPolicy(RetryPolicy.builder().numRetries(16).build()) + .build(); + return config; } /* Parse a url or return a sensible error */ @@ -1372,9 +1373,9 @@ public FormValidation doCheckUseInstanceProfileForCredentials(@QueryParameter bo return FormValidation.ok(); } try { - new InstanceProfileCredentialsProvider(false).getCredentials(); + InstanceProfileCredentialsProvider.builder().build().resolveCredentials(); return FormValidation.ok(); - } catch (AmazonClientException e) { + } catch (SdkException e) { return FormValidation.error(Messages.EC2Cloud_FailedToObtainCredentialsFromEC2(), e.getMessage()); } } @@ -1505,6 +1506,7 @@ public FormValidation doCheckSshKeysCredentialsId( public FormValidation doTestConnection( @AncestorInPath ItemGroup context, @QueryParameter String region, + @QueryParameter String altEC2Endpoint, @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId, @QueryParameter String sshKeysCredentialsId, @@ -1547,9 +1549,10 @@ public FormValidation doTestConnection( region = DEFAULT_EC2_HOST; } - AWSCredentialsProvider credentialsProvider = createCredentialsProvider( + AwsCredentialsProvider credentialsProvider = createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, getEc2EndpointUrl(region)); + Ec2Client ec2 = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, parseRegion(region), parseEndpoint(altEC2Endpoint)); ec2.describeInstances(); if (!privateKey.trim().isEmpty()) { @@ -1572,7 +1575,7 @@ public FormValidation doTestConnection( } validations.add(FormValidation.ok(Messages.EC2Cloud_Success())); return FormValidation.aggregate(validations); - } catch (AmazonClientException e) { + } catch (SdkException e) { LOGGER.log(Level.WARNING, "Failed to check EC2 credential", e); return FormValidation.error(e.getMessage()); } @@ -1627,14 +1630,14 @@ public ListBoxModel doFillRegionItems( ListBoxModel model = new ListBoxModel(); if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { try { - AWSCredentialsProvider credentialsProvider = + 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) { - String name = r.getRegionName(); + Ec2Client client = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, null, parseEndpoint(altEC2Endpoint)); + DescribeRegionsResponse regions = client.describeRegions(); + List regionList = regions.regions(); + for (software.amazon.awssdk.services.ec2.model.Region r : regionList) { + String name = r.regionName(); model.add(name, name); } } catch (SdkClientException ex) { @@ -1643,24 +1646,6 @@ public ListBoxModel doFillRegionItems( } return model; } - - // Will use the alternate EC2 endpoint if provided by the UI (via a @QueryParameter field), or use the default - // value if not specified. - // VisibleForTesting - URL determineEC2EndpointURL(@Nullable String altEC2Endpoint) throws MalformedURLException { - if (Util.fixEmpty(altEC2Endpoint) == null) { - return new URL(DEFAULT_EC2_ENDPOINT); - } - try { - 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}); - return new URL(DEFAULT_EC2_ENDPOINT); - } - } } public static void log(Logger logger, Level level, TaskListener listener, String message) { @@ -1698,11 +1683,16 @@ protected void doRun() throws IOException { try { if (ec2_cloud.connection != null) { List filters = new ArrayList<>(); - filters.add(new Filter("tag-key").withValues("bogus-EC2ConnectionKeepalive")); - DescribeInstancesRequest dir = new DescribeInstancesRequest().withFilters(filters); + filters.add(Filter.builder() + .name("tag-key") + .values("bogus-EC2ConnectionKeepalive") + .build()); + DescribeInstancesRequest dir = DescribeInstancesRequest.builder() + .filters(filters) + .build(); ec2_cloud.connection.describeInstances(dir); } - } catch (AmazonClientException e) { + } catch (SdkException e) { LOGGER.finer(() -> "Reconnecting to EC2 on: " + ec2_cloud.getDisplayName()); ec2_cloud.reconnectToEc2(); } diff --git a/src/main/java/hudson/plugins/ec2/EC2Computer.java b/src/main/java/hudson/plugins/ec2/EC2Computer.java index 44aa9dc19..fa68c1831 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Computer.java +++ b/src/main/java/hudson/plugins/ec2/EC2Computer.java @@ -23,24 +23,29 @@ */ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeInstanceTypesRequest; -import com.amazonaws.services.ec2.model.DescribeInstanceTypesResult; -import com.amazonaws.services.ec2.model.GetConsoleOutputRequest; -import com.amazonaws.services.ec2.model.GetConsoleOutputResult; -import com.amazonaws.services.ec2.model.Instance; import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.Util; import hudson.model.Node; import hudson.slaves.SlaveComputer; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; 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 org.kohsuke.stapler.verb.POST; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstanceTypesResponse; +import software.amazon.awssdk.services.ec2.model.GetConsoleOutputRequest; +import software.amazon.awssdk.services.ec2.model.GetConsoleOutputResponse; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceTypeHypervisor; /** * @author Kohsuke Kawaguchi @@ -101,9 +106,9 @@ public SlaveTemplate getSlaveTemplate() { /** * Gets the EC2 console output. */ - public String getConsoleOutput() throws AmazonClientException { + public String getConsoleOutput() throws SdkException { try { - return getDecodedConsoleOutputResponse().getOutput(); + return getDecodedConsoleOutputResponse().output(); } catch (InterruptedException e) { return null; } @@ -113,45 +118,48 @@ public String getConsoleOutput() throws AmazonClientException { * Gets the EC2 decoded console output. * @since TODO */ - public String getDecodedConsoleOutput() throws AmazonClientException { + public String getDecodedConsoleOutput() throws SdkException { try { - return getDecodedConsoleOutputResponse().getDecodedOutput(); + String encodedOutput = getDecodedConsoleOutputResponse().output(); + byte[] decoded = Base64.getDecoder().decode(encodedOutput); + return new String(decoded, StandardCharsets.UTF_8); } catch (InterruptedException e) { return null; } } - private GetConsoleOutputResult getDecodedConsoleOutputResponse() - throws AmazonClientException, InterruptedException { - AmazonEC2 ec2 = getCloud().connect(); - GetConsoleOutputRequest request = new GetConsoleOutputRequest(getInstanceId()); + private GetConsoleOutputResponse getDecodedConsoleOutputResponse() throws SdkException, InterruptedException { + Ec2Client ec2 = getCloud().connect(); + GetConsoleOutputRequest.Builder requestBuilder = GetConsoleOutputRequest.builder(); if (checkIfNitro()) { // Can only be used if instance has hypervisor Nitro - request.setLatest(true); + requestBuilder.latest(true); } - return ec2.getConsoleOutput(request); + return ec2.getConsoleOutput(requestBuilder.build()); } /** * Check if instance has hypervisor Nitro */ - private boolean checkIfNitro() throws AmazonClientException, InterruptedException { + private boolean checkIfNitro() throws SdkException, InterruptedException { try { if (isNitro == null) { - DescribeInstanceTypesRequest request = new DescribeInstanceTypesRequest(); - request.setInstanceTypes( - Collections.singletonList(describeInstance().getInstanceType())); - AmazonEC2 ec2 = getCloud().connect(); - DescribeInstanceTypesResult result = ec2.describeInstanceTypes(request); - if (result.getInstanceTypes().size() == 1) { - String hypervisor = result.getInstanceTypes().get(0).getHypervisor(); - isNitro = hypervisor.equals("nitro"); + DescribeInstanceTypesRequest request = DescribeInstanceTypesRequest.builder() + .instanceTypes( + Collections.singletonList(describeInstance().instanceType())) + .build(); + Ec2Client ec2 = getCloud().connect(); + DescribeInstanceTypesResponse result = ec2.describeInstanceTypes(request); + if (result.instanceTypes().size() == 1) { + InstanceTypeHypervisor hypervisor = + result.instanceTypes().get(0).hypervisor(); + isNitro = hypervisor == InstanceTypeHypervisor.NITRO; } else { isNitro = false; } } return isNitro; - } catch (AmazonClientException e) { + } catch (SdkException e) { LOGGER.log(Level.WARNING, "Could not describe-instance-types to check if instance is nitro based", e); isNitro = false; return isNitro; @@ -162,12 +170,12 @@ private boolean checkIfNitro() throws AmazonClientException, InterruptedExceptio * Obtains the instance state description in EC2. * *

- * This method returns a cached state, so it's not suitable to check {@link Instance#getState()} from the returned + * This method returns a cached state, so it's not suitable to check {@link Instance#state()} from the returned * instance (but all the other fields are valid as it won't change.) * * The cache can be flushed using {@link #updateInstanceDescription()} */ - public Instance describeInstance() throws AmazonClientException, InterruptedException { + public Instance describeInstance() throws SdkException, InterruptedException { if (ec2InstanceDescription == null) { ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); } @@ -177,7 +185,7 @@ public Instance describeInstance() throws AmazonClientException, InterruptedExce /** * This will flush any cached description held by {@link #describeInstance()}. */ - public Instance updateInstanceDescription() throws AmazonClientException, InterruptedException { + public Instance updateInstanceDescription() throws SdkException, InterruptedException { return ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); } @@ -187,32 +195,32 @@ public Instance updateInstanceDescription() throws AmazonClientException, Interr *

* Unlike {@link #describeInstance()}, this method always return the current status by calling EC2. */ - public InstanceState getState() throws AmazonClientException, InterruptedException { + public InstanceState getState() throws SdkException, InterruptedException { ec2InstanceDescription = CloudHelper.getInstanceWithRetry(getInstanceId(), getCloud()); - return InstanceState.find(ec2InstanceDescription.getState().getName()); + return InstanceState.find(ec2InstanceDescription.state().name().toString()); } /** * Number of milli-secs since the instance was started. */ - public long getUptime() throws AmazonClientException, InterruptedException { - return System.currentTimeMillis() - describeInstance().getLaunchTime().getTime(); + public long getUptime() throws SdkException, InterruptedException { + return Instant.now().until(describeInstance().launchTime(), ChronoUnit.MILLIS); } /** * Returns uptime in the human readable form. */ - public String getUptimeString() throws AmazonClientException, InterruptedException { + public String getUptimeString() throws SdkException, InterruptedException { return Util.getTimeSpanString(getUptime()); } /** - * Return the time this instance was launched in ms since the epoch. + * Return the Instant this instance was launched * - * @return Time this instance was launched, in ms since the epoch. + * @return Instant this instance was launched */ - public long getLaunchTime() throws InterruptedException { - return this.describeInstance().getLaunchTime().getTime(); + public Instant getLaunchTime() throws InterruptedException { + return this.describeInstance().launchTime(); } /** diff --git a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java index b268ed941..1c6c16c8b 100644 --- a/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java +++ b/src/main/java/hudson/plugins/ec2/EC2ComputerLauncher.java @@ -23,13 +23,13 @@ */ 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 software.amazon.awssdk.core.exception.SdkException; /** * {@link ComputerLauncher} for EC2 that wraps the real user-specified {@link ComputerLauncher}. @@ -44,7 +44,7 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) { try { EC2Computer computer = (EC2Computer) slaveComputer; launchScript(computer, listener); - } catch (AmazonClientException | IOException e) { + } catch (SdkException | IOException e) { e.printStackTrace(listener.error(e.getMessage())); if (slaveComputer.getNode() instanceof EC2AbstractSlave) { LOGGER.log( @@ -80,5 +80,5 @@ public void launch(SlaveComputer slaveComputer, TaskListener listener) { * Stage 2 of the launch. Called after the EC2 instance comes up. */ protected abstract void launchScript(EC2Computer computer, TaskListener listener) - throws AmazonClientException, IOException, InterruptedException; + throws SdkException, IOException, InterruptedException; } diff --git a/src/main/java/hudson/plugins/ec2/EC2Filter.java b/src/main/java/hudson/plugins/ec2/EC2Filter.java index fd7d707cb..c7f82229a 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Filter.java +++ b/src/main/java/hudson/plugins/ec2/EC2Filter.java @@ -23,7 +23,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.Filter; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; @@ -35,6 +34,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import org.kohsuke.stapler.DataBoundConstructor; +import software.amazon.awssdk.services.ec2.model.Filter; public class EC2Filter extends AbstractDescribableImpl { @NonNull @@ -96,7 +96,7 @@ private List getValuesList() { /* Helper method to convert EC2Filter to Filter */ @NonNull public Filter toFilter() { - return new Filter(name, getValuesList()); + return Filter.builder().name(name).values(getValuesList()).build(); } /* Helper method to convert list of EC2Filter to list of Filter */ diff --git a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java index 009ee0db9..7c0c4786d 100644 --- a/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java +++ b/src/main/java/hudson/plugins/ec2/EC2HostAddressProvider.java @@ -1,8 +1,8 @@ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.Instance; import java.util.Optional; import org.apache.commons.lang.StringUtils; +import software.amazon.awssdk.services.ec2.model.Instance; public class EC2HostAddressProvider { public static String unix(Instance instance, ConnectionStrategy strategy) { @@ -46,19 +46,19 @@ public static String windows(Instance instance, ConnectionStrategy strategy) { } private static String getPublicDnsName(Instance instance) { - return instance.getPublicDnsName(); + return instance.publicDnsName(); } private static String getPublicIpAddress(Instance instance) { - return instance.getPublicIpAddress(); + return instance.publicIpAddress(); } private static String getPrivateDnsName(Instance instance) { - return instance.getPrivateDnsName(); + return instance.privateDnsName(); } private static String getPrivateIpAddress(Instance instance) { - return instance.getPrivateIpAddress(); + return instance.privateIpAddress(); } private static Optional filterNonEmpty(String value) { diff --git a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java index c56c427d5..87e1d0e58 100644 --- a/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2OndemandSlave.java @@ -1,8 +1,5 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import hudson.Extension; import hudson.model.Computer; import hudson.model.Descriptor.FormException; @@ -20,6 +17,9 @@ import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest; /** * Agent running on EC2. @@ -447,15 +447,16 @@ public void terminate() { */ LOGGER.info("EC2 instance already terminated: " + getInstanceId()); } else { - AmazonEC2 ec2 = getCloud().connect(); - TerminateInstancesRequest request = - new TerminateInstancesRequest(Collections.singletonList(getInstanceId())); + Ec2Client ec2 = getCloud().connect(); + TerminateInstancesRequest request = TerminateInstancesRequest.builder() + .instanceIds(Collections.singletonList(getInstanceId())) + .build(); ec2.terminateInstances(request); LOGGER.info("Terminated EC2 instance (terminated): " + getInstanceId()); } Jenkins.get().removeNode(this); LOGGER.info("Removed EC2 instance from jenkins controller: " + getInstanceId()); - } catch (AmazonClientException | IOException e) { + } catch (SdkException | IOException e) { LOGGER.log(Level.WARNING, "Failed to terminate EC2 instance: " + getInstanceId(), e); } finally { synchronized (terminateScheduled) { diff --git a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java index b77a8fc06..290cb9600 100644 --- a/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java +++ b/src/main/java/hudson/plugins/ec2/EC2PrivateKey.java @@ -23,10 +23,8 @@ */ package hudson.plugins.ec2; -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.plugins.ec2.util.KeyPair; import hudson.util.Secret; import java.io.BufferedReader; import java.io.IOException; @@ -43,6 +41,9 @@ import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.KeyPairInfo; /** * RSA private key (the one that you generate with ec2-add-keypair.) @@ -114,29 +115,31 @@ public boolean isPrivateKey() throws IOException { /** * Finds the {@link KeyPairInfo} that corresponds to this key in EC2. */ - public com.amazonaws.services.ec2.model.KeyPair find(AmazonEC2 ec2) throws IOException, AmazonClientException { + public KeyPair find(Ec2Client ec2) throws IOException, SdkException { String fp = getFingerprint(); String pfp = getPublicFingerprint(); - for (KeyPairInfo kp : ec2.describeKeyPairs().getKeyPairs()) { - if (kp.getKeyFingerprint().equalsIgnoreCase(fp)) { - com.amazonaws.services.ec2.model.KeyPair keyPair = new com.amazonaws.services.ec2.model.KeyPair(); - keyPair.setKeyName(kp.getKeyName()); - keyPair.setKeyFingerprint(fp); - keyPair.setKeyMaterial(Secret.toString(privateKey)); - return keyPair; + for (KeyPairInfo kp : ec2.describeKeyPairs().keyPairs()) { + if (kp.keyFingerprint().equalsIgnoreCase(fp)) { + return new KeyPair( + KeyPairInfo.builder() + .keyName(kp.keyName()) + .keyFingerprint(fp) + .build(), + Secret.toString(privateKey)); } - if (kp.getKeyFingerprint().equalsIgnoreCase(pfp)) { - com.amazonaws.services.ec2.model.KeyPair keyPair = new com.amazonaws.services.ec2.model.KeyPair(); - keyPair.setKeyName(kp.getKeyName()); - keyPair.setKeyFingerprint(pfp); - keyPair.setKeyMaterial(Secret.toString(privateKey)); - return keyPair; + if (kp.keyFingerprint().equalsIgnoreCase(pfp)) { + return new KeyPair( + KeyPairInfo.builder() + .keyName(kp.keyName()) + .keyFingerprint(pfp) + .build(), + Secret.toString(privateKey)); } } return null; } - public String decryptWindowsPassword(String encodedPassword) throws AmazonClientException { + public String decryptWindowsPassword(String encodedPassword) throws SdkException { try { Cipher cipher = Cipher.getInstance("RSA/NONE/PKCS1Padding"); cipher.init( @@ -146,7 +149,7 @@ public String decryptWindowsPassword(String encodedPassword) throws AmazonClient byte[] plainText = cipher.doFinal(cipherText); return new String(plainText, StandardCharsets.US_ASCII); } catch (Exception e) { - throw new AmazonClientException("Unable to decode password:\n" + e.toString()); + throw SdkException.create("Unable to decode password", e); } } diff --git a/src/main/java/hudson/plugins/ec2/EC2Readiness.java b/src/main/java/hudson/plugins/ec2/EC2Readiness.java index 86892723e..c65dd7afe 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Readiness.java +++ b/src/main/java/hudson/plugins/ec2/EC2Readiness.java @@ -1,9 +1,9 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; +import software.amazon.awssdk.core.exception.SdkException; public interface EC2Readiness { boolean isReady(); - String getEc2ReadinessStatus() throws AmazonClientException; + String getEc2ReadinessStatus() throws SdkException; } diff --git a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java index 0ec4b4a80..6ccd15d01 100644 --- a/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java +++ b/src/main/java/hudson/plugins/ec2/EC2RetentionStrategy.java @@ -23,7 +23,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; import hudson.init.InitMilestone; import hudson.model.Descriptor; import hudson.model.Executor; @@ -33,6 +32,7 @@ import hudson.plugins.ec2.util.MinimumInstanceChecker; import hudson.slaves.RetentionStrategy; import java.time.Clock; +import java.time.Instant; import java.util.Objects; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; @@ -40,6 +40,7 @@ import java.util.logging.Logger; import jenkins.model.Jenkins; import org.kohsuke.stapler.DataBoundConstructor; +import software.amazon.awssdk.core.exception.SdkException; /** * {@link RetentionStrategy} for EC2. @@ -140,15 +141,15 @@ private long internalCheck(EC2Computer computer) { if (computer.isIdle() && !DISABLED) { final long uptime; - final long launchedAtMs; + final Instant launchedAt; InstanceState state; try { 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) { + launchedAt = computer.getLaunchTime(); + } catch (SdkException | InterruptedException e) { // We'll just retry next time we test for idleness. LOGGER.fine("Exception while checking host uptime for " + computer.getName() + ", will retry next check. Exception: " + e); @@ -202,8 +203,8 @@ private long internalCheck(EC2Computer computer) { } } - final long idleMilliseconds = - this.clock.millis() - Math.max(computer.getIdleStartMilliseconds(), launchedAtMs); + final long idleMilliseconds = this.clock.millis() + - Math.max(computer.getIdleStartMilliseconds(), launchedAt.getEpochSecond() * 1000); if (idleTerminationMinutes > 0) { // TODO: really think about the right strategy here, see @@ -290,7 +291,7 @@ public void start(EC2Computer c) { InstanceState state = null; try { state = c.getState(); - } catch (AmazonClientException | InterruptedException e) { + } catch (SdkException | InterruptedException e) { LOGGER.log(Level.FINE, "Error getting EC2 instance state for " + c.getName(), e); } if (!(InstanceState.PENDING.equals(state) || InstanceState.RUNNING.equals(state))) { diff --git a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java index 192d3b64c..93d1aa022 100644 --- a/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java +++ b/src/main/java/hudson/plugins/ec2/EC2SlaveMonitor.java @@ -1,7 +1,5 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.AmazonEC2Exception; import hudson.Extension; import hudson.model.AsyncPeriodicWork; import hudson.model.Node; @@ -12,6 +10,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; /** * @author Bruno Meneguello @@ -48,10 +48,10 @@ private void removeDeadNodes() { LOGGER.info("EC2 instance is dead: " + ec2Slave.getInstanceId()); ec2Slave.terminate(); } - } catch (AmazonClientException e) { - if (e instanceof AmazonEC2Exception + } catch (SdkException e) { + if (e instanceof Ec2Exception && EC2Cloud.EC2_REQUEST_EXPIRED_ERROR_CODE.equals( - ((AmazonEC2Exception) e).getErrorCode())) { + ((Ec2Exception) e).awsErrorDetails().errorCode())) { LOGGER.info("EC2 request expired, skipping consideration of " + ec2Slave.getInstanceId() + " due to unknown state."); } else { diff --git a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java index 66d8db405..2e7270064 100644 --- a/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java +++ b/src/main/java/hudson/plugins/ec2/EC2SpotSlave.java @@ -1,13 +1,5 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult; -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; @@ -23,6 +15,14 @@ import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.DataBoundConstructor; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.CancelSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsResponse; +import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest; +import software.amazon.awssdk.services.ec2.model.SpotInstanceState; +import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest; public class EC2SpotSlave extends EC2AbstractSlave implements EC2Readiness { private static final Logger LOGGER = Logger.getLogger(EC2SpotSlave.class.getName()); @@ -185,16 +185,18 @@ public void terminate() { Computer.threadPoolForRemoting.submit(() -> { try { // Cancel the spot request - AmazonEC2 ec2 = getCloud().connect(); + Ec2Client ec2 = getCloud().connect(); String instanceId = getInstanceId(); List requestIds = Collections.singletonList(spotInstanceRequestId); CancelSpotInstanceRequestsRequest cancelRequest = - new CancelSpotInstanceRequestsRequest(requestIds); + CancelSpotInstanceRequestsRequest.builder() + .spotInstanceRequestIds(requestIds) + .build(); try { ec2.cancelSpotInstanceRequests(cancelRequest); LOGGER.info("Cancelled Spot request: " + spotInstanceRequestId); - } catch (AmazonClientException e) { + } catch (SdkException e) { // Spot request is no longer valid LOGGER.log(Level.WARNING, "Failed to cancel Spot request: " + spotInstanceRequestId, e); } @@ -207,12 +209,13 @@ public void terminate() { */ LOGGER.info("EC2 instance already terminated: " + instanceId); } else { - TerminateInstancesRequest request = - new TerminateInstancesRequest(Collections.singletonList(instanceId)); + TerminateInstancesRequest request = TerminateInstancesRequest.builder() + .instanceIds(Collections.singletonList(instanceId)) + .build(); try { ec2.terminateInstances(request); LOGGER.info("Terminated EC2 instance (terminated): " + instanceId); - } catch (AmazonClientException e) { + } catch (SdkException e) { // Spot request is no longer valid LOGGER.log( Level.WARNING, @@ -251,20 +254,21 @@ public void terminate() { */ @CheckForNull SpotInstanceRequest getSpotRequest() { - AmazonEC2 ec2 = getCloud().connect(); + Ec2Client ec2 = getCloud().connect(); if (this.spotInstanceRequestId == null) { return null; } - DescribeSpotInstanceRequestsRequest dsirRequest = - new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(this.spotInstanceRequestId); + DescribeSpotInstanceRequestsRequest dsirRequest = DescribeSpotInstanceRequestsRequest.builder() + .spotInstanceRequestIds(this.spotInstanceRequestId) + .build(); try { - DescribeSpotInstanceRequestsResult dsirResult = ec2.describeSpotInstanceRequests(dsirRequest); - List siRequests = dsirResult.getSpotInstanceRequests(); + DescribeSpotInstanceRequestsResponse dsirResult = ec2.describeSpotInstanceRequests(dsirRequest); + List siRequests = dsirResult.spotInstanceRequests(); return siRequests.get(0); - } catch (AmazonClientException e) { + } catch (SdkException e) { // Spot request is no longer valid LOGGER.log( Level.WARNING, @@ -280,10 +284,11 @@ public boolean isSpotRequestDead() { return true; } - SpotInstanceState requestState = SpotInstanceState.fromValue(spotRequest.getState()); - return requestState == SpotInstanceState.Cancelled - || requestState == SpotInstanceState.Closed - || requestState == SpotInstanceState.Failed; + SpotInstanceState requestState = + SpotInstanceState.fromValue(spotRequest.state().toString()); + return requestState == SpotInstanceState.CANCELLED + || requestState == SpotInstanceState.CLOSED + || requestState == SpotInstanceState.FAILED; } /** @@ -298,7 +303,7 @@ public String getInstanceId() { if (StringUtils.isEmpty(instanceId)) { SpotInstanceRequest sr = getSpotRequest(); if (sr != null) { - instanceId = sr.getInstanceId(); + instanceId = sr.instanceId(); } } return instanceId; @@ -324,7 +329,7 @@ public String getDisplayName() { public String getEc2Type() { SpotInstanceRequest spotRequest = getSpotRequest(); if (spotRequest != null) { - String spotMaxBidPrice = spotRequest.getSpotPrice(); + String spotMaxBidPrice = spotRequest.spotPrice(); return Messages.EC2SpotSlave_Spot1() + spotMaxBidPrice.substring(0, spotMaxBidPrice.length() - 3) + Messages.EC2SpotSlave_Spot2(); @@ -341,8 +346,8 @@ public boolean isReady() { public String getEc2ReadinessStatus() { SpotInstanceRequest sr = getSpotRequest(); if (sr != null) { - return sr.getStatus().getMessage(); + return sr.status().message(); } - throw new AmazonClientException("No spot instance request"); + throw SdkException.builder().message("No spot instance request").build(); } } diff --git a/src/main/java/hudson/plugins/ec2/EC2Step.java b/src/main/java/hudson/plugins/ec2/EC2Step.java index 86580abe9..fe96ae88b 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Step.java +++ b/src/main/java/hudson/plugins/ec2/EC2Step.java @@ -23,7 +23,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.Instance; import hudson.Extension; import hudson.Util; import hudson.model.TaskListener; @@ -43,6 +42,7 @@ import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.verb.POST; +import software.amazon.awssdk.services.ec2.model.Instance; /** * Returns the instance provisioned. @@ -114,7 +114,7 @@ public ListBoxModel doFillTemplateItems(@QueryParameter String cloudName) { for (String labelList : template.labels.split(" ")) { r.add( labelList + " (AMI: " + template.getAmi() + ", REGION: " + ec2Cloud.getRegion() - + ", TYPE: " + template.type.name() + ")", + + ", TYPE: " + template.type + ")", labelList); } } diff --git a/src/main/java/hudson/plugins/ec2/EC2Tag.java b/src/main/java/hudson/plugins/ec2/EC2Tag.java index 49c0aed41..0d8a061e3 100644 --- a/src/main/java/hudson/plugins/ec2/EC2Tag.java +++ b/src/main/java/hudson/plugins/ec2/EC2Tag.java @@ -23,7 +23,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.Tag; import hudson.Extension; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; @@ -31,6 +30,7 @@ import java.util.List; import java.util.Objects; import org.kohsuke.stapler.DataBoundConstructor; +import software.amazon.awssdk.services.ec2.model.Tag; public class EC2Tag extends AbstractDescribableImpl { private final String name; @@ -51,8 +51,8 @@ public EC2Tag(String name, String value) { /* Constructor from Amazon Tag */ public EC2Tag(Tag t) { - name = t.getKey(); - value = t.getValue(); + name = t.key(); + value = t.value(); } public String getName() { diff --git a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java index 63b81e4e0..220d18169 100644 --- a/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java +++ b/src/main/java/hudson/plugins/ec2/InstanceTypeConverter.java @@ -23,7 +23,6 @@ */ package hudson.plugins.ec2; -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; @@ -31,6 +30,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import java.util.HashMap; import java.util.Map; +import software.amazon.awssdk.services.ec2.model.InstanceType; /* * Note this is used only to handle the metadata for older versions of the ec2-plugin. The current @@ -41,17 +41,17 @@ public class InstanceTypeConverter implements Converter { private static final Map TYPICAL_INSTANCE_TYPES = new HashMap<>(); static { - TYPICAL_INSTANCE_TYPES.put("DEFAULT", InstanceType.M1Small); - TYPICAL_INSTANCE_TYPES.put("LARGE", InstanceType.M1Large); - TYPICAL_INSTANCE_TYPES.put("XLARGE", InstanceType.M1Xlarge); - TYPICAL_INSTANCE_TYPES.put("MEDIUM_HCPU", InstanceType.C1Medium); - TYPICAL_INSTANCE_TYPES.put("XLARGE_HCPU", InstanceType.C1Xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM", InstanceType.M2Xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM_M3", InstanceType.M3Xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_DOUBLE_HMEM", InstanceType.M22xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM", InstanceType.M24xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM_M3", InstanceType.M32xlarge); - TYPICAL_INSTANCE_TYPES.put("XLARGE_CLUSTER_COMPUTE", InstanceType.Cc14xlarge); + TYPICAL_INSTANCE_TYPES.put("DEFAULT", InstanceType.M1_SMALL); + TYPICAL_INSTANCE_TYPES.put("LARGE", InstanceType.M1_LARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE", InstanceType.M1_XLARGE); + TYPICAL_INSTANCE_TYPES.put("MEDIUM_HCPU", InstanceType.C1_MEDIUM); + TYPICAL_INSTANCE_TYPES.put("XLARGE_HCPU", InstanceType.C1_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM", InstanceType.M2_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_HMEM_M3", InstanceType.M3_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_DOUBLE_HMEM", InstanceType.M2_2_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM", InstanceType.M2_4_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_QUAD_HMEM_M3", InstanceType.M3_2_XLARGE); + TYPICAL_INSTANCE_TYPES.put("XLARGE_CLUSTER_COMPUTE", InstanceType.CC1_4_XLARGE); } @Override diff --git a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java index adaa017f9..8f798a32e 100644 --- a/src/main/java/hudson/plugins/ec2/SlaveTemplate.java +++ b/src/main/java/hudson/plugins/ec2/SlaveTemplate.java @@ -18,53 +18,6 @@ */ package hudson.plugins.ec2; -import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.AmazonEC2Exception; -import com.amazonaws.services.ec2.model.BlockDeviceMapping; -import com.amazonaws.services.ec2.model.CancelSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.CreateTagsRequest; -import com.amazonaws.services.ec2.model.CreditSpecificationRequest; -import com.amazonaws.services.ec2.model.DescribeImagesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSubnetsRequest; -import com.amazonaws.services.ec2.model.DescribeSubnetsResult; -import com.amazonaws.services.ec2.model.Filter; -import com.amazonaws.services.ec2.model.HttpTokensState; -import com.amazonaws.services.ec2.model.IamInstanceProfileSpecification; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceMarketOptionsRequest; -import com.amazonaws.services.ec2.model.InstanceMetadataEndpointState; -import com.amazonaws.services.ec2.model.InstanceMetadataOptionsRequest; -import com.amazonaws.services.ec2.model.InstanceNetworkInterfaceSpecification; -import com.amazonaws.services.ec2.model.InstanceStateName; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.KeyPair; -import com.amazonaws.services.ec2.model.LaunchSpecification; -import com.amazonaws.services.ec2.model.MarketType; -import com.amazonaws.services.ec2.model.Placement; -import com.amazonaws.services.ec2.model.RequestSpotInstancesRequest; -import com.amazonaws.services.ec2.model.RequestSpotInstancesResult; -import com.amazonaws.services.ec2.model.Reservation; -import com.amazonaws.services.ec2.model.ResourceType; -import com.amazonaws.services.ec2.model.RunInstancesRequest; -import com.amazonaws.services.ec2.model.SecurityGroup; -import com.amazonaws.services.ec2.model.ShutdownBehavior; -import com.amazonaws.services.ec2.model.SpotInstanceRequest; -import com.amazonaws.services.ec2.model.SpotMarketOptions; -import com.amazonaws.services.ec2.model.SpotPlacement; -import com.amazonaws.services.ec2.model.StartInstancesRequest; -import com.amazonaws.services.ec2.model.StartInstancesResult; -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; @@ -85,6 +38,8 @@ import hudson.plugins.ec2.util.DeviceMappingParser; import hudson.plugins.ec2.util.EC2AgentConfig; import hudson.plugins.ec2.util.EC2AgentFactory; +import hudson.plugins.ec2.util.InstanceTypeCompat; +import hudson.plugins.ec2.util.KeyPair; import hudson.plugins.ec2.util.MinimumInstanceChecker; import hudson.plugins.ec2.util.MinimumNumberOfInstancesTimeRangeConfig; import hudson.security.Permission; @@ -96,7 +51,6 @@ import hudson.util.Secret; import java.io.IOException; import java.io.PrintStream; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -129,6 +83,55 @@ import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.CancelSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.CreateTagsRequest; +import software.amazon.awssdk.services.ec2.model.CreditSpecificationRequest; +import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; +import software.amazon.awssdk.services.ec2.model.DeviceType; +import software.amazon.awssdk.services.ec2.model.EbsBlockDevice; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; +import software.amazon.awssdk.services.ec2.model.Filter; +import software.amazon.awssdk.services.ec2.model.HttpTokensState; +import software.amazon.awssdk.services.ec2.model.IamInstanceProfileSpecification; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceMarketOptionsRequest; +import software.amazon.awssdk.services.ec2.model.InstanceMetadataEndpointState; +import software.amazon.awssdk.services.ec2.model.InstanceMetadataOptionsRequest; +import software.amazon.awssdk.services.ec2.model.InstanceNetworkInterfaceSpecification; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.MarketType; +import software.amazon.awssdk.services.ec2.model.Placement; +import software.amazon.awssdk.services.ec2.model.RequestSpotInstancesRequest; +import software.amazon.awssdk.services.ec2.model.RequestSpotInstancesResponse; +import software.amazon.awssdk.services.ec2.model.RequestSpotLaunchSpecification; +import software.amazon.awssdk.services.ec2.model.Reservation; +import software.amazon.awssdk.services.ec2.model.ResourceType; +import software.amazon.awssdk.services.ec2.model.RunInstancesMonitoringEnabled; +import software.amazon.awssdk.services.ec2.model.RunInstancesRequest; +import software.amazon.awssdk.services.ec2.model.SecurityGroup; +import software.amazon.awssdk.services.ec2.model.ShutdownBehavior; +import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest; +import software.amazon.awssdk.services.ec2.model.SpotMarketOptions; +import software.amazon.awssdk.services.ec2.model.SpotPlacement; +import software.amazon.awssdk.services.ec2.model.StartInstancesRequest; +import software.amazon.awssdk.services.ec2.model.StartInstancesResponse; +import software.amazon.awssdk.services.ec2.model.Subnet; +import software.amazon.awssdk.services.ec2.model.Tag; +import software.amazon.awssdk.services.ec2.model.TagSpecification; /** * Template of {@link EC2AbstractSlave} to launch. @@ -152,7 +155,7 @@ public class SlaveTemplate implements Describable { public final String remoteFS; - public final InstanceType type; + public String type; public final boolean ebsOptimized; @@ -286,7 +289,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + String type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -343,7 +346,9 @@ public SlaveTemplate( this.securityGroups = securityGroups; this.remoteFS = remoteFS; this.amiType = amiType; - this.type = type; + this.type = type != null && !type.isEmpty() + ? (new InstanceTypeCompat(type)).getInstanceType().toString() + : null; this.ebsOptimized = ebsOptimized; this.labels = Util.fixNull(labelString); this.mode = mode != null ? mode : Node.Mode.NORMAL; @@ -423,7 +428,100 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.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) { + this( + ami, + zone, + spotConfig, + securityGroups, + remoteFS, + (new InstanceTypeCompat(type)).getInstanceType().toString(), + ebsOptimized, + labelString, + mode, + description, + initScript, + tmpDir, + userData, + numExecutors, + remoteAdmin, + amiType, + javaPath, + jvmopts, + stopOnTerminate, + subnetId, + tags, + idleTerminationMinutes, + minimumNumberOfInstances, + minimumNumberOfSpareInstances, + instanceCapStr, + iamInstanceProfile, + deleteRootOnTermination, + useEphemeralDevices, + launchTimeoutStr, + associatePublicIp, + customDeviceMapping, + connectBySSHProcess, + monitoring, + t2Unlimited, + connectionStrategy, + maxTotalUses, + nodeProperties, + hostKeyVerificationStrategy, + tenancy, + ebsEncryptRootVolume, + metadataEndpointEnabled, + metadataTokensRequired, + metadataHopsLimit, + metadataSupported); + } + + @Deprecated + public SlaveTemplate( + String ami, + String zone, + SpotConfiguration spotConfig, + String securityGroups, + String remoteFS, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -468,7 +566,7 @@ public SlaveTemplate( spotConfig, securityGroups, remoteFS, - type, + (new InstanceTypeCompat(type)).getInstanceType().toString(), ebsOptimized, labelString, mode, @@ -516,7 +614,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -556,7 +654,7 @@ public SlaveTemplate( spotConfig, securityGroups, remoteFS, - type, + (new InstanceTypeCompat(type)).getInstanceType().toString(), ebsOptimized, labelString, mode, @@ -604,7 +702,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -686,7 +784,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -767,7 +865,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -847,7 +945,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -925,7 +1023,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1001,7 +1099,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1075,7 +1173,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1148,7 +1246,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1219,7 +1317,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1286,7 +1384,7 @@ public SlaveTemplate( SpotConfiguration spotConfig, String securityGroups, String remoteFS, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1353,7 +1451,7 @@ public SlaveTemplate( String securityGroups, String remoteFS, String sshPort, - InstanceType type, + com.amazonaws.services.ec2.model.InstanceType type, boolean ebsOptimized, String labelString, Node.Mode mode, @@ -1464,7 +1562,7 @@ public int getNumExecutors() { try { return Integer.parseInt(numExecutors); } catch (NumberFormatException e) { - return EC2AbstractSlave.toNumExecutors(type); + return EC2AbstractSlave.toNumExecutors(InstanceType.fromValue(type)); } } @@ -1748,7 +1846,7 @@ public enum ProvisionOptions { */ @NonNull public List provision(int number, EnumSet provisionOptions) - throws AmazonClientException, IOException { + throws SdkException, IOException { final Image image = getImage(); if (this.spotConfig != null) { if (provisionOptions.contains(ProvisionOptions.ALLOW_CREATE) @@ -1765,8 +1863,8 @@ 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())))) { + if ((node.getInstanceId().equals(instance.instanceId())) + && (!(instance.state().name().equals(InstanceStateName.STOPPED)))) { logInstanceCheck( instance, ". false - found existing corresponding Jenkins agent: " + node.getInstanceId()); return false; @@ -1777,104 +1875,120 @@ private boolean checkInstance(Instance instance) { } private void logInstanceCheck(Instance instance, String message) { - logProvisionInfo("checkInstance: " + instance.getInstanceId() + "." + message); + logProvisionInfo("checkInstance: " + instance.instanceId() + "." + message); } private boolean isSameIamInstanceProfile(Instance instance) { return StringUtils.isBlank(getIamInstanceProfile()) - || (instance.getIamInstanceProfile() != null - && instance.getIamInstanceProfile().getArn().equals(getIamInstanceProfile())); + || (instance.iamInstanceProfile() != null + && instance.iamInstanceProfile().arn().equals(getIamInstanceProfile())); } - private boolean isTerminatingOrShuttindDown(String instanceStateName) { - return instanceStateName.equalsIgnoreCase(InstanceStateName.Terminated.toString()) - || instanceStateName.equalsIgnoreCase(InstanceStateName.ShuttingDown.toString()); + private boolean isTerminatingOrShuttindDown(InstanceStateName instanceStateName) { + return instanceStateName.equals(InstanceStateName.TERMINATED) + || instanceStateName.equals(InstanceStateName.SHUTTING_DOWN); } private void logProvisionInfo(String message) { LOGGER.info(this + ". " + message); } - HashMap> makeRunInstancesRequestAndFilters(Image image, int number, AmazonEC2 ec2) + HashMap> makeRunInstancesRequestAndFilters(Image image, int number, Ec2Client ec2) throws IOException { return makeRunInstancesRequestAndFilters(image, number, ec2, true); } @Deprecated - HashMap> makeRunInstancesRequestAndFilters(int number, AmazonEC2 ec2) + HashMap> makeRunInstancesRequestAndFilters(int number, Ec2Client ec2) throws IOException { return makeRunInstancesRequestAndFilters(getImage(), number, ec2); } 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); + Image image, int number, Ec2Client ec2, boolean rotateSubnet) throws IOException { + String imageId = image.imageId(); + RunInstancesRequest.Builder riRequestBuilder = RunInstancesRequest.builder() + .imageId(image.imageId()) + .minCount(1) + .maxCount(number) + .instanceType(type) + .ebsOptimized(ebsOptimized) + .monitoring(RunInstancesMonitoringEnabled.builder() + .enabled(monitoring) + .build()); if (t2Unlimited) { - CreditSpecificationRequest creditRequest = new CreditSpecificationRequest(); - creditRequest.setCpuCredits("unlimited"); - riRequest.setCreditSpecification(creditRequest); + CreditSpecificationRequest creditRequest = + CreditSpecificationRequest.builder().cpuCredits("unlimited").build(); + riRequestBuilder.creditSpecification(creditRequest); } - setupBlockDeviceMappings(image, riRequest.getBlockDeviceMappings()); + riRequestBuilder.blockDeviceMappings(getBlockDeviceMappings(image)); if (stopOnTerminate) { - riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Stop); + riRequestBuilder.instanceInitiatedShutdownBehavior(ShutdownBehavior.STOP); logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Stop"); } else { - riRequest.setInstanceInitiatedShutdownBehavior(ShutdownBehavior.Terminate); + riRequestBuilder.instanceInitiatedShutdownBehavior(ShutdownBehavior.TERMINATE); logProvisionInfo("Setting Instance Initiated Shutdown Behavior : ShutdownBehavior.Terminate"); } List diFilters = new ArrayList<>(); - diFilters.add(new Filter("image-id").withValues(imageId)); - diFilters.add(new Filter("instance-type").withValues(type.toString())); + diFilters.add(Filter.builder().name("image-id").values(imageId).build()); + diFilters.add(Filter.builder().name("instance-type").values(type).build()); KeyPair keyPair = getKeyPair(ec2); if (keyPair == null) { logProvisionInfo("Could not retrieve a valid key pair."); return null; } - riRequest.setUserData(Base64.getEncoder().encodeToString(userData.getBytes(StandardCharsets.UTF_8))); - riRequest.setKeyName(keyPair.getKeyName()); - diFilters.add(new Filter("key-name").withValues(keyPair.getKeyName())); + riRequestBuilder.userData(Base64.getEncoder().encodeToString(userData.getBytes(StandardCharsets.UTF_8))); + riRequestBuilder.keyName(keyPair.getKeyPairInfo().keyName()); + diFilters.add(Filter.builder() + .name("key-name") + .values(keyPair.getKeyPairInfo().keyName()) + .build()); + Placement.Builder placementBuilder = Placement.builder(); if (StringUtils.isNotBlank(getZone())) { - Placement placement = new Placement(getZone()); if (getTenancyAttribute().equals(Tenancy.Dedicated)) { - placement.setTenancy("dedicated"); + placementBuilder.tenancy("dedicated"); } - riRequest.setPlacement(placement); - diFilters.add(new Filter("availability-zone").withValues(getZone())); + riRequestBuilder.placement(placementBuilder.build()); + diFilters.add( + Filter.builder().name("availability-zone").values(getZone()).build()); } if (getTenancyAttribute().equals(Tenancy.Host)) { - Placement placement = new Placement(); - placement.setTenancy("host"); - riRequest.setPlacement(placement); - diFilters.add(new Filter("tenancy").withValues(placement.getTenancy())); + placementBuilder.tenancy("host"); + Placement placement = placementBuilder.build(); + riRequestBuilder.placement(placement); + diFilters.add(Filter.builder() + .name("tenancy") + .values(placement.tenancyAsString()) + .build()); } else if (getTenancyAttribute().equals(Tenancy.Default)) { - Placement placement = new Placement(); - placement.setTenancy("default"); - riRequest.setPlacement(placement); - diFilters.add(new Filter("tenancy").withValues(placement.getTenancy())); + placementBuilder.tenancy("default"); + Placement placement = placementBuilder.build(); + riRequestBuilder.placement(placement); + diFilters.add(Filter.builder() + .name("tenancy") + .values(placement.tenancyAsString()) + .build()); } String subnetId = chooseSubnetId(rotateSubnet); LOGGER.log(Level.FINE, () -> String.format("Chose subnetId %s", subnetId)); - InstanceNetworkInterfaceSpecification net = new InstanceNetworkInterfaceSpecification(); + InstanceNetworkInterfaceSpecification.Builder netBuilder = InstanceNetworkInterfaceSpecification.builder(); if (StringUtils.isNotBlank(subnetId)) { if (getAssociatePublicIp()) { - net.setSubnetId(subnetId); + netBuilder.subnetId(subnetId); } else { - riRequest.setSubnetId(subnetId); + riRequestBuilder.subnetId(subnetId); } - diFilters.add(new Filter("subnet-id").withValues(subnetId)); + diFilters.add(Filter.builder().name("subnet-id").values(subnetId).build()); /* * If we have a subnet ID then we can only use VPC security groups @@ -1884,74 +1998,92 @@ HashMap> makeRunInstancesRequestAndFilters( if (!groupIds.isEmpty()) { if (getAssociatePublicIp()) { - net.setGroups(groupIds); + netBuilder.groups(groupIds); } else { - riRequest.setSecurityGroupIds(groupIds); + riRequestBuilder.securityGroupIds(groupIds); } - diFilters.add(new Filter("instance.group-id").withValues(groupIds)); + diFilters.add(Filter.builder() + .name("instance.group-id") + .values(groupIds) + .build()); } } } else { - List groupIds = - getSecurityGroupsBy("group-name", securityGroupSet, ec2).getSecurityGroups().stream() - .map(SecurityGroup::getGroupId) - .collect(Collectors.toList()); + List groupIds = getSecurityGroupsBy("group-name", securityGroupSet, ec2).securityGroups().stream() + .map(SecurityGroup::groupId) + .collect(Collectors.toList()); if (getAssociatePublicIp()) { - net.setGroups(groupIds); + netBuilder.groups(groupIds); } else { - riRequest.setSecurityGroups(securityGroupSet); + riRequestBuilder.securityGroups(securityGroupSet); } if (!groupIds.isEmpty()) { - diFilters.add(new Filter("instance.group-id").withValues(groupIds)); + diFilters.add(Filter.builder() + .name("instance.group-id") + .values(groupIds) + .build()); } } - net.setAssociatePublicIpAddress(getAssociatePublicIp()); - net.setDeviceIndex(0); + netBuilder.associatePublicIpAddress(getAssociatePublicIp()); + netBuilder.deviceIndex(0); if (getAssociatePublicIp()) { - riRequest.withNetworkInterfaces(net); + riRequestBuilder.networkInterfaces(netBuilder.build()); } HashSet instTags = buildTags(EC2Cloud.EC2_SLAVE_TYPE_DEMAND); for (Tag tag : instTags) { - diFilters.add(new Filter("tag:" + tag.getKey()).withValues(tag.getValue())); + diFilters.add(Filter.builder() + .name("tag:" + tag.key()) + .values(tag.value()) + .build()); } if (StringUtils.isNotBlank(getIamInstanceProfile())) { - riRequest.setIamInstanceProfile(new IamInstanceProfileSpecification().withArn(getIamInstanceProfile())); + riRequestBuilder.iamInstanceProfile(IamInstanceProfileSpecification.builder() + .arn(getIamInstanceProfile()) + .build()); } List tagList = new ArrayList<>(); - TagSpecification tagSpecification = new TagSpecification(); - tagSpecification.setTags(instTags); - tagList.add(tagSpecification.clone().withResourceType(ResourceType.Instance)); - tagList.add(tagSpecification.clone().withResourceType(ResourceType.Volume)); - tagList.add(tagSpecification.clone().withResourceType(ResourceType.NetworkInterface)); - riRequest.setTagSpecifications(tagList); + tagList.add(TagSpecification.builder() + .tags(instTags) + .resourceType(ResourceType.INSTANCE) + .build()); + tagList.add(TagSpecification.builder() + .tags(instTags) + .resourceType(ResourceType.VOLUME) + .build()); + tagList.add(TagSpecification.builder() + .tags(instTags) + .resourceType(ResourceType.NETWORK_INTERFACE) + .build()); + riRequestBuilder.tagSpecifications(tagList); if (metadataSupported) { - InstanceMetadataOptionsRequest instanceMetadataOptionsRequest = new InstanceMetadataOptionsRequest(); - instanceMetadataOptionsRequest.setHttpEndpoint( + InstanceMetadataOptionsRequest.Builder instanceMetadataOptionsRequestBuilder = + InstanceMetadataOptionsRequest.builder(); + instanceMetadataOptionsRequestBuilder.httpEndpoint( metadataEndpointEnabled - ? InstanceMetadataEndpointState.Enabled.toString() - : InstanceMetadataEndpointState.Disabled.toString()); - instanceMetadataOptionsRequest.setHttpPutResponseHopLimit( + ? InstanceMetadataEndpointState.ENABLED.toString() + : InstanceMetadataEndpointState.DISABLED.toString()); + instanceMetadataOptionsRequestBuilder.httpPutResponseHopLimit( metadataHopsLimit == null ? EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT : metadataHopsLimit); - instanceMetadataOptionsRequest.setHttpTokens( - metadataTokensRequired ? HttpTokensState.Required.toString() : HttpTokensState.Optional.toString()); - riRequest.setMetadataOptions(instanceMetadataOptionsRequest); + instanceMetadataOptionsRequestBuilder.httpTokens( + metadataTokensRequired ? HttpTokensState.REQUIRED.toString() : HttpTokensState.OPTIONAL.toString()); + riRequestBuilder.metadataOptions(instanceMetadataOptionsRequestBuilder.build()); } HashMap> ret = new HashMap<>(); - ret.put(riRequest, diFilters); + ret.put(riRequestBuilder.build(), diFilters); return ret; } @Deprecated HashMap> makeRunInstancesRequestAndFilters( - int number, AmazonEC2 ec2, boolean rotateSubnet) throws IOException { + int number, Ec2Client ec2, boolean rotateSubnet) throws IOException { return makeRunInstancesRequestAndFilters(getImage(), number, ec2, rotateSubnet); } @@ -1973,7 +2105,7 @@ private List provisionOndemand( boolean spotWithoutBidPrice, boolean fallbackSpotToOndemand) throws IOException { - AmazonEC2 ec2 = getParent().connect(); + Ec2Client ec2 = getParent().connect(); logProvisionInfo("Considering launching"); @@ -1984,11 +2116,12 @@ private List provisionOndemand( RunInstancesRequest riRequest = entry.getKey(); List diFilters = entry.getValue(); - DescribeInstancesRequest diRequest = new DescribeInstancesRequest().withFilters(diFilters); + DescribeInstancesRequest diRequest = + DescribeInstancesRequest.builder().filters(diFilters).build(); logProvisionInfo("Looking for existing instances with describe-instance: " + diRequest); - DescribeInstancesResult diResult = ec2.describeInstances(diRequest); + DescribeInstancesResponse diResult = ec2.describeInstances(diRequest); List orphansOrStopped = findOrphansOrStopped(diResult, number); if (orphansOrStopped.isEmpty() @@ -2004,32 +2137,37 @@ private List provisionOndemand( return toSlaves(orphansOrStopped); } - riRequest.setMaxCount(number - orphansOrStopped.size()); + RunInstancesRequest.Builder riRequestBuilder = riRequest.toBuilder(); + riRequestBuilder.maxCount(number - orphansOrStopped.size()); List newInstances; if (spotWithoutBidPrice) { - InstanceMarketOptionsRequest instanceMarketOptionsRequest = - new InstanceMarketOptionsRequest().withMarketType(MarketType.Spot); + InstanceMarketOptionsRequest.Builder instanceMarketOptionsRequestBuilder = + InstanceMarketOptionsRequest.builder().marketType(MarketType.SPOT); if (getSpotBlockReservationDuration() != 0) { - SpotMarketOptions spotOptions = - new SpotMarketOptions().withBlockDurationMinutes(getSpotBlockReservationDuration() * 60); - instanceMarketOptionsRequest.setSpotOptions(spotOptions); + SpotMarketOptions spotOptions = SpotMarketOptions.builder() + .blockDurationMinutes(getSpotBlockReservationDuration() * 60) + .build(); + instanceMarketOptionsRequestBuilder.spotOptions(spotOptions); } - riRequest.setInstanceMarketOptions(instanceMarketOptionsRequest); + riRequestBuilder.instanceMarketOptions(instanceMarketOptionsRequestBuilder.build()); try { - newInstances = ec2.runInstances(riRequest).getReservation().getInstances(); - } catch (AmazonEC2Exception e) { - if (fallbackSpotToOndemand && e.getErrorCode().equals("InsufficientInstanceCapacity")) { + newInstances = new ArrayList<>( + ec2.runInstances(riRequestBuilder.build()).instances()); + } catch (Ec2Exception e) { + if (fallbackSpotToOndemand && e.awsErrorDetails().errorCode().equals("InsufficientInstanceCapacity")) { 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(); + riRequestBuilder.instanceMarketOptions(instanceMarketOptionsRequestBuilder.build()); + newInstances = new ArrayList<>( + ec2.runInstances(riRequestBuilder.build()).instances()); } else { throw e; } } } else { - newInstances = ec2.runInstances(riRequest).getReservation().getInstances(); + newInstances = + new ArrayList<>(ec2.runInstances(riRequestBuilder.build()).instances()); } // Have to create a new instance @@ -2042,23 +2180,24 @@ private List provisionOndemand( return toSlaves(newInstances); } - void wakeOrphansOrStoppedUp(AmazonEC2 ec2, List orphansOrStopped) { + void wakeOrphansOrStoppedUp(Ec2Client ec2, List orphansOrStopped) { List instances = new ArrayList<>(); for (Instance instance : orphansOrStopped) { - if (instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopping.toString()) - || instance.getState().getName().equalsIgnoreCase(InstanceStateName.Stopped.toString())) { + if (instance.state().name().equals(InstanceStateName.STOPPING) + || instance.state().name().equals(InstanceStateName.STOPPED)) { logProvisionInfo("Found stopped instances - will start it: " + instance); - instances.add(instance.getInstanceId()); + instances.add(instance.instanceId()); } 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.state().name() + " instance: " + instance); } } if (!instances.isEmpty()) { - StartInstancesRequest siRequest = new StartInstancesRequest(instances); - StartInstancesResult siResult = ec2.startInstances(siRequest); + StartInstancesRequest siRequest = + StartInstancesRequest.builder().instanceIds(instances).build(); + StartInstancesResponse siResult = ec2.startInstances(siRequest); logProvisionInfo("Result of starting stopped instances:" + siResult); } } @@ -2077,19 +2216,19 @@ List toSlaves(List newInstances) throws IOException } } - List findOrphansOrStopped(DescribeInstancesResult diResult, int number) { + List findOrphansOrStopped(DescribeInstancesResponse diResult, int number) { List orphansOrStopped = new ArrayList<>(); int count = 0; - for (Reservation reservation : diResult.getReservations()) { - for (Instance instance : reservation.getInstances()) { + for (Reservation reservation : diResult.reservations()) { + for (Instance instance : reservation.instances()) { if (!isSameIamInstanceProfile(instance)) { logInstanceCheck( instance, - ". false - IAM Instance profile does not match: " + instance.getIamInstanceProfile()); + ". false - IAM Instance profile does not match: " + instance.iamInstanceProfile()); continue; } - if (isTerminatingOrShuttindDown(instance.getState().getName())) { + if (isTerminatingOrShuttindDown(instance.state().name())) { logInstanceCheck(instance, ". false - Instance is terminated or shutting down"); continue; } @@ -2109,53 +2248,55 @@ List findOrphansOrStopped(DescribeInstancesResult diResult, int number } private void setupRootDevice(Image image, List deviceMappings) { - if (!"ebs".equals(image.getRootDeviceType())) { + if (!DeviceType.EBS.equals(image.rootDeviceType())) { return; } // get the root device (only one expected in the blockmappings) - final List rootDeviceMappings = image.getBlockDeviceMappings(); - if (rootDeviceMappings.isEmpty()) { + if (deviceMappings.isEmpty()) { LOGGER.warning("AMI missing block devices"); return; } - BlockDeviceMapping rootMapping = rootDeviceMappings.get(0); - LOGGER.info("AMI had " + rootMapping.getDeviceName()); - LOGGER.info(rootMapping.getEbs().toString()); + BlockDeviceMapping rootMapping = deviceMappings.get(0); + LOGGER.info("AMI had " + rootMapping.deviceName()); + LOGGER.info(rootMapping.ebs().toString()); - // Create a shadow of the AMI mapping (doesn't like reusing rootMapping directly) - BlockDeviceMapping newMapping = rootMapping.clone(); + // Create a new AMI mapping as a copy of the existing one + BlockDeviceMapping.Builder newRootMappingBuilder = rootMapping.toBuilder(); + EbsBlockDevice.Builder newRootDeviceBuilder = rootMapping.ebs().toBuilder(); if (deleteRootOnTermination) { + newRootDeviceBuilder.deleteOnTermination(Boolean.TRUE); // Check if the root device is already in the mapping and update it - for (final BlockDeviceMapping mapping : deviceMappings) { - LOGGER.info("Request had " + mapping.getDeviceName()); - if (rootMapping.getDeviceName().equals(mapping.getDeviceName())) { - mapping.getEbs().setDeleteOnTermination(Boolean.TRUE); - return; + for (final BlockDeviceMapping mapping : image.blockDeviceMappings()) { + LOGGER.info("Request had " + mapping.deviceName()); + if (rootMapping.deviceName().equals(mapping.deviceName())) { + // Existing mapping found, replace with the copy + newRootMappingBuilder.ebs(newRootDeviceBuilder.build()); + deviceMappings.remove(0); + deviceMappings.add(0, newRootMappingBuilder.build()); } } - - // pass deleteRootOnTermination to shadow of the AMI mapping - newMapping.getEbs().setDeleteOnTermination(Boolean.TRUE); } - newMapping.getEbs().setEncrypted(ebsEncryptRootVolume.getValue()); + // New existing mapping found, add a new one as the root + newRootDeviceBuilder.encrypted(ebsEncryptRootVolume.getValue()); String message = String.format( "EBS default encryption value set to: %s (%s)", ebsEncryptRootVolume.getDisplayText(), ebsEncryptRootVolume.getValue()); logProvisionInfo(message); - deviceMappings.add(0, newMapping); + newRootMappingBuilder.ebs(newRootDeviceBuilder.build()); + deviceMappings.add(0, newRootMappingBuilder.build()); } private List getNewEphemeralDeviceMapping(Image image) { - final List oldDeviceMapping = image.getBlockDeviceMappings(); + final List oldDeviceMapping = image.blockDeviceMappings(); final Set occupiedDevices = new HashSet<>(); for (final BlockDeviceMapping mapping : oldDeviceMapping) { - occupiedDevices.add(mapping.getDeviceName()); + occupiedDevices.add(mapping.deviceName()); } final List available = @@ -2170,8 +2311,10 @@ private List getNewEphemeralDeviceMapping(Image image) { continue; } - final BlockDeviceMapping newMapping = - new BlockDeviceMapping().withDeviceName(deviceName).withVirtualName(available.get(0)); + final BlockDeviceMapping newMapping = BlockDeviceMapping.builder() + .deviceName(deviceName) + .virtualName(available.get(0)) + .build(); newDeviceMapping.add(newMapping); available.remove(0); @@ -2191,7 +2334,7 @@ private static List makeImageAttributeList(@CheckForNull String attr) { } @NonNull - private DescribeImagesRequest makeDescribeImagesRequest() throws AmazonClientException { + private DescribeImagesRequest makeDescribeImagesRequest() throws SdkException { List imageIds = Util.fixEmptyAndTrim(ami) == null ? Collections.emptyList() : Collections.singletonList(ami); List owners = makeImageAttributeList(amiOwners); @@ -2204,28 +2347,34 @@ private DescribeImagesRequest makeDescribeImagesRequest() throws AmazonClientExc int numAttrs = Stream.of(imageIds, owners, users, filters).mapToInt(List::size).sum(); if (numAttrs == 0) { - throw new AmazonClientException("Neither AMI ID nor AMI search attributes provided"); + throw SdkException.builder() + .message("Neither AMI ID nor AMI search attributes provided") + .build(); } - return new DescribeImagesRequest() - .withImageIds(imageIds) - .withOwners(owners) - .withExecutableUsers(users) - .withFilters(filters); + return DescribeImagesRequest.builder() + .imageIds(imageIds) + .owners(owners) + .executableUsers(users) + .filters(filters) + .build(); } @NonNull - private Image getImage() throws AmazonClientException { + private Image getImage() throws SdkException { DescribeImagesRequest request = makeDescribeImagesRequest(); LOGGER.info("Getting image for request " + request); - List images = getParent().connect().describeImages(request).getImages(); + List images = + new ArrayList<>(getParent().connect().describeImages(request).images()); if (images.isEmpty()) { - throw new AmazonClientException("Unable to find image for request " + request); + throw SdkException.builder() + .message("Unable to find image for request " + request) + .build(); } // Sort in reverse by creation date to get latest image - images.sort(Comparator.comparing(Image::getCreationDate).reversed()); + images.sort(Comparator.comparing(Image::creationDate).reversed()); return images.get(0); } @@ -2244,41 +2393,46 @@ private List provisionSpot(Image image, int number, EnumSet String.format("Chose subnetId %s", subnetId)); if (StringUtils.isNotBlank(subnetId)) { - net.setSubnetId(subnetId); + netBuilder.subnetId(subnetId); /* * If we have a subnet ID then we can only use VPC security groups @@ -2286,50 +2440,52 @@ private List provisionSpot(Image image, int number, EnumSet groupIds = getEc2SecurityGroups(ec2); if (!groupIds.isEmpty()) { - net.setGroups(groupIds); + netBuilder.groups(groupIds); } } } else { if (!securityGroupSet.isEmpty()) { List groupIds = - getSecurityGroupsBy("group-name", securityGroupSet, ec2).getSecurityGroups().stream() - .map(SecurityGroup::getGroupId) + getSecurityGroupsBy("group-name", securityGroupSet, ec2).securityGroups().stream() + .map(SecurityGroup::groupId) .collect(Collectors.toList()); - net.setGroups(groupIds); + netBuilder.groups(groupIds); } } String userDataString = Base64.getEncoder().encodeToString(userData.getBytes(StandardCharsets.UTF_8)); - launchSpecification.setUserData(userDataString); - launchSpecification.setKeyName(keyPair.getKeyName()); - launchSpecification.setInstanceType(type.toString()); + launchSpecificationBuilder.userData(userDataString); + launchSpecificationBuilder.keyName(keyPair.getKeyPairInfo().keyName()); + launchSpecificationBuilder.instanceType(type); - net.setAssociatePublicIpAddress(getAssociatePublicIp()); - net.setDeviceIndex(0); - launchSpecification.withNetworkInterfaces(net); + netBuilder.associatePublicIpAddress(getAssociatePublicIp()); + netBuilder.deviceIndex(0); + launchSpecificationBuilder.networkInterfaces(netBuilder.build()); HashSet instTags = buildTags(EC2Cloud.EC2_SLAVE_TYPE_SPOT); if (StringUtils.isNotBlank(getIamInstanceProfile())) { - launchSpecification.setIamInstanceProfile( - new IamInstanceProfileSpecification().withArn(getIamInstanceProfile())); + launchSpecificationBuilder.iamInstanceProfile(IamInstanceProfileSpecification.builder() + .arn(getIamInstanceProfile()) + .build()); } - setupBlockDeviceMappings(image, launchSpecification.getBlockDeviceMappings()); + launchSpecificationBuilder.blockDeviceMappings(getBlockDeviceMappings(image)); - spotRequest.setLaunchSpecification(launchSpecification); + spotRequestBuilder.launchSpecification(launchSpecificationBuilder.build()); if (getSpotBlockReservationDuration() != 0) { - spotRequest.setBlockDurationMinutes(getSpotBlockReservationDuration() * 60); + spotRequestBuilder.blockDurationMinutes(getSpotBlockReservationDuration() * 60); } - RequestSpotInstancesResult reqResult; + RequestSpotInstancesResponse reqResult; try { // Make the request for a new Spot instance - reqResult = ec2.requestSpotInstances(spotRequest); - } catch (AmazonEC2Exception e) { - if (spotConfig.getFallbackToOndemand() && e.getErrorCode().equals("MaxSpotInstanceCountExceeded")) { + reqResult = ec2.requestSpotInstances(spotRequestBuilder.build()); + } catch (Ec2Exception e) { + if (spotConfig.getFallbackToOndemand() + && e.awsErrorDetails().errorCode().equals("MaxSpotInstanceCountExceeded")) { logProvisionInfo( "There is no spot capacity available matching your request, falling back to on-demand instance."); return provisionOndemand(image, number, provisionOptions); @@ -2338,40 +2494,45 @@ private List provisionSpot(Image image, int number, EnumSet reqInstances = reqResult.getSpotInstanceRequests(); + List reqInstances = reqResult.spotInstanceRequests(); if (reqInstances.isEmpty()) { - throw new AmazonClientException("No spot instances found"); + throw SdkException.builder().message("No spot instances found").build(); } List slaves = new ArrayList<>(reqInstances.size()); for (SpotInstanceRequest spotInstReq : reqInstances) { if (spotInstReq == null) { - throw new AmazonClientException("Spot instance request is null"); + throw SdkException.builder() + .message("Spot instance request is null") + .build(); } - String slaveName = spotInstReq.getSpotInstanceRequestId(); + String slaveName = spotInstReq.spotInstanceRequestId(); if (spotConfig.getFallbackToOndemand()) { - for (int i = 0; i < 2 && spotInstReq.getStatus().getCode().equals("pending-evaluation"); i++) { + for (int i = 0; i < 2 && spotInstReq.status().code().equals("pending-evaluation"); i++) { LOGGER.info("Spot request " + slaveName + " is still pending evaluation"); Thread.sleep(5000); LOGGER.info("Fetching info about spot request " + slaveName); DescribeSpotInstanceRequestsRequest describeRequest = - new DescribeSpotInstanceRequestsRequest().withSpotInstanceRequestIds(slaveName); + DescribeSpotInstanceRequestsRequest.builder() + .spotInstanceRequestIds(slaveName) + .build(); spotInstReq = ec2.describeSpotInstanceRequests(describeRequest) - .getSpotInstanceRequests() + .spotInstanceRequests() .get(0); } List spotRequestBadCodes = Arrays.asList("capacity-not-available", "capacity-oversubscribed", "price-too-low"); - if (spotRequestBadCodes.contains(spotInstReq.getStatus().getCode())) { + if (spotRequestBadCodes.contains(spotInstReq.status().code())) { LOGGER.info( "There is no spot capacity available matching your request, falling back to on-demand instance."); List requestsToCancel = reqInstances.stream() - .map(SpotInstanceRequest::getSpotInstanceRequestId) + .map(SpotInstanceRequest::spotInstanceRequestId) .collect(Collectors.toList()); - CancelSpotInstanceRequestsRequest cancelRequest = - new CancelSpotInstanceRequestsRequest(requestsToCancel); + CancelSpotInstanceRequestsRequest cancelRequest = CancelSpotInstanceRequestsRequest.builder() + .spotInstanceRequestIds(requestsToCancel) + .build(); ec2.cancelSpotInstanceRequests(cancelRequest); return provisionOndemand(image, number, provisionOptions); } @@ -2379,14 +2540,15 @@ private List provisionSpot(Image image, int number, EnumSet provisionSpot(Image image, int number, EnumSet blockDeviceMappings) { - setupRootDevice(image, blockDeviceMappings); + private List getBlockDeviceMappings(Image image) { + List newMappings = new ArrayList<>(image.blockDeviceMappings()); + + setupRootDevice(image, newMappings); + if (useEphemeralDevices) { - setupEphemeralDeviceMapping(image, blockDeviceMappings); + newMappings.addAll(getNewEphemeralDeviceMapping(image)); } else { - setupCustomDeviceMapping(blockDeviceMappings); + if (StringUtils.isNotBlank(customDeviceMapping)) { + newMappings.addAll(DeviceMappingParser.parse(customDeviceMapping)); + } } + return newMappings; } private HashSet buildTags(String slaveType) { @@ -2415,7 +2583,7 @@ private HashSet buildTags(String slaveType) { HashSet instTags = new HashSet<>(); if (tags != null && !tags.isEmpty()) { for (EC2Tag t : tags) { - instTags.add(new Tag(t.getName(), t.getValue())); + instTags.add(Tag.builder().key(t.getName()).value(t.getValue()).build()); if (StringUtils.equals(t.getName(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)) { hasCustomTypeTag = true; } @@ -2425,20 +2593,25 @@ private HashSet buildTags(String slaveType) { } } if (!hasCustomTypeTag) { - instTags.add( - new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, EC2Cloud.getSlaveTypeTagValue(slaveType, description))); + instTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .value(EC2Cloud.getSlaveTypeTagValue(slaveType, description)) + .build()); } JenkinsLocationConfiguration jenkinsLocation = JenkinsLocationConfiguration.get(); if (!hasJenkinsServerUrlTag && jenkinsLocation.getUrl() != null) { - instTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SERVER_URL, jenkinsLocation.getUrl())); + instTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SERVER_URL) + .value(jenkinsLocation.getUrl()) + .build()); } return instTags; } protected EC2OndemandSlave newOndemandSlave(Instance inst) throws FormException, IOException { EC2AgentConfig.OnDemand config = new EC2AgentConfig.OnDemandBuilder() - .withName(getSlaveName(inst.getInstanceId())) - .withInstanceId(inst.getInstanceId()) + .withName(getSlaveName(inst.instanceId())) + .withInstanceId(inst.instanceId()) .withDescription(description) .withRemoteFS(remoteFS) .withNumExecutors(getNumExecutors()) @@ -2452,9 +2625,9 @@ protected EC2OndemandSlave newOndemandSlave(Instance inst) throws FormException, .withJvmopts(jvmopts) .withStopOnTerminate(stopOnTerminate) .withIdleTerminationMinutes(idleTerminationMinutes) - .withPublicDNS(inst.getPublicDnsName()) - .withPrivateDNS(inst.getPrivateDnsName()) - .withTags(EC2Tag.fromAmazonTags(inst.getTags())) + .withPublicDNS(inst.publicDnsName()) + .withPrivateDNS(inst.privateDnsName()) + .withTags(EC2Tag.fromAmazonTags(inst.tags())) .withCloudName(parent.name) .withLaunchTimeout(getLaunchTimeout()) .withAmiType(amiType) @@ -2471,8 +2644,8 @@ protected EC2OndemandSlave newOndemandSlave(Instance inst) throws FormException, protected EC2SpotSlave newSpotSlave(SpotInstanceRequest sir) throws FormException, IOException { EC2AgentConfig.Spot config = new EC2AgentConfig.SpotBuilder() - .withName(getSlaveName(sir.getSpotInstanceRequestId())) - .withSpotInstanceRequestId(sir.getSpotInstanceRequestId()) + .withName(getSlaveName(sir.spotInstanceRequestId())) + .withSpotInstanceRequestId(sir.spotInstanceRequestId()) .withDescription(description) .withRemoteFS(remoteFS) .withNumExecutors(getNumExecutors()) @@ -2485,7 +2658,7 @@ protected EC2SpotSlave newSpotSlave(SpotInstanceRequest sir) throws FormExceptio .withJavaPath(javaPath) .withJvmopts(jvmopts) .withIdleTerminationMinutes(idleTerminationMinutes) - .withTags(EC2Tag.fromAmazonTags(sir.getTags())) + .withTags(EC2Tag.fromAmazonTags(sir.tags())) .withCloudName(parent.name) .withLaunchTimeout(getLaunchTimeout()) .withAmiType(amiType) @@ -2499,15 +2672,18 @@ protected EC2SpotSlave newSpotSlave(SpotInstanceRequest sir) throws FormExceptio * Get a KeyPair from the configured information for the agent template */ @CheckForNull - private KeyPair getKeyPair(AmazonEC2 ec2) throws IOException, AmazonClientException { + private KeyPair getKeyPair(Ec2Client ec2) throws IOException, SdkException { EC2PrivateKey ec2PrivateKey = getParent().resolvePrivateKey(); if (ec2PrivateKey == null) { - throw new AmazonClientException( - "No keypair credential found. Please configure a credential in the Jenkins configuration."); + throw SdkException.builder() + .message("No keypair credential found. Please configure a credential in the Jenkins configuration.") + .build(); } KeyPair keyPair = ec2PrivateKey.find(ec2); if (keyPair == null) { - throw new AmazonClientException("No matching keypair found on EC2. Is the EC2 private key a valid one?"); + throw SdkException.builder() + .message("No matching keypair found on EC2. Is the EC2 private key a valid one?") + .build(); } LOGGER.fine("found matching keypair"); return keyPair; @@ -2523,20 +2699,21 @@ private KeyPair getKeyPair(AmazonEC2 ec2) throws IOException, AmazonClientExcept * @param params * @throws InterruptedException */ - private void updateRemoteTags(AmazonEC2 ec2, Collection instTags, String catchErrorCode, String... params) + private void updateRemoteTags(Ec2Client ec2, Collection instTags, String catchErrorCode, String... params) throws InterruptedException { for (int i = 0; i < 5; i++) { try { - CreateTagsRequest tagRequest = new CreateTagsRequest(); - tagRequest.withResources(params).setTags(instTags); - ec2.createTags(tagRequest); + ec2.createTags(CreateTagsRequest.builder() + .resources(params) + .tags(instTags) + .build()); break; - } catch (AmazonServiceException e) { - if (e.getErrorCode().equals(catchErrorCode)) { + } catch (AwsServiceException e) { + if (e.awsErrorDetails().errorCode().equals(catchErrorCode)) { Thread.sleep(5000); continue; } - LOGGER.log(Level.SEVERE, e.getErrorMessage(), e); + LOGGER.log(Level.SEVERE, e.awsErrorDetails().errorMessage(), e); } } } @@ -2544,72 +2721,79 @@ 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 { + private List getEc2SecurityGroups(Ec2Client ec2) throws SdkException { 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().isEmpty()) { + DescribeSecurityGroupsResponse groupResult = getSecurityGroupsBy("group-name", securityGroupSet, ec2); + if (groupResult.securityGroups().isEmpty()) { groupResult = getSecurityGroupsBy("group-id", securityGroupSet, ec2); } - for (SecurityGroup group : groupResult.getSecurityGroups()) { + for (SecurityGroup group : groupResult.securityGroups()) { 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()) { + group.groupId(), group.vpcId(), getCurrentSubnetId())); + if (group.vpcId() != null && !group.vpcId().isEmpty()) { List filters = new ArrayList<>(); - filters.add(new Filter("vpc-id").withValues(group.getVpcId())); - filters.add(new Filter("state").withValues("available")); - filters.add(new Filter("subnet-id").withValues(getCurrentSubnetId())); - - DescribeSubnetsRequest subnetReq = new DescribeSubnetsRequest(); - subnetReq.withFilters(filters); - DescribeSubnetsResult subnetResult = ec2.describeSubnets(subnetReq); - - List subnets = subnetResult.getSubnets(); + filters.add( + Filter.builder().name("vpc-id").values(group.vpcId()).build()); + filters.add(Filter.builder().name("state").values("available").build()); + filters.add(Filter.builder() + .name("subnet-id") + .values(getCurrentSubnetId()) + .build()); + + DescribeSubnetsResponse subnetResult = ec2.describeSubnets( + DescribeSubnetsRequest.builder().filters(filters).build()); + + List subnets = subnetResult.subnets(); if (subnets != null && !subnets.isEmpty()) { LOGGER.log(Level.FINE, () -> "Adding security group"); - groupIds.add(group.getGroupId()); + groupIds.add(group.groupId()); } } } if (securityGroupSet.size() != groupIds.size()) { - throw new AmazonClientException("Security groups must all be VPC security groups to work in a VPC context"); + throw SdkException.builder() + .message("Security groups must all be VPC security groups to work in a VPC context") + .build(); } return groupIds; } - private DescribeSecurityGroupsResult getSecurityGroupsBy( - String filterName, Set filterValues, AmazonEC2 ec2) { - DescribeSecurityGroupsRequest groupReq = new DescribeSecurityGroupsRequest(); - groupReq.withFilters(new Filter(filterName).withValues(filterValues)); + private DescribeSecurityGroupsResponse getSecurityGroupsBy( + String filterName, Set filterValues, Ec2Client ec2) { + DescribeSecurityGroupsRequest groupReq = DescribeSecurityGroupsRequest.builder() + .filters(Filter.builder().name(filterName).values(filterValues).build()) + .build(); return ec2.describeSecurityGroups(groupReq); } /** * Provisions a new EC2 agent based on the currently running instance on EC2, instead of starting a new one. */ - public EC2AbstractSlave attach(String instanceId, TaskListener listener) throws AmazonClientException, IOException { + public EC2AbstractSlave attach(String instanceId, TaskListener listener) throws SdkException, IOException { PrintStream logger = listener.getLogger(); - AmazonEC2 ec2 = getParent().connect(); + Ec2Client ec2 = getParent().connect(); try { logger.println("Attaching to " + instanceId); LOGGER.info("Attaching to " + instanceId); - DescribeInstancesRequest request = new DescribeInstancesRequest(); - request.setInstanceIds(Collections.singletonList(instanceId)); + DescribeInstancesRequest request = DescribeInstancesRequest.builder() + .instanceIds(Collections.singletonList(instanceId)) + .build(); Instance inst = ec2.describeInstances(request) - .getReservations() + .reservations() .get(0) - .getInstances() + .instances() .get(0); return newOndemandSlave(inst); } catch (FormException e) { @@ -2645,6 +2829,10 @@ protected Object readResolve() { amiType = new UnixData(rootCommandPrefix, slaveCommandPrefix, slaveCommandSuffix, sshPort, null); } + if (type != null && !type.isEmpty()) { + type = (new InstanceTypeCompat(type)).getInstanceType().toString(); + } + // 1.43 new parameters if (connectionStrategy == null) { connectionStrategy = @@ -2734,7 +2922,7 @@ public boolean isUseHTTPS() { * @param allSubnets if true, uses all subnets defined for this SlaveTemplate as the filter, else will only use the current subnet * @return DescribeInstancesResult of DescribeInstanceRequst constructed from this SlaveTemplate's configs */ - DescribeInstancesResult getDescribeInstanceResult(AmazonEC2 ec2, boolean allSubnets) throws IOException { + DescribeInstancesResponse getDescribeInstanceResult(Ec2Client ec2, boolean allSubnets) throws IOException { HashMap> runInstancesRequestFilterMap = makeRunInstancesRequestAndFilters(getImage(), 1, ec2, false); Map.Entry> entry = @@ -2745,7 +2933,7 @@ DescribeInstancesResult getDescribeInstanceResult(AmazonEC2 ec2, boolean allSubn /* remove any existing subnet-id filters */ List rmvFilters = new ArrayList<>(); for (Filter f : diFilters) { - if (f.getName().equals("subnet-id")) { + if (f.name().equals("subnet-id")) { rmvFilters.add(f); } } @@ -2754,12 +2942,15 @@ DescribeInstancesResult getDescribeInstanceResult(AmazonEC2 ec2, boolean allSubn } /* Add filter using all subnets defined for this SlaveTemplate */ - Filter subnetFilter = new Filter("subnet-id"); - subnetFilter.setValues(Arrays.asList(getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS))); + Filter subnetFilter = Filter.builder() + .name("subnet-id") + .values(Arrays.asList(getSubnetId().split(EC2_RESOURCE_ID_DELIMETERS))) + .build(); diFilters.add(subnetFilter); } - DescribeInstancesRequest diRequest = new DescribeInstancesRequest().withFilters(diFilters); + DescribeInstancesRequest diRequest = + DescribeInstancesRequest.builder().filters(diFilters).build(); return ec2.describeInstances(diRequest); } @@ -2823,6 +3014,18 @@ public FormValidation doCheckDescription(@QueryParameter String value) { } } + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") + public FormValidation doValidateType(@QueryParameter String value) { + InstanceType instanceType = InstanceType.fromValue(value); + + if (instanceType == InstanceType.UNKNOWN_TO_SDK_VERSION) { + return FormValidation.error("Instance type unknown to SDK version"); + } + + return FormValidation.ok(); + } + /*** * Check that the AMI requested is available in the cloud and can be used. */ @@ -2830,29 +3033,25 @@ public FormValidation doCheckDescription(@QueryParameter String value) { public FormValidation doValidateAmi( @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId, - @QueryParameter String ec2endpoint, @QueryParameter String region, + @QueryParameter String altEC2Endpoint, final @QueryParameter String ami, @QueryParameter String roleArn, @QueryParameter String roleSessionName) throws IOException { checkPermission(EC2Cloud.PROVISION); - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + AwsCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2; - if (region != null) { - ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); - } else { - ec2 = AmazonEC2Factory.getInstance().connect(credentialsProvider, new URL(ec2endpoint)); - } + Ec2Client ec2 = AmazonEC2Factory.getInstance() + .connect(credentialsProvider, EC2Cloud.parseRegion(region), EC2Cloud.parseEndpoint(altEC2Endpoint)); try { Image img = CloudHelper.getAmiImage(ec2, ami); if (img == null) { return FormValidation.error("No such AMI, or not usable with this accessId: " + ami); } - String ownerAlias = img.getImageOwnerAlias(); - return FormValidation.ok(img.getImageLocation() + (ownerAlias != null ? " by " + ownerAlias : "")); - } catch (AmazonClientException e) { + String ownerAlias = img.imageOwnerAlias(); + return FormValidation.ok(img.imageLocation() + (ownerAlias != null ? " by " + ownerAlias : "")); + } catch (SdkException e) { return FormValidation.error(e.getMessage()); } } @@ -3050,7 +3249,7 @@ public ListBoxModel doFillZoneItems( @QueryParameter String roleSessionName) throws IOException, ServletException { checkPermission(EC2Cloud.PROVISION); - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + AwsCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); return EC2AbstractSlave.fillZoneItems(credentialsProvider, region); } @@ -3079,6 +3278,23 @@ public List getNodePropertyDescriptors() { return NodePropertyDescriptor.for_(NodeProperty.all(), EC2AbstractSlave.class); } + @RequirePOST + @SuppressWarnings("lgtm[jenkins/no-permission-check]") + public ListBoxModel doFillTypeItems(@QueryParameter String type) { + ListBoxModel items = new ListBoxModel(); + + List knownValues = InstanceType.knownValues().stream() + .map(InstanceType::toString) + .sorted() + .collect(Collectors.toList()); + + for (String value : knownValues) { + items.add(new ListBoxModel.Option(value, value, Objects.equals(value, type))); + } + + return items; + } + @POST public ListBoxModel doFillConnectionStrategyItems(@QueryParameter String connectionStrategy) { return Stream.of(ConnectionStrategy.values()) diff --git a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java index 50328ce66..117795ee3 100644 --- a/src/main/java/hudson/plugins/ec2/SpotConfiguration.java +++ b/src/main/java/hudson/plugins/ec2/SpotConfiguration.java @@ -1,13 +1,5 @@ package hudson.plugins.ec2; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryRequest; -import com.amazonaws.services.ec2.model.DescribeSpotPriceHistoryResult; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.SpotPrice; import hudson.Extension; import hudson.Functions; import hudson.model.AbstractDescribableImpl; @@ -15,15 +7,24 @@ import hudson.plugins.ec2.util.AmazonEC2Factory; import hudson.util.FormValidation; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.Objects; import javax.servlet.ServletException; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeSpotPriceHistoryRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSpotPriceHistoryResponse; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.PlatformValues; +import software.amazon.awssdk.services.ec2.model.SpotPrice; public final class SpotConfiguration extends AbstractDescribableImpl { public final boolean useBidPrice; @@ -142,6 +143,7 @@ public FormValidation doCurrentSpotPrice( @QueryParameter boolean useInstanceProfileForCredentials, @QueryParameter String credentialsId, @QueryParameter String region, + @QueryParameter String altEC2Endpoint, @QueryParameter String type, @QueryParameter String zone, @QueryParameter String roleArn, @@ -156,23 +158,23 @@ public FormValidation doCurrentSpotPrice( // Connect to the EC2 cloud with the access id, secret key, and // region queried from the created cloud - AWSCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( + AwsCredentialsProvider credentialsProvider = EC2Cloud.createCredentialsProvider( useInstanceProfileForCredentials, credentialsId, roleArn, roleSessionName, region); - AmazonEC2 ec2 = - AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.getEc2EndpointUrl(region)); + Ec2Client ec2 = + AmazonEC2Factory.getInstance().connect(credentialsProvider, EC2Cloud.parseRegion(region), null); if (ec2 != null) { try { // Build a new price history request with the currently // selected type - DescribeSpotPriceHistoryRequest request = new DescribeSpotPriceHistoryRequest(); + DescribeSpotPriceHistoryRequest.Builder requestBuilder = DescribeSpotPriceHistoryRequest.builder(); // If a zone is specified, set the availability zone in the // request // Else, proceed with no availability zone which will result // with the cheapest Spot price if (CloudHelper.getAvailabilityZones(ec2).contains(zone)) { - request.setAvailabilityZone(zone); + requestBuilder.availabilityZone(zone); zoneStr = zone + " availability zone"; } else { zoneStr = region + " region"; @@ -204,27 +206,28 @@ public FormValidation doCurrentSpotPrice( Image img = CloudHelper.getAmiImage(ec2, ami); if (img != null) { Collection productDescriptions = new ArrayList<>(); - productDescriptions.add("Windows".equals(img.getPlatform()) ? "Windows" : "Linux/UNIX"); - request.setProductDescriptions(productDescriptions); + productDescriptions.add( + img.platform() == PlatformValues.WINDOWS ? "Windows" : "Linux/UNIX"); + requestBuilder.productDescriptions(productDescriptions); } } - Collection instanceType = new ArrayList<>(); - instanceType.add(ec2Type.toString()); - request.setInstanceTypes(instanceType); - request.setStartTime(new Date()); + Collection instanceType = new ArrayList<>(); + instanceType.add(ec2Type); + requestBuilder.instanceTypes(instanceType); + requestBuilder.startTime(Instant.now()); // Retrieve the price history request result and store the // current price - DescribeSpotPriceHistoryResult result = ec2.describeSpotPriceHistory(request); + DescribeSpotPriceHistoryResponse result = ec2.describeSpotPriceHistory(requestBuilder.build()); - if (!result.getSpotPriceHistory().isEmpty()) { - SpotPrice currentPrice = result.getSpotPriceHistory().get(0); + if (!result.spotPriceHistory().isEmpty()) { + SpotPrice currentPrice = result.spotPriceHistory().get(0); - cp = currentPrice.getSpotPrice(); + cp = currentPrice.spotPrice(); } - } catch (AmazonServiceException e) { + } catch (AwsServiceException e) { return FormValidation.error(e.getMessage()); } } diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java index 9ab15d263..42dcaa5c4 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2MacLauncher.java @@ -23,9 +23,6 @@ */ 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; @@ -48,6 +45,7 @@ import hudson.plugins.ec2.SlaveTemplate; import hudson.plugins.ec2.ssh.verifiers.HostKey; import hudson.plugins.ec2.ssh.verifiers.Messages; +import hudson.plugins.ec2.util.KeyPair; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.slaves.CommandLauncher; @@ -66,6 +64,9 @@ import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceType; /** * {@link ComputerLauncher} that connects to a Unix agent on EC2 by using SSH. @@ -132,7 +133,7 @@ protected String buildUpCommand(EC2Computer computer, String command) { @Override protected void launchScript(EC2Computer computer, TaskListener listener) - throws IOException, AmazonClientException, InterruptedException { + throws IOException, SdkException, InterruptedException { final Connection conn; Connection cleanupConn = null; // java's code path analysis for final // doesn't work that well. @@ -166,9 +167,10 @@ protected void launchScript(EC2Computer computer, TaskListener listener) } if (!readinessNode.isReady()) { - throw new AmazonClientException( - "Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) - + "s with status " + readinessNode.getEc2ReadinessStatus()); + throw SdkException.builder() + .message("Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + + "s with status " + readinessNode.getEc2ReadinessStatus()) + .build(); } } @@ -192,7 +194,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) KeyPair key = computer.getCloud().getKeyPair(); if (key == null || !cleanupConn.authenticateWithPublicKey( - computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { + computer.getRemoteAdmin(), key.getMaterial().toCharArray(), "")) { logWarning(computer, listener, "Authentication failed"); return; // failed to connect as root. } @@ -256,7 +258,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) final String javaPath = node.javaPath; try { Instance nodeInstance = computer.describeInstance(); - if (nodeInstance.getInstanceType().equals("mac2.metal")) { + if (nodeInstance.instanceType().equals(InstanceType.MAC2_METAL)) { LOGGER.info("Running Command for mac2.metal"); executeRemote( computer, @@ -386,7 +388,7 @@ private File createIdentityKeyFile(EC2Computer computer) throws IOException { } private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) - throws IOException, InterruptedException, AmazonClientException { + throws IOException, InterruptedException, SdkException { logInfo(computer, listener, "bootstrap()"); Connection bootstrapConn = null; try { @@ -402,13 +404,14 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp computer, listener, String.format( - "Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); + "Using private key %s (SHA-1 fingerprint %s)", + key.getKeyPairInfo().keyName(), key.getKeyPairInfo().keyFingerprint())); while (tries-- > 0) { logInfo(computer, listener, "Authenticating as " + computer.getRemoteAdmin()); try { bootstrapConn = connectToSsh(computer, listener, template); isAuthenticated = bootstrapConn.authenticateWithPublicKey( - computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); + computer.getRemoteAdmin(), key.getMaterial().toCharArray(), ""); } catch (IOException e) { logException(computer, listener, "Exception trying to authenticate", e); bootstrapConn.close(); @@ -432,7 +435,7 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp } private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) - throws AmazonClientException, InterruptedException { + throws SdkException, InterruptedException { final EC2AbstractSlave node = computer.getNode(); final long timeout = node == null ? 0L : node.getLaunchTimeoutInMillis(); final long startTime = System.currentTimeMillis(); @@ -440,9 +443,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla try { long waitTime = System.currentTimeMillis() - startTime; if (timeout > 0 && waitTime > timeout) { - throw new AmazonClientException("Timed out after " + (waitTime / 1000) - + " seconds of waiting for ssh to become available. (maximum timeout configured is " - + (timeout / 1000) + ")"); + throw SdkException.builder() + .message("Timed out after " + (waitTime / 1000) + + " seconds of waiting for ssh to become available. (maximum timeout configured is " + + (timeout / 1000) + ")") + .build(); } String host = getEC2HostAddress(computer, template); @@ -504,7 +509,7 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla if (computer.isOffline() && StringUtils.isNotBlank(computer.getOfflineCauseReason()) && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { - throw new AmazonClientException( + throw SdkException.create( "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."); diff --git a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java index f6194df8e..d28a4af0e 100644 --- a/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java +++ b/src/main/java/hudson/plugins/ec2/ssh/EC2UnixLauncher.java @@ -23,9 +23,6 @@ */ 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; @@ -49,6 +46,7 @@ import hudson.plugins.ec2.ssh.verifiers.HostKey; import hudson.plugins.ec2.ssh.verifiers.HostKeyHelper; import hudson.plugins.ec2.ssh.verifiers.Messages; +import hudson.plugins.ec2.util.KeyPair; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.slaves.CommandLauncher; @@ -68,6 +66,8 @@ import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.Instance; /** * {@link ComputerLauncher} that connects to a Unix agent on EC2 by using SSH. @@ -134,7 +134,7 @@ protected String buildUpCommand(EC2Computer computer, String command) { @Override protected void launchScript(EC2Computer computer, TaskListener listener) - throws IOException, AmazonClientException, InterruptedException { + throws IOException, SdkException, InterruptedException { final Connection conn; Connection cleanupConn = null; // java's code path analysis for final // doesn't work that well. @@ -168,9 +168,10 @@ protected void launchScript(EC2Computer computer, TaskListener listener) } if (!readinessNode.isReady()) { - throw new AmazonClientException( - "Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) - + "s with status " + readinessNode.getEc2ReadinessStatus()); + throw SdkException.builder() + .message("Node still not ready, timed out after " + (readinessTries * readinessSleepMs / 1000) + + "s with status " + readinessNode.getEc2ReadinessStatus()) + .build(); } } @@ -195,7 +196,7 @@ protected void launchScript(EC2Computer computer, TaskListener listener) KeyPair key = computer.getCloud().getKeyPair(); if (key == null || !cleanupConn.authenticateWithPublicKey( - computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), "")) { + computer.getRemoteAdmin(), key.getMaterial().toCharArray(), "")) { logWarning(computer, listener, "Authentication failed"); return; // failed to connect as root. } @@ -411,7 +412,7 @@ private File createHostKeyFile(EC2Computer computer, String ec2HostAddress, Task } private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemplate template) - throws IOException, InterruptedException, AmazonClientException { + throws IOException, InterruptedException, SdkException { logInfo(computer, listener, "bootstrap()"); Connection bootstrapConn = null; try { @@ -427,13 +428,14 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp computer, listener, String.format( - "Using private key %s (SHA-1 fingerprint %s)", key.getKeyName(), key.getKeyFingerprint())); + "Using private key %s (SHA-1 fingerprint %s)", + key.getKeyPairInfo().keyName(), key.getKeyPairInfo().keyFingerprint())); while (tries-- > 0) { logInfo(computer, listener, "Authenticating as " + computer.getRemoteAdmin()); try { bootstrapConn = connectToSsh(computer, listener, template); isAuthenticated = bootstrapConn.authenticateWithPublicKey( - computer.getRemoteAdmin(), key.getKeyMaterial().toCharArray(), ""); + computer.getRemoteAdmin(), key.getMaterial().toCharArray(), ""); } catch (IOException e) { logException(computer, listener, "Exception trying to authenticate", e); bootstrapConn.close(); @@ -457,7 +459,7 @@ private boolean bootstrap(EC2Computer computer, TaskListener listener, SlaveTemp } private Connection connectToSsh(EC2Computer computer, TaskListener listener, SlaveTemplate template) - throws AmazonClientException, InterruptedException { + throws SdkException, InterruptedException { final EC2AbstractSlave node = computer.getNode(); final long timeout = node == null ? 0L : node.getLaunchTimeoutInMillis(); final long startTime = System.currentTimeMillis(); @@ -465,9 +467,11 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla try { long waitTime = System.currentTimeMillis() - startTime; if (timeout > 0 && waitTime > timeout) { - throw new AmazonClientException("Timed out after " + (waitTime / 1000) - + " seconds of waiting for ssh to become available. (maximum timeout configured is " - + (timeout / 1000) + ")"); + throw SdkException.builder() + .message("Timed out after " + (waitTime / 1000) + + " seconds of waiting for ssh to become available. (maximum timeout configured is " + + (timeout / 1000) + ")") + .build(); } String host = getEC2HostAddress(computer, template); @@ -529,7 +533,7 @@ private Connection connectToSsh(EC2Computer computer, TaskListener listener, Sla if (computer.isOffline() && StringUtils.isNotBlank(computer.getOfflineCauseReason()) && computer.getOfflineCauseReason().equals(Messages.OfflineCause_SSHKeyCheckFailed())) { - throw new AmazonClientException( + throw SdkException.create( "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."); diff --git a/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java b/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java index 780470b39..39be4eeb3 100644 --- a/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java +++ b/src/main/java/hudson/plugins/ec2/util/AmazonEC2Factory.java @@ -1,10 +1,11 @@ package hudson.plugins.ec2.util; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; import hudson.ExtensionPoint; -import java.net.URL; +import java.net.URI; import jenkins.model.Jenkins; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; public interface AmazonEC2Factory extends ExtensionPoint { @@ -20,5 +21,5 @@ static AmazonEC2Factory getInstance() { return instance; } - AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL ec2Endpoint); + Ec2Client connect(AwsCredentialsProvider credentialsProvider, Region region, URI endpoint); } diff --git a/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java b/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java index 190b1f921..5c988205c 100644 --- a/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java +++ b/src/main/java/hudson/plugins/ec2/util/AmazonEC2FactoryImpl.java @@ -1,20 +1,28 @@ package hudson.plugins.ec2.util; -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; +import java.net.URI; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.Ec2ClientBuilder; @Extension public class AmazonEC2FactoryImpl implements AmazonEC2Factory { @Override - public AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL ec2Endpoint) { - AmazonEC2 client = - new AmazonEC2Client(credentialsProvider, EC2Cloud.createClientConfiguration(ec2Endpoint.getHost())); - client.setEndpoint(ec2Endpoint.toString()); - return client; + public Ec2Client connect(AwsCredentialsProvider credentialsProvider, Region region, URI endpoint) { + Ec2ClientBuilder ec2ClientBuilder = Ec2Client.builder() + .credentialsProvider(credentialsProvider) + .httpClient(EC2Cloud.getHttpClient()) + .overrideConfiguration(EC2Cloud.createClientOverrideConfiguration()); + if (region != null) { + ec2ClientBuilder.region(region); + } + if (endpoint != null) { + ec2ClientBuilder.endpointOverride(endpoint); + } + return ec2ClientBuilder.build(); } } diff --git a/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java b/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java index 5716b1ef4..d6a0816ba 100644 --- a/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java +++ b/src/main/java/hudson/plugins/ec2/util/DeviceMappingParser.java @@ -23,11 +23,11 @@ */ package hudson.plugins.ec2.util; -import com.amazonaws.services.ec2.model.BlockDeviceMapping; -import com.amazonaws.services.ec2.model.EbsBlockDevice; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.StringUtils; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.EbsBlockDevice; public class DeviceMappingParser { @@ -42,17 +42,18 @@ public static List parse(String customDeviceMapping) { String device = mappingPair[0]; String blockDevice = mappingPair[1]; - BlockDeviceMapping deviceMapping = new BlockDeviceMapping().withDeviceName(device); + BlockDeviceMapping.Builder deviceMappingBuilder = + BlockDeviceMapping.builder().deviceName(device); if (blockDevice.equals("none")) { - deviceMapping.setNoDevice("none"); + deviceMappingBuilder.noDevice("none"); } else if (blockDevice.startsWith("ephemeral")) { - deviceMapping.setVirtualName(blockDevice); + deviceMappingBuilder.virtualName(blockDevice); } else { - deviceMapping.setEbs(parseEbs(blockDevice)); + deviceMappingBuilder.ebs(parseEbs(blockDevice)); } - deviceMappings.add(deviceMapping); + deviceMappings.add(deviceMappingBuilder.build()); } return deviceMappings; @@ -63,30 +64,30 @@ private static EbsBlockDevice parseEbs(String blockDevice) { String[] parts = blockDevice.split(":"); - EbsBlockDevice ebs = new EbsBlockDevice(); + EbsBlockDevice.Builder ebsBuilder = EbsBlockDevice.builder(); if (StringUtils.isNotBlank(getOrEmpty(parts, 0))) { - ebs.setSnapshotId(parts[0]); + ebsBuilder.snapshotId(parts[0]); } if (StringUtils.isNotBlank(getOrEmpty(parts, 1))) { - ebs.setVolumeSize(Integer.valueOf(parts[1])); + ebsBuilder.volumeSize(Integer.valueOf(parts[1])); } if (StringUtils.isNotBlank(getOrEmpty(parts, 2))) { - ebs.setDeleteOnTermination(Boolean.valueOf(parts[2])); + ebsBuilder.deleteOnTermination(Boolean.valueOf(parts[2])); } if (StringUtils.isNotBlank(getOrEmpty(parts, 3))) { - ebs.setVolumeType(parts[3]); + ebsBuilder.volumeType(parts[3]); } if (StringUtils.isNotBlank(getOrEmpty(parts, 4))) { - ebs.setIops(Integer.valueOf(parts[4])); + ebsBuilder.iops(Integer.valueOf(parts[4])); } if (StringUtils.isNotBlank(getOrEmpty(parts, 5))) { - ebs.setEncrypted(parts[5].equals("encrypted")); + ebsBuilder.encrypted(parts[5].equals("encrypted")); } if (StringUtils.isNotBlank(getOrEmpty(parts, 6))) { - ebs.setThroughput(Integer.valueOf(parts[6])); + ebsBuilder.throughput(Integer.valueOf(parts[6])); } - return ebs; + return ebsBuilder.build(); } private static String getOrEmpty(String[] arr, int idx) { diff --git a/src/main/java/hudson/plugins/ec2/util/InstanceTypeCompat.java b/src/main/java/hudson/plugins/ec2/util/InstanceTypeCompat.java new file mode 100644 index 000000000..52400132e --- /dev/null +++ b/src/main/java/hudson/plugins/ec2/util/InstanceTypeCompat.java @@ -0,0 +1,38 @@ +package hudson.plugins.ec2.util; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import software.amazon.awssdk.services.ec2.model.InstanceType; + +@Restricted(NoExternalUse.class) +public final class InstanceTypeCompat { + + private final InstanceType instanceType; + + public InstanceTypeCompat(String instanceType) { + /* + * Attempt to find correct AWS SDK for Java v2 instance type from instance type string. Accept the value of the + * enum, e.g. m1.large. + */ + InstanceType foundInstanceType = InstanceType.fromValue(instanceType); + if (foundInstanceType == InstanceType.UNKNOWN_TO_SDK_VERSION) { + /* + * Also accept the name of the Enum in the AWS SDK for Java v1, e.g. M1Large. Name of the enum in AWS SDK + * for Java v2 is different, e.g. M1_LARGE. + */ + com.amazonaws.services.ec2.model.InstanceType oldInstanceType = + com.amazonaws.services.ec2.model.InstanceType.valueOf(instanceType); + foundInstanceType = InstanceType.fromValue(oldInstanceType.toString()); + } + this.instanceType = foundInstanceType; + } + + public InstanceTypeCompat(com.amazonaws.services.ec2.model.InstanceType instanceType) { + // Attempt to find correct AWS SDK for Java v2 instance type from AWS SDK for Java v1 instance type + this.instanceType = InstanceType.fromValue(instanceType.toString()); + } + + public InstanceType getInstanceType() { + return this.instanceType; + } +} diff --git a/src/main/java/hudson/plugins/ec2/util/KeyPair.java b/src/main/java/hudson/plugins/ec2/util/KeyPair.java new file mode 100644 index 000000000..964fe1cba --- /dev/null +++ b/src/main/java/hudson/plugins/ec2/util/KeyPair.java @@ -0,0 +1,22 @@ +package hudson.plugins.ec2.util; + +import java.util.Objects; +import software.amazon.awssdk.services.ec2.model.KeyPairInfo; + +public class KeyPair { + private final KeyPairInfo keyPairInfo; + private final String material; + + public KeyPair(KeyPairInfo keyPairInfo, String material) { + this.keyPairInfo = Objects.requireNonNull(keyPairInfo); + this.material = Objects.requireNonNull(material); + } + + public KeyPairInfo getKeyPairInfo() { + return keyPairInfo; + } + + public String getMaterial() { + return material; + } +} diff --git a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java index a91a54c6c..0db18016c 100644 --- a/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java +++ b/src/main/java/hudson/plugins/ec2/win/EC2WindowsLauncher.java @@ -1,9 +1,5 @@ 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; @@ -25,10 +21,16 @@ import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.GetPasswordDataRequest; +import software.amazon.awssdk.services.ec2.model.GetPasswordDataResponse; +import software.amazon.awssdk.services.ec2.model.Instance; public class EC2WindowsLauncher extends EC2ComputerLauncher { private static final String AGENT_JAR = "remoting.jar"; @@ -37,7 +39,7 @@ public class EC2WindowsLauncher extends EC2ComputerLauncher { @Override protected void launchScript(EC2Computer computer, TaskListener listener) - throws IOException, AmazonClientException, InterruptedException { + throws IOException, SdkException, InterruptedException { final PrintStream logger = listener.getLogger(); EC2AbstractSlave node = computer.getNode(); if (node == null) { @@ -128,25 +130,28 @@ public void onClosed(Channel channel, IOException cause) { @NonNull private WinConnection connectToWinRM( EC2Computer computer, EC2AbstractSlave node, SlaveTemplate template, PrintStream logger) - throws AmazonClientException, InterruptedException { + throws SdkException, InterruptedException { final long minTimeout = 3000; long timeout = node.getLaunchTimeoutInMillis(); // timeout is less than 0 when jenkins is booting up. if (timeout < minTimeout) { timeout = minTimeout; } - final long startTime = System.currentTimeMillis(); + final Instant startTime = Instant.now(); logger.println(node.getDisplayName() + " booted at " + node.getCreatedTime()); - boolean alreadyBooted = (startTime - node.getCreatedTime()) > TimeUnit.MINUTES.toMillis(3); + boolean alreadyBooted = + node.getCreatedTime().until(startTime, ChronoUnit.MILLIS) > TimeUnit.MINUTES.toMillis(3); WinConnection connection = null; while (true) { boolean allowSelfSignedCertificate = node.isAllowSelfSignedCertificate(); try { - long waitTime = System.currentTimeMillis() - startTime; + long waitTime = startTime.until(Instant.now(), ChronoUnit.MILLIS); if (waitTime > timeout) { - throw new AmazonClientException( - "Timed out after " + (waitTime / 1000) + " seconds of waiting for winrm to be connected"); + throw SdkException.builder() + .message("Timed out after " + (waitTime / 1000) + + " seconds of waiting for winrm to be connected") + .build(); } if (connection == null) { @@ -161,17 +166,19 @@ private WinConnection connectToWinRM( } if (!node.isSpecifyPassword()) { - GetPasswordDataResult result; + GetPasswordDataResponse result; try { result = node.getCloud() .connect() - .getPasswordData(new GetPasswordDataRequest(instance.getInstanceId())); + .getPasswordData(GetPasswordDataRequest.builder() + .instanceId(instance.instanceId()) + .build()); } catch (Exception e) { logger.println("Unexpected Exception: " + e.toString()); Thread.sleep(sleepBetweenAttempts); continue; } - String passwordData = result.getPasswordData(); + String passwordData = result.passwordData(); if (passwordData == null || passwordData.isEmpty()) { logger.println("Waiting for password to be available. Sleeping 10s."); Thread.sleep(sleepBetweenAttempts); @@ -232,7 +239,7 @@ private WinConnection connectToWinRM( computer.setTemporarilyOffline(true, OfflineCause.create(Messages._OfflineCause_SSLException())); // 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); + throw SdkException.create("The SSL connection failed while negotiating SSL", e); } logger.println("Waiting for WinRM to come up. Sleeping 10s."); Thread.sleep(sleepBetweenAttempts); diff --git a/src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly b/src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly index 66f495528..cb0d986c2 100644 --- a/src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly +++ b/src/main/resources/hudson/plugins/ec2/EC2Cloud/config-entries.jelly @@ -53,5 +53,5 @@ THE SOFTWARE. - + diff --git a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly index 7c9b47880..4620a4bfd 100644 --- a/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly +++ b/src/main/resources/hudson/plugins/ec2/SlaveTemplate/config.jelly @@ -37,7 +37,7 @@ THE SOFTWARE. validateButton only marshals simple types and can't handle the filters repeatableProperty. --> - + @@ -58,7 +58,7 @@ THE SOFTWARE. - ${it.name()} + diff --git a/src/main/resources/hudson/plugins/ec2/SpotConfiguration/config.jelly b/src/main/resources/hudson/plugins/ec2/SpotConfiguration/config.jelly index fa0701ac1..d2dbb8a4f 100644 --- a/src/main/resources/hudson/plugins/ec2/SpotConfiguration/config.jelly +++ b/src/main/resources/hudson/plugins/ec2/SpotConfiguration/config.jelly @@ -26,7 +26,7 @@ THE SOFTWARE. - + diff --git a/src/test/java/hudson/plugins/ec2/CloudHelperTest.java b/src/test/java/hudson/plugins/ec2/CloudHelperTest.java index b22219df1..344ad2c38 100644 --- a/src/test/java/hudson/plugins/ec2/CloudHelperTest.java +++ b/src/test/java/hudson/plugins/ec2/CloudHelperTest.java @@ -2,12 +2,6 @@ import static org.junit.Assert.assertEquals; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.Reservation; import java.util.Collections; import java.util.List; import org.junit.Before; @@ -18,6 +12,13 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.Reservation; @RunWith(MockitoJUnitRunner.class) public class CloudHelperTest { @@ -44,8 +45,8 @@ public void init() throws Exception { public void testGetInstanceHappyPath() throws Exception { /* Mocked items */ EC2Cloud spyCloud = Mockito.spy(cloud); - AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); - DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); + Ec2Client mockEc2 = Mockito.mock(Ec2Client.class); + DescribeInstancesResponse mockedDIResult = Mockito.mock(DescribeInstancesResponse.class); Reservation mockedReservation = Mockito.mock(Reservation.class); List reservationResults = Collections.singletonList(mockedReservation); Instance mockedInstance = Mockito.mock(Instance.class); @@ -53,8 +54,8 @@ public void testGetInstanceHappyPath() throws Exception { Mockito.doReturn(mockEc2).when(spyCloud).connect(); Mockito.doReturn(mockedDIResult).when(mockEc2).describeInstances(Mockito.any(DescribeInstancesRequest.class)); - Mockito.doReturn(reservationResults).when(mockedDIResult).getReservations(); - Mockito.doReturn(instanceResults).when(mockedReservation).getInstances(); + Mockito.doReturn(reservationResults).when(mockedDIResult).reservations(); + Mockito.doReturn(instanceResults).when(mockedReservation).instances(); /* Actual call to test*/ Instance result = CloudHelper.getInstance("test-instance-id", spyCloud); @@ -65,20 +66,24 @@ public void testGetInstanceHappyPath() throws Exception { public void testGetInstanceWithRetryInstanceNotFound() throws Exception { /* Mocked items */ EC2Cloud spyCloud = Mockito.spy(cloud); - AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); - DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); + Ec2Client mockEc2 = Mockito.mock(Ec2Client.class); + DescribeInstancesResponse mockedDIResult = Mockito.mock(DescribeInstancesResponse.class); Reservation mockedReservation = Mockito.mock(Reservation.class); List reservationResults = Collections.singletonList(mockedReservation); Instance mockedInstance = Mockito.mock(Instance.class); List instanceResults = Collections.singletonList(mockedInstance); - AmazonServiceException amazonServiceException = new AmazonServiceException("test exception"); - amazonServiceException.setErrorCode("InvalidInstanceID.NotFound"); - - Answer answerWithRetry = new Answer<>() { + AwsServiceException amazonServiceException = AwsServiceException.builder() + .message("test exception") + .awsErrorDetails(AwsErrorDetails.builder() + .errorCode("InvalidInstanceID.NotFound") + .build()) + .build(); + + Answer answerWithRetry = new Answer<>() { private boolean first = true; @Override - public DescribeInstancesResult answer(InvocationOnMock invocation) throws Throwable { + public DescribeInstancesResponse answer(InvocationOnMock invocation) throws Throwable { if (first) { first = false; throw amazonServiceException; @@ -89,8 +94,8 @@ public DescribeInstancesResult answer(InvocationOnMock invocation) throws Throwa Mockito.doReturn(mockEc2).when(spyCloud).connect(); Mockito.doAnswer(answerWithRetry).when(mockEc2).describeInstances(Mockito.any(DescribeInstancesRequest.class)); - Mockito.doReturn(reservationResults).when(mockedDIResult).getReservations(); - Mockito.doReturn(instanceResults).when(mockedReservation).getInstances(); + Mockito.doReturn(reservationResults).when(mockedDIResult).reservations(); + Mockito.doReturn(instanceResults).when(mockedReservation).instances(); /* Actual call to test*/ Instance result = CloudHelper.getInstanceWithRetry("test-instance-id", spyCloud); @@ -101,20 +106,22 @@ public DescribeInstancesResult answer(InvocationOnMock invocation) throws Throwa public void testGetInstanceWithRetryRequestExpired() throws Exception { /* Mocked items */ EC2Cloud spyCloud = Mockito.spy(cloud); - AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); - DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); + Ec2Client mockEc2 = Mockito.mock(Ec2Client.class); + DescribeInstancesResponse mockedDIResult = Mockito.mock(DescribeInstancesResponse.class); Reservation mockedReservation = Mockito.mock(Reservation.class); List reservationResults = Collections.singletonList(mockedReservation); Instance mockedInstance = Mockito.mock(Instance.class); List instanceResults = Collections.singletonList(mockedInstance); - AmazonServiceException amazonServiceException = new AmazonServiceException("test exception"); - amazonServiceException.setErrorCode("RequestExpired"); - - Answer answerWithRetry = new Answer<>() { + AwsServiceException amazonServiceException = AwsServiceException.builder() + .message("test exception") + .awsErrorDetails( + AwsErrorDetails.builder().errorCode("RequestExpired").build()) + .build(); + Answer answerWithRetry = new Answer<>() { private boolean first = true; @Override - public DescribeInstancesResult answer(InvocationOnMock invocation) throws Throwable { + public DescribeInstancesResponse answer(InvocationOnMock invocation) throws Throwable { if (first) { first = false; throw amazonServiceException; @@ -125,8 +132,8 @@ public DescribeInstancesResult answer(InvocationOnMock invocation) throws Throwa Mockito.doReturn(mockEc2).when(spyCloud).connect(); Mockito.doAnswer(answerWithRetry).when(mockEc2).describeInstances(Mockito.any(DescribeInstancesRequest.class)); - Mockito.doReturn(reservationResults).when(mockedDIResult).getReservations(); - Mockito.doReturn(instanceResults).when(mockedReservation).getInstances(); + Mockito.doReturn(reservationResults).when(mockedDIResult).reservations(); + Mockito.doReturn(instanceResults).when(mockedReservation).instances(); /* Actual call to test*/ Instance result = CloudHelper.getInstanceWithRetry("test-instance-id", spyCloud); diff --git a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java index a4856d658..d2e617668 100644 --- a/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2AbstractSlaveTest.java @@ -2,13 +2,13 @@ import static org.junit.Assert.assertEquals; -import com.amazonaws.services.ec2.model.InstanceType; import hudson.model.Node; import java.util.ArrayList; import java.util.List; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import software.amazon.awssdk.services.ec2.model.InstanceType; public class EC2AbstractSlaveTest { @@ -75,7 +75,7 @@ public void testMaxUsesBackwardCompat() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, diff --git a/src/test/java/hudson/plugins/ec2/EC2CloudTest.java b/src/test/java/hudson/plugins/ec2/EC2CloudTest.java index 3a01e27fd..e5bb483f4 100644 --- a/src/test/java/hudson/plugins/ec2/EC2CloudTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2CloudTest.java @@ -29,7 +29,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import com.amazonaws.services.ec2.AmazonEC2; import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl; import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey; import com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey; @@ -54,6 +53,7 @@ import org.jvnet.hudson.test.JenkinsRule; import org.mockito.Mockito; import org.xml.sax.SAXException; +import software.amazon.awssdk.services.ec2.Ec2Client; /** * @author Kohsuke Kawaguchi @@ -93,7 +93,7 @@ public void testConfigRoundtrip() throws Exception { @Test public void testAmazonEC2FactoryGetInstance() throws Exception { EC2Cloud cloud = r.jenkins.clouds.get(EC2Cloud.class); - AmazonEC2 connection = cloud.connect(); + Ec2Client connection = cloud.connect(); Assert.assertNotNull(connection); Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); } @@ -103,7 +103,7 @@ public void testAmazonEC2FactoryWorksIfSessionNameMissing() throws Exception { r.jenkins.clouds.replace(new EC2Cloud( "us-east-1", true, "abc", "us-east-1", null, "ghi", "3", Collections.emptyList(), "roleArn", null)); EC2Cloud cloud = r.jenkins.clouds.get(EC2Cloud.class); - AmazonEC2 connection = cloud.connect(); + Ec2Client connection = cloud.connect(); Assert.assertNotNull(connection); Assert.assertTrue(Mockito.mockingDetails(connection).isMock()); } diff --git a/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java b/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java index 15f613822..b024cb35a 100644 --- a/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2CloudUnitTest.java @@ -32,16 +32,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.Tag; import hudson.model.Node; import hudson.plugins.ec2.util.AmazonEC2FactoryMockImpl; import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,6 +50,11 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.Tag; /** * Unit tests related to {@link EC2Cloud}, but do not require a Jenkins instance. @@ -64,15 +62,6 @@ @RunWith(MockitoJUnitRunner.Silent.class) public class EC2CloudUnitTest { - @Test - public void testEC2EndpointURLCreation() throws MalformedURLException { - EC2Cloud.DescriptorImpl descriptor = new EC2Cloud.DescriptorImpl(); - - assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL(null)); - assertEquals(new URL(EC2Cloud.DEFAULT_EC2_ENDPOINT), descriptor.determineEC2EndpointURL("")); - assertEquals(new URL("https://www.abc.com"), descriptor.determineEC2EndpointURL("https://www.abc.com")); - } - @Test public void testInstaceCap() throws Exception { EC2Cloud cloud = new EC2Cloud( @@ -130,9 +119,13 @@ public void testSpotInstanceCount() throws Exception { List instances = new ArrayList<>(); for (int i = 0; i <= numberOfSpotInstanceRequests; i++) { - instances.add(new Instance() - .withInstanceId("id" + i) - .withTags(new Tag().withKey("jenkins_slave_type").withValue("spot"))); + instances.add(Instance.builder() + .instanceId("id" + i) + .tags(Tag.builder() + .key("jenkins_slave_type") + .value("spot") + .build()) + .build()); } AmazonEC2FactoryMockImpl.instances = instances; @@ -152,20 +145,6 @@ public void testSpotInstanceCount() throws Exception { } } - @Test - public void testCNPartition() { - assertEquals( - EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "ec2"), "ec2.cn-northwest-1.amazonaws.com.cn"); - assertEquals( - EC2Cloud.getAwsPartitionHostForService("cn-northwest-1", "s3"), "s3.cn-northwest-1.amazonaws.com.cn"); - } - - @Test - public void testNormalPartition() { - assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "ec2"), "ec2.us-east-1.amazonaws.com"); - assertEquals(EC2Cloud.getAwsPartitionHostForService("us-east-1", "s3"), "s3.us-east-1.amazonaws.com"); - } - @Test public void testSlaveTemplateAddition() throws Exception { EC2Cloud cloud = new EC2Cloud( @@ -185,7 +164,7 @@ public void testSlaveTemplateAddition() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -247,7 +226,7 @@ public void testSlaveTemplateUpdate() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -292,7 +271,7 @@ public void testSlaveTemplateUpdate() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -339,7 +318,7 @@ public void testSlaveTemplateUpdate() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -401,11 +380,11 @@ public void testReattachOrphanStoppedNodes() throws Exception { "roleArn", "roleSessionName"); EC2Cloud spyCloud = Mockito.spy(cloud); - AmazonEC2 mockEc2 = Mockito.mock(AmazonEC2.class); + Ec2Client mockEc2 = Mockito.mock(Ec2Client.class); Jenkins mockJenkins = Mockito.mock(Jenkins.class); EC2AbstractSlave mockOrphanNode = Mockito.mock(EC2AbstractSlave.class); SlaveTemplate mockSlaveTemplate = Mockito.mock(SlaveTemplate.class); - DescribeInstancesResult mockedDIResult = Mockito.mock(DescribeInstancesResult.class); + DescribeInstancesResponse mockedDIResult = Mockito.mock(DescribeInstancesResponse.class); Instance mockedInstance = Mockito.mock(Instance.class); List listOfMockedInstances = new ArrayList<>(); listOfMockedInstances.add(mockedInstance); @@ -432,20 +411,20 @@ public Void answer(InvocationOnMock invocation) { Mockito.doReturn(mockEc2).when(spyCloud).connect(); Mockito.doReturn(mockedDIResult) .when(mockSlaveTemplate) - .getDescribeInstanceResult(Mockito.any(AmazonEC2.class), eq(true)); + .getDescribeInstanceResult(Mockito.any(Ec2Client.class), eq(true)); Mockito.doReturn(listOfMockedInstances) .when(mockSlaveTemplate) .findOrphansOrStopped(eq(mockedDIResult), Mockito.anyInt()); Mockito.doNothing() .when(mockSlaveTemplate) - .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); + .wakeOrphansOrStoppedUp(Mockito.any(Ec2Client.class), eq(listOfMockedInstances)); /* Actual call to test*/ spyCloud.attemptReattachOrphanOrStoppedNodes(mockJenkins, mockSlaveTemplate, 1); /* Checks */ Mockito.verify(mockSlaveTemplate, times(1)) - .wakeOrphansOrStoppedUp(Mockito.any(AmazonEC2.class), eq(listOfMockedInstances)); + .wakeOrphansOrStoppedUp(Mockito.any(Ec2Client.class), eq(listOfMockedInstances)); Node[] expectedNodes = {mockOrphanNode}; assertArrayEquals(expectedNodes, listOfJenkinsNodes.toArray()); } diff --git a/src/test/java/hudson/plugins/ec2/EC2FilterTest.java b/src/test/java/hudson/plugins/ec2/EC2FilterTest.java index c61180e10..93611fcb1 100644 --- a/src/test/java/hudson/plugins/ec2/EC2FilterTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2FilterTest.java @@ -28,11 +28,11 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; -import com.amazonaws.services.ec2.model.Filter; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.Test; +import software.amazon.awssdk.services.ec2.model.Filter; public class EC2FilterTest { @Test @@ -43,8 +43,8 @@ public void testSingleValue() throws Exception { assertEquals("EC2Filter{name=\"name\", values=\"value\"}", ec2Filter.toString()); Filter filter = ec2Filter.toFilter(); assertNotNull(filter); - assertEquals("name", filter.getName()); - assertEquals(List.of("value"), filter.getValues()); + assertEquals("name", filter.name()); + assertEquals(List.of("value"), filter.values()); } @Test @@ -55,8 +55,8 @@ public void testMultiValue() throws Exception { assertEquals("EC2Filter{name=\"name\", values=\"value1 'value 2'\"}", ec2Filter.toString()); Filter filter = ec2Filter.toFilter(); assertNotNull(filter); - assertEquals("name", filter.getName()); - assertEquals(Arrays.asList("value1", "value 2"), filter.getValues()); + assertEquals("name", filter.name()); + assertEquals(Arrays.asList("value1", "value 2"), filter.values()); } @Test @@ -67,8 +67,8 @@ public void testEmptyValue() throws Exception { assertEquals("EC2Filter{name=\"\", values=\"\"}", ec2Filter.toString()); Filter filter = ec2Filter.toFilter(); assertNotNull(filter); - assertEquals("", filter.getName()); - assertEquals(Collections.emptyList(), filter.getValues()); + assertEquals("", filter.name()); + assertEquals(Collections.emptyList(), filter.values()); } @Test diff --git a/src/test/java/hudson/plugins/ec2/EC2HostAddressProviderTest.java b/src/test/java/hudson/plugins/ec2/EC2HostAddressProviderTest.java index 7ea9e3c71..2d0f4817d 100644 --- a/src/test/java/hudson/plugins/ec2/EC2HostAddressProviderTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2HostAddressProviderTest.java @@ -5,8 +5,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.amazonaws.services.ec2.model.Instance; import org.junit.Test; +import software.amazon.awssdk.services.ec2.model.Instance; public class EC2HostAddressProviderTest { @@ -15,7 +15,7 @@ public void unix_publicDnsStrategy_isPresent() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PUBLIC_DNS; - when(instance.getPublicDnsName()).thenReturn("ec2-0-0-0-0.compute-1.amazonaws.com"); + when(instance.publicDnsName()).thenReturn("ec2-0-0-0-0.compute-1.amazonaws.com"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("ec2-0-0-0-0.compute-1.amazonaws.com")); } @@ -25,8 +25,8 @@ public void unix_publicDnsStrategy_notPresent() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PUBLIC_DNS; - when(instance.getPublicDnsName()).thenReturn(""); - when(instance.getPublicIpAddress()).thenReturn("0.0.0.0"); + when(instance.publicDnsName()).thenReturn(""); + when(instance.publicIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("0.0.0.0")); } @@ -36,7 +36,7 @@ public void unix_publicIpStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PUBLIC_IP; - when(instance.getPublicIpAddress()).thenReturn("0.0.0.0"); + when(instance.publicIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("0.0.0.0")); } @@ -46,7 +46,7 @@ public void unix_privateDnsStrategy_isPresent() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PRIVATE_DNS; - when(instance.getPrivateDnsName()).thenReturn("0-0-0-0.ec2.internal"); + when(instance.privateDnsName()).thenReturn("0-0-0-0.ec2.internal"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("0-0-0-0.ec2.internal")); } @@ -56,8 +56,8 @@ public void unix_privateDnsStrategy_notPresent() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PRIVATE_DNS; - when(instance.getPrivateDnsName()).thenReturn(""); - when(instance.getPrivateIpAddress()).thenReturn("0.0.0.0"); + when(instance.privateDnsName()).thenReturn(""); + when(instance.privateIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("0.0.0.0")); } @@ -67,7 +67,7 @@ public void unix_privateIpStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PRIVATE_IP; - when(instance.getPrivateIpAddress()).thenReturn("0.0.0.0"); + when(instance.privateIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.unix(instance, strategy), equalTo("0.0.0.0")); } @@ -77,8 +77,8 @@ public void windows_privateDnsStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PRIVATE_DNS; - when(instance.getPrivateDnsName()).thenReturn("0-0-0-0.ec2.internal"); - when(instance.getPrivateIpAddress()).thenReturn("0.0.0.0"); + when(instance.privateDnsName()).thenReturn("0-0-0-0.ec2.internal"); + when(instance.privateIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.windows(instance, strategy), equalTo("0.0.0.0")); } @@ -88,8 +88,8 @@ public void windows_privateIpStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PRIVATE_IP; - when(instance.getPrivateDnsName()).thenReturn(""); - when(instance.getPrivateIpAddress()).thenReturn("0.0.0.0"); + when(instance.privateDnsName()).thenReturn(""); + when(instance.privateIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.windows(instance, strategy), equalTo("0.0.0.0")); } @@ -99,8 +99,8 @@ public void windows_publicDnsStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PUBLIC_DNS; - when(instance.getPublicDnsName()).thenReturn("ec2-0-0-0-0.compute-1.amazonaws.com"); - when(instance.getPublicIpAddress()).thenReturn("0.0.0.0"); + when(instance.publicDnsName()).thenReturn("ec2-0-0-0-0.compute-1.amazonaws.com"); + when(instance.publicIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.windows(instance, strategy), equalTo("0.0.0.0")); } @@ -110,8 +110,8 @@ public void windows_publicIpStrategy() { Instance instance = mock(Instance.class); ConnectionStrategy strategy = ConnectionStrategy.PUBLIC_IP; - when(instance.getPublicDnsName()).thenReturn(""); - when(instance.getPublicIpAddress()).thenReturn("0.0.0.0"); + when(instance.publicDnsName()).thenReturn(""); + when(instance.publicIpAddress()).thenReturn("0.0.0.0"); assertThat(EC2HostAddressProvider.windows(instance, strategy), equalTo("0.0.0.0")); } diff --git a/src/test/java/hudson/plugins/ec2/EC2PrivateKeyTest.java b/src/test/java/hudson/plugins/ec2/EC2PrivateKeyTest.java index 898b18fca..a709d0719 100644 --- a/src/test/java/hudson/plugins/ec2/EC2PrivateKeyTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2PrivateKeyTest.java @@ -25,12 +25,12 @@ import static org.junit.Assert.assertEquals; -import com.amazonaws.AmazonClientException; import java.io.IOException; import java.security.Security; import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Issue; +import software.amazon.awssdk.core.exception.SdkException; /** * @author Kohsuke Kawaguchi @@ -82,7 +82,7 @@ public void testPublicFingerprint() throws IOException { } @Test - public void testDecryptPassword() throws AmazonClientException { + public void testDecryptPassword() throws SdkException { EC2PrivateKey k = getPrivateKey(); final String password = k.decryptWindowsPassword( "LXOVLWjPem85Gy3B/AbIQ2/aQKIT5PZuq+Egg97EJSLKXVkdxDSdP7XbfpkKSNSZdhLV8XAE/vfAvU7MU2FfKArxZ6vvN2Gy8ukSoQW2UC2p1xm8ygI4sr40+Op2Hva/Svcjka4sVLYJy/ySTZCEedFkEzxhPV7FM6KHhZZ9L56hxSJe1P/7E1dY2pxbJbj/QeAKu8ps/RYTHPgqvTw0oeq1/Sal362nNPPAG+aznocazp8g0gNhySg58/9i+xjtyqm4mjWsN0p8s4JhRKPZ/iHJSqu+ZfaJqURXd9OHLdnzkvHpyzKCbU3URmnY3dha4B8dlPGAX0z3hSEtaI0LJA=="); @@ -90,7 +90,7 @@ public void testDecryptPassword() throws AmazonClientException { } @Test - public void testDecryptPasswordwNewLines() throws AmazonClientException { + public void testDecryptPasswordwNewLines() throws SdkException { EC2PrivateKey k = getPrivateKey(); final String password = k.decryptWindowsPassword( "\r\nLXOVLWjPem85Gy3B/AbIQ2/aQKIT5PZuq+Egg97EJSLKXVkdxDSdP7XbfpkKSNSZdhLV8XAE/vfAvU7MU2FfKArxZ6vvN2Gy8ukSoQW2UC2p1xm8ygI4sr40+Op2Hva/Svcjka4sVLYJy/ySTZCEedFkEzxhPV7FM6KHhZZ9L56hxSJe1P/7E1dY2pxbJbj/QeAKu8ps/RYTHPgqvTw0oeq1/Sal362nNPPAG+aznocazp8g0gNhySg58/9i+xjtyqm4mjWsN0p8s4JhRKPZ/iHJSqu+ZfaJqURXd9OHLdnzkvHpyzKCbU3URmnY3dha4B8dlPGAX0z3hSEtaI0LJA==\r\n"); diff --git a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java index 8857c3fb2..c4e8c8aaf 100644 --- a/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2RetentionStrategyTest.java @@ -8,8 +8,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.InstanceType; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.model.Executor; import hudson.model.Label; @@ -49,6 +47,8 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; import org.springframework.security.core.Authentication; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.InstanceType; public class EC2RetentionStrategyTest { @@ -262,9 +262,7 @@ void idleTimeout() { }; EC2Computer computer = new EC2Computer(slave) { - private final long launchedAtMs = Instant.now() - .minus(Duration.ofSeconds(minutes * 60L + seconds)) - .toEpochMilli(); + private final Instant launchedAt = Instant.now().minus(Duration.ofSeconds(minutes * 60L + seconds)); @Override public EC2AbstractSlave getNode() { @@ -272,13 +270,13 @@ public EC2AbstractSlave getNode() { } @Override - public long getUptime() throws AmazonClientException, InterruptedException { + public long getUptime() throws SdkException, InterruptedException { return ((minutes * 60L) + seconds) * 1000L; } @Override - public long getLaunchTime() throws InterruptedException { - return this.launchedAtMs; + public Instant getLaunchTime() throws InterruptedException { + return this.launchedAt; } @Override @@ -299,7 +297,7 @@ public SlaveTemplate getSlaveTemplate() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -406,9 +404,7 @@ void idleTimeout() { } }; EC2Computer computer = new EC2Computer(slave) { - private final long launchedAtMs = Instant.now() - .minus(Duration.ofSeconds(minutes * 60L + seconds)) - .toEpochMilli(); + private final Instant launchedAt = Instant.now().minus(Duration.ofSeconds(minutes * 60L + seconds)); @Override public EC2AbstractSlave getNode() { @@ -416,13 +412,13 @@ public EC2AbstractSlave getNode() { } @Override - public long getUptime() throws AmazonClientException, InterruptedException { + public long getUptime() throws SdkException, InterruptedException { return ((minutes * 60L) + seconds) * 1000L; } @Override - public long getLaunchTime() throws InterruptedException { - return this.launchedAtMs; + public Instant getLaunchTime() throws InterruptedException { + return this.launchedAt; } @Override @@ -443,7 +439,7 @@ public SlaveTemplate getSlaveTemplate() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -737,7 +733,7 @@ public void testRetentionDespiteIdleWithMinimumInstances() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -846,7 +842,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRange() throws null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -945,7 +941,7 @@ public void testRetentionIdleWithMinimumInstanceInactiveTimeRange() throws Excep null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1030,7 +1026,7 @@ public void testRetentionDespiteIdleWithMinimumInstanceActiveTimeRangeAfterMidni null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1129,7 +1125,7 @@ public void testRetentionStopsAfterActiveRangeEnds() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, diff --git a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java index 17a45742e..60e983b55 100644 --- a/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2SlaveMonitorTest.java @@ -1,6 +1,5 @@ package hudson.plugins.ec2; -import com.amazonaws.services.ec2.model.InstanceType; import hudson.model.Node; import hudson.plugins.ec2.util.SSHCredentialHelper; import java.security.Security; @@ -12,6 +11,7 @@ import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import software.amazon.awssdk.services.ec2.model.InstanceType; public class EC2SlaveMonitorTest { @@ -32,7 +32,7 @@ public void testMinimumNumberOfInstances() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -102,7 +102,7 @@ public void testMinimumNumberOfSpareInstances() throws Exception { null, "defaultsecgroup", "remotefs", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "label", Node.Mode.NORMAL, diff --git a/src/test/java/hudson/plugins/ec2/EC2StepTest.java b/src/test/java/hudson/plugins/ec2/EC2StepTest.java index 0a2ac9251..7b107cc51 100644 --- a/src/test/java/hudson/plugins/ec2/EC2StepTest.java +++ b/src/test/java/hudson/plugins/ec2/EC2StepTest.java @@ -8,8 +8,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; -import com.amazonaws.services.ec2.AmazonEC2Client; -import com.amazonaws.services.ec2.model.AmazonEC2Exception; import hudson.model.PeriodicWork; import hudson.model.Result; import hudson.plugins.ec2.util.AmazonEC2FactoryMockImpl; @@ -27,6 +25,9 @@ import org.mockito.Mock; import org.mockito.internal.stubbing.answers.ThrowsException; import org.mockito.junit.MockitoJUnitRunner; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; /** * @author Alicia Doblas @@ -65,11 +66,10 @@ public void setup() throws Exception { @Test public void testExpiredConnection() { when(cl.connect()).thenCallRealMethod(); - when(cl.getEc2EndpointUrl()).thenCallRealMethod(); when(cl.createCredentialsProvider()).thenCallRealMethod(); // not expired ec2 client - AmazonEC2Client notExpiredClient = AmazonEC2FactoryMockImpl.createAmazonEC2Mock(); + Ec2Client notExpiredClient = AmazonEC2FactoryMockImpl.createAmazonEC2Mock(); AmazonEC2FactoryMockImpl.mock = notExpiredClient; assertSame("EC2 client not expired should be reused", notExpiredClient, cl.connect()); @@ -77,14 +77,17 @@ public void testExpiredConnection() { // based on a real exception // > Request has expired. (Service: AmazonEC2; Status Code: 400; Error Code: RequestExpired; Request ID: // 00000000-0000-0000-0000-000000000000) - AmazonEC2Exception expiredException = new AmazonEC2Exception("Request has expired"); - expiredException.setServiceName("AmazonEC2"); - expiredException.setStatusCode(400); - expiredException.setErrorCode("RequestExpired"); - expiredException.setRequestId("00000000-0000-0000-0000-000000000000"); - - AmazonEC2Client expiredClient = - AmazonEC2FactoryMockImpl.createAmazonEC2Mock(new ThrowsException(expiredException)); + Ec2Exception.Builder expiredExceptionBuilder = Ec2Exception.builder(); + expiredExceptionBuilder.message("Request has expired"); + expiredExceptionBuilder.statusCode(400); + expiredExceptionBuilder.awsErrorDetails(AwsErrorDetails.builder() + .serviceName("AmazonEC2") + .errorCode("RequestExpired") + .build()); + expiredExceptionBuilder.requestId("00000000-0000-0000-0000-000000000000"); + + Ec2Client expiredClient = + AmazonEC2FactoryMockImpl.createAmazonEC2Mock(new ThrowsException(expiredExceptionBuilder.build())); AmazonEC2FactoryMockImpl.mock = expiredClient; PeriodicWork work = PeriodicWork.all().get(EC2Cloud.EC2ConnectionUpdater.class); assertNotNull(work); diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java index 9ec683015..ea93b28d7 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateTest.java @@ -32,35 +32,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.model.AmazonEC2Exception; -import com.amazonaws.services.ec2.model.DescribeImagesRequest; -import com.amazonaws.services.ec2.model.DescribeImagesResult; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; -import com.amazonaws.services.ec2.model.DescribeSubnetsRequest; -import com.amazonaws.services.ec2.model.DescribeSubnetsResult; -import com.amazonaws.services.ec2.model.HttpTokensState; -import com.amazonaws.services.ec2.model.IamInstanceProfile; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceMetadataEndpointState; -import com.amazonaws.services.ec2.model.InstanceMetadataOptionsRequest; -import com.amazonaws.services.ec2.model.InstanceNetworkInterfaceSpecification; -import com.amazonaws.services.ec2.model.InstanceState; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.KeyPair; -import com.amazonaws.services.ec2.model.RequestSpotInstancesRequest; -import com.amazonaws.services.ec2.model.Reservation; -import com.amazonaws.services.ec2.model.RunInstancesRequest; -import com.amazonaws.services.ec2.model.RunInstancesResult; -import com.amazonaws.services.ec2.model.SecurityGroup; -import com.amazonaws.services.ec2.model.Subnet; import hudson.Util; import hudson.model.Node; import hudson.plugins.ec2.SlaveTemplate.ProvisionOptions; +import hudson.plugins.ec2.util.KeyPair; import hudson.plugins.ec2.util.MinimumNumberOfInstancesTimeRangeConfig; import java.io.IOException; import java.util.ArrayList; @@ -77,6 +52,34 @@ import org.jvnet.hudson.test.JenkinsRule; import org.mockito.ArgumentCaptor; import org.xml.sax.SAXException; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeImagesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; +import software.amazon.awssdk.services.ec2.model.Ec2Exception; +import software.amazon.awssdk.services.ec2.model.HttpTokensState; +import software.amazon.awssdk.services.ec2.model.IamInstanceProfile; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceMetadataEndpointState; +import software.amazon.awssdk.services.ec2.model.InstanceMetadataOptionsRequest; +import software.amazon.awssdk.services.ec2.model.InstanceNetworkInterfaceSpecification; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.KeyPairInfo; +import software.amazon.awssdk.services.ec2.model.RequestSpotInstancesRequest; +import software.amazon.awssdk.services.ec2.model.Reservation; +import software.amazon.awssdk.services.ec2.model.RunInstancesRequest; +import software.amazon.awssdk.services.ec2.model.RunInstancesResponse; +import software.amazon.awssdk.services.ec2.model.SecurityGroup; +import software.amazon.awssdk.services.ec2.model.Subnet; /** * Basic test to validate SlaveTemplate. @@ -87,7 +90,7 @@ public class SlaveTemplateTest { private final SpotConfiguration TEST_SPOT_CFG = null; private final String TEST_SEC_GROUPS = "default"; private final String TEST_REMOTE_FS = "foo"; - private final InstanceType TEST_INSTANCE_TYPE = InstanceType.M1Large; + private final InstanceType TEST_INSTANCE_TYPE = InstanceType.M1_LARGE; private final boolean TEST_EBSO = false; private final String TEST_LABEL = "ttt"; @@ -109,7 +112,7 @@ public void testConfigRoundtrip() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -178,7 +181,7 @@ public void testConfigRoundtripWithCustomSSHHostKeyVerificationStrategy() throws TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -255,7 +258,7 @@ public void testConfigWithSpotBidPrice() throws Exception { spotConfig, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -326,7 +329,7 @@ public void testSpotConfigWithoutBidPrice() throws Exception { spotConfig, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -389,7 +392,7 @@ public void testWindowsConfigRoundTrip() throws Exception { null, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -452,7 +455,7 @@ public void testUnixConfigRoundTrip() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -520,7 +523,7 @@ public void testMinimumNumberOfInstancesActiveRangeConfig() throws Exception { spotConfig, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -592,7 +595,7 @@ public void provisionOndemandSetsAwsNetworkingOnEc2Request() throws Exception { TEST_SPOT_CFG, securityGroups, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -637,7 +640,7 @@ public void provisionOndemandSetsAwsNetworkingOnEc2Request() throws Exception { TEST_SPOT_CFG, securityGroups, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -681,7 +684,7 @@ public void provisionOndemandSetsAwsNetworkingOnEc2Request() throws Exception { templates.add(orig); templates.add(noSubnet); for (SlaveTemplate template : templates) { - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -689,18 +692,18 @@ public void provisionOndemandSetsAwsNetworkingOnEc2Request() throws Exception { verify(mockedEC2).runInstances(riRequestCaptor.capture()); RunInstancesRequest actualRequest = riRequestCaptor.getValue(); - List actualNets = actualRequest.getNetworkInterfaces(); + List actualNets = actualRequest.networkInterfaces(); assertEquals(actualNets.size(), 0); String templateSubnet = Util.fixEmpty(template.getSubnetId()); - assertEquals(actualRequest.getSubnetId(), templateSubnet); + assertEquals(actualRequest.subnetId(), templateSubnet); if (templateSubnet != null) { assertEquals( - actualRequest.getSecurityGroupIds(), + actualRequest.securityGroupIds(), Stream.of("some-group-id").collect(Collectors.toList())); } else { assertEquals( - actualRequest.getSecurityGroups(), + actualRequest.securityGroups(), Stream.of(securityGroups).collect(Collectors.toList())); } } @@ -726,7 +729,7 @@ public void provisionOndemandSetsAwsNetworkingOnNetworkInterface() throws Except TEST_SPOT_CFG, securityGroups, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -771,7 +774,7 @@ public void provisionOndemandSetsAwsNetworkingOnNetworkInterface() throws Except TEST_SPOT_CFG, securityGroups, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -815,7 +818,7 @@ public void provisionOndemandSetsAwsNetworkingOnNetworkInterface() throws Except templates.add(orig); templates.add(noSubnet); for (SlaveTemplate template : templates) { - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -824,13 +827,13 @@ public void provisionOndemandSetsAwsNetworkingOnNetworkInterface() throws Except RunInstancesRequest actualRequest = riRequestCaptor.getValue(); InstanceNetworkInterfaceSpecification actualNet = - actualRequest.getNetworkInterfaces().get(0); + actualRequest.networkInterfaces().get(0); - assertEquals(actualNet.getSubnetId(), Util.fixEmpty(template.getSubnetId())); - assertEquals(actualNet.getGroups(), Stream.of("some-group-id").collect(Collectors.toList())); - assertNull(actualRequest.getSubnetId()); - assertEquals(actualRequest.getSecurityGroupIds(), Collections.emptyList()); - assertEquals(actualRequest.getSecurityGroups(), Collections.emptyList()); + assertEquals(actualNet.subnetId(), Util.fixEmpty(template.getSubnetId())); + assertEquals(actualNet.groups(), Stream.of("some-group-id").collect(Collectors.toList())); + assertNull(actualRequest.subnetId()); + assertEquals(actualRequest.securityGroupIds(), Collections.emptyList()); + assertEquals(actualRequest.securityGroups(), Collections.emptyList()); } } @@ -854,7 +857,7 @@ public void provisionSpotFallsBackToOndemandWhenSpotQuotaExceeded() throws Excep spotConfig, securityGroups, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -894,13 +897,17 @@ public void provisionSpotFallsBackToOndemandWhenSpotQuotaExceeded() throws Excep EC2AbstractSlave.DEFAULT_METADATA_HOPS_LIMIT, EC2AbstractSlave.DEFAULT_METADATA_SUPPORTED); - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); + + AwsServiceException quotaExceededException = Ec2Exception.builder() + .statusCode(400) + .requestId("00000000-0000-0000-0000-000000000000") + .awsErrorDetails(AwsErrorDetails.builder() + .serviceName("AmazonEC2") + .errorCode("MaxSpotInstanceCountExceeded") + .build()) + .build(); - AmazonEC2Exception quotaExceededException = new AmazonEC2Exception("Request has expired"); - quotaExceededException.setServiceName("AmazonEC2"); - quotaExceededException.setStatusCode(400); - quotaExceededException.setErrorCode("MaxSpotInstanceCountExceeded"); - quotaExceededException.setRequestId("00000000-0000-0000-0000-000000000000"); when(mockedEC2.requestSpotInstances(any(RequestSpotInstancesRequest.class))) .thenThrow(quotaExceededException); @@ -909,58 +916,60 @@ public void provisionSpotFallsBackToOndemandWhenSpotQuotaExceeded() throws Excep verify(mockedEC2).runInstances(any(RunInstancesRequest.class)); } - private AmazonEC2 setupTestForProvisioning(SlaveTemplate template) throws Exception { + private Ec2Client setupTestForProvisioning(SlaveTemplate template) throws Exception { EC2Cloud mockedCloud = mock(EC2Cloud.class); - AmazonEC2 mockedEC2 = mock(AmazonEC2.class); + Ec2Client mockedEC2 = mock(Ec2Client.class); EC2PrivateKey mockedPrivateKey = mock(EC2PrivateKey.class); - KeyPair mockedKeyPair = new KeyPair(); - mockedKeyPair.setKeyName("some-key-name"); + KeyPair mockedKeyPair = + new KeyPair(KeyPairInfo.builder().keyName("some-key-name").build(), "some-material"); when(mockedPrivateKey.find(mockedEC2)).thenReturn(mockedKeyPair); when(mockedCloud.connect()).thenReturn(mockedEC2); when(mockedCloud.resolvePrivateKey()).thenReturn(mockedPrivateKey); template.parent = mockedCloud; - DescribeImagesResult mockedImagesResult = mock(DescribeImagesResult.class); - Image mockedImage = new Image(); - mockedImage.setImageId(template.getAmi()); - when(mockedImagesResult.getImages()).thenReturn(Stream.of(mockedImage).collect(Collectors.toList())); + DescribeImagesResponse mockedImagesResult = mock(DescribeImagesResponse.class); + Image mockedImage = Image.builder().imageId(template.getAmi()).build(); + when(mockedImagesResult.images()).thenReturn(Stream.of(mockedImage).collect(Collectors.toList())); when(mockedEC2.describeImages(any(DescribeImagesRequest.class))).thenReturn(mockedImagesResult); - DescribeSecurityGroupsResult mockedSecurityGroupsResult = mock(DescribeSecurityGroupsResult.class); - SecurityGroup mockedSecurityGroup = new SecurityGroup(); - mockedSecurityGroup.setVpcId("some-vpc-id"); - mockedSecurityGroup.setGroupId("some-group-id"); + DescribeSecurityGroupsResponse mockedSecurityGroupsResult = mock(DescribeSecurityGroupsResponse.class); + SecurityGroup mockedSecurityGroup = SecurityGroup.builder() + .vpcId("some-vpc-id") + .groupId("some-group-id") + .build(); List mockedSecurityGroups = Stream.of(mockedSecurityGroup).collect(Collectors.toList()); - when(mockedSecurityGroupsResult.getSecurityGroups()).thenReturn(mockedSecurityGroups); + when(mockedSecurityGroupsResult.securityGroups()).thenReturn(mockedSecurityGroups); when(mockedEC2.describeSecurityGroups(any(DescribeSecurityGroupsRequest.class))) .thenReturn(mockedSecurityGroupsResult); - DescribeSubnetsResult mockedDescribeSubnetsResult = mock(DescribeSubnetsResult.class); - Subnet mockedSubnet = new Subnet(); + DescribeSubnetsResponse mockedDescribeSubnetsResult = mock(DescribeSubnetsResponse.class); + Subnet mockedSubnet = Subnet.builder().build(); List mockedSubnets = Stream.of(mockedSubnet).collect(Collectors.toList()); - when(mockedDescribeSubnetsResult.getSubnets()).thenReturn(mockedSubnets); + when(mockedDescribeSubnetsResult.subnets()).thenReturn(mockedSubnets); when(mockedEC2.describeSubnets(any(DescribeSubnetsRequest.class))).thenReturn(mockedDescribeSubnetsResult); - IamInstanceProfile mockedInstanceProfile = new IamInstanceProfile(); - mockedInstanceProfile.setArn(template.getIamInstanceProfile()); - InstanceState mockInstanceState = new InstanceState(); - mockInstanceState.setName("not terminated"); - Instance mockedInstance = new Instance(); - mockedInstance.setState(mockInstanceState); - mockedInstance.setIamInstanceProfile(mockedInstanceProfile); - Reservation mockedReservation = new Reservation(); - mockedReservation.setInstances(Stream.of(mockedInstance).collect(Collectors.toList())); + IamInstanceProfile.Builder mockedInstanceProfileBuilder = IamInstanceProfile.builder(); + mockedInstanceProfileBuilder.arn(template.getIamInstanceProfile()); + Instance mockedInstance = Instance.builder() + .state(software.amazon.awssdk.services.ec2.model.InstanceState.builder() + .name(InstanceStateName.RUNNING) + .build()) + .iamInstanceProfile(mockedInstanceProfileBuilder.build()) + .build(); + Reservation mockedReservation = Reservation.builder() + .instances(Stream.of(mockedInstance).collect(Collectors.toList())) + .build(); List mockedReservations = Stream.of(mockedReservation).collect(Collectors.toList()); - DescribeInstancesResult mockedDescribedInstancesResult = mock(DescribeInstancesResult.class); - when(mockedDescribedInstancesResult.getReservations()).thenReturn(mockedReservations); + DescribeInstancesResponse mockedDescribedInstancesResult = mock(DescribeInstancesResponse.class); + when(mockedDescribedInstancesResult.reservations()).thenReturn(mockedReservations); when(mockedEC2.describeInstances(any(DescribeInstancesRequest.class))) .thenReturn(mockedDescribedInstancesResult); - RunInstancesResult mockedResult = mock(RunInstancesResult.class); - when(mockedResult.getReservation()).thenReturn(mockedReservation); + RunInstancesResponse mockedResult = mock(RunInstancesResponse.class); + when(mockedResult.reservationId()).thenReturn(mockedReservation.reservationId()); when(mockedEC2.runInstances(any(RunInstancesRequest.class))).thenReturn(mockedResult); return mockedEC2; @@ -975,7 +984,7 @@ public void testMacConfig() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, "foo", - InstanceType.Mac1Metal, + InstanceType.MAC1_METAL.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1035,7 +1044,7 @@ public void testAgentName() { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1080,7 +1089,7 @@ public void testAgentName() { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1141,7 +1150,7 @@ public void testMetadataV2Config() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1202,7 +1211,7 @@ public void provisionOnDemandWithUnsupportedInstanceMetadata() throws Exception TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1242,7 +1251,7 @@ public void provisionOnDemandWithUnsupportedInstanceMetadata() throws Exception 2, false); - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -1250,7 +1259,7 @@ public void provisionOnDemandWithUnsupportedInstanceMetadata() throws Exception verify(mockedEC2).runInstances(riRequestCaptor.capture()); RunInstancesRequest actualRequest = riRequestCaptor.getValue(); - InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.getMetadataOptions(); + InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.metadataOptions(); assertNull(metadataOptionsRequest); } @@ -1262,7 +1271,7 @@ public void provisionOnDemandSetsMetadataV1Options() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1302,7 +1311,7 @@ public void provisionOnDemandSetsMetadataV1Options() throws Exception { 2, true); - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -1310,10 +1319,10 @@ public void provisionOnDemandSetsMetadataV1Options() throws Exception { verify(mockedEC2).runInstances(riRequestCaptor.capture()); RunInstancesRequest actualRequest = riRequestCaptor.getValue(); - InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.getMetadataOptions(); - assertEquals(metadataOptionsRequest.getHttpEndpoint(), InstanceMetadataEndpointState.Enabled.toString()); - assertEquals(metadataOptionsRequest.getHttpTokens(), HttpTokensState.Optional.toString()); - assertEquals(metadataOptionsRequest.getHttpPutResponseHopLimit(), Integer.valueOf(2)); + InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.metadataOptions(); + assertEquals(metadataOptionsRequest.httpEndpoint(), InstanceMetadataEndpointState.ENABLED); + assertEquals(metadataOptionsRequest.httpTokens(), HttpTokensState.OPTIONAL); + assertEquals(metadataOptionsRequest.httpPutResponseHopLimit(), Integer.valueOf(2)); } @Test @@ -1324,7 +1333,7 @@ public void provisionOnDemandSetsMetadataV2Options() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1364,7 +1373,7 @@ public void provisionOnDemandSetsMetadataV2Options() throws Exception { 2, true); - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -1372,10 +1381,10 @@ public void provisionOnDemandSetsMetadataV2Options() throws Exception { verify(mockedEC2).runInstances(riRequestCaptor.capture()); RunInstancesRequest actualRequest = riRequestCaptor.getValue(); - InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.getMetadataOptions(); - assertEquals(metadataOptionsRequest.getHttpEndpoint(), InstanceMetadataEndpointState.Enabled.toString()); - assertEquals(metadataOptionsRequest.getHttpTokens(), HttpTokensState.Required.toString()); - assertEquals(metadataOptionsRequest.getHttpPutResponseHopLimit(), Integer.valueOf(2)); + InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.metadataOptions(); + assertEquals(metadataOptionsRequest.httpEndpoint(), InstanceMetadataEndpointState.ENABLED); + assertEquals(metadataOptionsRequest.httpTokens(), HttpTokensState.REQUIRED); + assertEquals(metadataOptionsRequest.httpPutResponseHopLimit(), Integer.valueOf(2)); } @Test @@ -1386,7 +1395,7 @@ public void provisionOnDemandSetsMetadataDefaultOptions() throws Exception { TEST_SPOT_CFG, TEST_SEC_GROUPS, TEST_REMOTE_FS, - TEST_INSTANCE_TYPE, + TEST_INSTANCE_TYPE.toString(), TEST_EBSO, TEST_LABEL, Node.Mode.NORMAL, @@ -1426,7 +1435,7 @@ public void provisionOnDemandSetsMetadataDefaultOptions() throws Exception { null, true); - AmazonEC2 mockedEC2 = setupTestForProvisioning(template); + Ec2Client mockedEC2 = setupTestForProvisioning(template); ArgumentCaptor riRequestCaptor = ArgumentCaptor.forClass(RunInstancesRequest.class); @@ -1434,10 +1443,10 @@ public void provisionOnDemandSetsMetadataDefaultOptions() throws Exception { verify(mockedEC2).runInstances(riRequestCaptor.capture()); RunInstancesRequest actualRequest = riRequestCaptor.getValue(); - InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.getMetadataOptions(); - assertEquals(metadataOptionsRequest.getHttpEndpoint(), InstanceMetadataEndpointState.Enabled.toString()); - assertEquals(metadataOptionsRequest.getHttpTokens(), HttpTokensState.Required.toString()); - assertEquals(metadataOptionsRequest.getHttpPutResponseHopLimit(), Integer.valueOf(1)); + InstanceMetadataOptionsRequest metadataOptionsRequest = actualRequest.metadataOptions(); + assertEquals(metadataOptionsRequest.httpEndpoint(), InstanceMetadataEndpointState.ENABLED); + assertEquals(metadataOptionsRequest.httpTokens(), HttpTokensState.REQUIRED); + assertEquals(metadataOptionsRequest.httpPutResponseHopLimit(), Integer.valueOf(1)); } private HtmlForm getConfigForm(EC2Cloud ac) throws IOException, SAXException { diff --git a/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java b/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java index 861622086..d10fc8aeb 100644 --- a/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java +++ b/src/test/java/hudson/plugins/ec2/SlaveTemplateUnitTest.java @@ -7,18 +7,6 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import com.amazonaws.AmazonClientException; -import com.amazonaws.AmazonServiceException; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.AmazonEC2Client; -import com.amazonaws.services.ec2.model.BlockDeviceMapping; -import com.amazonaws.services.ec2.model.CreateTagsResult; -import com.amazonaws.services.ec2.model.DescribeImagesRequest; -import com.amazonaws.services.ec2.model.EbsBlockDevice; -import com.amazonaws.services.ec2.model.Filter; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.InstanceType; -import com.amazonaws.services.ec2.model.Tag; import hudson.model.Node; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -36,6 +24,19 @@ import org.junit.Before; import org.junit.Test; import org.jvnet.hudson.test.Issue; +import software.amazon.awssdk.awscore.exception.AwsErrorDetails; +import software.amazon.awssdk.awscore.exception.AwsServiceException; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.CreateTagsRequest; +import software.amazon.awssdk.services.ec2.model.CreateTagsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest; +import software.amazon.awssdk.services.ec2.model.EbsBlockDevice; +import software.amazon.awssdk.services.ec2.model.Filter; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.InstanceType; +import software.amazon.awssdk.services.ec2.model.Tag; public class SlaveTemplateUnitTest { @@ -51,11 +52,19 @@ public void setUp() throws Exception { @Test public void testUpdateRemoteTags() throws Exception { - AmazonEC2 ec2 = new AmazonEC2Client() { + Ec2Client ec2 = new Ec2Client() { @Override - public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRequest createTagsRequest) { + public CreateTagsResponse createTags(CreateTagsRequest createTagsRequest) { return null; } + + @Override + public void close() {} + + @Override + public String serviceName() { + return "AmazonEC2"; + } }; String ami = "ami1"; @@ -75,7 +84,7 @@ public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRe null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -121,11 +130,17 @@ protected Object readResolve() { }; ArrayList awsTags = new ArrayList<>(); - awsTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, "value1")); - awsTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, "value2")); + awsTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .value("value1") + .build()); + awsTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .value("value2") + .build()); Method updateRemoteTags = SlaveTemplate.class.getDeclaredMethod( - "updateRemoteTags", AmazonEC2.class, Collection.class, String.class, String[].class); + "updateRemoteTags", Ec2Client.class, Collection.class, String.class, String[].class); updateRemoteTags.setAccessible(true); final Object[] params = {ec2, awsTags, "InvalidInstanceRequestID.NotFound", new String[] {instanceId}}; updateRemoteTags.invoke(orig, params); @@ -134,14 +149,25 @@ protected Object readResolve() { @Test public void testUpdateRemoteTagsInstanceNotFound() throws Exception { - AmazonEC2 ec2 = new AmazonEC2Client() { + Ec2Client ec2 = new Ec2Client() { @Override - public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRequest createTagsRequest) { - AmazonServiceException e = - new AmazonServiceException("Instance not found - InvalidInstanceRequestID.NotFound"); - e.setErrorCode("InvalidInstanceRequestID.NotFound"); + public CreateTagsResponse createTags(CreateTagsRequest createTagsRequest) { + AwsServiceException e = AwsServiceException.builder() + .message("Instance not found - InvalidInstanceRequestID.NotFound") + .awsErrorDetails(AwsErrorDetails.builder() + .errorCode("InvalidInstanceRequestID.NotFound") + .build()) + .build(); throw e; } + + @Override + public void close() {} + + @Override + public String serviceName() { + return "AmazonEC2"; + } }; String ami = "ami1"; @@ -161,7 +187,7 @@ public CreateTagsResult createTags(com.amazonaws.services.ec2.model.CreateTagsRe null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -207,11 +233,17 @@ protected Object readResolve() { }; ArrayList awsTags = new ArrayList<>(); - awsTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, "value1")); - awsTags.add(new Tag(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE, "value2")); + awsTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .value("value1") + .build()); + awsTags.add(Tag.builder() + .key(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) + .value("value2") + .build()); Method updateRemoteTags = SlaveTemplate.class.getDeclaredMethod( - "updateRemoteTags", AmazonEC2.class, Collection.class, String.class, String[].class); + "updateRemoteTags", Ec2Client.class, Collection.class, String.class, String[].class); updateRemoteTags.setAccessible(true); final Object[] params = {ec2, awsTags, "InvalidSpotInstanceRequestID.NotFound", new String[] {instanceId}}; updateRemoteTags.invoke(orig, params); @@ -219,7 +251,7 @@ protected Object readResolve() { assertEquals(5, handler.getRecords().size()); for (LogRecord logRecord : handler.getRecords()) { - String log = logRecord.getMessage(); + String log = logRecord.getThrown().getMessage(); assertTrue(log.contains("Instance not found - InvalidInstanceRequestID.NotFound")); } } @@ -244,7 +276,7 @@ private void doTestMakeDescribeImagesRequest( Method makeDescribeImagesRequest = SlaveTemplate.class.getDeclaredMethod("makeDescribeImagesRequest"); makeDescribeImagesRequest.setAccessible(true); if (shouldRaise) { - assertThrows(AmazonClientException.class, () -> { + assertThrows(SdkException.class, () -> { try { makeDescribeImagesRequest.invoke(template); } catch (InvocationTargetException e) { @@ -253,10 +285,10 @@ private void doTestMakeDescribeImagesRequest( }); } else { DescribeImagesRequest request = (DescribeImagesRequest) makeDescribeImagesRequest.invoke(template); - assertEquals(expectedImageIds, request.getImageIds()); - assertEquals(expectedOwners, request.getOwners()); - assertEquals(expectedUsers, request.getExecutableUsers()); - assertEquals(expectedFilters, request.getFilters()); + assertEquals(expectedImageIds, request.imageIds()); + assertEquals(expectedOwners, request.owners()); + assertEquals(expectedUsers, request.executableUsers()); + assertEquals(expectedFilters, request.filters()); } } @@ -269,7 +301,7 @@ public void testMakeDescribeImagesRequest() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -377,7 +409,10 @@ protected Object readResolve() { testFilters = Collections.singletonList(new EC2Filter("foo", "bar")); expectedOwners = Collections.singletonList("self"); expectedUsers = Collections.singletonList("self"); - expectedFilters = Collections.singletonList(new Filter("foo", Collections.singletonList("bar"))); + expectedFilters = Collections.singletonList(Filter.builder() + .name("foo") + .values(Collections.singletonList("bar")) + .build()); doTestMakeDescribeImagesRequest( template, testImageId, @@ -422,8 +457,14 @@ protected Object readResolve() { // Make sure multiple filters pass through correctly testFilters = Arrays.asList(new EC2Filter("foo", "bar"), new EC2Filter("baz", "blah")); expectedFilters = Arrays.asList( - new Filter("foo", Collections.singletonList("bar")), - new Filter("baz", Collections.singletonList("blah"))); + Filter.builder() + .name("foo") + .values(Collections.singletonList("bar")) + .build(), + Filter.builder() + .name("baz") + .values(Collections.singletonList("blah")) + .build()); doTestMakeDescribeImagesRequest( template, testImageId, @@ -446,7 +487,10 @@ protected Object readResolve() { }; expectedOwners = Arrays.asList("self", "amazon"); expectedUsers = Arrays.asList("self", "all"); - expectedFilters = Collections.singletonList(new Filter("foo", Arrays.asList("a'quote", "s p a c e s"))); + expectedFilters = Collections.singletonList(Filter.builder() + .name("foo") + .values(Arrays.asList("a'quote", "s p a c e s")) + .build()); for (String[] entry : testCases) { logger.info("Multivalue test entry: [" + String.join(",", entry) + "]"); @@ -475,7 +519,7 @@ private Boolean checkEncryptedForSetupRootDevice(EbsEncryptRootVolume rootVolume null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -519,21 +563,21 @@ protected Object readResolve() { return null; } }; - List deviceMappings = new ArrayList(); - deviceMappings.add(deviceMappings); - - Image image = new Image(); - image.setRootDeviceType("ebs"); - BlockDeviceMapping blockDeviceMapping = new BlockDeviceMapping(); - blockDeviceMapping.setEbs(new EbsBlockDevice()); - image.getBlockDeviceMappings().add(blockDeviceMapping); + BlockDeviceMapping blockDeviceMapping = BlockDeviceMapping.builder() + .ebs(EbsBlockDevice.builder().build()) + .build(); + Image image = Image.builder() + .rootDeviceType("ebs") + .blockDeviceMappings(blockDeviceMapping) + .build(); + List deviceMappings = new ArrayList<>(image.blockDeviceMappings()); if (rootVolumeEnum instanceof EbsEncryptRootVolume) { template.ebsEncryptRootVolume = rootVolumeEnum; } Method setupRootDevice = SlaveTemplate.class.getDeclaredMethod("setupRootDevice", Image.class, List.class); setupRootDevice.setAccessible(true); setupRootDevice.invoke(template, image, deviceMappings); - return image.getBlockDeviceMappings().get(0).getEbs().getEncrypted(); + return deviceMappings.get(0).ebs().encrypted(); } @Test @@ -568,7 +612,7 @@ public void testNullTimeoutShouldReturnMaxInt() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -618,7 +662,7 @@ public void testUpdateAmi() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -672,7 +716,7 @@ public void test0TimeoutShouldReturnMaxInt() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -722,7 +766,7 @@ public void testNegativeTimeoutShouldReturnMaxInt() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -772,7 +816,7 @@ public void testNonNumericTimeoutShouldReturnMaxInt() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -822,7 +866,7 @@ public void testAssociatePublicIpSetting() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -872,7 +916,7 @@ public void testConnectUsingPublicIpSetting() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -922,7 +966,7 @@ public void testConnectUsingPublicIpSettingWithDefaultSetting() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -972,7 +1016,7 @@ public void testBackwardCompatibleUnixData() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1024,7 +1068,7 @@ public void testChooseSpaceDelimitedSubnetId() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1081,7 +1125,7 @@ public void testChooseCommaDelimitedSubnetId() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1138,7 +1182,7 @@ public void testChooseSemicolonDelimitedSubnetId() throws Exception { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, @@ -1196,7 +1240,7 @@ public void testConnectionStrategyDeprecatedFieldsAreExported() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, diff --git a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java index 3844c5991..06e80774e 100644 --- a/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java +++ b/src/test/java/hudson/plugins/ec2/TemplateLabelsTest.java @@ -26,7 +26,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import com.amazonaws.services.ec2.model.InstanceType; import hudson.model.Label; import hudson.model.Node; import hudson.model.labels.LabelAtom; @@ -36,6 +35,7 @@ import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import software.amazon.awssdk.services.ec2.model.InstanceType; public class TemplateLabelsTest { @@ -63,7 +63,7 @@ private void setUpCloud(String label, Node.Mode mode) throws Exception { null, "default", "zone", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, label, mode, diff --git a/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java b/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java index 18d0b3aab..0cc61c987 100644 --- a/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java +++ b/src/test/java/hudson/plugins/ec2/ssh/verifiers/SshHostKeyVerificationStrategyTest.java @@ -4,8 +4,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.emptyString; -import com.amazonaws.AmazonClientException; -import com.amazonaws.services.ec2.model.InstanceType; import com.trilead.ssh2.Connection; import com.trilead.ssh2.ServerHostKeyVerifier; import hudson.model.Node; @@ -28,6 +26,8 @@ import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; import org.testcontainers.containers.Container; +import software.amazon.awssdk.core.exception.SdkException; +import software.amazon.awssdk.services.ec2.model.InstanceType; public class SshHostKeyVerificationStrategyTest { private static final String COMPUTER_NAME = "MockInstanceForTest"; @@ -397,7 +397,7 @@ public String getEc2Type() { } @Override - public String getDecodedConsoleOutput() throws AmazonClientException { + public String getDecodedConsoleOutput() throws SdkException { return console; } @@ -419,7 +419,7 @@ public SlaveTemplate getSlaveTemplate() { null, "default", "foo", - InstanceType.M1Large, + InstanceType.M1_LARGE.toString(), false, "ttt", Node.Mode.NORMAL, diff --git a/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java b/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java index 7d04514e2..0259ecc55 100644 --- a/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java +++ b/src/test/java/hudson/plugins/ec2/util/AmazonEC2FactoryMockImpl.java @@ -2,54 +2,53 @@ import static org.mockito.Mockito.mock; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.services.ec2.AmazonEC2; -import com.amazonaws.services.ec2.AmazonEC2Client; -import com.amazonaws.services.ec2.model.BlockDeviceMapping; -import com.amazonaws.services.ec2.model.DescribeImagesRequest; -import com.amazonaws.services.ec2.model.DescribeImagesResult; -import com.amazonaws.services.ec2.model.DescribeInstancesRequest; -import com.amazonaws.services.ec2.model.DescribeInstancesResult; -import com.amazonaws.services.ec2.model.DescribeKeyPairsResult; -import com.amazonaws.services.ec2.model.DescribeRegionsResult; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest; -import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; -import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsResult; -import com.amazonaws.services.ec2.model.DescribeSubnetsRequest; -import com.amazonaws.services.ec2.model.DescribeSubnetsResult; -import com.amazonaws.services.ec2.model.EbsBlockDevice; -import com.amazonaws.services.ec2.model.Image; -import com.amazonaws.services.ec2.model.Instance; -import com.amazonaws.services.ec2.model.InstanceState; -import com.amazonaws.services.ec2.model.InstanceStateChange; -import com.amazonaws.services.ec2.model.InstanceStateName; -import com.amazonaws.services.ec2.model.KeyPairInfo; -import com.amazonaws.services.ec2.model.Region; -import com.amazonaws.services.ec2.model.Reservation; -import com.amazonaws.services.ec2.model.RunInstancesRequest; -import com.amazonaws.services.ec2.model.RunInstancesResult; -import com.amazonaws.services.ec2.model.SecurityGroup; -import com.amazonaws.services.ec2.model.SpotInstanceRequest; -import com.amazonaws.services.ec2.model.SpotInstanceState; -import com.amazonaws.services.ec2.model.SpotInstanceType; -import com.amazonaws.services.ec2.model.Subnet; -import com.amazonaws.services.ec2.model.Tag; -import com.amazonaws.services.ec2.model.TagSpecification; -import com.amazonaws.services.ec2.model.TerminateInstancesRequest; -import com.amazonaws.services.ec2.model.TerminateInstancesResult; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.Extension; import hudson.plugins.ec2.EC2Cloud; -import java.net.URL; +import java.net.URI; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.stream.Collectors; import jenkins.model.Jenkins; import org.mockito.Mockito; import org.mockito.stubbing.Answer; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.ec2.Ec2Client; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.DescribeImagesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeImagesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest; +import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse; +import software.amazon.awssdk.services.ec2.model.DescribeKeyPairsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSpotInstanceRequestsResponse; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsRequest; +import software.amazon.awssdk.services.ec2.model.DescribeSubnetsResponse; +import software.amazon.awssdk.services.ec2.model.EbsBlockDevice; +import software.amazon.awssdk.services.ec2.model.Image; +import software.amazon.awssdk.services.ec2.model.Instance; +import software.amazon.awssdk.services.ec2.model.InstanceState; +import software.amazon.awssdk.services.ec2.model.InstanceStateChange; +import software.amazon.awssdk.services.ec2.model.InstanceStateName; +import software.amazon.awssdk.services.ec2.model.KeyPairInfo; +import software.amazon.awssdk.services.ec2.model.Reservation; +import software.amazon.awssdk.services.ec2.model.RunInstancesRequest; +import software.amazon.awssdk.services.ec2.model.RunInstancesResponse; +import software.amazon.awssdk.services.ec2.model.SecurityGroup; +import software.amazon.awssdk.services.ec2.model.SpotInstanceRequest; +import software.amazon.awssdk.services.ec2.model.SpotInstanceState; +import software.amazon.awssdk.services.ec2.model.SpotInstanceType; +import software.amazon.awssdk.services.ec2.model.Subnet; +import software.amazon.awssdk.services.ec2.model.Tag; +import software.amazon.awssdk.services.ec2.model.TagSpecification; +import software.amazon.awssdk.services.ec2.model.TerminateInstancesRequest; +import software.amazon.awssdk.services.ec2.model.TerminateInstancesResponse; @Extension public class AmazonEC2FactoryMockImpl implements AmazonEC2Factory { @@ -57,7 +56,7 @@ public class AmazonEC2FactoryMockImpl implements AmazonEC2Factory { /** * public static to allow supplying own mock to tests. */ - public static AmazonEC2Client mock; + public static Ec2Client mock; public static List instances; @@ -66,7 +65,7 @@ public class AmazonEC2FactoryMockImpl implements AmazonEC2Factory { * * @return mocked AmazonEC2 */ - public static AmazonEC2Client createAmazonEC2Mock() { + public static Ec2Client createAmazonEC2Mock() { instances = new ArrayList<>(); // Reset for each new mock. In the real world, the client is stateless, but this // is convenient for testing. return createAmazonEC2Mock(null); @@ -79,12 +78,12 @@ public static AmazonEC2Client createAmazonEC2Mock() { * nullable * @return mocked AmazonEC2 */ - public static AmazonEC2Client createAmazonEC2Mock(@Nullable Answer defaultAnswer) { - AmazonEC2Client mock; + public static Ec2Client createAmazonEC2Mock(@Nullable Answer defaultAnswer) { + Ec2Client mock; if (defaultAnswer != null) { - mock = mock(AmazonEC2Client.class, defaultAnswer); + mock = mock(Ec2Client.class, defaultAnswer); } else { - mock = mock(AmazonEC2Client.class); + mock = mock(Ec2Client.class); mockDescribeRegions(mock); mockDescribeInstances(mock); mockDescribeImages(mock); @@ -98,173 +97,204 @@ public static AmazonEC2Client createAmazonEC2Mock(@Nullable Answer defaultAnswer return mock; } - private static void mockDescribeRegions(AmazonEC2Client mock) { - DescribeRegionsResult describeRegionsResultMock = mock(DescribeRegionsResult.class); - Mockito.doReturn(Collections.singletonList(new Region().withRegionName(EC2Cloud.DEFAULT_EC2_HOST))) + private static void mockDescribeRegions(Ec2Client mock) { + DescribeRegionsResponse describeRegionsResultMock = mock(DescribeRegionsResponse.class); + Mockito.doReturn(Collections.singletonList(software.amazon.awssdk.services.ec2.model.Region.builder() + .regionName(EC2Cloud.DEFAULT_EC2_HOST) + .build())) .when(describeRegionsResultMock) - .getRegions(); + .regions(); Mockito.doReturn(describeRegionsResultMock).when(mock).describeRegions(); } - private static void mockDescribeInstances(AmazonEC2Client mock) { + private static void mockDescribeInstances(Ec2Client mock) { Mockito.doCallRealMethod().when(mock).describeInstances(); // This will just pass on to // describeInstances(describeInstancesRequest) Mockito.doAnswer(invocationOnMock -> { DescribeInstancesRequest request = invocationOnMock.getArgument(0); - if (request.getInstanceIds() != null - && !request.getInstanceIds().isEmpty()) { - return new DescribeInstancesResult() - .withReservations(new Reservation() - .withInstances(instances.stream() + if (request.instanceIds() != null && !request.instanceIds().isEmpty()) { + return DescribeInstancesResponse.builder() + .reservations(Reservation.builder() + .instances(instances.stream() .filter(instance -> - request.getInstanceIds().contains(instance.getInstanceId())) - .collect(Collectors.toList()))); + request.instanceIds().contains(instance.instanceId())) + .collect(Collectors.toList())) + .build()) + .build(); } - return new DescribeInstancesResult().withReservations(new Reservation().withInstances(instances)); + return DescribeInstancesResponse.builder() + .reservations( + Reservation.builder().instances(instances).build()) + .build(); }) .when(mock) .describeInstances(Mockito.any(DescribeInstancesRequest.class)); } - private static void mockDescribeSpotInstanceRequests(AmazonEC2Client mock) { - DescribeSpotInstanceRequestsResult describeSpotInstanceRequestsResult = - new DescribeSpotInstanceRequestsResult(); + private static void mockDescribeSpotInstanceRequests(Ec2Client mock) { + DescribeSpotInstanceRequestsResponse.Builder describeSpotInstanceRequestsResultBuilder = + DescribeSpotInstanceRequestsResponse.builder(); List spotInstanceRequests = new ArrayList<>(); for (Instance instance : instances) { - SpotInstanceRequest spotInstanceRequest = new SpotInstanceRequest() - .withInstanceId(instance.getInstanceId()) - .withTags(instance.getTags()) - .withState(SpotInstanceState.Active) - .withType(SpotInstanceType.OneTime); + SpotInstanceRequest spotInstanceRequest = SpotInstanceRequest.builder() + .instanceId(instance.instanceId()) + .tags(instance.tags()) + .state(SpotInstanceState.ACTIVE) + .type(SpotInstanceType.ONE_TIME) + .build(); spotInstanceRequests.add(spotInstanceRequest); } Mockito.doAnswer(invocationOnMock -> { DescribeSpotInstanceRequestsRequest request = invocationOnMock.getArgument(0); - int paginationSize = request.getMaxResults(); + int paginationSize = request.maxResults(); if (paginationSize == 0) { paginationSize = instances.size(); } - if (instances.size() > paginationSize && request.getNextToken() == null) { - describeSpotInstanceRequestsResult.setNextToken("token"); - describeSpotInstanceRequestsResult.setSpotInstanceRequests( + if (instances.size() > paginationSize && request.nextToken() == null) { + describeSpotInstanceRequestsResultBuilder.nextToken("token"); + describeSpotInstanceRequestsResultBuilder.spotInstanceRequests( spotInstanceRequests.subList(0, 100)); } else if (instances.size() <= paginationSize) { - describeSpotInstanceRequestsResult.setSpotInstanceRequests(spotInstanceRequests); - describeSpotInstanceRequestsResult.setNextToken(null); + describeSpotInstanceRequestsResultBuilder.spotInstanceRequests(spotInstanceRequests); + describeSpotInstanceRequestsResultBuilder.nextToken(null); } else { - describeSpotInstanceRequestsResult.setSpotInstanceRequests( + describeSpotInstanceRequestsResultBuilder.spotInstanceRequests( spotInstanceRequests.subList(100, spotInstanceRequests.size() - 1)); - describeSpotInstanceRequestsResult.setNextToken(null); + describeSpotInstanceRequestsResultBuilder.nextToken(null); } - return describeSpotInstanceRequestsResult; + return describeSpotInstanceRequestsResultBuilder.build(); }) .when(mock) .describeSpotInstanceRequests(Mockito.any(DescribeSpotInstanceRequestsRequest.class)); } - private static void mockDescribeImages(AmazonEC2Client mock) { + private static void mockDescribeImages(Ec2Client mock) { Mockito.doAnswer(invocationOnMock -> { DescribeImagesRequest request = invocationOnMock.getArgument(0); - return new DescribeImagesResult() - .withImages(request.getImageIds().stream() + return DescribeImagesResponse.builder() + .images(request.imageIds().stream() .map(AmazonEC2FactoryMockImpl::createMockImage) - .collect(Collectors.toList())); + .collect(Collectors.toList())) + .build(); }) .when(mock) .describeImages(Mockito.any(DescribeImagesRequest.class)); } private static Image createMockImage(String amiId) { - return new Image() - .withImageId(amiId) - .withRootDeviceType("ebs") - .withBlockDeviceMappings( - new BlockDeviceMapping().withDeviceName("/dev/null").withEbs(new EbsBlockDevice())); + return Image.builder() + .imageId(amiId) + .rootDeviceType("ebs") + .blockDeviceMappings(BlockDeviceMapping.builder() + .deviceName("/dev/null") + .ebs(EbsBlockDevice.builder().build()) + .build()) + .build(); } - private static void mockDescribeKeyPairs(AmazonEC2Client mock) { + private static void mockDescribeKeyPairs(Ec2Client mock) { Mockito.doAnswer(invocationOnMock -> { - KeyPairInfo keyPairInfo = new KeyPairInfo(); - keyPairInfo.setKeyFingerprint(Jenkins.get() - .clouds - .get(EC2Cloud.class) - .resolvePrivateKey() - .getFingerprint()); - return new DescribeKeyPairsResult().withKeyPairs(keyPairInfo); + KeyPairInfo keyPairInfo = KeyPairInfo.builder() + .keyFingerprint(Jenkins.get() + .clouds + .get(EC2Cloud.class) + .resolvePrivateKey() + .getFingerprint()) + .build(); + return DescribeKeyPairsResponse.builder() + .keyPairs(keyPairInfo) + .build(); }) .when(mock) .describeKeyPairs(); } - private static void mockDescribeSecurityGroups(AmazonEC2Client mock) { - Mockito.doAnswer(invocationOnMock -> new DescribeSecurityGroupsResult() - .withSecurityGroups(new SecurityGroup().withVpcId("whatever"))) + private static void mockDescribeSecurityGroups(Ec2Client mock) { + Mockito.doAnswer(invocationOnMock -> DescribeSecurityGroupsResponse.builder() + .securityGroups( + SecurityGroup.builder().vpcId("whatever").build()) + .build()) .when(mock) .describeSecurityGroups(Mockito.any(DescribeSecurityGroupsRequest.class)); } - private static void mockDescribeSubnets(AmazonEC2Client mock) { - Mockito.doAnswer(invocationOnMock -> new DescribeSubnetsResult().withSubnets(new Subnet())) + private static void mockDescribeSubnets(Ec2Client mock) { + Mockito.doAnswer(invocationOnMock -> DescribeSubnetsResponse.builder() + .subnets(Subnet.builder().build()) + .build()) .when(mock) .describeSubnets(Mockito.any(DescribeSubnetsRequest.class)); } - private static void mockRunInstances(AmazonEC2Client mock) { + private static void mockRunInstances(Ec2Client mock) { Mockito.doAnswer(invocationOnMock -> { RunInstancesRequest request = invocationOnMock.getArgument(0); - List tags = request.getTagSpecifications().stream() - .map(TagSpecification::getTags) + List tags = request.tagSpecifications().stream() + .map(TagSpecification::tags) .flatMap(List::stream) .collect(Collectors.toList()); List localInstances = new ArrayList<>(); - for (int i = 0; i < request.getMaxCount(); i++) { - Instance instance = new Instance() - .withInstanceId(String.valueOf(Math.random())) - .withInstanceType(request.getInstanceType()) - .withImageId(request.getImageId()) - .withTags(tags) - .withState(new com.amazonaws.services.ec2.model.InstanceState() - .withName(InstanceStateName.Running)) - .withLaunchTime(new Date()); + for (int i = 0; i < request.maxCount(); i++) { + Instance instance = Instance.builder() + .instanceId(String.valueOf(Math.random())) + .instanceType(request.instanceType()) + .imageId(request.imageId()) + .tags(tags) + .state(InstanceState.builder() + .name(InstanceStateName.RUNNING) + .build()) + .launchTime(Instant.now()) + .build(); localInstances.add(instance); } instances.addAll(localInstances); - return new RunInstancesResult().withReservation(new Reservation().withInstances(localInstances)); + return RunInstancesResponse.builder() + .reservationId(Reservation.builder() + .instances(localInstances) + .build() + .reservationId()) + .instances(localInstances) + .build(); }) .when(mock) .runInstances(Mockito.any(RunInstancesRequest.class)); } - private static void mockTerminateInstances(AmazonEC2Client mock) { + private static void mockTerminateInstances(Ec2Client mock) { Mockito.doAnswer(invocationOnMock -> { TerminateInstancesRequest request = invocationOnMock.getArgument(0); List instancesToRemove = new ArrayList<>(); - request.getInstanceIds().forEach(instanceId -> instances.stream() - .filter(instance -> instance.getInstanceId().equals(instanceId)) + request.instanceIds().forEach(instanceId -> instances.stream() + .filter(instance -> instance.instanceId().equals(instanceId)) .findFirst() .ifPresent(instancesToRemove::add)); instances.removeAll(instancesToRemove); - return new TerminateInstancesResult() - .withTerminatingInstances(instancesToRemove.stream() - .map(instance -> new InstanceStateChange() - .withInstanceId(instance.getInstanceId()) - .withPreviousState(new InstanceState().withName(InstanceStateName.Stopping)) - .withCurrentState( - new InstanceState().withName(InstanceStateName.Terminated))) - .collect(Collectors.toList())); + return TerminateInstancesResponse.builder() + .terminatingInstances(instancesToRemove.stream() + .map(instance -> InstanceStateChange.builder() + .instanceId(instance.instanceId()) + .previousState(InstanceState.builder() + .name(InstanceStateName.STOPPING) + .build()) + .currentState(InstanceState.builder() + .name(InstanceStateName.TERMINATED) + .build()) + .build()) + .collect(Collectors.toList())) + .build(); }) .when(mock) .terminateInstances(Mockito.any(TerminateInstancesRequest.class)); } @Override - public AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL ec2Endpoint) { + public Ec2Client connect(AwsCredentialsProvider credentialsProvider, Region region, URI endpoint) { if (mock == null) { mock = createAmazonEC2Mock(); } diff --git a/src/test/java/hudson/plugins/ec2/util/DeviceMappingParserTest.java b/src/test/java/hudson/plugins/ec2/util/DeviceMappingParserTest.java index 42690a672..217f8729a 100644 --- a/src/test/java/hudson/plugins/ec2/util/DeviceMappingParserTest.java +++ b/src/test/java/hudson/plugins/ec2/util/DeviceMappingParserTest.java @@ -25,20 +25,21 @@ import static org.junit.Assert.assertEquals; -import com.amazonaws.services.ec2.model.BlockDeviceMapping; -import com.amazonaws.services.ec2.model.EbsBlockDevice; import java.util.ArrayList; import java.util.List; import org.junit.Test; +import software.amazon.awssdk.services.ec2.model.BlockDeviceMapping; +import software.amazon.awssdk.services.ec2.model.EbsBlockDevice; public class DeviceMappingParserTest { @Test public void testParserWithAmi() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdb") - .withEbs(new EbsBlockDevice().withSnapshotId("snap-7eb96d16"))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdb") + .ebs(EbsBlockDevice.builder().snapshotId("snap-7eb96d16").build()) + .build()); String customDeviceMappings = "/dev/sdb=snap-7eb96d16"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -48,12 +49,14 @@ public void testParserWithAmi() { @Test public void testParserWithTermination() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdc") - .withEbs(new EbsBlockDevice() - .withSnapshotId("snap-7eb96d16") - .withVolumeSize(80) - .withDeleteOnTermination(false))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdc") + .ebs(EbsBlockDevice.builder() + .snapshotId("snap-7eb96d16") + .volumeSize(80) + .deleteOnTermination(false) + .build()) + .build()); String customDeviceMappings = "/dev/sdc=snap-7eb96d16:80:false"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -63,14 +66,16 @@ public void testParserWithTermination() { @Test public void testParserWithIo() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdc") - .withEbs(new EbsBlockDevice() - .withSnapshotId("snap-7eb96d16") - .withVolumeSize(80) - .withDeleteOnTermination(false) - .withVolumeType("io1") - .withIops(100))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdc") + .ebs(EbsBlockDevice.builder() + .snapshotId("snap-7eb96d16") + .volumeSize(80) + .deleteOnTermination(false) + .volumeType("io1") + .iops(100) + .build()) + .build()); String customDeviceMappings = "/dev/sdc=snap-7eb96d16:80:false:io1:100"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -80,8 +85,10 @@ public void testParserWithIo() { @Test public void testParserWithSize() { List expected = new ArrayList<>(); - expected.add( - new BlockDeviceMapping().withDeviceName("/dev/sdd").withEbs(new EbsBlockDevice().withVolumeSize(120))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdd") + .ebs(EbsBlockDevice.builder().volumeSize(120).build()) + .build()); String customDeviceMappings = "/dev/sdd=:120"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -91,9 +98,10 @@ public void testParserWithSize() { @Test public void testParserWithEncrypted() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdd") - .withEbs(new EbsBlockDevice().withVolumeSize(120).withEncrypted(true))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdd") + .ebs(EbsBlockDevice.builder().volumeSize(120).encrypted(true).build()) + .build()); String customDeviceMappings = "/dev/sdd=:120::::encrypted"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -103,9 +111,10 @@ public void testParserWithEncrypted() { @Test public void testParserWithThroughput() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdd") - .withEbs(new EbsBlockDevice().withVolumeSize(120).withThroughput(1000))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdd") + .ebs(EbsBlockDevice.builder().volumeSize(120).throughput(1000).build()) + .build()); String customDeviceMappings = "/dev/sdd=:120:::::1000"; List actual = DeviceMappingParser.parse(customDeviceMappings); @@ -115,11 +124,14 @@ public void testParserWithThroughput() { @Test public void testParserWithMultiple() { List expected = new ArrayList<>(); - expected.add(new BlockDeviceMapping() - .withDeviceName("/dev/sdd") - .withEbs(new EbsBlockDevice().withVolumeSize(120).withEncrypted(true))); - expected.add( - new BlockDeviceMapping().withDeviceName("/dev/sdc").withEbs(new EbsBlockDevice().withVolumeSize(120))); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdd") + .ebs(EbsBlockDevice.builder().volumeSize(120).encrypted(true).build()) + .build()); + expected.add(BlockDeviceMapping.builder() + .deviceName("/dev/sdc") + .ebs(EbsBlockDevice.builder().volumeSize(120).build()) + .build()); String customDeviceMappings = "/dev/sdd=:120::::encrypted,/dev/sdc=:120"; List actual = DeviceMappingParser.parse(customDeviceMappings); diff --git a/src/test/java/hudson/plugins/ec2/util/EC2AgentFactoryMockImpl.java b/src/test/java/hudson/plugins/ec2/util/EC2AgentFactoryMockImpl.java index 9ffbeda9f..7fa372927 100644 --- a/src/test/java/hudson/plugins/ec2/util/EC2AgentFactoryMockImpl.java +++ b/src/test/java/hudson/plugins/ec2/util/EC2AgentFactoryMockImpl.java @@ -1,6 +1,5 @@ package hudson.plugins.ec2.util; -import com.amazonaws.AmazonClientException; import hudson.Extension; import hudson.model.Computer; import hudson.model.Descriptor; @@ -15,6 +14,7 @@ import hudson.slaves.NodeProperty; import java.io.IOException; import java.util.List; +import software.amazon.awssdk.core.exception.SdkException; @Extension public class EC2AgentFactoryMockImpl implements EC2AgentFactory { @@ -259,7 +259,7 @@ public boolean isOffline() { } @Override - public long getUptime() throws AmazonClientException { + public long getUptime() throws SdkException { return 3500000; } } diff --git a/src/test/resources/hudson/plugins/ec2/Mac.yml b/src/test/resources/hudson/plugins/ec2/Mac.yml index c1d160364..10d31122b 100644 --- a/src/test/resources/hudson/plugins/ec2/Mac.yml +++ b/src/test/resources/hudson/plugins/ec2/Mac.yml @@ -9,6 +9,6 @@ jenkins: - description: ami: "ami-5678" labelString: "mac clear" - type: "Mac1Metal" + type: "mac1.metal" remoteFS: "/Users/jenkins" mode: "NORMAL" diff --git a/src/test/resources/hudson/plugins/ec2/MacData.yml b/src/test/resources/hudson/plugins/ec2/MacData.yml index bb637519b..dc96e0419 100644 --- a/src/test/resources/hudson/plugins/ec2/MacData.yml +++ b/src/test/resources/hudson/plugins/ec2/MacData.yml @@ -9,7 +9,7 @@ jenkins: - description: ami: "ami-12345" labelString: "mac metal" - type: "Mac1Metal" + type: "mac1.metal" remoteFS: "/Users/ec2-user" mode: "NORMAL" tenancy: Host diff --git a/src/test/resources/hudson/plugins/ec2/MacDataExport.yml b/src/test/resources/hudson/plugins/ec2/MacDataExport.yml index bf6f1eee8..0dedf2588 100644 --- a/src/test/resources/hudson/plugins/ec2/MacDataExport.yml +++ b/src/test/resources/hudson/plugins/ec2/MacDataExport.yml @@ -34,6 +34,6 @@ stopOnTerminate: false t2Unlimited: false tenancy: Host - type: Mac1Metal + type: "mac1.metal" useEphemeralDevices: false useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/Unix-withMinimumInstancesTimeRange.yml b/src/test/resources/hudson/plugins/ec2/Unix-withMinimumInstancesTimeRange.yml index 402d00fa2..690500c76 100644 --- a/src/test/resources/hudson/plugins/ec2/Unix-withMinimumInstancesTimeRange.yml +++ b/src/test/resources/hudson/plugins/ec2/Unix-withMinimumInstancesTimeRange.yml @@ -9,7 +9,7 @@ jenkins: - description: ami: "ami-123456" labelString: "linux ubuntu" - type: "T2Micro" + type: "t2.micro" remoteFS: "/home/ec2-user" mode: "NORMAL" minimumNumberOfInstancesTimeRangeConfig: diff --git a/src/test/resources/hudson/plugins/ec2/Unix.yml b/src/test/resources/hudson/plugins/ec2/Unix.yml index 13081a0f0..0681b4063 100644 --- a/src/test/resources/hudson/plugins/ec2/Unix.yml +++ b/src/test/resources/hudson/plugins/ec2/Unix.yml @@ -9,6 +9,6 @@ jenkins: - description: ami: "ami-5678" labelString: "linux clear" - type: "T3Micro" + type: "t3.micro" remoteFS: "/mnt/jenkins" mode: "NORMAL" diff --git a/src/test/resources/hudson/plugins/ec2/UnixData-withAltEndpointAndJavaPath.yml b/src/test/resources/hudson/plugins/ec2/UnixData-withAltEndpointAndJavaPath.yml index bcaeb6dd6..fb53b85d4 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixData-withAltEndpointAndJavaPath.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixData-withAltEndpointAndJavaPath.yml @@ -11,7 +11,7 @@ jenkins: - description: ami: "ami-12345" labelString: "linux ubuntu" - type: "T2Micro" + type: "t2.micro" remoteFS: "/home/ec2-user" mode: "NORMAL" javaPath: "/opt/jdk-11/bin/java" diff --git a/src/test/resources/hudson/plugins/ec2/UnixData.yml b/src/test/resources/hudson/plugins/ec2/UnixData.yml index c7237d80e..4a4bfb34b 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixData.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixData.yml @@ -9,7 +9,7 @@ jenkins: - description: ami: "ami-12345" labelString: "linux ubuntu" - type: "T2Micro" + type: "t2.micro" remoteFS: "/home/ec2-user" mode: "NORMAL" spotConfig: diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml b/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml index 3c6b208bb..8e9899905 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixDataExport-withAltEndpointAndJavaPath.yml @@ -39,6 +39,6 @@ stopOnTerminate: false t2Unlimited: false tenancy: Default - type: T2Micro + type: "t2.micro" useEphemeralDevices: false useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml b/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml index a2c23fa65..89f131bb4 100644 --- a/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml +++ b/src/test/resources/hudson/plugins/ec2/UnixDataExport.yml @@ -39,6 +39,6 @@ stopOnTerminate: false t2Unlimited: false tenancy: Default - type: T2Micro + type: "t2.micro" useEphemeralDevices: false useInstanceProfileForCredentials: true diff --git a/src/test/resources/hudson/plugins/ec2/WindowsData.yml b/src/test/resources/hudson/plugins/ec2/WindowsData.yml index 31bc37f15..fff116a61 100644 --- a/src/test/resources/hudson/plugins/ec2/WindowsData.yml +++ b/src/test/resources/hudson/plugins/ec2/WindowsData.yml @@ -9,7 +9,7 @@ jenkins: - description: ami: "ami-abc123" labelString: "windows vs2019" - type: "M5aLarge" + type: "m5a.large" remoteFS: "C:/jenkins" mode: "NORMAL" amiType: