mirror of https://github.com/ansible/ansible.git
[cloud][aws] New module: cloudfront_invalidation (#35601)
* Remove redundant default for target_paths * Make invalidation requests idempotent * Remove redundant exception handling around boto3_conn * Add cloudfront_invalidation to the changelogpull/35623/merge
parent
36bd24f121
commit
4faad64579
@ -0,0 +1,275 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# Copyright (c) 2017 Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||||
|
'status': ['preview'],
|
||||||
|
'supported_by': 'community'}
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
|
||||||
|
module: cloudfront_invalidation
|
||||||
|
|
||||||
|
short_description: create invalidations for aws cloudfront distributions
|
||||||
|
description:
|
||||||
|
- Allows for invalidation of a batch of paths for a CloudFront distribution.
|
||||||
|
|
||||||
|
requirements:
|
||||||
|
- boto3 >= 1.0.0
|
||||||
|
- python >= 2.6
|
||||||
|
|
||||||
|
version_added: "2.5"
|
||||||
|
|
||||||
|
author: Willem van Ketwich (@wilvk)
|
||||||
|
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- aws
|
||||||
|
- ec2
|
||||||
|
|
||||||
|
options:
|
||||||
|
distribution_id:
|
||||||
|
description:
|
||||||
|
- The id of the cloudfront distribution to invalidate paths for. Can be specified insted of the alias.
|
||||||
|
required: false
|
||||||
|
alias:
|
||||||
|
description:
|
||||||
|
- The alias of the cloudfront distribution to invalidate paths for. Can be specified instead of distribution_id.
|
||||||
|
required: false
|
||||||
|
caller_reference:
|
||||||
|
description:
|
||||||
|
- A unique reference identifier for the invalidation paths.
|
||||||
|
required: false
|
||||||
|
default: current datetime stamp
|
||||||
|
target_paths:
|
||||||
|
description:
|
||||||
|
- A list of paths on the distribution to invalidate. Each path should begin with '/'. Wildcards are allowed. eg. '/foo/bar/*'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
notes:
|
||||||
|
- does not support check mode
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
|
||||||
|
- name: create a batch of invalidations using a distribution_id for a reference
|
||||||
|
cloudfront_invalidation:
|
||||||
|
distribution_id: E15BU8SDCGSG57
|
||||||
|
caller_reference: testing 123
|
||||||
|
target_paths:
|
||||||
|
- /testpathone/test1.css
|
||||||
|
- /testpathtwo/test2.js
|
||||||
|
- /testpaththree/test3.ss
|
||||||
|
|
||||||
|
- name: create a batch of invalidations using an alias as a reference and one path using a wildcard match
|
||||||
|
cloudfront_invalidation:
|
||||||
|
alias: alias.test.com
|
||||||
|
caller_reference: testing 123
|
||||||
|
target_paths:
|
||||||
|
- /testpathone/test4.css
|
||||||
|
- /testpathtwo/test5.js
|
||||||
|
- /testpaththree/*
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
invalidation:
|
||||||
|
description: The invalidation's information.
|
||||||
|
returned: always
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
create_time:
|
||||||
|
description: The date and time the invalidation request was first made.
|
||||||
|
returned: always
|
||||||
|
type: string
|
||||||
|
sample: '2018-02-01T15:50:41.159000+00:00'
|
||||||
|
id:
|
||||||
|
description: The identifier for the invalidation request.
|
||||||
|
returned: always
|
||||||
|
type: string
|
||||||
|
sample: I2G9MOWJZFV612
|
||||||
|
invalidation_batch:
|
||||||
|
description: The current invalidation information for the batch request.
|
||||||
|
returned: always
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
caller_reference:
|
||||||
|
description: The value used to uniquely identify an invalidation request.
|
||||||
|
returned: always
|
||||||
|
type: string
|
||||||
|
sample: testing 123
|
||||||
|
paths:
|
||||||
|
description: A dict that contains information about the objects that you want to invalidate.
|
||||||
|
returned: always
|
||||||
|
type: complex
|
||||||
|
contains:
|
||||||
|
items:
|
||||||
|
description: A list of the paths that you want to invalidate.
|
||||||
|
returned: always
|
||||||
|
type: list
|
||||||
|
sample:
|
||||||
|
- /testpathtwo/test2.js
|
||||||
|
- /testpathone/test1.css
|
||||||
|
- /testpaththree/test3.ss
|
||||||
|
quantity:
|
||||||
|
description: The number of objects that you want to invalidate.
|
||||||
|
returned: always
|
||||||
|
type: int
|
||||||
|
sample: 3
|
||||||
|
status:
|
||||||
|
description: The status of the invalidation request.
|
||||||
|
returned: always
|
||||||
|
type: string
|
||||||
|
sample: Completed
|
||||||
|
location:
|
||||||
|
description: The fully qualified URI of the distribution and invalidation batch request.
|
||||||
|
returned: always
|
||||||
|
type: string
|
||||||
|
sample: https://cloudfront.amazonaws.com/2017-03-25/distribution/E1ZID6KZJECZY7/invalidation/I2G9MOWJZFV622
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible.module_utils.ec2 import get_aws_connection_info
|
||||||
|
from ansible.module_utils.ec2 import ec2_argument_spec, boto3_conn
|
||||||
|
from ansible.module_utils.ec2 import snake_dict_to_camel_dict
|
||||||
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
||||||
|
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||||
|
from ansible.module_utils.aws.cloudfront_facts import CloudFrontFactsServiceManager
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
from botocore.exceptions import ClientError, BotoCoreError
|
||||||
|
except ImportError:
|
||||||
|
pass # caught by imported AnsibleAWSModule
|
||||||
|
|
||||||
|
|
||||||
|
class CloudFrontInvalidationServiceManager(object):
|
||||||
|
"""
|
||||||
|
Handles CloudFront service calls to AWS for invalidations
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, module):
|
||||||
|
self.module = module
|
||||||
|
self.create_client('cloudfront')
|
||||||
|
|
||||||
|
def create_client(self, resource):
|
||||||
|
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(self.module, boto3=True)
|
||||||
|
self.client = boto3_conn(self.module, conn_type='client', resource=resource, region=region, endpoint=ec2_url, **aws_connect_kwargs)
|
||||||
|
|
||||||
|
def create_invalidation(self, distribution_id, invalidation_batch):
|
||||||
|
current_invalidation_response = self.get_invalidation(distribution_id, invalidation_batch['CallerReference'])
|
||||||
|
try:
|
||||||
|
response = self.client.create_invalidation(DistributionId=distribution_id, InvalidationBatch=invalidation_batch)
|
||||||
|
response.pop('ResponseMetadata', None)
|
||||||
|
if current_invalidation_response:
|
||||||
|
return response, False
|
||||||
|
else:
|
||||||
|
return response, True
|
||||||
|
except BotoCoreError as e:
|
||||||
|
self.module.fail_json_aws(e, msg="Error creating CloudFront invalidations.")
|
||||||
|
except ClientError as e:
|
||||||
|
if ('Your request contains a caller reference that was used for a previous invalidation batch '
|
||||||
|
'for the same distribution.' in e.response['Error']['Message']):
|
||||||
|
self.module.warn("InvalidationBatch target paths are not modifiable. "
|
||||||
|
"To make a new invalidation please update caller_reference.")
|
||||||
|
return current_invalidation_response, False
|
||||||
|
else:
|
||||||
|
self.module.fail_json_aws(e, msg="Error creating CloudFront invalidations.")
|
||||||
|
|
||||||
|
def get_invalidation(self, distribution_id, caller_reference):
|
||||||
|
current_invalidation = {}
|
||||||
|
# find all invalidations for the distribution
|
||||||
|
try:
|
||||||
|
paginator = self.client.get_paginator('list_invalidations')
|
||||||
|
invalidations = paginator.paginate(DistributionId=distribution_id).build_full_result()['InvalidationList'].get('Items', [])
|
||||||
|
invalidation_ids = [inv['Id'] for inv in invalidations]
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
self.module.fail_json_aws(e, msg="Error listing CloudFront invalidations.")
|
||||||
|
|
||||||
|
# check if there is an invalidation with the same caller reference
|
||||||
|
for inv_id in invalidation_ids:
|
||||||
|
try:
|
||||||
|
invalidation = self.client.get_invalidation(DistributionId=distribution_id, Id=inv_id)['Invalidation']
|
||||||
|
caller_ref = invalidation.get('InvalidationBatch', {}).get('CallerReference')
|
||||||
|
except (BotoCoreError, ClientError) as e:
|
||||||
|
self.module.fail_json_aws(e, msg="Error getting Cloudfront invalidation {0}".format(inv_id))
|
||||||
|
if caller_ref == caller_reference:
|
||||||
|
current_invalidation = invalidation
|
||||||
|
break
|
||||||
|
|
||||||
|
current_invalidation.pop('ResponseMetadata', None)
|
||||||
|
return current_invalidation
|
||||||
|
|
||||||
|
|
||||||
|
class CloudFrontInvalidationValidationManager(object):
|
||||||
|
"""
|
||||||
|
Manages Cloudfront validations for invalidation batches
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, module):
|
||||||
|
self.module = module
|
||||||
|
self.__cloudfront_facts_mgr = CloudFrontFactsServiceManager(module)
|
||||||
|
|
||||||
|
def validate_distribution_id(self, distribution_id, alias):
|
||||||
|
try:
|
||||||
|
if distribution_id is None and alias is None:
|
||||||
|
self.module.fail_json(msg="distribution_id or alias must be specified")
|
||||||
|
if distribution_id is None:
|
||||||
|
distribution_id = self.__cloudfront_facts_mgr.get_distribution_id_from_domain_name(alias)
|
||||||
|
return distribution_id
|
||||||
|
except (ClientError, BotoCoreError) as e:
|
||||||
|
self.module.fail_json_aws(e, msg="Error validating parameters.")
|
||||||
|
|
||||||
|
def create_aws_list(self, invalidation_batch):
|
||||||
|
aws_list = {}
|
||||||
|
aws_list["Quantity"] = len(invalidation_batch)
|
||||||
|
aws_list["Items"] = invalidation_batch
|
||||||
|
return aws_list
|
||||||
|
|
||||||
|
def validate_invalidation_batch(self, invalidation_batch, caller_reference):
|
||||||
|
try:
|
||||||
|
if caller_reference is not None:
|
||||||
|
valid_caller_reference = caller_reference
|
||||||
|
else:
|
||||||
|
valid_caller_reference = datetime.datetime.now().isoformat()
|
||||||
|
valid_invalidation_batch = {
|
||||||
|
'paths': self.create_aws_list(invalidation_batch),
|
||||||
|
'caller_reference': valid_caller_reference
|
||||||
|
}
|
||||||
|
return valid_invalidation_batch
|
||||||
|
except (ClientError, BotoCoreError) as e:
|
||||||
|
self.module.fail_json_aws(e, msg="Error validating invalidation batch.")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = ec2_argument_spec()
|
||||||
|
|
||||||
|
argument_spec.update(dict(
|
||||||
|
caller_reference=dict(),
|
||||||
|
distribution_id=dict(),
|
||||||
|
alias=dict(),
|
||||||
|
target_paths=dict(required=True, type='list')
|
||||||
|
))
|
||||||
|
|
||||||
|
module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=False, mutually_exclusive=[['distribution_id', 'alias']])
|
||||||
|
|
||||||
|
validation_mgr = CloudFrontInvalidationValidationManager(module)
|
||||||
|
service_mgr = CloudFrontInvalidationServiceManager(module)
|
||||||
|
|
||||||
|
caller_reference = module.params.get('caller_reference')
|
||||||
|
distribution_id = module.params.get('distribution_id')
|
||||||
|
alias = module.params.get('alias')
|
||||||
|
target_paths = module.params.get('target_paths')
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
distribution_id = validation_mgr.validate_distribution_id(distribution_id, alias)
|
||||||
|
valid_target_paths = validation_mgr.validate_invalidation_batch(target_paths, caller_reference)
|
||||||
|
valid_pascal_target_paths = snake_dict_to_camel_dict(valid_target_paths, True)
|
||||||
|
result, changed = service_mgr.create_invalidation(distribution_id, valid_pascal_target_paths)
|
||||||
|
|
||||||
|
module.exit_json(changed=changed, **camel_dict_to_snake_dict(result))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue