• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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