From 64a2077787904f144f77839660071f35f25d181f Mon Sep 17 00:00:00 2001
From: Mick Bass <mick.bass@47lining.com>
Date: Wed, 24 Dec 2014 17:04:25 -0700
Subject: [PATCH] Add support for AWS Security Token Service (temporary
 credentials) to all AWS cloud modules.

---
 cloud/amazon/cloudformation.py   | 31 +++++--------------------
 cloud/amazon/ec2_vpc.py          | 40 +++++++++++++-------------------
 cloud/amazon/elasticache.py      | 38 +++++++++++-------------------
 cloud/amazon/rds_param_group.py  | 23 +++++-------------
 cloud/amazon/rds_subnet_group.py | 23 +++++-------------
 cloud/amazon/route53.py          | 18 ++++----------
 cloud/amazon/s3.py               | 27 +++++++--------------
 7 files changed, 59 insertions(+), 141 deletions(-)

diff --git a/cloud/amazon/cloudformation.py b/cloud/amazon/cloudformation.py
index 1da173e0a39..e1a16b99620 100644
--- a/cloud/amazon/cloudformation.py
+++ b/cloud/amazon/cloudformation.py
@@ -41,12 +41,6 @@ options:
     required: false
     default: {}
     aliases: []
-  region:
-    description:
-      - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
-    required: true
-    default: null
-    aliases: ['aws_region', 'ec2_region']
   state:
     description:
       - If state is "present", stack will be created.  If state is "present" and if stack exists and template has changed, it will be updated.
@@ -75,29 +69,17 @@ options:
     default: null
     aliases: []
     version_added: "1.4"
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used.
-    required: false
-    default: null
-    aliases: [ 'ec2_secret_key', 'secret_key' ]
-    version_added: "1.5"
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: null
-    aliases: [ 'ec2_access_key', 'access_key' ]
-    version_added: "1.5"
   region:
     description:
-      - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
-    required: false
+      - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used.
+    required: true
+    default: null
     aliases: ['aws_region', 'ec2_region']
     version_added: "1.5"
 
 requirements: [ "boto" ]
 author: James S. Martin
+extends_documentation_fragment: aws
 '''
 
 EXAMPLES = '''
@@ -220,7 +202,7 @@ def main():
     template_parameters = module.params['template_parameters']
     tags = module.params['tags']
 
-    ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     kwargs = dict()
     if tags is not None:
@@ -236,8 +218,7 @@ def main():
     try:
         cfn = boto.cloudformation.connect_to_region(
                   region,
-                  aws_access_key_id=aws_access_key,
-                  aws_secret_access_key=aws_secret_key,
+                  **aws_connect_kwargs
               )
     except boto.exception.NoAuthHandlerFound, e:
         module.fail_json(msg=str(e))
diff --git a/cloud/amazon/ec2_vpc.py b/cloud/amazon/ec2_vpc.py
index 486cd0d38a9..ede2834e44f 100644
--- a/cloud/amazon/ec2_vpc.py
+++ b/cloud/amazon/ec2_vpc.py
@@ -96,33 +96,13 @@ options:
     aliases: []
   region:
     description:
-      - region in which the resource exists.
-    required: false
+      - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used.
+    required: true
     default: null
     aliases: ['aws_region', 'ec2_region']
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used.
-    required: false
-    default: None
-    aliases: ['ec2_secret_key', 'secret_key' ]
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: None
-    aliases: ['ec2_access_key', 'access_key' ]
-  validate_certs:
-    description:
-      - When set to "no", SSL certificates will not be validated for boto versions >= 2.6.0.
-    required: false
-    default: "yes"
-    choices: ["yes", "no"]
-    aliases: []
-    version_added: "1.5"
-
 requirements: [ "boto" ]
 author: Carson Gee
+extends_documentation_fragment: aws
 '''
 
 EXAMPLES = '''
@@ -599,7 +579,19 @@ def main():
 
     state = module.params.get('state')
 
