summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/cloud/amazon/aws_api_gateway.py
diff options
context:
space:
mode:
authorMichael De La Rue <butigh.margoteen@dfgh.net>2017-06-05 15:25:56 +0100
committerRyan Brown <sb@ryansb.com>2017-06-05 10:25:56 -0400
commite28845018d0f283249119221e60f3a980d3e3c89 (patch)
tree21d04463c982a631b176be183794e2d991240286 /lib/ansible/modules/cloud/amazon/aws_api_gateway.py
parent087b5277f15ba35602cf2214014df52744298ec3 (diff)
downloadansible-e28845018d0f283249119221e60f3a980d3e3c89.tar.gz
[cloud] New module: AWS API Gageway module (#20230)
* Ultra basic api-gateway module based of lambda.py * Ultra basic deployment added to api-gateway module * ApiGateway module Allow creation of APIs, more documentation and better return value * ApiGateway module incorporate review feedback * ApiGateway module flake8 cleanup * APIGateway module - more review fixes. * slightly better messages in api_gateway module * AWS api_gateway module - try to improve messages in case of exceptions * rename api_gateway module to aws_api_gateway as discussed in PR 20230 * aws_api_gateway - Allow delivery of swagger either as text or dictionary. * aws_api_gateway module - introduce 'unit' tests, improve imports using them and small fixes * aws_api_gateway module - move path expand_user to avoid early typecheck * aws_api_gateway - version means version of metadata not module - fix to 1.0 * aws_api_gateway module - Rely on module_utils.ec2 for imports & path type for expanduser / cleanups * aws_api_gateway module - heavy cleanup and refactor of code + cloud retry functionality. * api_gateway_module - failing test case for handling more than one deployment in succession and API deletion * add TooManyRequestsException to AWSRetry exception list - makes API deployment work. * api_gateway_module - Fixes for various review comments + errors from various linters * api_gateway_module - Fixes for more review comments + linter error * api_gateway_module - Major refactor into sensible functions - create_response becomes configure_response * api_gateway_module - should be working under python3; remove test exclusion * api_gateway_module - finish off remaining review fixes - use ansible defaults and fix mutually exclusive * api_gateway_module - attempt to improve handling of botocore errors in python3 * api_gateway_module - implement state=absent / API deletion
Diffstat (limited to 'lib/ansible/modules/cloud/amazon/aws_api_gateway.py')
-rw-r--r--lib/ansible/modules/cloud/amazon/aws_api_gateway.py337
1 files changed, 337 insertions, 0 deletions
diff --git a/lib/ansible/modules/cloud/amazon/aws_api_gateway.py b/lib/ansible/modules/cloud/amazon/aws_api_gateway.py
new file mode 100644
index 0000000000..cfe4aa2269
--- /dev/null
+++ b/lib/ansible/modules/cloud/amazon/aws_api_gateway.py
@@ -0,0 +1,337 @@
+#!/usr/bin/python
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = '''
+---
+module: aws_api_gateway
+short_description: Manage AWS API Gateway APIs
+description:
+ - Allows for the management of API Gatway APIs
+ - Normally you should give the api_id since there is no other
+ stable guaranteed unique identifier for the API. If you do
+ not give api_id then a new API will be create each time
+ this is run.
+ - Beware that there are very hard limits on the rate that
+ you can call API Gateway's REST API. You may need to patch
+ your boto. See https://github.com/boto/boto3/issues/876
+ and discuss with your AWS rep.
+ - swagger_file and swagger_text are passed directly on to AWS
+ transparently whilst swagger_dict is an ansible dict which is
+ converted to JSON before the API definitions are uploaded.
+version_added: '2.4'
+requirements: [ boto3 ]
+options:
+ api_id:
+ description:
+ - The ID of the API you want to manage.
+ state:
+ description:
+ - NOT IMPLEMENTED Create or delete API - currently we always create.
+ default: present
+ choices: [ 'present', 'absent' ]
+ swagger_file:
+ description:
+ - JSON or YAML file containing swagger definitions for API.
+ Exactly one of swagger_file, swagger_text or swagger_dict must
+ be present.
+ swagger_text:
+ description:
+ - Swagger definitions for API in JSON or YAML as a string direct
+ from playbook.
+ swagger_dict:
+ description:
+ - Swagger definitions API ansible dictionary which will be
+ converted to JSON and uploaded.
+ stage:
+ description:
+ - The name of the stage the API should be deployed to.
+ deploy_desc:
+ description:
+ - Description of the deployment - recorded and visible in the
+ AWS console.
+ default: Automatic deployment by Ansible.
+author:
+ - 'Michael De La Rue (@mikedlr)'
+extends_documentation_fragment:
+ - aws
+notes:
+ - A future version of this module will probably use tags or another
+ ID so that an API can be create only once.
+ - As an early work around an intermediate version will probably do
+ the same using a tag embedded in the API name.
+
+'''
+
+EXAMPLES = '''
+# Update API resources for development
+tasks:
+- name: update API
+ aws_api_gateway:
+ api_id: 'abc123321cba'
+ state: present
+ swagger_file: my_api.yml
+
+# update definitions and deploy API to production
+tasks:
+- name: deploy API
+ aws_api_gateway:
+ api_id: 'abc123321cba'
+ state: present
+ swagger_file: my_api.yml
+ stage: production
+ deploy_desc: Make auth fix available.
+'''
+
+RETURN = '''
+output:
+ description: the data returned by put_restapi in boto3
+ returned: success
+ type: dict
+ sample:
+ 'data':
+ {
+ "id": "abc123321cba",
+ "name": "MY REST API",
+ "createdDate": 1484233401
+ }
+'''
+
+import json
+from ansible.module_utils.basic import AnsibleModule, traceback
+from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info, boto3_conn, camel_dict_to_snake_dict, AWSRetry
+
+from ansible.module_utils.ec2 import HAS_BOTO3
+
+try:
+ import botocore
+ HAS_BOTOCORE = True
+except ImportError:
+ HAS_BOTOCORE = False
+
+
+def main():
+ argument_spec = ec2_argument_spec()
+ argument_spec.update(
+ dict(
+ api_id=dict(type='str', required=False),
+ state=dict(type='str', default='present', choices=['present', 'absent']),
+ swagger_file=dict(type='path', default=None, aliases=['src', 'api_file']),
+ swagger_dict=dict(type='json', default=None),
+ swagger_text=dict(type='str', default=None),
+ stage=dict(type='str', default=None),
+ deploy_desc=dict(type='str', default="Automatic deployment by Ansible."),
+ )
+ )
+
+ mutually_exclusive = [['swagger_file', 'swagger_dict', 'swagger_text']] # noqa: F841
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False,
+ mutually_exclusive=mutually_exclusive)
+
+ api_id = module.params.get('api_id')
+ state = module.params.get('state') # noqa: F841
+ swagger_file = module.params.get('swagger_file')
+ swagger_dict = module.params.get('swagger_dict')
+ swagger_text = module.params.get('swagger_text')
+ stage = module.params.get('stage')
+ deploy_desc = module.params.get('deploy_desc')
+
+# check_mode = module.check_mode
+ changed = False
+
+ if not HAS_BOTO3:
+ module.fail_json(msg='Python module "boto3" is missing, please install boto3')
+
+ if not HAS_BOTOCORE:
+ module.fail_json(msg='Python module "botocore" is missing, please install it')
+
+ region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
+ try:
+ client = boto3_conn(module, conn_type='client', resource='apigateway',
+ region=region, endpoint=ec2_url, **aws_connect_kwargs)
+ except botocore.exceptions.NoRegionError:
+ module.fail_json(msg="Region must be specified as a parameter, in "
+ "AWS_DEFAULT_REGION environment variable or in boto configuration file")
+ except (botocore.exceptions.ValidationError, botocore.exceptions.ClientError) as e:
+ fail_json_aws(module, e, msg="connecting to AWS")
+
+ changed = True # for now it will stay that way until we can sometimes avoid change
+
+ conf_res = None
+ dep_res = None
+ del_res = None
+
+ if state == "present":
+ if api_id is None:
+ api_id = create_empty_api(module, client)
+ api_data = get_api_definitions(module, swagger_file=swagger_file,
+ swagger_dict=swagger_dict, swagger_text=swagger_text)
+ conf_res, dep_res = ensure_api_in_correct_state(module, client, api_id=api_id,
+ api_data=api_data, stage=stage,
+ deploy_desc=deploy_desc)
+ if state == "absent":
+ del_res = delete_rest_api(module, client, api_id)
+
+ exit_args = {"changed": changed, "api_id": api_id}
+
+ if conf_res is not None:
+ exit_args['configure_response'] = camel_dict_to_snake_dict(conf_res)
+ if dep_res is not None:
+ exit_args['deploy_response'] = camel_dict_to_snake_dict(dep_res)
+ if del_res is not None:
+ exit_args['delete_response'] = camel_dict_to_snake_dict(del_res)
+
+ module.exit_json(**exit_args)
+
+
+def get_api_definitions(module, swagger_file=None, swagger_dict=None, swagger_text=None):
+ apidata = None
+ if swagger_file is not None:
+ try:
+ with open(swagger_file) as f:
+ apidata = f.read()
+ except OSError as e:
+ msg = "Failed trying to read swagger file {}: {}".format(str(swagger_file), str(e))
+ module.fail_json(msg=msg, exception=traceback.format_exc())
+ if swagger_dict is not None:
+ apidata = json.dumps(swagger_dict)
+ if swagger_text is not None:
+ apidata = swagger_text
+
+ if apidata is None:
+ module.fail_json(msg='module error - failed to get API data')
+ return apidata
+
+
+def create_empty_api(module, client):
+ """
+ creates a new empty API ready to be configured. The description is
+ temporarily set to show the API as incomplete but should be
+ updated when the API is configured.
+ """
+ desc = "Incomplete API creation by ansible aws_api_gateway module"
+ try:
+ awsret = create_api(client, name="ansible-temp-api", description=desc)
+ except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError) as e:
+ fail_json_aws(module, e, msg="creating API")
+ return awsret["id"]
+
+
+def delete_rest_api(module, client, api_id):
+ """
+ creates a new empty API ready to be configured. The description is
+ temporarily set to show the API as incomplete but should be
+ updated when the API is configured.
+ """
+ try:
+ delete_response = delete_api(client, api_id=api_id)
+ except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError) as e:
+ fail_json_aws(module, e, msg="deleting API {}".format(api_id))
+ return delete_response
+
+
+def ensure_api_in_correct_state(module, client, api_id=None, api_data=None, stage=None,
+ deploy_desc=None):
+ """Make sure that we have the API configured and deployed as instructed.
+
+ This function first configures the API correctly uploading the
+ swagger definitions and then deploys those. Configuration and
+ deployment should be closely tied because there is only one set of
+ definitions so if we stop, they may be updated by someone else and
+ then we deploy the wrong configuration.
+ """
+
+ configure_response = None
+ try:
+ configure_response = configure_api(client, api_data=api_data, api_id=api_id)
+ except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError) as e:
+ fail_json_aws(module, e, msg="configuring API {}".format(api_id))
+
+ deploy_response = None
+
+ if stage:
+ try:
+ deploy_response = create_deployment(client, api_id=api_id, stage=stage,
+ description=deploy_desc)
+ except (botocore.exceptions.ClientError, botocore.exceptions.EndpointConnectionError) as e:
+ msg = "deploying api {} to stage {}".format(api_id, stage)
+ fail_json_aws(module, e, msg)
+
+ return configure_response, deploy_response
+
+
+# There is a PR open to merge fail_json_aws this into the standard module code;
+# see https://github.com/ansible/ansible/pull/23882
+def fail_json_aws(module, exception, msg=None):
+ """call fail_json with processed exception
+ function for converting exceptions thrown by AWS SDK modules,
+ botocore, boto3 and boto, into nice error messages.
+ """
+ last_traceback = traceback.format_exc()
+
+ try:
+ except_msg = exception.message
+ except AttributeError:
+ except_msg = str(exception)
+
+ if msg is not None:
+ message = '{}: {}'.format(msg, except_msg)
+ else:
+ message = except_msg
+
+ try:
+ response = exception.response
+ except AttributeError:
+ response = None
+
+ if response is None:
+ module.fail_json(msg=message, traceback=last_traceback)
+ else:
+ module.fail_json(msg=message, traceback=last_traceback,
+ **camel_dict_to_snake_dict(response))
+
+
+retry_params = {"tries": 10, "delay": 5, "backoff": 1.2}
+
+
+@AWSRetry.backoff(**retry_params)
+def create_api(client, name=None, description=None):
+ return client.create_rest_api(name="ansible-temp-api", description=description)
+
+
+@AWSRetry.backoff(**retry_params)
+def delete_api(client, api_id=None):
+ return client.delete_rest_api(restApiId=api_id)
+
+
+@AWSRetry.backoff(**retry_params)
+def configure_api(client, api_data=None, api_id=None, mode="overwrite"):
+ return client.put_rest_api(body=api_data, restApiId=api_id, mode=mode)
+
+
+@AWSRetry.backoff(**retry_params)
+def create_deployment(client, api_id=None, stage=None, description=None):
+ # we can also get None as an argument so we don't do this as a defult
+ return client.create_deployment(restApiId=api_id, stageName=stage, description=description)
+
+
+if __name__ == '__main__':
+ main()