1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.regions.internal.util; 17 18 import java.io.IOException; 19 import java.net.URI; 20 import java.net.URISyntaxException; 21 import java.util.Arrays; 22 import java.util.Collections; 23 import java.util.HashMap; 24 import java.util.LinkedList; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.concurrent.ConcurrentHashMap; 28 import org.slf4j.Logger; 29 import org.slf4j.LoggerFactory; 30 import software.amazon.awssdk.annotations.SdkInternalApi; 31 import software.amazon.awssdk.annotations.SdkTestInternalApi; 32 import software.amazon.awssdk.core.SdkSystemSetting; 33 import software.amazon.awssdk.core.exception.SdkClientException; 34 import software.amazon.awssdk.core.exception.SdkServiceException; 35 import software.amazon.awssdk.core.util.SdkUserAgent; 36 import software.amazon.awssdk.profiles.ProfileProperty; 37 import software.amazon.awssdk.protocols.jsoncore.JsonNode; 38 import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; 39 import software.amazon.awssdk.regions.util.HttpResourcesUtils; 40 import software.amazon.awssdk.regions.util.ResourcesEndpointProvider; 41 42 /** 43 44 * 45 * Utility class for retrieving Amazon EC2 instance metadata. 46 * 47 * <p> 48 * <b>Note</b>: this is an internal API subject to change. Users of the SDK 49 * should not depend on this. 50 * 51 * <p> 52 * You can use the data to build more generic AMIs that can be modified by 53 * configuration files supplied at launch time. For example, if you run web 54 * servers for various small businesses, they can all use the same AMI and 55 * retrieve their content from the Amazon S3 bucket you specify at launch. To 56 * add a new customer at any time, simply create a bucket for the customer, add 57 * their content, and launch your AMI.<br> 58 * <p> 59 * If {@link SdkSystemSetting#AWS_EC2_METADATA_DISABLED} is set to true, EC2 metadata usage 60 * will be disabled and {@link SdkClientException} will be thrown for any metadata retrieval attempt. 61 * <p> 62 * If {@link SdkSystemSetting#AWS_EC2_METADATA_V1_DISABLED} or {@link ProfileProperty#EC2_METADATA_V1_DISABLED} 63 * is set to true, data will only be loaded from EC2 metadata service if a token is successfully retrieved - 64 * fallback to load data without a token will be disabled. 65 * <p> 66 * More information about Amazon EC2 Metadata 67 * 68 * @see <a 69 * href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html">Amazon 70 * EC2 User Guide: Instance Metadata</a> 71 */ 72 @SdkInternalApi 73 public final class EC2MetadataUtils { 74 private static final JsonNodeParser JSON_PARSER = JsonNode.parser(); 75 76 /** Default resource path for credentials in the Amazon EC2 Instance Metadata Service. */ 77 private static final String REGION = "region"; 78 private static final String INSTANCE_IDENTITY_DOCUMENT = "instance-identity/document"; 79 private static final String INSTANCE_IDENTITY_SIGNATURE = "instance-identity/signature"; 80 private static final String EC2_METADATA_ROOT = "/latest/meta-data"; 81 private static final String EC2_USERDATA_ROOT = "/latest/user-data/"; 82 private static final String EC2_DYNAMICDATA_ROOT = "/latest/dynamic/"; 83 84 private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token"; 85 86 private static final int DEFAULT_QUERY_ATTEMPTS = 3; 87 private static final int MINIMUM_RETRY_WAIT_TIME_MILLISECONDS = 250; 88 private static final Logger log = LoggerFactory.getLogger(EC2MetadataUtils.class); 89 private static final Map<String, String> CACHE = new ConcurrentHashMap<>(); 90 91 private static final Ec2MetadataDisableV1Resolver EC2_METADATA_DISABLE_V1_RESOLVER = Ec2MetadataDisableV1Resolver.create(); 92 private static final Object FALLBACK_LOCK = new Object(); 93 private static volatile Boolean IS_INSECURE_FALLBACK_DISABLED; 94 95 private static final InstanceProviderTokenEndpointProvider TOKEN_ENDPOINT_PROVIDER = 96 new InstanceProviderTokenEndpointProvider(); 97 98 private static final Ec2MetadataConfigProvider EC2_METADATA_CONFIG_PROVIDER = Ec2MetadataConfigProvider.builder() 99 .build(); 100 EC2MetadataUtils()101 private EC2MetadataUtils() { 102 } 103 104 /** 105 * Get the AMI ID used to launch the instance. 106 */ getAmiId()107 public static String getAmiId() { 108 return fetchData(EC2_METADATA_ROOT + "/ami-id"); 109 } 110 111 /** 112 * Get the index of this instance in the reservation. 113 */ getAmiLaunchIndex()114 public static String getAmiLaunchIndex() { 115 return fetchData(EC2_METADATA_ROOT + "/ami-launch-index"); 116 } 117 118 /** 119 * Get the manifest path of the AMI with which the instance was launched. 120 */ getAmiManifestPath()121 public static String getAmiManifestPath() { 122 return fetchData(EC2_METADATA_ROOT + "/ami-manifest-path"); 123 } 124 125 /** 126 * Get the list of AMI IDs of any instances that were rebundled to created 127 * this AMI. Will only exist if the AMI manifest file contained an 128 * ancestor-amis key. 129 */ getAncestorAmiIds()130 public static List<String> getAncestorAmiIds() { 131 return getItems(EC2_METADATA_ROOT + "/ancestor-ami-ids"); 132 } 133 134 /** 135 * Notifies the instance that it should reboot in preparation for bundling. 136 * Valid values: none | shutdown | bundle-pending. 137 */ getInstanceAction()138 public static String getInstanceAction() { 139 return fetchData(EC2_METADATA_ROOT + "/instance-action"); 140 } 141 142 /** 143 * Get the ID of this instance. 144 */ getInstanceId()145 public static String getInstanceId() { 146 return fetchData(EC2_METADATA_ROOT + "/instance-id"); 147 } 148 149 /** 150 * Get the type of the instance. 151 */ getInstanceType()152 public static String getInstanceType() { 153 return fetchData(EC2_METADATA_ROOT + "/instance-type"); 154 } 155 156 /** 157 * Get the local hostname of the instance. In cases where multiple network 158 * interfaces are present, this refers to the eth0 device (the device for 159 * which device-number is 0). 160 */ getLocalHostName()161 public static String getLocalHostName() { 162 return fetchData(EC2_METADATA_ROOT + "/local-hostname"); 163 } 164 165 /** 166 * Get the MAC address of the instance. In cases where multiple network 167 * interfaces are present, this refers to the eth0 device (the device for 168 * which device-number is 0). 169 */ getMacAddress()170 public static String getMacAddress() { 171 return fetchData(EC2_METADATA_ROOT + "/mac"); 172 } 173 174 /** 175 * Get the private IP address of the instance. In cases where multiple 176 * network interfaces are present, this refers to the eth0 device (the 177 * device for which device-number is 0). 178 */ getPrivateIpAddress()179 public static String getPrivateIpAddress() { 180 return fetchData(EC2_METADATA_ROOT + "/local-ipv4"); 181 } 182 183 /** 184 * Get the Availability Zone in which the instance launched. 185 */ getAvailabilityZone()186 public static String getAvailabilityZone() { 187 return fetchData(EC2_METADATA_ROOT + "/placement/availability-zone"); 188 } 189 190 /** 191 * Get the list of product codes associated with the instance, if any. 192 */ getProductCodes()193 public static List<String> getProductCodes() { 194 return getItems(EC2_METADATA_ROOT + "/product-codes"); 195 } 196 197 /** 198 * Get the public key. Only available if supplied at instance launch time. 199 */ getPublicKey()200 public static String getPublicKey() { 201 return fetchData(EC2_METADATA_ROOT + "/public-keys/0/openssh-key"); 202 } 203 204 /** 205 * Get the ID of the RAM disk specified at launch time, if applicable. 206 */ getRamdiskId()207 public static String getRamdiskId() { 208 return fetchData(EC2_METADATA_ROOT + "/ramdisk-id"); 209 } 210 211 /** 212 * Get the ID of the reservation. 213 */ getReservationId()214 public static String getReservationId() { 215 return fetchData(EC2_METADATA_ROOT + "/reservation-id"); 216 } 217 218 /** 219 * Get the list of names of the security groups applied to the instance. 220 */ getSecurityGroups()221 public static List<String> getSecurityGroups() { 222 return getItems(EC2_METADATA_ROOT + "/security-groups"); 223 } 224 225 /** 226 * Get the signature of the instance. 227 */ getInstanceSignature()228 public static String getInstanceSignature() { 229 return fetchData(EC2_DYNAMICDATA_ROOT + INSTANCE_IDENTITY_SIGNATURE); 230 } 231 232 /** 233 * Returns the current region of this running EC2 instance; or null if 234 * it is unable to do so. The method avoids interpreting other parts of the 235 * instance info JSON document to minimize potential failure. 236 * <p> 237 * The instance info is only guaranteed to be a JSON document per 238 * http://docs 239 * .aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html 240 */ getEC2InstanceRegion()241 public static String getEC2InstanceRegion() { 242 return doGetEC2InstanceRegion(getData( 243 EC2_DYNAMICDATA_ROOT + INSTANCE_IDENTITY_DOCUMENT)); 244 } 245 doGetEC2InstanceRegion(final String json)246 static String doGetEC2InstanceRegion(final String json) { 247 if (null != json) { 248 try { 249 return JSON_PARSER.parse(json) 250 .field(REGION) 251 .map(JsonNode::text) 252 .orElseThrow(() -> new IllegalStateException("Region not included in metadata.")); 253 } catch (Exception e) { 254 log.warn("Unable to parse EC2 instance info (" + json + ") : " + e.getMessage(), e); 255 } 256 } 257 return null; 258 } 259 260 /** 261 * Get the virtual devices associated with the ami, root, ebs, and swap. 262 */ getBlockDeviceMapping()263 public static Map<String, String> getBlockDeviceMapping() { 264 Map<String, String> blockDeviceMapping = new HashMap<>(); 265 266 List<String> devices = getItems(EC2_METADATA_ROOT 267 + "/block-device-mapping"); 268 for (String device : devices) { 269 blockDeviceMapping.put(device, getData(EC2_METADATA_ROOT 270 + "/block-device-mapping/" + device)); 271 } 272 return blockDeviceMapping; 273 } 274 275 /** 276 * Get the list of network interfaces on the instance. 277 */ getNetworkInterfaces()278 public static List<NetworkInterface> getNetworkInterfaces() { 279 List<NetworkInterface> networkInterfaces = new LinkedList<>(); 280 281 List<String> macs = getItems(EC2_METADATA_ROOT 282 + "/network/interfaces/macs/"); 283 for (String mac : macs) { 284 String key = mac.trim(); 285 if (key.endsWith("/")) { 286 key = key.substring(0, key.length() - 1); 287 } 288 networkInterfaces.add(new NetworkInterface(key)); 289 } 290 return networkInterfaces; 291 } 292 293 /** 294 * Get the metadata sent to the instance 295 */ getUserData()296 public static String getUserData() { 297 return getData(EC2_USERDATA_ROOT); 298 } 299 300 /** 301 * Retrieve some of the data from http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html as a typed 302 * object. This entire class will be removed as part of https://github.com/aws/aws-sdk-java-v2/issues/61, so don't rely on 303 * this sticking around. 304 * 305 * This should not be removed until https://github.com/aws/aws-sdk-java-v2/issues/61 is implemented. 306 */ getInstanceInfo()307 public static InstanceInfo getInstanceInfo() { 308 return doGetInstanceInfo(getData(EC2_DYNAMICDATA_ROOT + INSTANCE_IDENTITY_DOCUMENT)); 309 } 310 doGetInstanceInfo(String json)311 static InstanceInfo doGetInstanceInfo(String json) { 312 if (json != null) { 313 try { 314 Map<String, JsonNode> jsonNode = JSON_PARSER.parse(json).asObject(); 315 return new InstanceInfo(stringValue(jsonNode.get("pendingTime")), 316 stringValue(jsonNode.get("instanceType")), 317 stringValue(jsonNode.get("imageId")), 318 stringValue(jsonNode.get("instanceId")), 319 stringArrayValue(jsonNode.get("billingProducts")), 320 stringValue(jsonNode.get("architecture")), 321 stringValue(jsonNode.get("accountId")), 322 stringValue(jsonNode.get("kernelId")), 323 stringValue(jsonNode.get("ramdiskId")), 324 stringValue(jsonNode.get("region")), 325 stringValue(jsonNode.get("version")), 326 stringValue(jsonNode.get("availabilityZone")), 327 stringValue(jsonNode.get("privateIp")), 328 stringArrayValue(jsonNode.get("devpayProductCodes")), 329 stringArrayValue(jsonNode.get("marketplaceProductCodes"))); 330 } catch (Exception e) { 331 log.warn("Unable to parse dynamic EC2 instance info (" + json + ") : " + e.getMessage(), e); 332 } 333 } 334 return null; 335 } 336 stringValue(JsonNode jsonNode)337 private static String stringValue(JsonNode jsonNode) { 338 if (jsonNode == null || !jsonNode.isString()) { 339 return null; 340 } 341 342 return jsonNode.asString(); 343 } 344 stringArrayValue(JsonNode jsonNode)345 private static String[] stringArrayValue(JsonNode jsonNode) { 346 if (jsonNode == null || !jsonNode.isArray()) { 347 return null; 348 } 349 350 return jsonNode.asArray() 351 .stream() 352 .filter(JsonNode::isString) 353 .map(JsonNode::asString) 354 .toArray(String[]::new); 355 } 356 getData(String path)357 public static String getData(String path) { 358 return getData(path, DEFAULT_QUERY_ATTEMPTS); 359 } 360 getData(String path, int tries)361 public static String getData(String path, int tries) { 362 List<String> items = getItems(path, tries, true); 363 if (null != items && items.size() > 0) { 364 return items.get(0); 365 } 366 return null; 367 } 368 getItems(String path)369 public static List<String> getItems(String path) { 370 return getItems(path, DEFAULT_QUERY_ATTEMPTS, false); 371 } 372 getItems(String path, int tries)373 public static List<String> getItems(String path, int tries) { 374 return getItems(path, tries, false); 375 } 376 377 @SdkTestInternalApi clearCache()378 public static void clearCache() { 379 CACHE.clear(); 380 } 381 382 @SdkTestInternalApi resetIsFallbackDisableResolved()383 public static void resetIsFallbackDisableResolved() { 384 IS_INSECURE_FALLBACK_DISABLED = null; 385 } 386 getItems(String path, int tries, boolean slurp)387 private static List<String> getItems(String path, int tries, boolean slurp) { 388 if (tries == 0) { 389 throw SdkClientException.builder().message("Unable to contact EC2 metadata service.").build(); 390 } 391 392 if (SdkSystemSetting.AWS_EC2_METADATA_DISABLED.getBooleanValueOrThrow()) { 393 throw SdkClientException.builder().message("EC2 metadata usage is disabled.").build(); 394 } 395 396 List<String> items; 397 398 String token = getToken(); 399 400 try { 401 String hostAddress = EC2_METADATA_CONFIG_PROVIDER.getEndpoint(); 402 String response = doReadResource(new URI(hostAddress + path), token); 403 if (slurp) { 404 items = Collections.singletonList(response); 405 } else { 406 items = Arrays.asList(response.split("\n")); 407 } 408 return items; 409 } catch (SdkClientException ace) { 410 log.warn("Unable to retrieve the requested metadata."); 411 return null; 412 } catch (IOException | URISyntaxException | RuntimeException e) { 413 // If there is no retry available, just throw exception instead of pausing. 414 if (tries - 1 == 0) { 415 throw SdkClientException.builder().message("Unable to contact EC2 metadata service.").cause(e).build(); 416 } 417 418 // Retry on any other exceptions 419 int pause = (int) (Math.pow(2, DEFAULT_QUERY_ATTEMPTS - tries) * MINIMUM_RETRY_WAIT_TIME_MILLISECONDS); 420 try { 421 Thread.sleep(pause < MINIMUM_RETRY_WAIT_TIME_MILLISECONDS ? MINIMUM_RETRY_WAIT_TIME_MILLISECONDS 422 : pause); 423 } catch (InterruptedException e1) { 424 Thread.currentThread().interrupt(); 425 } 426 return getItems(path, tries - 1, slurp); 427 } 428 } 429 doReadResource(URI resource, String token)430 private static String doReadResource(URI resource, String token) throws IOException { 431 return HttpResourcesUtils.instance().readResource(new DefaultEndpointProvider(resource, token), "GET"); 432 } 433 getToken()434 public static String getToken() { 435 try { 436 return HttpResourcesUtils.instance().readResource(TOKEN_ENDPOINT_PROVIDER, "PUT"); 437 } catch (Exception e) { 438 439 boolean is400ServiceException = e instanceof SdkServiceException 440 && ((SdkServiceException) e).statusCode() == 400; 441 442 // metadata resolution must not continue to the token-less flow for a 400 443 if (is400ServiceException) { 444 throw SdkClientException.builder() 445 .message("Unable to fetch metadata token") 446 .cause(e) 447 .build(); 448 } 449 return handleTokenErrorResponse(e); 450 } 451 } 452 handleTokenErrorResponse(Exception e)453 private static String handleTokenErrorResponse(Exception e) { 454 if (isInsecureFallbackDisabled()) { 455 String message = String.format("Failed to retrieve IMDS token, and fallback to IMDS v1 is disabled via the " 456 + "%s system property, %s environment variable, or %s configuration file profile" 457 + " setting.", 458 SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.environmentVariable(), 459 SdkSystemSetting.AWS_EC2_METADATA_V1_DISABLED.property(), 460 ProfileProperty.EC2_METADATA_V1_DISABLED); 461 throw SdkClientException.builder() 462 .message(message) 463 .cause(e) 464 .build(); 465 } 466 return null; 467 } 468 isInsecureFallbackDisabled()469 private static boolean isInsecureFallbackDisabled() { 470 if (IS_INSECURE_FALLBACK_DISABLED == null) { 471 synchronized (FALLBACK_LOCK) { 472 if (IS_INSECURE_FALLBACK_DISABLED == null) { 473 IS_INSECURE_FALLBACK_DISABLED = EC2_METADATA_DISABLE_V1_RESOLVER.resolve(); 474 } 475 } 476 } 477 return IS_INSECURE_FALLBACK_DISABLED; 478 } 479 fetchData(String path)480 private static String fetchData(String path) { 481 return fetchData(path, false); 482 } 483 fetchData(String path, boolean force)484 private static String fetchData(String path, boolean force) { 485 return fetchData(path, force, DEFAULT_QUERY_ATTEMPTS); 486 } 487 488 /** 489 * Fetch data using the given path 490 * 491 * @param path the path 492 * @param force whether to force to override the value in the cache 493 * @param attempts the number of attempts that should be executed. 494 * @return the value retrieved from the path 495 */ fetchData(String path, boolean force, int attempts)496 public static String fetchData(String path, boolean force, int attempts) { 497 if (SdkSystemSetting.AWS_EC2_METADATA_DISABLED.getBooleanValueOrThrow()) { 498 throw SdkClientException.builder().message("EC2 metadata usage is disabled.").build(); 499 } 500 501 try { 502 if (force || !CACHE.containsKey(path)) { 503 CACHE.put(path, getData(path, attempts)); 504 } 505 return CACHE.get(path); 506 } catch (SdkClientException e) { 507 throw e; 508 } catch (RuntimeException e) { 509 return null; 510 } 511 } 512 513 /** 514 * All of the metada associated with a network interface on the instance. 515 */ 516 public static class NetworkInterface { 517 private String path; 518 private String mac; 519 520 private List<String> availableKeys; 521 private Map<String, String> data = new HashMap<>(); 522 NetworkInterface(String macAddress)523 public NetworkInterface(String macAddress) { 524 mac = macAddress; 525 path = "/network/interfaces/macs/" + mac + "/"; 526 } 527 528 /** 529 * The interface's Media Acess Control (mac) address 530 */ getMacAddress()531 public String getMacAddress() { 532 return mac; 533 } 534 535 /** 536 * The ID of the owner of the network interface.<br> 537 * In multiple-interface environments, an interface can be attached by a 538 * third party, such as Elastic Load Balancing. Traffic on an interface 539 * is always billed to the interface owner. 540 */ getOwnerId()541 public String getOwnerId() { 542 return getData("owner-id"); 543 } 544 545 /** 546 * The interface's profile. 547 */ getProfile()548 public String getProfile() { 549 return getData("profile"); 550 } 551 552 /** 553 * The interface's local hostname. 554 */ getHostname()555 public String getHostname() { 556 return getData("local-hostname"); 557 } 558 559 /** 560 * The private IP addresses associated with the interface. 561 */ getLocalIPv4s()562 public List<String> getLocalIPv4s() { 563 return getItems("local-ipv4s"); 564 } 565 566 /** 567 * The interface's public hostname. 568 */ getPublicHostname()569 public String getPublicHostname() { 570 return getData("public-hostname"); 571 } 572 573 /** 574 * The elastic IP addresses associated with the interface.<br> 575 * There may be multiple IP addresses on an instance. 576 */ getPublicIPv4s()577 public List<String> getPublicIPv4s() { 578 return getItems("public-ipv4s"); 579 } 580 581 /** 582 * Security groups to which the network interface belongs. 583 */ getSecurityGroups()584 public List<String> getSecurityGroups() { 585 return getItems("security-groups"); 586 } 587 588 /** 589 * IDs of the security groups to which the network interface belongs. 590 * Returned only for Amazon EC2 instances launched into a VPC. 591 */ getSecurityGroupIds()592 public List<String> getSecurityGroupIds() { 593 return getItems("security-group-ids"); 594 } 595 596 /** 597 * The CIDR block of the Amazon EC2-VPC subnet in which the interface 598 * resides.<br> 599 * Returned only for Amazon EC2 instances launched into a VPC. 600 */ getSubnetIPv4CidrBlock()601 public String getSubnetIPv4CidrBlock() { 602 return getData("subnet-ipv4-cidr-block"); 603 } 604 605 /** 606 * ID of the subnet in which the interface resides.<br> 607 * Returned only for Amazon EC2 instances launched into a VPC. 608 */ getSubnetId()609 public String getSubnetId() { 610 return getData("subnet-id"); 611 } 612 613 /** 614 * The CIDR block of the Amazon EC2-VPC in which the interface 615 * resides.<br> 616 * Returned only for Amazon EC2 instances launched into a VPC. 617 */ getVpcIPv4CidrBlock()618 public String getVpcIPv4CidrBlock() { 619 return getData("vpc-ipv4-cidr-block"); 620 } 621 622 /** 623 * ID of the Amazon EC2-VPC in which the interface resides.<br> 624 * Returned only for Amazon EC2 instances launched into a VPC. 625 */ getVpcId()626 public String getVpcId() { 627 return getData("vpc-id"); 628 } 629 630 /** 631 * Get the private IPv4 address(es) that are associated with the 632 * public-ip address and assigned to that interface. 633 * 634 * @param publicIp 635 * The public IP address 636 * @return Private IPv4 address(es) associated with the public IP 637 * address. 638 */ getIPv4Association(String publicIp)639 public List<String> getIPv4Association(String publicIp) { 640 return getItems(EC2_METADATA_ROOT + path + "ipv4-associations/" + publicIp); 641 } 642 getData(String key)643 private String getData(String key) { 644 if (data.containsKey(key)) { 645 return data.get(key); 646 } 647 648 // Since the keys are variable, cache a list of which ones are 649 // available 650 // to prevent unnecessary trips to the service. 651 if (null == availableKeys) { 652 availableKeys = EC2MetadataUtils.getItems(EC2_METADATA_ROOT 653 + path); 654 } 655 656 if (availableKeys.contains(key)) { 657 data.put(key, EC2MetadataUtils.getData(EC2_METADATA_ROOT + path 658 + key)); 659 return data.get(key); 660 } else { 661 return null; 662 } 663 } 664 getItems(String key)665 private List<String> getItems(String key) { 666 if (null == availableKeys) { 667 availableKeys = EC2MetadataUtils.getItems(EC2_METADATA_ROOT 668 + path); 669 } 670 671 if (availableKeys.contains(key)) { 672 return EC2MetadataUtils 673 .getItems(EC2_METADATA_ROOT + path + key); 674 } else { 675 return Collections.emptyList(); 676 } 677 } 678 } 679 680 private static final class DefaultEndpointProvider implements ResourcesEndpointProvider { 681 private final URI endpoint; 682 private final String metadataToken; 683 DefaultEndpointProvider(URI endpoint, String metadataToken)684 private DefaultEndpointProvider(URI endpoint, String metadataToken) { 685 this.endpoint = endpoint; 686 this.metadataToken = metadataToken; 687 } 688 689 @Override endpoint()690 public URI endpoint() { 691 return endpoint; 692 } 693 694 @Override headers()695 public Map<String, String> headers() { 696 Map<String, String> requestHeaders = new HashMap<>(); 697 requestHeaders.put("User-Agent", SdkUserAgent.create().userAgent()); 698 requestHeaders.put("Accept", "*/*"); 699 requestHeaders.put("Connection", "keep-alive"); 700 701 if (metadataToken != null) { 702 requestHeaders.put(EC2_METADATA_TOKEN_HEADER, metadataToken); 703 } 704 705 return requestHeaders; 706 } 707 } 708 709 710 public static class InstanceInfo { 711 private final String pendingTime; 712 private final String instanceType; 713 private final String imageId; 714 private final String instanceId; 715 private final String[] billingProducts; 716 private final String architecture; 717 private final String accountId; 718 private final String kernelId; 719 private final String ramdiskId; 720 private final String region; 721 private final String version; 722 private final String availabilityZone; 723 private final String privateIp; 724 private final String[] devpayProductCodes; 725 private final String[] marketplaceProductCodes; 726 InstanceInfo( String pendingTime, String instanceType, String imageId, String instanceId, String[] billingProducts, String architecture, String accountId, String kernelId, String ramdiskId, String region, String version, String availabilityZone, String privateIp, String[] devpayProductCodes, String[] marketplaceProductCodes)727 public InstanceInfo( 728 String pendingTime, 729 String instanceType, 730 String imageId, 731 String instanceId, 732 String[] billingProducts, 733 String architecture, 734 String accountId, 735 String kernelId, 736 String ramdiskId, 737 String region, 738 String version, 739 String availabilityZone, 740 String privateIp, 741 String[] devpayProductCodes, 742 String[] marketplaceProductCodes) { 743 744 this.pendingTime = pendingTime; 745 this.instanceType = instanceType; 746 this.imageId = imageId; 747 this.instanceId = instanceId; 748 this.billingProducts = billingProducts == null 749 ? null : billingProducts.clone(); 750 this.architecture = architecture; 751 this.accountId = accountId; 752 this.kernelId = kernelId; 753 this.ramdiskId = ramdiskId; 754 this.region = region; 755 this.version = version; 756 this.availabilityZone = availabilityZone; 757 this.privateIp = privateIp; 758 this.devpayProductCodes = devpayProductCodes == null 759 ? null : devpayProductCodes.clone(); 760 this.marketplaceProductCodes = marketplaceProductCodes == null 761 ? null : marketplaceProductCodes.clone(); 762 } 763 getPendingTime()764 public String getPendingTime() { 765 return pendingTime; 766 } 767 getInstanceType()768 public String getInstanceType() { 769 return instanceType; 770 } 771 getImageId()772 public String getImageId() { 773 return imageId; 774 } 775 getInstanceId()776 public String getInstanceId() { 777 return instanceId; 778 } 779 getBillingProducts()780 public String[] getBillingProducts() { 781 return billingProducts == null ? null : billingProducts.clone(); 782 } 783 getArchitecture()784 public String getArchitecture() { 785 return architecture; 786 } 787 getAccountId()788 public String getAccountId() { 789 return accountId; 790 } 791 getKernelId()792 public String getKernelId() { 793 return kernelId; 794 } 795 getRamdiskId()796 public String getRamdiskId() { 797 return ramdiskId; 798 } 799 getRegion()800 public String getRegion() { 801 return region; 802 } 803 getVersion()804 public String getVersion() { 805 return version; 806 } 807 getAvailabilityZone()808 public String getAvailabilityZone() { 809 return availabilityZone; 810 } 811 getPrivateIp()812 public String getPrivateIp() { 813 return privateIp; 814 } 815 getDevpayProductCodes()816 public String[] getDevpayProductCodes() { 817 return devpayProductCodes == null ? null : devpayProductCodes.clone(); 818 } 819 getMarketplaceProductCodes()820 public String[] getMarketplaceProductCodes() { 821 return marketplaceProductCodes == null ? null : marketplaceProductCodes.clone(); 822 } 823 } 824 } 825