-    vpc_conn = ec2_connect(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
+
+    # If we have a region specified, connect to its endpoint.
+    if region:
+        try:
+            vpc_conn = boto.vpc.connect_to_region(
+                region,
+                **aws_connect_kwargs
+            )
+        except boto.exception.NoAuthHandlerFound, e:
+            module.fail_json(msg = str(e))
+    else:
+        module.fail_json(msg="region must be specified")
 
     if module.params.get('state') == 'absent':
         vpc_id = module.params.get('vpc_id')
diff --git a/cloud/amazon/elasticache.py b/cloud/amazon/elasticache.py
index 18be34560a5..0840ee517a2 100644
--- a/cloud/amazon/elasticache.py
+++ b/cloud/amazon/elasticache.py
@@ -92,24 +92,13 @@ options:
     required: false
     default: no
     choices: [ "yes", "no" ]
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used. 
-    required: false
-    default: None
-    aliases: ['ec2_secret_key', 'secret_key']
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: None
-    aliases: ['ec2_access_key', 'access_key']
   region:
     description:
-      - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
-    required: false
+      - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used.
+    required: true
+    default: null
     aliases: ['aws_region', 'ec2_region']
-
+extends_documentation_fragment: aws
 """
 
 EXAMPLES = """
@@ -163,7 +152,7 @@ class ElastiCacheManager(object):
     def __init__(self, module, name, engine, cache_engine_version, node_type,
                  num_nodes, cache_port, cache_subnet_group,
                  cache_security_groups, security_group_ids, zone, wait,
-                 hard_modify, aws_access_key, aws_secret_key, region):
+                 hard_modify, region, **aws_connect_kwargs):
         self.module = module
         self.name = name
         self.engine = engine
@@ -178,9 +167,8 @@ class ElastiCacheManager(object):
         self.wait = wait
         self.hard_modify = hard_modify
 
-        self.aws_access_key = aws_access_key
-        self.aws_secret_key = aws_secret_key
         self.region = region
+        self.aws_connect_kwargs = aws_connect_kwargs
 
         self.changed = False
         self.data = None
@@ -433,9 +421,10 @@ class ElastiCacheManager(object):
         try:
             endpoint = "elasticache.%s.amazonaws.com" % self.region
             connect_region = RegionInfo(name=self.region, endpoint=endpoint)
-            return ElastiCacheConnection(aws_access_key_id=self.aws_access_key,
-                                         aws_secret_access_key=self.aws_secret_key,
-                                         region=connect_region)
+            return ElastiCacheConnection(
+                region=connect_region,
+                **self.aws_connect_kwargs
+            )
         except boto.exception.NoAuthHandlerFound, e:
             self.module.fail_json(msg=e.message)
 
@@ -509,7 +498,7 @@ def main():
         argument_spec=argument_spec,
     )
 
-    ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     name = module.params['name']
     state = module.params['state']
@@ -537,7 +526,7 @@ def main():
         module.fail_json(msg="'num_nodes' is a required parameter. Please specify num_nodes > 0")
 
     if not region:
-        module.fail_json(msg=str("Either region or EC2_REGION environment variable must be set."))
+        module.fail_json(msg=str("Either region or AWS_REGION or EC2_REGION environment variable or boto config aws_region or ec2_region must be set."))
 
     elasticache_manager = ElastiCacheManager(module, name, engine,
                                              cache_engine_version, node_type,
@@ -545,8 +534,7 @@ def main():
                                              cache_subnet_group,
                                              cache_security_groups,
                                              security_group_ids, zone, wait,
-                                             hard_modify, aws_access_key,
-                                             aws_secret_key, region)
+                                             hard_modify, region, **aws_connect_kwargs)
 
     if state == 'present':
         elasticache_manager.ensure_present()
diff --git a/cloud/amazon/rds_param_group.py b/cloud/amazon/rds_param_group.py
index d1559ac78ae..8653f04f7e8 100644
--- a/cloud/amazon/rds_param_group.py
+++ b/cloud/amazon/rds_param_group.py
@@ -63,24 +63,13 @@ options:
     choices: [ 'mysql5.1', 'mysql5.5', 'mysql5.6', 'oracle-ee-11.2', 'oracle-se-11.2', 'oracle-se1-11.2', 'postgres9.3', 'sqlserver-ee-10.5', 'sqlserver-ee-11.0', 'sqlserver-ex-10.5', 'sqlserver-ex-11.0', 'sqlserver-se-10.5', 'sqlserver-se-11.0', 'sqlserver-web-10.5', 'sqlserver-web-11.0']
   region:
     description:
-      - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
+      - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used.
     required: true
     default: null
-    aliases: [ 'aws_region', 'ec2_region' ]
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: null
-    aliases: [ 'ec2_access_key', 'access_key' ]
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used. 
-    required: false
-    default: null
-    aliases: [ 'ec2_secret_key', 'secret_key' ]
+    aliases: ['aws_region', 'ec2_region']
 requirements: [ "boto" ]
 author: Scott Anderson
+extends_documentation_fragment: aws
 '''
 
 EXAMPLES = '''
@@ -248,13 +237,13 @@ def main():
                 module.fail_json(msg = str("Parameter %s not allowed for state='absent'" % not_allowed))
 
     # Retrieve any AWS settings from the environment.
-    ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     if not region:
-        module.fail_json(msg = str("region not specified and unable to determine region from EC2_REGION."))
+        module.fail_json(msg = str("Either region or AWS_REGION or EC2_REGION environment variable or boto config aws_region or ec2_region must be set."))
 
     try:
-        conn = boto.rds.connect_to_region(region, aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_key)
+        conn = boto.rds.connect_to_region(region, **aws_connect_kwargs)
     except boto.exception.BotoServerError, e:
         module.fail_json(msg = e.error_message)
 
diff --git a/cloud/amazon/rds_subnet_group.py b/cloud/amazon/rds_subnet_group.py
index 40170ba5f4b..0ff41496792 100644
--- a/cloud/amazon/rds_subnet_group.py
+++ b/cloud/amazon/rds_subnet_group.py
@@ -49,24 +49,13 @@ options:
     aliases: []
   region:
     description:
-      - The AWS region to use. If not specified then the value of the EC2_REGION environment variable, if any, is used.
+      - The AWS region to use. If not specified then the value of the AWS_REGION or EC2_REGION environment variable, if any, is used.
     required: true
     default: null
-    aliases: [ 'aws_region', 'ec2_region' ]
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: null
-    aliases: [ 'ec2_access_key', 'access_key' ]
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used. 
-    required: false
-    default: null
-    aliases: [ 'ec2_secret_key', 'secret_key' ]
+    aliases: ['aws_region', 'ec2_region']
 requirements: [ "boto" ]
 author: Scott Anderson
+extends_documentation_fragment: aws
 '''
 
 EXAMPLES = '''
@@ -121,13 +110,13 @@ def main():
                 module.fail_json(msg = str("Parameter %s not allowed for state='absent'" % not_allowed))
 
     # Retrieve any AWS settings from the environment.
