skip to Main Content

In our code, to get the ec2 instance’s region we are using EC2MetadataUtils.getEC2InstanceRegion(), and we just realized we must not use EC2MetadataUtils because it is an internal API that is subject to change.

Note: this is an internal API subject to change. Users of the SDK should not depend on this.

Did some google searches but was unable to find an alternate solution, Is there any alternative solution available to get the ec2 instance’s region?

Thanks for any help!

2

Answers


  1. This is the implementation of the class: https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/util/EC2MetadataUtils.java

    I have found no Java alternative for this, despite searching on google, so, I have realized that a deeper research is needed. I describe the possibilities that you have as follows:

    1. You can leave this as it is

    The warning clearly suggests that it’s a good idea to use an alternative, but the nonexistence of a ready-made alternative and possible goodies of future versions of the class are good counter-arguments, so you can ignore this note for now.

    2. You can download the open-source library and search for calls of this method

    If you find the calls for this method somewhere else in the library and you are able to somehow use it instead, then that may be an alternative. For instance, after cloning with

    git clone [email protected]:aws/aws-sdk-java.git
    

    and searching for occurrences of this method with:

    grep -rn 'yourpath' -e "getEC2InstanceRegion"
    

    I have got these results:

    <path>/aws-sdk-java/aws-java-sdk-core/src/main/java/com/amazonaws/util/EC2MetadataUtils.java:286:    public static String getEC2InstanceRegion() {
    <path>/aws-sdk-java/aws-java-sdk-core/src/main/java/com/amazonaws/regions/InstanceMetadataRegionProvider.java:59:            return EC2MetadataUtils.getEC2InstanceRegion();
    <path>/aws-sdk-java/aws-java-sdk-core/src/main/java/com/amazonaws/regions/Regions.java:110:            final String region = EC2MetadataUtils.getEC2InstanceRegion();
    

    The first match is the definition of the method.

    The second match looks like this:

    /*
     * Copyright 2011-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.0 (the "License").
     * You may not use this file except in compliance with the License.
     * A copy of the License is located at
     *
     *  http://aws.amazon.com/apache2.0
     *
     * or in the "license" file accompanying this file. This file is distributed
     * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
     * express or implied. See the License for the specific language governing
     * permissions and limitations under the License.
     */
    package com.amazonaws.regions;
    
    import com.amazonaws.AmazonClientException;
    import com.amazonaws.SDKGlobalConfiguration;
    import com.amazonaws.util.EC2MetadataUtils;
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    
    /**
     * Attempts to load region information from the EC2 Metadata service. If the application is not
     * running on EC2 or {@link SDKGlobalConfiguration#isEc2MetadataDisabled()} returns true,
     * this provider will return null.
     */
    public class InstanceMetadataRegionProvider extends AwsRegionProvider {
    
        private static final Log LOG = LogFactory.getLog(InstanceMetadataRegionProvider.class);
    
        /**
         * Cache region as it will not change during the lifetime of the JVM.
         */
        private volatile String region;
    
        /**
         * @throws AmazonClientException if {@link SDKGlobalConfiguration#isEc2MetadataDisabled()} is true
         */
        @Override
        public String getRegion() {
            if (SDKGlobalConfiguration.isEc2MetadataDisabled()) {
                throw new AmazonClientException("AWS_EC2_METADATA_DISABLED is set to true, not loading region from EC2 Instance "
                                             + "Metadata service");
            }
    
            if (region == null) {
                synchronized (this) {
                    if (region == null) {
                        this.region = tryDetectRegion();
                    }
                }
            }
            return region;
        }
    
        private String tryDetectRegion() {
            try {
                return EC2MetadataUtils.getEC2InstanceRegion();
            } catch (AmazonClientException sce) {
                LOG.debug("Ignoring failure to retrieve the region: " + sce.getMessage());
                return null;
            }
        }
    }
    

    So, it seems that the getRegion method of InstanceMetadataRegionProvider looks like the alternative that you were looking for.

    The third match looks like this:

    /*
     * Copyright 2013-2022 Amazon Technologies, Inc.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at:
     *
     *    http://aws.amazon.com/apache2.0
     *
     * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
     * OR CONDITIONS OF ANY KIND, either express or implied. See the
     * License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.amazonaws.regions;
    
    import com.amazonaws.AmazonClientException;
    import org.apache.commons.logging.LogFactory;
    
    import com.amazonaws.util.EC2MetadataUtils;
    
    /**
     * Enumeration of region names
     */
    public enum Regions {
    
        GovCloud("us-gov-west-1", "AWS GovCloud (US)"),
        US_GOV_EAST_1("us-gov-east-1", "AWS GovCloud (US-East)"),
        US_EAST_1("us-east-1", "US East (N. Virginia)"),
        US_EAST_2("us-east-2", "US East (Ohio)"),
        US_WEST_1("us-west-1", "US West (N. California)"),
        US_WEST_2("us-west-2", "US West (Oregon)"),
        EU_WEST_1("eu-west-1", "EU (Ireland)"),
        EU_WEST_2("eu-west-2", "EU (London)"),
        EU_WEST_3("eu-west-3", "EU (Paris)"),
        EU_CENTRAL_1("eu-central-1", "EU (Frankfurt)"),
        EU_NORTH_1("eu-north-1", "EU (Stockholm)"),
        EU_SOUTH_1("eu-south-1", "EU (Milan)"),
        AP_EAST_1("ap-east-1", "Asia Pacific (Hong Kong)"),
        AP_SOUTH_1("ap-south-1", "Asia Pacific (Mumbai)"),
        AP_SOUTHEAST_1("ap-southeast-1", "Asia Pacific (Singapore)"),
        AP_SOUTHEAST_2("ap-southeast-2", "Asia Pacific (Sydney)"),
        AP_SOUTHEAST_3("ap-southeast-3", "Asia Pacific (Jakarta)"),
        AP_NORTHEAST_1("ap-northeast-1", "Asia Pacific (Tokyo)"),
        AP_NORTHEAST_2("ap-northeast-2", "Asia Pacific (Seoul)"),
        AP_NORTHEAST_3("ap-northeast-3", "Asia Pacific (Osaka)"),
    
        SA_EAST_1("sa-east-1", "South America (Sao Paulo)"),
        CN_NORTH_1("cn-north-1", "China (Beijing)"),
        CN_NORTHWEST_1("cn-northwest-1", "China (Ningxia)"),
        CA_CENTRAL_1("ca-central-1", "Canada (Central)"),
        ME_SOUTH_1("me-south-1", "Middle East (Bahrain)"),
        AF_SOUTH_1("af-south-1", "Africa (Cape Town)"),
        US_ISO_EAST_1("us-iso-east-1", "US ISO East"),
        US_ISOB_EAST_1("us-isob-east-1", "US ISOB East (Ohio)"),
        US_ISO_WEST_1("us-iso-west-1", "US ISO West")
        ;
    
        /**
         * The default region that new customers in the US are encouraged to use
         * when using AWS services for the first time.
         */
        public static final Regions DEFAULT_REGION = US_WEST_2;
    
        private final String name;
        private final String description;
    
        private Regions(String name, String description) {
            this.name = name;
            this.description = description;
        }
    
        /**
         * The name of this region, used in the regions.xml file to identify it.
         */
        public String getName() {
            return name;
        }
    
        /**
         * Descriptive readable name for this region.
         */
        public String getDescription() {
            return description;
        }
    
        /**
         * Returns a region enum corresponding to the given region name.
         *
         * @param regionName
         *            The name of the region. Ex.: eu-west-1
         * @return Region enum representing the given region name.
         */
        public static Regions fromName(String regionName) {
            for (Regions region : Regions.values()) {
                if (region.getName().equals(regionName)) {
                    return region;
                }
            }
            throw new IllegalArgumentException("Cannot create enum from " + regionName + " value!");
        }
    
        /**
         * Returns a Region object representing the region the application is
         * running in, when running in EC2. If this method is called from a non-EC2
         * environment, it will return null.
         */
        public static Region getCurrentRegion() {
            try {
                final String region = EC2MetadataUtils.getEC2InstanceRegion();
                if (region != null)
                    return RegionUtils.getRegion(region);
            } catch (AmazonClientException e) {
                LogFactory.getLog(Regions.class).debug(
                    "Ignoring failure to retrieve the region: " + e.getMessage());
            }
            return null;
        }
    }
    

    So, getCurrentRegion at Regions looks like another alternative. If you manage to use one of these for your purpose successfully, then it will be easy to refactor and it also makes sense to refactor accordingly.

    3. Copy and rename the class

    If the first two options are unfeasible for you, then you can copy and rename the class, so you will be able to make sure that this method will remain unchanged even if the internal API is changed. This is not a very elegant approach and it is not easy to implement either, as the class has dependencies, so, as a result, you will have some difficulties to resolve, but we know in advance that this is a possible solution.

    4. Finally the do-it-yourself approach

    This is an article about retrieving instance metadata: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html

    As we can see, among the metadata information of the instance, you can find the region: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html

    enter image description here

    A command example that uses curl and jq that looks like

    curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region
    

    can be found here: https://www.howtouselinux.com/post/find-ec2-instance-region-info-in-aws

    Login or Signup to reply.
  2. The region information in which the EC2 instance is running is provided as part of the instance identity document.

    As explained in the AWS documentation and pointed out as well by @LajosArpad in his very good answer, this information can be obtained from the following URL from inside the EC2 instance, running curl, for instance:

    curl http://169.254.169.254/latest/dynamic/instance-identity/document
    

    The response provided will be similar to the following:

    {
        "devpayProductCodes" : null,
        "marketplaceProductCodes" : [ "1abc2defghijklm3nopqrs4tu" ], 
        "availabilityZone" : "us-west-2b",
        "privateIp" : "10.158.112.84",
        "version" : "2017-09-30",
        "instanceId" : "i-1234567890abcdef0",
        "billingProducts" : null,
        "instanceType" : "t2.micro",
        "accountId" : "123456789012",
        "imageId" : "ami-5fb8c835",
        "pendingTime" : "2016-11-19T16:32:11Z",
        "architecture" : "x86_64",
        "kernelId" : null,
        "ramdiskId" : null,
        "region" : "us-west-2"
    }
    

    Please, note the region field.

    EC2MetadataUtils basically, of course, with more detail, and in a more structured way, is reading this information as well to give you the EC2 region in which the instance is running.

    You could try reproducing a minimal version of the functionality exposed by EC2MetadataUtils in your own code.

    The exact way will depend on the libraries you want to use to perform the task but, independently of them, you will need to read the returned document, parse it as JSON, and get the region field.

    For instance, using the standard Java API for JSON processing, and using the example provided in the documentation, you could try something like the following to obtain the region in one row:

    URL url = new URL("http://169.254.169.254/latest/dynamic/instance-identity/document");
    try (InputStream is = url.openStream();
         JsonReader reader = Json.createReader(is)) {
    
         JsonObject instanceDocument = reader.readObject();
         String region = instanceDocument.getString("region");
         // return it or use it as you need to
    }
    

    As I told, you can use other libraries (Jackson, org.json, Gson, … for JSON processing, or commons-io, etc. to URL/InputStream read and write) as well.

    The code uses IDMSv1, and could be improved in many different ways, verifying the identity document signature, etcetera, but I hope it helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search