From 2b0b61e2926543f2beacda76d8981416dae40fb4 Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Mon, 18 Aug 2025 14:11:26 -0700 Subject: [PATCH] Add medialive_sdi_source(+_info) module and integration tests Date: Thu Apr 24 12:00:00 2025 -0700 Author: Brenton Buxell --- meta/runtime.yml | 2 + plugins/modules/medialive_sdi_source.py | 367 ++++++++++++++++++ plugins/modules/medialive_sdi_source_info.py | 208 ++++++++++ .../targets/medialive_sdi_source/aliases | 1 + .../medialive_sdi_source/meta/main.yml | 5 + .../medialive_sdi_source/tasks/cleanup.yml | 18 + .../medialive_sdi_source/tasks/main.yml | 341 ++++++++++++++++ 7 files changed, 942 insertions(+) create mode 100644 plugins/modules/medialive_sdi_source.py create mode 100644 plugins/modules/medialive_sdi_source_info.py create mode 100644 tests/integration/targets/medialive_sdi_source/aliases create mode 100644 tests/integration/targets/medialive_sdi_source/meta/main.yml create mode 100644 tests/integration/targets/medialive_sdi_source/tasks/cleanup.yml create mode 100644 tests/integration/targets/medialive_sdi_source/tasks/main.yml diff --git a/meta/runtime.yml b/meta/runtime.yml index 15743d53e91..a9d0eff9b53 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -143,6 +143,8 @@ action_groups: - lightsail - lightsail_snapshot - lightsail_static_ip + - medialive_sdi_source_info + - medialive_sdi_source - mq_broker - mq_broker_config - mq_broker_info diff --git a/plugins/modules/medialive_sdi_source.py b/plugins/modules/medialive_sdi_source.py new file mode 100644 index 00000000000..d85dab9c103 --- /dev/null +++ b/plugins/modules/medialive_sdi_source.py @@ -0,0 +1,367 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r""" +--- +module: medialive_sdi_source +version_added: 10.1.0 +short_description: Manage AWS MediaLive Anywhere SDI sources +description: + - A module for creating, updating and deleting AWS MediaLive Anywhere SDI sources. + - This module requires boto3 >= 1.37.34. +author: + - "Brenton Buxell (@buxell)" +options: + id: + description: + - The ID of the SDI source. + required: false + type: str + aliases: ['sdi_source_id'] + name: + description: + - The name of the SDI source. + required: false + type: str + aliases: ['sdi_source_name'] + state: + description: + - Create/update or remove the SDI source. + required: false + choices: ['present', 'absent'] + default: 'present' + type: str + mode: + description: + - Applies only if the type is 'QUAD' + - Specify the mode for handling the quad-link signal, 'QUADRANT' or 'INTERLEAVE' + type: str + required: false + choices: ['QUADRANT', 'INTERLEAVE'] + type: + description: + - Specify the type of the SDI source + - SINGLE, the source is a single-link source + - QUAD, the source is one part of a quad-link source + - Defaults to 'SINGLE' when creating a new SDI source + default: 'SINGLE' + type: str + required: false + choices: ['SINGLE', 'QUAD'] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +- name: Create a MediaLive Anywhere SDI source + community.aws.medialive_sdi_source: + name: 'ExampleSdiSource' + state: present + type: 'QUAD' + mode: 'INTERLEAVE' + +- name: Delete a MediaLive Anywhere SDI source + community.aws.medialive_sdi_source: + name: 'ExampleSdiSource' + state: absent +""" + +RETURN = r""" +changed: + description: if a MediaLive SDI Source has been created, updated or deleted + returned: always + type: bool + sample: + changed: true +sdi_source: + description: The details of the SDI source + returned: success + type: dict + contains: + arn: + description: The ARN of the SDI source. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:sdiSource/123456" + id: + description: The ID of the SDI source + type: str + returned: success + example: "123456" + inputs: + description: The list of inputs that are currently using this SDI source + type: list + elements: str + returned: success + example: ["Input1", "Input2"] + mode: + description: Applies only if the type is QUAD. The mode for handling the quad-link signal QUADRANT or INTERLEAVE + type: str + returned: success + example: "QUADRANT" + name: + description: The name of the SDI source + type: str + returned: success + example: "ExampleSdiSource" + state: + description: The state of the SDI source. Either 'IDLE', 'IN_USE' or 'DELETED' + type: str + returned: success + example: "IN_USE" + type: + description: The type of the SDI source + type: str + returned: success + example: "SINGLE" +""" + +import uuid +from typing import Dict + +try: + from botocore.exceptions import ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict, snake_dict_to_camel_dict, recursive_diff +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError + + +class MediaLiveSdiSourceManager: + """Manage AWS MediaLive Anywhere SDI sources""" + + def __init__(self, module: AnsibleAWSModule): + """ + Initialize the MediaLiveSdiSourceManager + + Args: + module: AnsibleAWSModule instance + """ + self.module = module + self.client = module.client('medialive') + self._sdi_source = {} + self.changed = False + + @property + def sdi_source(self): + return self._sdi_source + + @sdi_source.setter + def sdi_source(self, sdi_source: Dict): + sdi_source = camel_dict_to_snake_dict(sdi_source) + if sdi_source.get('response_metadata'): + del sdi_source['response_metadata'] + + # Handle nested sdi_source + if sdi_source.get('sdi_source'): + sdi_source = sdi_source.get('sdi_source') # type: ignore + + if sdi_source.get('id'): + sdi_source['sdi_source_id'] = sdi_source.get('id') + del sdi_source['id'] + self._sdi_source = sdi_source + + def do_create_sdi_source(self, params): + """ + Create a new MediaLive SDI source + + Args: + params: Parameters for SDI source creation + """ + allowed_params = ['name', 'type', 'mode'] + required_params = ['name', 'type'] + + for param in required_params: + if not params.get(param): + raise AnsibleAWSError(message=f'The {param} parameter is required when creating a new SDI source') + + create_params = { k: v for k, v in params.items() if k in allowed_params and v } + create_params = snake_dict_to_camel_dict(create_params, capitalize_first=True) + + try: + response = self.client.create_sdi_source(**create_params) # type: ignore + self.sdi_source = response + self.changed = True + except (ClientError, BotoCoreError) as e: # type: ignore + raise AnsibleAWSError( + message='Unable to create Medialive SDI Source', + exception=e + ) + + def do_update_sdi_source(self, params): + """ + Update a new MediaLive SDI source + + Args: + params: Parameters for SDI source update + """ + if not params.get('sdi_source_id'): + raise AnsibleAWSError(message='The sdi_source_id parameter is required during SDI source update.') + + allowed_params = ['sdi_source_id', 'name', 'mode', 'type'] + + # Make sure current_params is always a subset of update_params, so we don't update unnecessarily + current_params = { + k: v for k, v in self.sdi_source.items() + if k in allowed_params and k in params and params[k] + } + update_params = { k: v for k, v in params.items() if k in allowed_params and v } + + # Short circuit + if not recursive_diff(current_params, update_params): + return + + update_params = snake_dict_to_camel_dict(update_params, capitalize_first=True) + + try: + response = self.client.update_sdi_source(**update_params) # type: ignore + self.sdi_source = response + self.changed = True + except (ClientError, BotoCoreError) as e: # type: ignore + raise AnsibleAWSError( + message='Unable to update Medialive SDI Source', + exception=e + ) + + def get_sdi_source_by_name(self, name: str): + """ + Find an SDI source by name + + Args: + name: The name of the SDI source to find + """ + try: + paginator = self.client.get_paginator('list_sdi_sources') # type: ignore + found = [] + for page in paginator.paginate(): + for sdi_source in page.get('SdiSources', []): + if sdi_source.get('Name') == name: + found.append(sdi_source.get('Id')) + if len(found) > 1: + raise AnsibleAWSError( + message='Found more than one Medialive SDI Sources under the same name' + ) + elif len(found) == 1: + self.get_sdi_source_by_id(found[0]) + + except (ClientError, BotoCoreError) as e: # type: ignore + raise AnsibleAWSError( + message='Unable to get Medialive SDI Source', + exception=e + ) + + def get_sdi_source_by_id(self, sdi_source_id: str): + """ + Get an SDI source by ID + + Args: + sdi_source_id: The ID of the SDI source to retrieve + """ + try: + self.sdi_source = self.client.describe_sdi_source(SdiSourceId=sdi_source_id) # type: ignore + return True + except is_boto3_error_code('ResourceNotFoundException'): + self.sdi_source = {} + + def delete_sdi_source_by_id(self, sdi_source_id: str): + """ + Delete a MediaLive SDI source + + Args: + sdi_source_id: ID of the SDI source to delete + """ + try: + self.sdi_source = self.client.delete_sdi_source(SdiSourceId=sdi_source_id) # type: ignore + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.sdi_source = {} + except (ClientError, BotoCoreError) as e: # type: ignore + raise AnsibleAWSError( + message='Unable to delete Medialive SDI source', + exception=e + ) + + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['sdi_source_id']), + name=dict(type='str', required=False, aliases=['sdi_source_name']), + state=dict(type='str', default='present', choices=['present', 'absent']), + type=dict(type='str', required=False, choices=['SINGLE', 'QUAD']), + mode=dict(type='str', required=False, choices=['QUADRANT', 'INTERLEAVE']) + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + # Extract module parameters + sdi_source_id = module.params.get('id') + sdi_source_name = module.params.get('name') + state = module.params.get('state') + source_type = module.params.get('type') + mode = module.params.get('mode') + + # Initialize the manager + manager = MediaLiveSdiSourceManager(module) + + # Find the SDI source by ID or name + # Update manager.sdi_source with the details + if sdi_source_id: + manager.get_sdi_source_by_id(sdi_source_id) + elif sdi_source_name: + manager.get_sdi_source_by_name(sdi_source_name) + sdi_source_id = manager.sdi_source.get('sdi_source_id') + + # Do nothing in check mode + if module.check_mode: + module.exit_json(changed=True) + + # Handle present state + if state == 'present': + + # Case update + if manager.sdi_source: + update_params = { + 'name': sdi_source_name, + 'sdi_source_id': sdi_source_id, + 'mode': mode, + 'type': source_type + } + manager.do_update_sdi_source(update_params) + + # Case create + else: + if not source_type: + source_type = 'SINGLE' + create_params = { + 'mode': mode, + 'name': sdi_source_name, + 'request_id': str(uuid.uuid4()), + 'type': source_type + } + + manager.do_create_sdi_source(create_params) + + # Handle absent state + elif state == 'absent': + if manager.sdi_source and manager.sdi_source.get('state') != 'DELETED': + # SDI source exists, delete it + sdi_source_id = manager.sdi_source.get('sdi_source_id') + manager.delete_sdi_source_by_id(sdi_source_id) # type: ignore + + module.exit_json(changed=manager.changed, sdi_source=manager.sdi_source) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_sdi_source_info.py b/plugins/modules/medialive_sdi_source_info.py new file mode 100644 index 00000000000..f02e52c7277 --- /dev/null +++ b/plugins/modules/medialive_sdi_source_info.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +DOCUMENTATION = r""" +--- +module: medialive_sdi_source_info +version_added: 10.1.0 +short_description: Gather AWS MediaLive Anywhere SDI source info +description: + - Get details about a AWS MediaLive Anywhere SDI source. + - This module requires boto3 >= 1.37.34. +author: + - "Brenton Buxell (@bbuxell)" +options: + id: + description: + - The ID of the SDI source. + required: false + type: str + aliases: ['sdi_source_id'] + name: + description: + - The name of the SDI source. + required: false + type: str + aliases: ['sdi_source_name'] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 + - amazon.aws.tags +""" + +EXAMPLES = r""" +- name: Create a MediaLive Anywhere SDI source by name + community.aws.medialive_sdi_source_info: + name: 'ExampleSdiSource' + register: found_source + +- name: Create a MediaLive Anywhere SDI source by ID + community.aws.medialive_sdi_source_info: + id: '1234567' + register: found_source +""" + +RETURN = r""" +sdi_source: + description: The details of the SDI source + returned: success + type: dict + contains: + arn: + description: The ARN of the SDI source. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:sdiSource/123456" + id: + description: The ID of the SDI source + type: str + returned: success + example: "123456" + inputs: + description: The list of inputs that are currently using this SDI source + type: list + elements: str + returned: success + example: ["Input1", "Input2"] + mode: + description: Applies only if the type is QUAD. The mode for handling the quad-link signal QUADRANT or INTERLEAVE + type: str + returned: success + example: "QUADRANT" + name: + description: The name of the SDI source + type: str + returned: success + example: "ExampleSdiSource" + state: + description: The state of the SDI source + type: str + returned: success + example: "IN_USE" + type: + description: The type of the SDI source + type: str + returned: success + example: "SINGLE" +""" + +from typing import Dict + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError + + +class MediaLiveSdiSourceGetter: + """Manage AWS MediaLive Anywhere SDI sources""" + + def __init__(self, module: AnsibleAWSModule): + """ + Initialize the MediaLiveSdiSourceManager + + Args: + module: AnsibleAWSModule instance + """ + self.module = module + self.client = module.client('medialive') + self._sdi_source = {} + + @property + def sdi_source(self): + return self._sdi_source + + @sdi_source.setter + def sdi_source(self, sdi_source: Dict): + sdi_source = camel_dict_to_snake_dict(sdi_source) + if sdi_source.get('response_metadata'): + del sdi_source['response_metadata'] + + # Handle nested sdi_source + if sdi_source.get('sdi_source'): + sdi_source = sdi_source.get('sdi_source') # type: ignore + + if sdi_source.get('id'): + sdi_source['sdi_source_id'] = sdi_source.get('id') + del sdi_source['id'] + self._sdi_source = sdi_source + + def get_sdi_source_by_name(self, name: str): + """ + Find an SDI source by name + + Args: + name: The name of the SDI source to find + """ + try: + paginator = self.client.get_paginator('list_sdi_sources') # type: ignore + found = [] + for page in paginator.paginate(): + for sdi_source in page.get('SdiSources', []): + if sdi_source.get('Name') == name: + found.append(sdi_source.get('Id')) + if len(found) > 1: + raise AnsibleAWSError( + message='Found more than one Medialive SDI Sources under the same name' + ) + elif len(found) == 1: + self.get_sdi_source_by_id(found[0]) + + except (ClientError, BotoCoreError) as e: # type: ignore + raise AnsibleAWSError( + message='Unable to get Medialive SDI Source', + exception=e + ) + + def get_sdi_source_by_id(self, sdi_source_id: str): + """ + Get an SDI source by ID + + Args: + sdi_source_id: The ID of the SDI source to retrieve + """ + try: + self.sdi_source = self.client.describe_sdi_source(SdiSourceId=sdi_source_id) # type: ignore + return True + except is_boto3_error_code('ResourceNotFoundException'): + self.sdi_source = {} + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['sdi_source_id']), + name=dict(type='str', required=False, aliases=['sdi_source_name']) + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + # Do nothing in check mode + if module.check_mode: + module.exit_json(changed=True) + + # Extract module parameters + sdi_source_id = module.params.get('id') + sdi_source_name = module.params.get('name') + + # Initialize the getter + getter = MediaLiveSdiSourceGetter(module) + + # Find the SDI source by ID or name + # Update manager.sdi_source with the details + if sdi_source_id: + getter.get_sdi_source_by_id(sdi_source_id) + elif sdi_source_name: + getter.get_sdi_source_by_name(sdi_source_name) + + module.exit_json(changed=False, sdi_source=getter.sdi_source) + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/medialive_sdi_source/aliases b/tests/integration/targets/medialive_sdi_source/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_sdi_source/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_sdi_source/meta/main.yml b/tests/integration/targets/medialive_sdi_source/meta/main.yml new file mode 100644 index 00000000000..42b16d695fd --- /dev/null +++ b/tests/integration/targets/medialive_sdi_source/meta/main.yml @@ -0,0 +1,5 @@ +dependencies: + - role: setup_botocore_pip + vars: + botocore_version: "1.37.34" + boto3_version: "1.37.34" diff --git a/tests/integration/targets/medialive_sdi_source/tasks/cleanup.yml b/tests/integration/targets/medialive_sdi_source/tasks/cleanup.yml new file mode 100644 index 00000000000..0526bd27267 --- /dev/null +++ b/tests/integration/targets/medialive_sdi_source/tasks/cleanup.yml @@ -0,0 +1,18 @@ +--- +- name: Cleanup - ensure alternatively named SDI source is deleted + community.aws.medialive_sdi_source: + name: "{{ alt_sdi_source_name }}" + state: absent + ignore_errors: true + +- name: Cleanup - ensure single SDI source is deleted + community.aws.medialive_sdi_source: + name: "{{ single_sdi_source_name }}" + state: absent + ignore_errors: true + +- name: Cleanup - ensure quad SDI source is deleted + community.aws.medialive_sdi_source: + name: "{{ quad_sdi_source_name }}" + state: absent + ignore_errors: true diff --git a/tests/integration/targets/medialive_sdi_source/tasks/main.yml b/tests/integration/targets/medialive_sdi_source/tasks/main.yml new file mode 100644 index 00000000000..21ea1fc7b57 --- /dev/null +++ b/tests/integration/targets/medialive_sdi_source/tasks/main.yml @@ -0,0 +1,341 @@ +--- +# Integration tests for medialive_sdi_source module +- name: Wrap MediaLive sdi source tests with AWS credentials + module_defaults: + group/aws: + access_key: '{{ aws_access_key }}' + secret_key: '{{ aws_secret_key }}' + session_token: '{{ security_token | default(omit) }}' + region: '{{ aws_region }}' + set_fact: + ansible_python_interpreter: "{{ botocore_virtualenv_interpreter }}" + + block: + # Setup test variables + - name: Set up test variables + ansible.builtin.set_fact: + single_sdi_source_name: "{{ resource_prefix }}-sdi-source-single" + alt_sdi_source_name: "{{ resource_prefix }}-alt-sdi-source" + quad_sdi_source_name: "{{ resource_prefix }}-sdi-source-quad" + sdi_source_mode: "QUADRANT" + sdi_source_mode_interleave: "INTERLEAVE" + quad_source_type: "QUAD" + single_source_type: "SINGLE" + + # Create MediaLive SDI source + - name: Create a single MediaLive SDI source + community.aws.medialive_sdi_source: + name: '{{ single_sdi_source_name }}' + type: '{{ single_source_type }}' + state: present + register: create_single_result + + - name: Assert source was created successfully + ansible.builtin.assert: + that: + - create_single_result.changed == true + - create_single_result.sdi_source is defined + - create_single_result.sdi_source.name == single_sdi_source_name + - create_single_result.sdi_source.type == single_source_type + - create_single_result.sdi_source.state == "IDLE" + - create_single_result.sdi_source.arn is defined + fail_msg: "Single source creation failed or returned unexpected data" + success_msg: "Single source created successfully with expected properties" + when: create_single_result is defined and create_single_result.sdi_source is defined + + - name: Capture sdi_source_id + ansible.builtin.set_fact: + sdi_source_id: '{{ create_single_result.sdi_source.sdi_source_id }}' + + - name: Create the same source again (idempotency check) + community.aws.medialive_sdi_source: + name: '{{ single_sdi_source_name }}' + type: '{{ single_source_type }}' + state: present + register: idempotency_result + when: sdi_source_id is defined + + - name: Assert idempotency result + ansible.builtin.assert: + that: + - idempotency_result.changed == false + - idempotency_result.sdi_source is defined + - idempotency_result.sdi_source.name == single_sdi_source_name + - idempotency_result.sdi_source.type == single_source_type + - idempotency_result.sdi_source.state == "IDLE" + - idempotency_result.sdi_source.arn is defined + fail_msg: "Idempotency check failed" + success_msg: "Idempotency check passed" + when: idempotency_result is defined and idempotency_result.sdi_source is defined + + # Create quad + - name: Create a quad MediaLive SDI source + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + mode: '{{ sdi_source_mode }}' + type: '{{ quad_source_type }}' + state: present + register: create_quad_result + + - name: Assert quad source was created successfully + ansible.builtin.assert: + that: + - create_quad_result.changed == true + - create_quad_result.sdi_source is defined + - create_quad_result.sdi_source.name == quad_sdi_source_name + - create_quad_result.sdi_source.type == quad_source_type + - create_quad_result.sdi_source.mode == sdi_source_mode + - create_quad_result.sdi_source.state == "IDLE" + - create_quad_result.sdi_source.arn is defined + fail_msg: "Quad source creation failed or returned unexpected data" + success_msg: "Quad source created successfully with expected properties" + when: create_quad_result is defined and create_quad_result.sdi_source is defined + + - name: Create the quad source again (idempotency check) + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + mode: '{{ sdi_source_mode }}' + type: '{{ quad_source_type }}' + state: present + register: idempotency_quad_result + when: create_quad_result is defined + + - name: Assert idempotency result + ansible.builtin.assert: + that: + - idempotency_quad_result.changed == false + - idempotency_quad_result.sdi_source is defined + - idempotency_quad_result.sdi_source.name == quad_sdi_source_name + - idempotency_quad_result.sdi_source.type == quad_source_type + - idempotency_quad_result.sdi_source.mode == sdi_source_mode + - idempotency_quad_result.sdi_source.state == "IDLE" + - idempotency_quad_result.sdi_source.arn is defined + fail_msg: "Idempotency check failed" + success_msg: "Idempotency check passed" + when: idempotency_quad_result is defined and idempotency_quad_result.sdi_source is defined + + # Get sdi source by its id + - name: Get SDI source by its ID + community.aws.medialive_sdi_source_info: + id: '{{ sdi_source_id }}' + register: get_by_id_result + when: sdi_source_id is defined + + - name: Assert get by id result + ansible.builtin.assert: + that: + - get_by_id_result is defined + - get_by_id_result.sdi_source is defined + - get_by_id_result.sdi_source.name == single_sdi_source_name + - get_by_id_result.sdi_source.type == single_source_type + - get_by_id_result.sdi_source.state == "IDLE" + - get_by_id_result.sdi_source.arn is defined + fail_msg: "Get by ID failed or returned unexpected data" + success_msg: "Get by ID passed" + when: get_by_id_result is defined and get_by_id_result.sdi_source is defined + + # Get SDI source by its name + - name: Get SDI source by its name + community.aws.medialive_sdi_source_info: + name: '{{ single_sdi_source_name }}' + register: get_by_name_result + when: sdi_source_id is defined + + - name: Assert get by name result + ansible.builtin.assert: + that: + - get_by_name_result is defined + - get_by_name_result.sdi_source is defined + - get_by_name_result.sdi_source.name == single_sdi_source_name + - get_by_name_result.sdi_source.sdi_source_id == sdi_source_id + - get_by_name_result.sdi_source.type == single_source_type + - get_by_name_result.sdi_source.state == "IDLE" + - get_by_name_result.sdi_source.arn is defined + fail_msg: "Get by name failed or returned unexpected data" + success_msg: "Get by name passed" + when: get_by_name_result is defined and sdi_source_id is defined and get_by_name_result.sdi_source is defined + + # Update SDI source + - name: Update SDI source name by its ID + community.aws.medialive_sdi_source: + id: '{{ sdi_source_id }}' + name: '{{ alt_sdi_source_name }}' + state: present + register: update_result + when: sdi_source_id is defined + + - name: Assert update result + ansible.builtin.assert: + that: + - update_result.changed == true + - update_result.sdi_source is defined + - update_result.sdi_source.name == alt_sdi_source_name + - update_result.sdi_source.type == single_source_type + - update_result.sdi_source.state == "IDLE" + - update_result.sdi_source.arn is defined + fail_msg: "Update failed or returned unexpected data" + success_msg: "Update passed" + when: update_result is defined and update_result.sdi_source is defined + + - name: Update the same source again (idempotency check) + community.aws.medialive_sdi_source: + id: '{{ sdi_source_id }}' + name: '{{ alt_sdi_source_name }}' + state: present + register: idempotency_update_result + when: sdi_source_id is defined + + - name: Assert idempotency update result + ansible.builtin.assert: + that: + - idempotency_update_result.changed == false + - idempotency_update_result.sdi_source is defined + - idempotency_update_result.sdi_source.name == alt_sdi_source_name + - idempotency_update_result.sdi_source.type == single_source_type + - idempotency_update_result.sdi_source.state == "IDLE" + - idempotency_update_result.sdi_source.arn is defined + fail_msg: "Idempotency update check failed" + success_msg: "Idempotency update check passed" + when: idempotency_update_result is defined and idempotency_update_result.sdi_source is defined + + - name: Update SDI source mode by its name + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + type: '{{ quad_source_type }}' + mode: '{{ sdi_source_mode_interleave }}' + state: present + register: mode_update_result + when: create_quad_result is defined + + - name: Assert update result + ansible.builtin.assert: + that: + - mode_update_result.changed == true + - mode_update_result.sdi_source is defined + - mode_update_result.sdi_source.name == quad_sdi_source_name + - mode_update_result.sdi_source.type == quad_source_type + - mode_update_result.sdi_source.mode == sdi_source_mode_interleave + - mode_update_result.sdi_source.state == "IDLE" + - mode_update_result.sdi_source.arn is defined + fail_msg: "SDI mode update failed or returned unexpected data" + success_msg: "SDI mode update passed" + when: mode_update_result is defined and mode_update_result.sdi_source is defined + + - name: Update the same source again (idempotency check) + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + type: '{{ quad_source_type }}' + mode: '{{ sdi_source_mode_interleave }}' + state: present + register: idempotency_mode_update_result + when: create_quad_result is defined + + - name: Assert idempotency update result + ansible.builtin.assert: + that: + - idempotency_mode_update_result.changed == false + - idempotency_mode_update_result.sdi_source is defined + - idempotency_mode_update_result.sdi_source.name == quad_sdi_source_name + - idempotency_mode_update_result.sdi_source.type == quad_source_type + - idempotency_mode_update_result.sdi_source.mode == sdi_source_mode_interleave + - idempotency_mode_update_result.sdi_source.state == "IDLE" + - idempotency_mode_update_result.sdi_source.arn is defined + fail_msg: "Idempotency update check failed" + success_msg: "Idempotency update check passed" + when: idempotency_mode_update_result is defined and idempotency_mode_update_result.sdi_source is defined + + # Delete SDI source + - name: Delete SDI source by ID + community.aws.medialive_sdi_source: + id: '{{ sdi_source_id }}' + state: absent + register: delete_result + when: sdi_source_id is defined + + - name: Assert delete result + ansible.builtin.assert: + that: + - delete_result.changed == true + - delete_result.sdi_source is defined + - delete_result.sdi_source.name == alt_sdi_source_name + - delete_result.sdi_source.type == single_source_type + - delete_result.sdi_source.state == "DELETED" + - delete_result.sdi_source.arn is defined + fail_msg: "Delete failed or returned unexpected data" + success_msg: "Delete passed" + when: delete_result is defined and delete_result.sdi_source is defined + + - name: Try to delete the source again (idempotency check) + community.aws.medialive_sdi_source: + id: '{{ sdi_source_id }}' + state: absent + register: idempotency_delete_result + when: sdi_source_id is defined + + - name: Assert idempotency delete result + ansible.builtin.assert: + that: + - idempotency_delete_result.changed == false + - idempotency_delete_result.sdi_source is defined + - idempotency_delete_result.sdi_source.name == alt_sdi_source_name + - idempotency_delete_result.sdi_source.type == single_source_type + - idempotency_delete_result.sdi_source.state == "DELETED" + - idempotency_delete_result.sdi_source.arn is defined + fail_msg: "Idempotency delete check failed" + success_msg: "Idempotency delete check passed" + when: idempotency_delete_result is defined and idempotency_delete_result.sdi_source is defined + + - name: Delete SDI source by name + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + state: absent + register: delete_by_name_result + when: create_quad_result is defined + + - name: Assert delete by name result + ansible.builtin.assert: + that: + - delete_by_name_result.changed == true + - delete_by_name_result.sdi_source is defined + - delete_by_name_result.sdi_source.name == quad_sdi_source_name + - delete_by_name_result.sdi_source.type == quad_source_type + - delete_by_name_result.sdi_source.state == "DELETED" + - delete_by_name_result.sdi_source.arn is defined + fail_msg: "Delete failed or returned unexpected data" + success_msg: "Delete passed" + when: delete_by_name_result is defined and delete_by_name_result.sdi_source is defined + + - name: Try to delete the source again (idempotency check) + community.aws.medialive_sdi_source: + name: '{{ quad_sdi_source_name }}' + state: absent + register: idempotency_delete_by_name_result + when: create_single_result is defined + + - name: Assert idempotency delete by name result + ansible.builtin.assert: + that: + - idempotency_delete_by_name_result.changed == false + - idempotency_delete_by_name_result.sdi_source is defined + - idempotency_delete_by_name_result.sdi_source == {} + fail_msg: "Idempotency delete check failed" + success_msg: "Idempotency delete check passed" + when: idempotency_delete_by_name_result is defined and idempotency_delete_by_name_result.sdi_source is defined + + # Error handling and cleanup + rescue: + - name: Capture error + ansible.builtin.set_fact: + test_failed: true + + - name: Clean up + ansible.builtin.include_tasks: cleanup.yml + + - name: Fail with useful error message + ansible.builtin.fail: + msg: "MediaLive SDI source integration tests failed. See previous errors for details." + when: test_failed | default(false) + + always: + - name: Ensure all test resources are cleaned up + ansible.builtin.include_tasks: cleanup.yml \ No newline at end of file