-    region, ec2_url, aws_connect_params = get_aws_connection_info(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     if not region:
-        module.fail_json(msg = str("region not specified and unable to determine region from EC2_REGION."))
+        module.fail_json(msg = str("Either region or AWS_REGION or EC2_REGION environment variable or boto config aws_region or ec2_region must be set."))
 
     try:
-        conn = boto.rds.connect_to_region(region, **aws_connect_params)
+        conn = boto.rds.connect_to_region(region, **aws_connection_kwargs)
     except boto.exception.BotoServerError, e:
         module.fail_json(msg = e.error_message)
 
diff --git a/cloud/amazon/route53.py b/cloud/amazon/route53.py
index 6778870ad75..97689a985b4 100644
--- a/cloud/amazon/route53.py
+++ b/cloud/amazon/route53.py
@@ -74,18 +74,6 @@ options:
     required: false
     default: null
     aliases: []
-  aws_secret_key:
-    description:
-      - AWS secret key. 
-    required: false
-    default: null
-    aliases: ['ec2_secret_key', 'secret_key']
-  aws_access_key:
-    description:
-      - AWS access key. 
-    required: false
-    default: null
-    aliases: ['ec2_access_key', 'access_key']
   overwrite:
     description:
       - Whether an existing record should be overwritten on create if values do not match
@@ -106,6 +94,7 @@ options:
     version_added: "1.9"
 requirements: [ "boto" ]
 author: Bruce Pennypacker
+extends_documentation_fragment: aws
 '''
 
 # FIXME: the command stuff should have a more state like configuration alias -- MPD
@@ -177,6 +166,7 @@ try:
     import boto
     import boto.ec2
     from boto import route53
+    from boto.route53 import Route53Connection
     from boto.route53.record import ResourceRecordSets
 except ImportError:
     print "failed=True msg='boto required for this module'"
@@ -225,7 +215,7 @@ def main():
     retry_interval_in       = module.params.get('retry_interval')
     private_zone_in         = module.params.get('private_zone')
 
-    region, ec2_url, aws_connect_params = get_aws_connection_info(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     value_list = ()
 
@@ -252,7 +242,7 @@ def main():
 
     # connect to the route53 endpoint 
     try:
-        conn = boto.route53.Route53Connection(**aws_connect_params)
+        conn = Route53Connection(**aws_connect_kwargs)
     except boto.exception.BotoServerError, e:
         module.fail_json(msg = e.error_message)
 
diff --git a/cloud/amazon/s3.py b/cloud/amazon/s3.py
index ad8ed0d028c..d6ff9602fc0 100644
--- a/cloud/amazon/s3.py
+++ b/cloud/amazon/s3.py
@@ -71,18 +71,6 @@ options:
         - "S3 URL endpoint for usage with Eucalypus, fakes3, etc.  Otherwise assumes AWS"
     default: null
     aliases: [ S3_URL ]
-  aws_secret_key:
-    description:
-      - AWS secret key. If not set then the value of the AWS_SECRET_KEY environment variable is used. 
-    required: false
-    default: null
-    aliases: ['ec2_secret_key', 'secret_key']
-  aws_access_key:
-    description:
-      - AWS access key. If not set then the value of the AWS_ACCESS_KEY environment variable is used.
-    required: false
-    default: null
-    aliases: [ 'ec2_access_key', 'access_key' ]
   metadata:
     description:
       - Metadata for PUT operation, as a dictionary of 'key=value' and 'key=value,key=value'.
@@ -91,13 +79,13 @@ options:
     version_added: "1.6"
   region:
     description:
-     - "AWS region to create the bucket in. If not set then the value of the EC2_REGION and AWS_REGION environment variables are checked, followed by the aws_region and ec2_region settings in the Boto config file.  If none of those are set the region defaults to the S3 Location: US Standard.  Prior to ansible 1.8 this parameter could be specified but had no effect."
+     - "AWS region to create the bucket in. If not set then the value of the AWS_REGION and EC2_REGION environment variables are checked, followed by the aws_region and ec2_region settings in the Boto config file.  If none of those are set the region defaults to the S3 Location: US Standard.  Prior to ansible 1.8 this parameter could be specified but had no effect."
     required: false
     default: null
     version_added: "1.8"
-
 requirements: [ "boto" ]
 author: Lester Wade, Ralph Tice
+extends_documentation_fragment: aws
 '''
 
 EXAMPLES = '''
@@ -130,6 +118,7 @@ from boto.s3.connection import OrdinaryCallingFormat
 try:
     import boto
     from boto.s3.connection import Location
+    from boto.s3.connection import S3Connection
 except ImportError:
     print "failed=True msg='boto required for this module'"
     sys.exit(1)
@@ -301,7 +290,7 @@ def main():
     overwrite = module.params.get('overwrite')
     metadata = module.params.get('metadata')
 
-    ec2_url, aws_access_key, aws_secret_key, region = get_ec2_creds(module)
+    region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module)
 
     if region in ('us-east-1', '', None):
         # S3ism for the US Standard region
@@ -323,13 +312,13 @@ def main():
     try:
         if is_fakes3(s3_url):
             fakes3 = urlparse.urlparse(s3_url)
-            s3 = boto.connect_s3(
-                aws_access_key,
-                aws_secret_key,
+            s3 = S3Connection(
                 is_secure=False,
                 host=fakes3.hostname,
                 port=fakes3.port,
-                calling_format=OrdinaryCallingFormat())
+                calling_format=OrdinaryCallingFormat(),
+                **aws_connect_kwargs
+            )
         elif is_walrus(s3_url):
             walrus = urlparse.urlparse(s3_url).hostname
             s3 = boto.connect_walrus(walrus, aws_access_key, aws_secret_key)