From c714da7fac136c84cfbba57a801c48d874234cff Mon Sep 17 00:00:00 2001 From: Sloane Hertel Date: Thu, 26 Oct 2017 15:18:31 -0400 Subject: [PATCH] cloudformation_facts: don't fail on nonexistent stack - fixes #23419 (#23758) * Allow cloudformation_facts to exit gracefully if stack does not exist make cloudformation_facts pep8 remove from legacy files remove unnecessary if statement Allow cloudformation_facts to exit gracefully if stack does not exist version 2 fix documentation errors add an example for a hard-fail if a stack doesn't exist * Remove extra whitespace * Use the .response attribute since .message isn't present with Python 3 * Don't fail if no stack name is provided and no stacks exist. --- .../cloud/amazon/cloudformation_facts.py | 39 ++++++++++++------- test/sanity/pep8/legacy-files.txt | 1 - 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/ansible/modules/cloud/amazon/cloudformation_facts.py b/lib/ansible/modules/cloud/amazon/cloudformation_facts.py index 2b186b66917..adcfd99b5f9 100644 --- a/lib/ansible/modules/cloud/amazon/cloudformation_facts.py +++ b/lib/ansible/modules/cloud/amazon/cloudformation_facts.py @@ -91,6 +91,13 @@ EXAMPLES = ''' stack_resources: true stack_policy: true +# Fail if the stack doesn't exist +- name: try to get facts about a stack but fail if it doesn't exist + cloudformation_facts: + stack_name: nonexistent-stack + all_facts: yes + failed_when: cloudformation['nonexistent-stack'] is undefined + # Example dictionary outputs for stack_outputs, stack_parameters and stack_resources: # "stack_outputs": { # "ApplicationDatabaseName": "dazvlpr01xj55a.ap-southeast-2.rds.amazonaws.com", @@ -113,38 +120,38 @@ EXAMPLES = ''' RETURN = ''' stack_description: description: Summary facts about the stack - returned: always + returned: if the stack exists type: dict stack_outputs: description: Dictionary of stack outputs keyed by the value of each output 'OutputKey' parameter and corresponding value of each output 'OutputValue' parameter - returned: always + returned: if the stack exists type: dict stack_parameters: description: Dictionary of stack parameters keyed by the value of each parameter 'ParameterKey' parameter and corresponding value of each parameter 'ParameterValue' parameter - returned: always + returned: if the stack exists type: dict stack_events: description: All stack events for the stack - returned: only if all_facts or stack_events is true + returned: only if all_facts or stack_events is true and the stack exists type: list stack_policy: description: Describes the stack policy for the stack - returned: only if all_facts or stack_policy is true + returned: only if all_facts or stack_policy is true and the stack exists type: dict stack_template: description: Describes the stack template for the stack - returned: only if all_facts or stack_template is true + returned: only if all_facts or stack_template is true and the stack exists type: dict stack_resource_list: description: Describes stack resources for the stack - returned: only if all_facts or stack_resourses is true + returned: only if all_facts or stack_resourses is true and the stack exists type: list stack_resources: description: Dictionary of stack resources keyed by the value of each resource 'LogicalResourceId' parameter and corresponding value of each resource 'PhysicalResourceId' parameter - returned: only if all_facts or stack_resourses is true + returned: only if all_facts or stack_resourses is true and the stack exists type: dict ''' @@ -159,6 +166,7 @@ try: except ImportError: HAS_BOTO3 = False +from ansible.module_utils._text import to_native from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.ec2 import (get_aws_connection_info, ec2_argument_spec, boto3_conn, camel_dict_to_snake_dict, AWSRetry, boto3_tag_list_to_ansible_dict) @@ -191,22 +199,25 @@ class CloudFormationServiceManager: kwargs = {'StackName': stack_name} if stack_name else {} func = partial(self.client.describe_stacks, **kwargs) response = self.paginated_response(func, 'Stacks') - if response: + if response is not None: return response self.module.fail_json(msg="Error describing stack(s) - an empty response was returned") except Exception as e: - self.module.fail_json(msg="Error describing stack(s) - " + str(e), exception=traceback.format_exc()) + if 'does not exist' in e.response['Error']['Message']: + # missing stack, don't bail. + return {} + self.module.fail_json(msg="Error describing stack - " + to_native(e), exception=traceback.format_exc()) def list_stack_resources(self, stack_name): try: - func = partial(self.client.list_stack_resources,StackName=stack_name) + func = partial(self.client.list_stack_resources, StackName=stack_name) return self.paginated_response(func, 'StackResourceSummaries') except Exception as e: self.module.fail_json(msg="Error listing stack resources - " + str(e), exception=traceback.format_exc()) def describe_stack_events(self, stack_name): try: - func = partial(self.client.describe_stack_events,StackName=stack_name) + func = partial(self.client.describe_stack_events, StackName=stack_name) return self.paginated_response(func, 'StackEvents') except Exception as e: self.module.fail_json(msg="Error describing stack events - " + str(e), exception=traceback.format_exc()) @@ -233,7 +244,7 @@ class CloudFormationServiceManager: Returns expanded response for paginated operations. The 'result_key' is used to define the concatenated results that are combined from each paginated response. ''' - args=dict() + args = dict() if next_token: args['NextToken'] = next_token response = func(**args) @@ -243,6 +254,7 @@ class CloudFormationServiceManager: return result return result + self.paginated_response(func, result_key, next_token) + def to_dict(items, key, value): ''' Transforms a list of items to a Key/Value dictionary ''' if items: @@ -250,6 +262,7 @@ def to_dict(items, key, value): else: return dict() + def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt index 7f04ff2a492..efd06460da1 100644 --- a/test/sanity/pep8/legacy-files.txt +++ b/test/sanity/pep8/legacy-files.txt @@ -12,7 +12,6 @@ lib/ansible/modules/cloud/openstack/_os_server_actions.py lib/ansible/modules/cloud/ovirt/_ovirt_affinity_groups.py lib/ansible/modules/cloud/amazon/aws_kms.py lib/ansible/modules/cloud/amazon/cloudformation.py -lib/ansible/modules/cloud/amazon/cloudformation_facts.py lib/ansible/modules/cloud/amazon/cloudfront_facts.py lib/ansible/modules/cloud/amazon/dynamodb_table.py lib/ansible/modules/cloud/amazon/ec2_ami_copy.py