From 4628e9a2cabdb76c9f8b59fed2895917e3e10a46 Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Thu, 12 Jun 2025 10:27:46 -0700 Subject: [PATCH 01/11] feat(medialive): add Elemental MediaLive modules to community collection Adds new modules for AWS Elemental MediaLive management: - medialive_sdi_source: Manage SDI source configurations - medialive_node: Handle node operations - medialive_network: Configure network settings - medialive_input: Manage input configurations - medialive_cluster: Control cluster operations - medialive_channel_placement_group: Manage channel placement groups This enhancement enables comprehensive management of MediaLive resources through Ansible automation, supporting bring-your-own-device Elemental Anywhere systems. Co-authored-by: Sergey Papyan Co-authored-by: David Teach Co-authored-by: Brenton Buxell --- changelogs/fragments/medialive_cluster.yml | 3 + changelogs/fragments/medialive_network.yml | 3 + changelogs/fragments/medialive_sdi_source.yml | 3 + meta/runtime.yml | 13 + plugins/modules/medialive_channel.py | 7467 +++++++++++++++++ .../medialive_channel_placement_group.py | 523 ++ .../medialive_channel_placement_group_info.py | 145 + plugins/modules/medialive_cluster.py | 608 ++ plugins/modules/medialive_cluster_info.py | 231 + plugins/modules/medialive_input.py | 964 +++ plugins/modules/medialive_input_info.py | 180 + plugins/modules/medialive_network.py | 565 ++ plugins/modules/medialive_network_info.py | 234 + plugins/modules/medialive_node.py | 665 ++ plugins/modules/medialive_node_info.py | 265 + .../modules/medialive_node_registration.py | 177 + plugins/modules/medialive_sdi_source.py | 367 + plugins/modules/medialive_sdi_source_info.py | 212 + .../medialive_channel_placement_group/aliases | 1 + .../meta/main.yml | 4 + .../tasks/cleanup.yml | 33 + .../tasks/main.yml | 268 + .../targets/medialive_cluster/aliases | 1 + .../targets/medialive_cluster/meta/main.yml | 4 + .../targets/medialive_cluster/tasks/main.yml | 300 + .../targets/medialive_input/aliases | 1 + .../targets/medialive_input/meta/main.yml | 4 + .../targets/medialive_input/tasks/main.yml | 217 + .../targets/medialive_network/aliases | 1 + .../targets/medialive_network/meta/main.yml | 4 + .../targets/medialive_network/tasks/main.yml | 192 + .../targets/medialive_node/aliases | 1 + .../targets/medialive_node/meta/main.yml | 5 + .../targets/medialive_node/tasks/cleanup.yml | 41 + .../targets/medialive_node/tasks/main.yml | 376 + .../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 + 39 files changed, 14443 insertions(+) create mode 100644 changelogs/fragments/medialive_cluster.yml create mode 100644 changelogs/fragments/medialive_network.yml create mode 100644 changelogs/fragments/medialive_sdi_source.yml create mode 100644 plugins/modules/medialive_channel.py create mode 100644 plugins/modules/medialive_channel_placement_group.py create mode 100644 plugins/modules/medialive_channel_placement_group_info.py create mode 100644 plugins/modules/medialive_cluster.py create mode 100644 plugins/modules/medialive_cluster_info.py create mode 100644 plugins/modules/medialive_input.py create mode 100644 plugins/modules/medialive_input_info.py create mode 100644 plugins/modules/medialive_network.py create mode 100644 plugins/modules/medialive_network_info.py create mode 100644 plugins/modules/medialive_node.py create mode 100644 plugins/modules/medialive_node_info.py create mode 100644 plugins/modules/medialive_node_registration.py 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_channel_placement_group/aliases create mode 100644 tests/integration/targets/medialive_channel_placement_group/meta/main.yml create mode 100644 tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml create mode 100644 tests/integration/targets/medialive_channel_placement_group/tasks/main.yml create mode 100644 tests/integration/targets/medialive_cluster/aliases create mode 100644 tests/integration/targets/medialive_cluster/meta/main.yml create mode 100644 tests/integration/targets/medialive_cluster/tasks/main.yml create mode 100644 tests/integration/targets/medialive_input/aliases create mode 100644 tests/integration/targets/medialive_input/meta/main.yml create mode 100644 tests/integration/targets/medialive_input/tasks/main.yml create mode 100644 tests/integration/targets/medialive_network/aliases create mode 100644 tests/integration/targets/medialive_network/meta/main.yml create mode 100644 tests/integration/targets/medialive_network/tasks/main.yml create mode 100644 tests/integration/targets/medialive_node/aliases create mode 100644 tests/integration/targets/medialive_node/meta/main.yml create mode 100644 tests/integration/targets/medialive_node/tasks/cleanup.yml create mode 100644 tests/integration/targets/medialive_node/tasks/main.yml 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/changelogs/fragments/medialive_cluster.yml b/changelogs/fragments/medialive_cluster.yml new file mode 100644 index 00000000000..35773eb2fe2 --- /dev/null +++ b/changelogs/fragments/medialive_cluster.yml @@ -0,0 +1,3 @@ +minor_changes: + - medialive_cluster_info - New module for gathering info about AWS MediaLive Anywhere clusters + - medialive_cluster - New module for managing AWS MediaLive Anywhere clusters diff --git a/changelogs/fragments/medialive_network.yml b/changelogs/fragments/medialive_network.yml new file mode 100644 index 00000000000..2cc89cf87d2 --- /dev/null +++ b/changelogs/fragments/medialive_network.yml @@ -0,0 +1,3 @@ +minor_changes: + - medialive_network_info - New module for gathering info about AWS MediaLive Anywhere networks + - medialive_network - New module for managing AWS MediaLive Anywhere networks diff --git a/changelogs/fragments/medialive_sdi_source.yml b/changelogs/fragments/medialive_sdi_source.yml new file mode 100644 index 00000000000..5b9aff55d75 --- /dev/null +++ b/changelogs/fragments/medialive_sdi_source.yml @@ -0,0 +1,3 @@ +minor_changes: + - medialive_sdi_source - New module for managing AWS MediaLive Anywhere SDI sources + - medialive_sdi_source_info - New module for gathering info about AWS MediaLive Anywhere SDI sources diff --git a/meta/runtime.yml b/meta/runtime.yml index 15743d53e91..438eae427ed 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -143,6 +143,19 @@ action_groups: - lightsail - lightsail_snapshot - lightsail_static_ip + - medialive_channel_placement_group_info + - medialive_channel_placement_group + - medialive_cluster_info + - medialive_cluster + - medialive_network_info + - medialive_network + - medialive_node + - medialive_node_info + - medialive_node_registration + - medialive_sdi_source + - medialive_sdi_source_info + - medialive_input + - medialive_input_info - mq_broker - mq_broker_config - mq_broker_info diff --git a/plugins/modules/medialive_channel.py b/plugins/modules/medialive_channel.py new file mode 100644 index 00000000000..fab2d0873e7 --- /dev/null +++ b/plugins/modules/medialive_channel.py @@ -0,0 +1,7467 @@ +#!/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_channel +version_added: 10.1.0 +short_description: Manage AWS MediaLive Anywhere Channels +description: + - A module for creating, updating and deleting AWS MediaLive Channels. + - This module includes basic functionality for managing channels, but does not include input validation + - Requires boto3 >= 1.37.30 +author: + - "Sergey Papyan" +options: + cdi_input_specification: + description: + - Specification of CDI inputs for this channel + type: dict + suboptions: + resolution: + description: + - Maximum CDI input resolution + type: str + choices: ['SD', 'HD', 'FHD', 'UHD'] + channel_class: + description: + - The class for this channel. STANDARD for a channel with two pipelines or SINGLE_PIPELINE for a channel with one pipeline. + type: str + choices: ['STANDARD', 'SINGLE_PIPELINE'] + destinations: + description: + - A list of output destinations for this channel. Defines where and how the outputs of the MediaLive channel should be delivered. + type: list + elements: dict + suboptions: + id: + description: + - User-specified id. This is used in an output group or an output. + type: str + media_package_settings: + description: + - Destination settings for a MediaPackage output; one destination for both encoders. + type: list + elements: dict + suboptions: + channel_id: + description: + - ID of the channel in MediaPackage that is the destination for this output group. You do not need to specify the individual inputs in MediaPackage; MediaLive will handle the connection of the two MediaLive pipelines to the two MediaPackage inputs. The MediaPackage channel and MediaLive channel must be in the same region. + type: str + channel_group: + description: + - Name of the channel group in MediaPackageV2. Only use if you are sending CMAF Ingest output to a CMAF ingest endpoint on a MediaPackage channel that uses MediaPackage v2. + type: str + channel_name: + description: + - Name of the channel in MediaPackageV2. Only use if you are sending CMAF Ingest output to a CMAF ingest endpoint on a MediaPackage channel that uses MediaPackage v2. + type: str + multiplex_settings: + description: + - Destination settings for a Multiplex output; one destination for both encoders. + type: dict + suboptions: + multiplex_id: + description: + - The ID of the Multiplex that the encoder is providing output to. You do not need to specify the individual inputs to the Multiplex; MediaLive will handle the connection of the two MediaLive pipelines to the two Multiplex instances. The Multiplex must be in the same region as the Channel. + type: str + program_name: + description: + - The program name of the Multiplex program that the encoder is providing output to. + type: str + settings: + description: + - Destination settings for a standard output; one destination for each redundant encoder. + type: list + elements: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + stream_name: + description: + - Stream name for RTMP destinations (URLs of type rtmp://) + type: str + url: + description: + - A URL specifying a destination + type: str + username: + description: + - username for destination + type: str + srt_settings: + description: + - SRT settings for an SRT output; one destination for each redundant encoder. + type: list + elements: dict + suboptions: + encryption_passphrase_secret_arn: + description: + - Arn used to extract the password from Secrets Manager + type: str + stream_id: + description: + - Stream id for SRT destinations (URLs of type srt://) + type: str + url: + description: + - A URL specifying a destination + type: str + logical_interface_names: + description: + - A list of logical interface names used for network isolation in MediaLive Anywhere deployments. + + type: list + elements: str + encoder_settings: + description: + - Encoder Settings + type: dict + suboptions: + audio_descriptions: + description: + - Placeholder documentation for __listOfAudioDescription + type: list + elements: dict + suboptions: + audio_normalization_settings: + description: + - Advanced audio normalization settings. + type: dict + suboptions: + algorithm: + description: + - Audio normalization algorithm to use. itu17701 conforms to the CALM Act specification, itu17702 conforms to the EBU R-128 specification. + type: str + choices: ['ITU_1770_1', 'ITU_1770_2'] + algorithm_control: + description: + - When set to correctAudio the output audio is corrected using the chosen algorithm. If set to measureOnly, the audio will be measured but not adjusted. + type: str + choices: ['CORRECT_AUDIO'] + target_lkfs: + description: + - Target LKFS(loudness) to adjust volume to. If no value is entered, a default value will be used according to the chosen algorithm. + - The CALM Act (1770-1) recommends a target of -24 LKFS. The EBU R-128 specification (1770-2) recommends a target of -23 LKFS. + type: float + audio_selector_name: + description: + - The name of the AudioSelector used as the source for this AudioDescription. + type: str + audio_type: + description: + - Applies only if audioTypeControl is useConfigured. The values for audioType are defined in ISO-IEC 13818-1. + type: str + choices: ['CLEAN_EFFECTS', 'HEARING_IMPAIRED', 'UNDEFINED', 'VISUAL_IMPAIRED_COMMENTARY'] + audio_type_control: + description: + - Determines how audio type is determined. followInput - If the input contains an ISO 639 audioType, then that value is passed through to the output. + - If the input contains no ISO 639 audioType, the value in Audio Type is included in the output. + - useConfigured - The value in Audio Type is included in the output. + - Note that this field and audioType are both ignored if inputType is broadcasterMixedAd. + type: str + choices: ['FOLLOW_INPUT', 'USE_CONFIGURED'] + audio_watermarking_settings: + description: + - Settings to configure one or more solutions that insert audio watermarks in the audio encode + type: dict + suboptions: + nielsen_watermarks_settings: + description: + - Settings to configure Nielsen Watermarks in the audio encode + type: dict + suboptions: + nielsen_cbet_settings: + description: + - Complete these fields only if you want to insert watermarks of type Nielsen CBET + type: dict + suboptions: + cbet_check_digit_string: + description: + - Enter the CBET check digits to use in the watermark. + type: str + cbet_stepaside: + description: + - Determines the method of CBET insertion mode when prior encoding is detected on the same layer. + type: str + choices: ['DISABLED', 'ENABLED'] + csid: + description: + - Enter the CBET Source ID (CSID) to use in the watermark + type: str + nielsen_distribution_type: + description: + - Choose the distribution types that you want to assign to the watermarks - PROGRAM_CONTENT or FINAL_DISTRIBUTOR + type: str + choices: ['FINAL_DISTRIBUTOR', 'PROGRAM_CONTENT'] + nielsen_naes_ii_nw_settings: + description: + - Complete these fields only if you want to insert watermarks of type Nielsen NAES II (N2) and Nielsen NAES VI (NW). + type: dict + suboptions: + check_digit_string: + description: + - Enter the check digit string for the watermark + type: str + sid: + description: + - Enter the Nielsen Source ID (SID) to include in the watermark + type: float + timezone: + description: + - Choose the timezone for the time stamps in the watermark. If not provided, the timestamps will be in Coordinated Universal Time (UTC) + type: str + choices: + - 'AMERICA_PUERTO_RICO' + - 'US_ALASKA' + - 'US_ARIZONA' + - 'US_CENTRAL' + - 'US_EASTERN' + - 'US_HAWAII' + - 'US_MOUNTAIN' + - 'US_PACIFIC' + - 'US_SAMOA' + - 'UTC' + codec_settings: + description: + - Audio codec settings. + type: dict + suboptions: + aac_settings: + description: + - Aac Settings + type: dict + suboptions: + bitrate: + description: + - Average bitrate in bits/second. Valid values depend on rate control mode and profile. + type: float + coding_mode: + description: + - Mono, Stereo, or 5.1 channel layout. Valid values depend on rate control mode and profile. + - The adReceiverMix setting receives a stereo description plus control track and emits a mono AAC encode of the description track, with control data emitted in the PES header as per ETSI TS 101 154 Annex E. + type: str + choices: + - 'AD_RECEIVER_MIX' + - 'CODING_MODE_1_0' + - 'CODING_MODE_1_1' + - 'CODING_MODE_2_0' + - 'CODING_MODE_5_1' + input_type: + description: + - Set to "broadcasterMixedAd" when input contains pre-mixed main audio + AD (narration) as a stereo pair. + - The Audio Type field (audioType) will be set to 3, which signals to downstream systems that this stream contains "broadcaster mixed AD". + - Note that the input received by the encoder must contain pre-mixed audio; the encoder does not perform the mixing. + - The values in audioTypeControl and audioType (in AudioDescription) are ignored when set to broadcasterMixedAd. + - Leave set to "normal" when input does not contain pre-mixed audio + AD. + type: str + choices: ['BROADCASTER_MIXED_AD', 'NORMAL'] + profile: + description: + - AAC Profile. + type: str + choices: ['HEV1', 'HEV2', 'LC'] + rate_control_mode: + description: + - Rate Control Mode. + type: str + choices: ['CBR', 'VBR'] + raw_format: + description: + - Sets LATM / LOAS AAC output for raw containers. + type: str + choices: ['LATM_LOAS', 'NONE'] + sample_rate: + description: + - Sample rate in Hz. Valid values depend on rate control mode and profile. + type: float + spec: + description: + - Use MPEG-2 AAC audio instead of MPEG-4 AAC audio for raw or MPEG-2 Transport Stream containers. + type: str + choices: ['MPEG2', 'MPEG4'] + vbr_quality: + description: + - VBR Quality Level - Only used if rateControlMode is VBR. + type: str + choices: ['HIGH', 'LOW', 'MEDIUM_HIGH', 'MEDIUM_LOW'] + ac3_settings: + description: + - Ac3 Settings + type: dict + suboptions: + bitrate: + description: + - Average bitrate in bits/second. Valid bitrates depend on the coding mode. + type: float + bitstream_mode: + description: + - Specifies the bitstream mode (bsmod) for the emitted AC-3 stream. See ATSC A/52-2012 for background on these values. + type: str + choices: + - 'COMMENTARY' + - 'COMPLETE_MAIN' + - 'DIALOGUE' + - 'EMERGENCY' + - 'HEARING_IMPAIRED' + - 'MUSIC_AND_EFFECTS' + - 'VISUALLY_IMPAIRED' + - 'VOICE_OVER' + coding_mode: + description: + - Dolby Digital coding mode. Determines number of channels. + type: str + choices: ['CODING_MODE_1_0', 'CODING_MODE_1_1', 'CODING_MODE_2_0', 'CODING_MODE_3_2_LFE'] + dialnorm: + description: + - Sets the dialnorm for the output. If excluded and input audio is Dolby Digital, dialnorm will be passed through. + type: float + drc_profile: + description: + - If set to filmStandard, adds dynamic range compression signaling to the output bitstream as defined in the Dolby Digital specification. + type: str + choices: ['FILM_STANDARD', 'NONE'] + lfe_filter: + description: + - When set to enabled, applies a 120Hz lowpass filter to the LFE channel prior to encoding. Only valid in codingMode32Lfe mode. + type: str + choices: ['DISABLED', 'ENABLED'] + metadata_control: + description: + - When set to "followInput", encoder metadata will be sourced from the DD, DD+, or DolbyE decoder that supplied this audio data. If audio was not supplied from one of these streams, then the static metadata settings will be used. + type: str + choices: ['FOLLOW_INPUT', 'USE_CONFIGURED'] + attenuation_control: + description: + - Applies a 3 dB attenuation to the surround channels. Applies only when the coding mode parameter is CODING_MODE_3_2_LFE. + type: str + choices: ['ATTENUATE_3_DB', 'NONE'] + eac3_atmos_settings: + description: + - Eac3 Atmos Settings + type: dict + suboptions: + bitrate: + description: + - Average bitrate in bits/second. Valid bitrates depend on the coding mode. + type: float + coding_mode: + description: + - Dolby Digital Plus with Dolby Atmos coding mode. Determines number of channels. + type: str + choices: ['CODING_MODE_5_1_4', 'CODING_MODE_7_1_4', 'CODING_MODE_9_1_6'] + dialnorm: + description: + - Sets the dialnorm for the output. Default 23. + type: int + drc_line: + description: + - Sets the Dolby dynamic range compression profile. + type: str + choices: + - 'FILM_LIGHT' + - 'FILM_STANDARD' + - 'MUSIC_LIGHT' + - 'MUSIC_STANDARD' + - 'NONE' + - 'SPEECH' + drc_rf: + description: + - Sets the profile for heavy Dolby dynamic range compression, ensures that the instantaneous signal peaks do not exceed specified levels. + type: str + choices: + - 'FILM_LIGHT' + - 'FILM_STANDARD' + - 'MUSIC_LIGHT' + - 'MUSIC_STANDARD' + - 'NONE' + - 'SPEECH' + height_trim: + description: + - Height dimensional trim. Sets the maximum amount to attenuate the height channels when the downstream player isn't configured to handle Dolby Digital Plus with Dolby Atmos and must remix the channels. + type: float + surround_trim: + description: + - Surround dimensional trim. Sets the maximum amount to attenuate the surround channels when the downstream player isn't configured to handle Dolby Digital Plus with Dolby Atmos and must remix the channels. + type: float + eac3_settings: + description: + - Eac3 Settings + type: dict + suboptions: + attenuation_control: + description: + - When set to attenuate3Db, applies a 3 dB attenuation to the surround channels. Only used for 3/2 coding mode. + type: str + choices: ['ATTENUATE_3_DB', 'NONE'] + bitrate: + description: + - Average bitrate in bits/second. Valid bitrates depend on the coding mode. + type: float + bitstream_mode: + description: + - Specifies the bitstream mode (bsmod) for the emitted E-AC-3 stream. See ATSC A/52-2012 (Annex E) for background on these values. + type: str + choices: + - 'COMMENTARY' + - 'COMPLETE_MAIN' + - 'EMERGENCY' + - 'HEARING_IMPAIRED' + - 'VISUALLY_IMPAIRED' + coding_mode: + description: + - Dolby Digital Plus coding mode. Determines number of channels. + type: str + choices: ['CODING_MODE_1_0', 'CODING_MODE_2_0', 'CODING_MODE_3_2'] + dc_filter: + description: + - When set to enabled, activates a DC highpass filter for all input channels. + type: str + choices: ['DISABLED', 'ENABLED'] + dialnorm: + description: + - Sets the dialnorm for the output. If blank and input audio is Dolby Digital Plus, dialnorm will be passed through. + type: float + drc_line: + description: + - Sets the Dolby dynamic range compression profile. + type: str + choices: + - 'FILM_LIGHT' + - 'FILM_STANDARD' + - 'MUSIC_LIGHT' + - 'MUSIC_STANDARD' + - 'NONE' + - 'SPEECH' + drc_rf: + description: + - Sets the profile for heavy Dolby dynamic range compression, ensures that the instantaneous signal peaks do not exceed specified levels. + type: str + choices: + - 'FILM_LIGHT' + - 'FILM_STANDARD' + - 'MUSIC_LIGHT' + - 'MUSIC_STANDARD' + - 'NONE' + - 'SPEECH' + lfe_control: + description: + - When encoding 3/2 audio, setting to lfe enables the LFE channel + type: str + choices: ['LFE', 'NO_LFE'] + lfe_filter: + description: + - When set to enabled, applies a 120Hz lowpass filter to the LFE channel prior to encoding. Only valid with codingMode32 coding mode. + type: str + choices: ['DISABLED', 'ENABLED'] + lo_ro_center_mix_level: + description: + - Left only/Right only center mix level. Only used for 3/2 coding mode. + type: float + lo_ro_surround_mix_level: + description: + - Left only/Right only surround mix level. Only used for 3/2 coding mode. + type: float + lt_rt_center_mix_level: + description: + - Left total/Right total center mix level. Only used for 3/2 coding mode. + type: float + lt_rt_surround_mix_level: + description: + - Left total/Right total surround mix level. Only used for 3/2 coding mode. + type: float + metadata_control: + description: + - When set to followInput, encoder metadata will be sourced from the DD, DD+, or DolbyE decoder that supplied this audio data. If audio was not supplied from one of these streams, then the static metadata settings will be used. + type: str + choices: ['FOLLOW_INPUT', 'USE_CONFIGURED'] + passthrough_control: + description: + - When set to whenPossible, input DD+ audio will be passed through if it is present on the input. This detection is dynamic over the life of the transcode. Inputs that alternate between DD+ and non-DD+ content will have a consistent DD+ output as the system alternates between passthrough and encoding. + type: str + choices: ['NO_PASSTHROUGH', 'WHEN_POSSIBLE'] + phase_control: + description: + - When set to shift90Degrees, applies a 90-degree phase shift to the surround channels. Only used for 3/2 coding mode. + type: str + choices: ['NO_SHIFT', 'SHIFT_90_DEGREES'] + stereo_downmix: + description: + - Stereo downmix preference. Only used for 3/2 coding mode. + type: str + choices: ['DPL2', 'LO_RO', 'LT_RT', 'NOT_INDICATED'] + surround_ex_mode: + description: + - When encoding 3/2 audio, sets whether an extra center back surround channel is matrix encoded into the left and right surround channels. + type: str + choices: ['DISABLED', 'ENABLED', 'NOT_INDICATED'] + surround_mode: + description: + - When encoding 2/0 audio, sets whether Dolby Surround is matrix encoded into the two channels. + type: str + choices: ['DISABLED', 'ENABLED', 'NOT_INDICATED'] + mp2_settings: + description: + - Mp2 Settings + type: dict + suboptions: + bitrate: + description: + - Average bitrate in bits/second. + type: float + coding_mode: + description: + - The MPEG2 Audio coding mode. Valid values are codingMode10 (for mono) or codingMode20 (for stereo). + type: str + choices: ['CODING_MODE_1_0', 'CODING_MODE_2_0'] + sample_rate: + description: + - Sample rate in Hz. + type: float + pass_through_settings: + description: + - Pass Through Settings + type: dict + wav_settings: + description: + - Wav Settings + type: dict + suboptions: + bit_depth: + description: + - Bits per sample. + type: float + coding_mode: + description: + - The audio coding mode for the WAV audio. The mode determines the number of channels in the audio. + type: str + choices: ['CODING_MODE_1_0', 'CODING_MODE_2_0', 'CODING_MODE_4_0', 'CODING_MODE_8_0'] + sample_rate: + description: + - Sample rate in Hz. + type: float + language_code: + description: + - RFC 5646 language code representing the language of the audio output track. Only used if languageControlMode is useConfigured, or there is no ISO 639 language code specified in the input. + type: str + language_code_control: + description: + - Choosing followInput will cause the ISO 639 language code of the output to follow the ISO 639 language code of the input. The languageCode will be used when useConfigured is set, or when followInput is selected but there is no ISO 639 language code specified by the input. + type: str + choices: ['FOLLOW_INPUT', 'USE_CONFIGURED'] + name: + description: + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. + type: str + remix_settings: + description: + - Settings that control how input audio channels are remixed into the output audio channels. + type: dict + suboptions: + channel_mappings: + description: + - Mapping of input channels to output channels, with appropriate gain adjustments. + type: list + elements: dict + suboptions: + input_channel_levels: + description: + - Indices and gain values for each input channel that should be remixed into this output channel. + type: list + elements: dict + suboptions: + gain: + description: + - Remixing value. Units are in dB and acceptable values are within the range from -60 (mute) and 6 dB. + type: int + input_channel: + description: + - The index of the input channel used as a source. + type: int + output_channel: + description: + - The index of the output channel being produced. + type: int + channels_in: + description: + - Number of input channels to be used. + type: int + channels_out: + description: + - Number of output channels to be produced. Valid values are 1, 2, 4, 6, 8 + type: int + stream_name: + description: + - Used for MS Smooth and Apple HLS outputs. Indicates the name displayed by the player (eg. English, or Director Commentary). + type: str + audio_dash_roles: + description: + - Identifies the DASH roles to assign to this audio output. Applies only when the audio output is configured for DVB DASH accessibility signaling. + type: list + elements: str + choices: + - 'ALTERNATE' + - 'COMMENTARY' + - 'DESCRIPTION' + - 'DUB' + - 'EMERGENCY' + - 'ENHANCED-AUDIO-INTELLIGIBILITY' + - 'KARAOKE' + - 'MAIN' + - 'SUPPLEMENTARY' + dvb_dash_accessibility: + description: + - Identifies DVB DASH accessibility signaling in this audio output. Used in Microsoft Smooth Streaming outputs to signal accessibility information to packagers. + type: str + choices: + - 'DVBDASH_1_VISUALLY_IMPAIRED' + - 'DVBDASH_2_HARD_OF_HEARING' + - 'DVBDASH_3_SUPPLEMENTAL_COMMENTARY' + - 'DVBDASH_4_DIRECTORS_COMMENTARY' + - 'DVBDASH_5_EDUCATIONAL_NOTES' + - 'DVBDASH_6_MAIN_PROGRAM' + - 'DVBDASH_7_CLEAN_FEED' + avail_blanking: + description: + - Settings for ad avail blanking. + type: dict + suboptions: + avail_blanking_image: + description: + - Blanking image to be used. Leave empty for solid black. Only bmp and png images are supported. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + state: + description: + - When set to enabled, causes video, audio and captions to be blanked when insertion metadata is added. + type: str + choices: ['DISABLED', 'ENABLED'] + avail_configuration: + description: + - Event-wide configuration settings for ad avail insertion. + type: dict + suboptions: + avail_settings: + description: + - Controls how SCTE-35 messages create cues. Splice Insert mode treats all segmentation signals traditionally. With Time Signal APOS mode only Time Signal Placement Opportunity and Break messages create segment breaks. With ESAM mode, signals are forwarded to an ESAM server for possible update. + type: dict + suboptions: + esam: + description: + - Esam + type: dict + suboptions: + acquisition_point_id: + description: + - Sent as acquisitionPointIdentity to identify the MediaLive channel to the POIS. + type: str + ad_avail_offset: + description: + - When specified, this offset (in milliseconds) is added to the input Ad Avail PTS time. This only applies to embedded SCTE 104/35 messages and does not apply to OOB messages. + type: int + password_param: + description: + - Documentation update needed + type: str + pois_endpoint: + description: + - The URL of the signal conditioner endpoint on the Placement Opportunity Information System (POIS). MediaLive sends SignalProcessingEvents here when SCTE-35 messages are read. + type: str + username: + description: + - Documentation update needed + type: str + zone_identity: + description: + - Optional data sent as zoneIdentity to identify the MediaLive channel to the POIS. + type: str + scte35_splice_insert: + description: + - Typical configuration that applies breaks on splice inserts in addition to time signal placement opportunities, breaks, and advertisements. + type: dict + suboptions: + ad_avail_offset: + description: + - When specified, this offset (in milliseconds) is added to the input Ad Avail PTS time. This only applies to embedded SCTE 104/35 messages and does not apply to OOB messages. + type: int + no_regional_blackout_flag: + description: + - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates + type: str + choices: ['FOLLOW', 'IGNORE'] + web_delivery_allowed_flag: + description: + - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates + type: str + choices: ['FOLLOW', 'IGNORE'] + scte35_time_signal_apos: + description: + - Atypical configuration that applies segment breaks only on SCTE-35 time signal placement opportunities and breaks. + type: dict + suboptions: + ad_avail_offset: + description: + - When specified, this offset (in milliseconds) is added to the input Ad Avail PTS time. This only applies to embedded SCTE 104/35 messages and does not apply to OOB messages. + type: int + no_regional_blackout_flag: + description: + - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates + type: str + choices: ['FOLLOW', 'IGNORE'] + web_delivery_allowed_flag: + description: + - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates + type: str + choices: ['FOLLOW', 'IGNORE'] + scte35_segmentation_scope: + description: + - Configures whether SCTE 35 passthrough triggers segment breaks in all output groups that use segmented outputs. Insertion of a SCTE 35 message typically results in a segment break, in addition to the regular cadence of breaks. The segment breaks appear in video outputs, audio outputs, and captions outputs (if any). + - ALL_OUTPUT_GROUPS - Default. Insert the segment break in in all output groups that have segmented outputs. This is the legacy behavior. + - SCTE35_ENABLED_OUTPUT_GROUPS - Insert the segment break only in output groups that have SCTE 35 passthrough enabled. This is the recommended value, because it reduces unnecessary segment breaks. + type: str + choices: ['ALL_OUTPUT_GROUPS', 'SCTE35_ENABLED_OUTPUT_GROUPS'] + blackout_slate: + description: + - Settings for blackout slate. + type: dict + suboptions: + blackout_slate_image: + description: + - Blackout slate image to be used. Leave empty for solid black. Only bmp and png images are supported. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + network_end_blackout: + description: + - Setting to enabled causes the encoder to blackout the video, audio, and captions, and raise the "Network Blackout Image" slate when an SCTE104/35 Network End Segmentation Descriptor is encountered. The blackout will be lifted when the Network Start Segmentation Descriptor is encountered. The Network End and Network Start descriptors must contain a network ID that matches the value entered in "Network ID". + type: str + choices: ['DISABLED', 'ENABLED'] + network_end_blackout_image: + description: + - Path to local file to use as Network End Blackout image. Image will be scaled to fill the entire output raster. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + network_id: + description: + - Provides Network ID that matches EIDR ID format (e.g., "10.XXXX/XXXX-XXXX-XXXX-XXXX-XXXX-C"). + type: str + state: + description: + - When set to enabled, causes video, audio and captions to be blanked when indicated by program metadata. + type: str + choices: ['DISABLED', 'ENABLED'] + caption_descriptions: + description: + - Settings for caption decriptions + type: list + elements: dict + suboptions: + accessibility: + description: + - Indicates whether the caption track implements accessibility features such as written descriptions of spoken dialog, music, and sounds. This signaling is added to HLS output group and MediaPackage output group. + type: str + choices: ['DOES_NOT_IMPLEMENT_ACCESSIBILITY_FEATURES', 'IMPLEMENTS_ACCESSIBILITY_FEATURES'] + caption_selector_name: + description: + - Specifies which input caption selector to use as a caption source when generating output captions. This field should match a captionSelector name. + type: str + destination_settings: + description: + - Additional settings for captions destination that depend on the destination type. + type: dict + suboptions: + arib_destination_settings: + description: + - Arib Destination Settings + type: dict + burn_in_destination_settings: + description: + - Burn In Destination Settings + type: dict + suboptions: + alignment: + description: + - If no explicit xPosition or yPosition is provided, setting alignment to centered will place the captions at the bottom center of the output. Similarly, setting a left alignment will align captions to the bottom left of the output. If x and y positions are given in conjunction with the alignment parameter, the font will be justified (either left or centered) relative to those coordinates. Selecting "smart" justification will left-justify live subtitles and center-justify pre-recorded subtitles. All burn-in and DVB-Sub font settings must match. + type: str + choices: ['CENTERED', 'LEFT', 'SMART'] + background_color: + description: + - Specifies the color of the rectangle behind the captions. All burn-in and DVB-Sub font settings must match. + type: str + choices: ['BLACK', 'NONE', 'WHITE'] + background_opacity: + description: + - Specifies the opacity of the background rectangle. 255 is opaque; 0 is transparent. Leaving this parameter out is equivalent to setting it to 0 (transparent). All burn-in and DVB-Sub font settings must match. + type: int + font: + description: + - External font file used for caption burn-in. File extension must be 'ttf' or 'tte'. Although the user can select output fonts for many different types of input captions, embedded, STL and teletext sources use a strict grid system. Using external fonts with these caption sources could cause unexpected display of proportional fonts. All burn-in and DVB-Sub font settings must match. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + font_color: + description: + - Specifies the color of the burned-in captions. This option is not valid for source captions that are STL, 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. + type: str + choices: + - 'BLACK' + - 'BLUE' + - 'GREEN' + - 'RED' + - 'WHITE' + - 'YELLOW' + font_opacity: + description: + - TODO + type: int + font_resolution: + description: + - TODO + type: int + font_size: + description: + - TODO + type: str + + outline_color: + description: + - TODO + type: str + choices: + - 'BLACK' + - 'BLUE' + - 'GREEN' + - 'RED' + - 'WHITE' + - 'YELLOW' + outline_size: + description: + - TODO + type: int + shadow_color: + description: + - TODO + type: str + choices: ['BLACK', 'NONE', 'WHITE'] + shadow_opacity: + description: + - TODO + type: int + shadow_x_offset: + description: + - TODO + type: int + shadow_y_offset: + description: + - TODO + type: int + teletext_grid_control: + description: + - TODO + type: str + choices: ['FIXED', 'SCALED'] + x_position: + description: + - TODO + type: int + y_position: + description: + - TODO + type: int + dvb_sub_destination_settings: + description: + - TODO + type: dict + suboptions: + alignment: + description: + - TODO + type: str + choices: ['CENTERED', 'LEFT', 'SMART'] + background_color: + description: + - TODO + type: str + choices: ['BLACK', 'NONE', 'WHITE'] + background_opacity: + description: + - TODO + type: int + font: + description: + - TODO + type: dict + suboptions: + password_param: + description: + - TODO + type: str + uri: + description: + - TODO + type: str + username: + description: + - TODO + type: str + font_color: + description: + - TODO + type: str + choices: + - 'BLACK' + - 'BLUE' + - 'GREEN' + - 'RED' + - 'WHITE' + - 'YELLOW' + font_opacity: + description: + - TODO + type: int + font_resolution: + description: + - TODO + type: int + font_size: + description: + - TODO + type: str + + outline_color: + description: + - TODO + type: str + choices: + - 'BLACK' + - 'BLUE' + - 'GREEN' + - 'RED' + - 'WHITE' + - 'YELLOW' + outline_size: + description: + - TODO + type: int + shadow_color: + description: + - TODO + type: str + choices: ['BLACK', 'NONE', 'WHITE'] + shadow_opacity: + description: + - TODO + type: int + shadow_x_offset: + description: + - TODO + type: int + shadow_y_offset: + description: + - TODO + type: int + teletext_grid_control: + description: + - TODO + type: str + choices: ['FIXED', 'SCALED'] + x_position: + description: + - TODO + type: int + y_position: + description: + - TODO + type: int + ebu_tt_d_destination_settings: + description: + - TODO + type: dict + suboptions: + copyright_holder: + description: + - TODO + type: str + fill_line_gap: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + font_family: + description: + - TODO + type: str + style_control: + description: + - TODO + type: str + choices: ['EXCLUDE', 'INCLUDE'] + default_font_size: + description: + - TODO + type: int + default_line_height: + description: + - TODO + type: int + embedded_destination_settings: + description: + - TODO + type: dict + embedded_plus_scte20_destination_settings: + description: + - TODO + type: dict + rtmp_caption_info_destination_settings: + description: + - TODO + type: dict + scte20_plus_embedded_destination_settings: + description: + - TODO + type: dict + scte27_destination_settings: + description: + - TODO + type: dict + smpte_tt_destination_settings: + description: + - TODO + type: dict + teletext_destination_settings: + description: + - TODO + type: dict + ttml_destination_settings: + description: + - TODO + type: dict + suboptions: + style_control: + description: + - TODO + type: str + choices: ['PASSTHROUGH', 'USE_CONFIGURED'] + webvtt_destination_settings: + description: + - TODO + type: dict + suboptions: + style_control: + description: + - TODO + type: str + choices: ['NO_STYLE_DATA', 'PASSTHROUGH'] + language_code: + description: + - TODO + type: str + language_description: + description: + - TODO + type: str + name: + description: + - TODO + type: str + caption_dash_roles: + description: + - TODO + type: list + elements: str + choices: + - 'ALTERNATE' + - 'CAPTION' + - 'COMMENTARY' + - 'DESCRIPTION' + - 'DUB' + - 'EASYREADER' + - 'EMERGENCY' + - 'FORCED-SUBTITLE' + - 'KARAOKE' + - 'MAIN' + - 'METADATA' + - 'SUBTITLE' + - 'SUPPLEMENTARY' + dvb_dash_accessibility: + description: + - TODO + type: str + choices: + - 'DVBDASH_1_VISUALLY_IMPAIRED' + - 'DVBDASH_2_HARD_OF_HEARING' + - 'DVBDASH_3_SUPPLEMENTAL_COMMENTARY' + - 'DVBDASH_4_DIRECTORS_COMMENTARY' + - 'DVBDASH_5_EDUCATIONAL_NOTES' + - 'DVBDASH_6_MAIN_PROGRAM' + - 'DVBDASH_7_CLEAN_FEED' + feature_activations: + description: + - Feature Activations + type: dict + suboptions: + input_prepare_schedule_actions: + description: + - Enables the Input Prepare feature. You can create Input Prepare actions in the schedule only if this feature is enabled. If you disable the feature on an existing schedule, make sure that you first delete all input prepare actions from the schedule. + type: str + choices: ['DISABLED', 'ENABLED'] + output_static_image_overlay_schedule_actions: + description: + - Enables the output static image overlay feature. Enabling this feature allows you to send channel schedule updates to display/clear/modify image overlays on an output-by-output bases. + type: str + choices: ['DISABLED', 'ENABLED'] + global_configuration: + description: + - Configuration settings that apply to the event as a whole. + type: dict + suboptions: + initial_audio_gain: + description: + - Value to set the initial audio gain for the Live Event. + type: int + input_end_action: + description: + - Indicates the action to take when the current input completes (e.g. end-of-file). When switchAndLoopInputs is configured the encoder will restart at the beginning of the first input. When "none" is configured the encoder will transcode either black, a solid color, or a user specified slate images per the "Input Loss Behavior" configuration until the next input switch occurs (which is controlled through the Channel Schedule API). + type: str + choices: ['NONE', 'SWITCH_AND_LOOP_INPUTS'] + input_loss_behavior: + description: + - Settings for system actions when input is lost. + type: dict + suboptions: + black_frame_msec: + description: + - Documentation update needed + type: int + input_loss_image_color: + description: + - When input loss image type is "color" this field specifies the color to use. Value - 6 hex characters representing the values of RGB. + type: str + input_loss_image_slate: + description: + - When input loss image type is "slate" these fields specify the parameters for accessing the slate. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + input_loss_image_type: + description: + - Indicates whether to substitute a solid color or a slate into the output after input loss exceeds blackFrameMsec. + type: str + choices: ['COLOR', 'SLATE'] + repeat_frame_msec: + description: + - Documentation update needed + type: int + output_locking_mode: + description: + - Indicates how MediaLive pipelines are synchronized. PIPELINE_LOCKING - MediaLive will attempt to synchronize the output of each pipeline to the other. EPOCH_LOCKING - MediaLive will attempt to synchronize the output of each pipeline to the Unix epoch. DISABLED - MediaLive will not attempt to synchronize the output of pipelines. We advise against disabling output locking because it has negative side effects in most workflows. For more information, see the section about output locking (pipeline locking) in the Medialive user guide. + type: str + choices: ['EPOCH_LOCKING', 'PIPELINE_LOCKING', 'DISABLED'] + output_timing_source: + description: + - Indicates whether the rate of frames emitted by the Live encoder should be paced by its system clock (which optionally may be locked to another source via NTP) or should be locked to the clock of the source that is providing the input stream. + type: str + choices: ['INPUT_CLOCK', 'SYSTEM_CLOCK'] + support_low_framerate_inputs: + description: + - Adjusts video input buffer for streams with very low video framerates. This is commonly set to enabled for music channels with less than one video frame per second. + type: str + choices: ['DISABLED', 'ENABLED'] + output_locking_settings: + description: + - Advanced output locking settings + type: dict + suboptions: + epoch_locking_settings: + description: + - Epoch Locking Settings + type: dict + suboptions: + custom_epoch: + description: + - Optional. Enter a value here to use a custom epoch, instead of the standard epoch (which started at 1970-01-01T00:00:00 UTC). Specify the start time of the custom epoch, in YYYY-MM-DDTHH:MM:SS in UTC. The time must be 2000-01-01T00:00:00 or later. Always set the MM:SS portion to 00:00. + type: str + jam_sync_time: + description: + - Optional. Enter a time for the jam sync. The default is midnight UTC. When epoch locking is enabled, MediaLive performs a daily jam sync on every output encode to ensure timecodes don't diverge from the wall clock. The jam sync applies only to encodes with frame rate of 29.97 or 59.94 FPS. To override, enter a time in HH:MM:SS in UTC. Always set the MM:SS portion to 00:00. + type: str + pipeline_locking_settings: + description: + - Pipeline Locking Settings + type: dict + motion_graphics_configuration: + description: + - Settings for motion graphics. + type: dict + suboptions: + motion_graphics_insertion: + description: + - Motion Graphics Insertion + type: str + choices: ['DISABLED', 'ENABLED'] + motion_graphics_settings: + description: + - Motion Graphics Settings + type: dict + suboptions: + html_motion_graphics_settings: + description: + - Html Motion Graphics Settings + type: dict + nielsen_configuration: + description: + - Nielsen configuration settings. + type: dict + suboptions: + distributor_id: + description: + - Enter the Distributor ID assigned to your organization by Nielsen. + type: str + nielsen_pcm_to_id3_tagging: + description: + - Enables Nielsen PCM to ID3 tagging + type: str + choices: ['DISABLED', 'ENABLED'] + output_groups: + description: + - Placeholder documentation for __listOfOutputGroup + type: list + elements: dict + suboptions: + name: + description: + - Custom output group name optionally defined by the user. + type: str + output_group_settings: + description: + - Settings associated with the output group. + type: dict + suboptions: + archive_group_settings: + description: + - Archive Group Settings + type: dict + suboptions: + archive_cdn_settings: + description: + - Parameters that control interactions with the CDN. + type: dict + suboptions: + archive_s3_settings: + description: + - Archive S3 Settings + type: dict + suboptions: + canned_acl: + description: + - Specify the canned ACL to apply to each S3 request. Defaults to none. + type: str + choices: + - 'AUTHENTICATED_READ' + - 'BUCKET_OWNER_FULL_CONTROL' + - 'BUCKET_OWNER_READ' + - 'PUBLIC_READ' + destination: + description: + - A directory and base filename where archive files should be written. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + rollover_interval: + description: + - Number of seconds to write to archive file before closing and starting a new one. + type: int + frame_capture_group_settings: + description: + - Frame Capture Group Settings + type: dict + suboptions: + destination: + description: + - The destination for the frame capture files. Either the URI for an Amazon S3 bucket and object, plus a file name prefix (for example, s3ssl://sportsDelivery/highlights/20180820/curling-) or the URI for a MediaStore container, plus a file name prefix (for example, mediastoressl://sportsDelivery/20180820/curling-). The final file names consist of the prefix from the destination field (for example, "curling-") + name modifier + the counter (5 digits, starting from 00001) + extension (which is always .jpg). For example, curling-low.00001.jpg + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + frame_capture_cdn_settings: + description: + - Parameters that control interactions with the CDN. + type: dict + suboptions: + frame_capture_s3_settings: + description: + - Frame Capture S3 Settings + type: dict + suboptions: + canned_acl: + description: + - Specify the canned ACL to apply to each S3 request. Defaults to none. + type: str + choices: + - 'AUTHENTICATED_READ' + - 'BUCKET_OWNER_FULL_CONTROL' + - 'BUCKET_OWNER_READ' + - 'PUBLIC_READ' + hls_group_settings: + description: + - Hls Group Settings + type: dict + suboptions: + ad_markers: + description: + - Choose one or more ad marker types to pass SCTE35 signals through to this group of Apple HLS outputs. + type: list + elements: str + choices: ['ADOBE', 'ELEMENTAL', 'ELEMENTAL_SCTE35'] + base_url_content: + description: + - A partial URI prefix that will be prepended to each output in the media .m3u8 file. Can be used if base manifest is delivered from a different URL than the main .m3u8 file. + type: str + base_url_content1: + description: + - Optional. One value per output group. This field is required only if you are completing Base URL content A, and the downstream system has notified you that the media files for pipeline 1 of all outputs are in a location different from the media files for pipeline 0. + type: str + base_url_manifest: + description: + - A partial URI prefix that will be prepended to each output in the media .m3u8 file. Can be used if base manifest is delivered from a different URL than the main .m3u8 file. + type: str + base_url_manifest1: + description: + - Optional. One value per output group. Complete this field only if you are completing Base URL manifest A, and the downstream system has notified you that the child manifest files for pipeline 1 of all outputs are in a location different from the child manifest files for pipeline 0. + type: str + caption_language_mappings: + description: + - Mapping of up to 4 caption channels to caption languages. Is only meaningful if captionLanguageSetting is set to "insert". + type: list + elements: dict + suboptions: + caption_channel: + description: + - The closed caption channel being described by this CaptionLanguageMapping. Each channel mapping must have a unique channel number (maximum of 4) + type: int + language_code: + description: + - Three character ISO 639-2 language code (see http://www.loc.gov/standards/iso639-2) + type: str + language_description: + description: + - Textual description of language + type: str + caption_language_setting: + description: + - Applies only to 608 Embedded output captions. insert - Include CLOSED-CAPTIONS lines in the manifest. Specify at least one language in the CC1 Language Code field. One CLOSED-CAPTION line is added for each Language Code you specify. Make sure to specify the languages in the order in which they appear in the original source (if the source is embedded format) or the order of the caption selectors (if the source is other than embedded). Otherwise, languages in the manifest will not match up properly with the output captions. none - Include CLOSED-CAPTIONS=NONE line in the manifest. omit - Omit any CLOSED-CAPTIONS line from the manifest. + type: str + choices: ['INSERT', 'NONE', 'OMIT'] + client_cache: + description: + - When set to "disabled", sets the #EXT-X-ALLOW-CACHE:no tag in the manifest, which prevents clients from saving media segments for later replay. + type: str + choices: ['DISABLED', 'ENABLED'] + codec_specification: + description: + - Specification to use (RFC-6381 or the default RFC-4281) during m3u8 playlist generation. + type: str + choices: ['RFC_4281', 'RFC_6381'] + constant_iv: + description: + - For use with encryptionType. This is a 128-bit, 16-byte hex value represented by a 32-character text string. If ivSource is set to "explicit" then this parameter is required and is used as the IV for encryption. + type: str + destination: + description: + - A directory or HTTP destination for the HLS segments, manifest files, and encryption keys (if enabled). + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + directory_structure: + description: + - Place segments in subdirectories. + type: str + choices: ['SINGLE_DIRECTORY', 'SUBDIRECTORY_PER_STREAM'] + discontinuity_tags: + description: + - Specifies whether to insert EXT-X-DISCONTINUITY tags in the HLS child manifests for this output group. Typically, choose Insert because these tags are required in the manifest (according to the HLS specification) and serve an important purpose. Choose Never Insert only if the downstream system is doing real-time failover (without using the MediaLive automatic failover feature) and only if that downstream system has advised you to exclude the tags. + type: str + choices: ['INSERT', 'NEVER_INSERT'] + encryption_type: + description: + - Encrypts the segments with the given encryption scheme. Exclude this parameter if no encryption is desired. + type: str + choices: ['AES128', 'SAMPLE_AES'] + hls_cdn_settings: + description: + - Parameters that control interactions with the CDN. + type: dict + suboptions: + hls_akamai_settings: + description: + - Hls Akamai Settings + type: dict + suboptions: + connection_retry_interval: + description: + - Number of seconds to wait before retrying connection to the CDN if the connection is lost. + type: int + filecache_duration: + description: + - Size in seconds of file cache for streaming outputs. + type: int + http_transfer_mode: + description: + - Specify whether or not to use chunked transfer encoding to Akamai. User should contact Akamai to enable this feature. + type: str + choices: ['CHUNKED', 'NON_CHUNKED'] + num_retries: + description: + - Number of retry attempts that will be made before the Live Event is put into an error state. Applies only if the CDN destination URI begins with "s3" or "mediastore". For other URIs, the value is always 3. + type: int + restart_delay: + description: + - If a streaming output fails, number of seconds to wait until a restart is initiated. A value of 0 means never restart. + type: int + salt: + description: + - Salt for authenticated Akamai. + type: str + token: + description: + - Token parameter for authenticated akamai. If not specified, _gda_ is used. + type: str + hls_basic_put_settings: + description: + - Hls Basic Put Settings + type: dict + suboptions: + connection_retry_interval: + description: + - Number of seconds to wait before retrying connection to the CDN if the connection is lost. + type: int + filecache_duration: + description: + - Size in seconds of file cache for streaming outputs. + type: int + num_retries: + description: + - Number of retry attempts that will be made before the Live Event is put into an error state. Applies only if the CDN destination URI begins with "s3" or "mediastore". For other URIs, the value is always 3. + type: int + restart_delay: + description: + - If a streaming output fails, number of seconds to wait until a restart is initiated. A value of 0 means never restart. + type: int + hls_media_store_settings: + description: + - Hls Media Store Settings + type: dict + suboptions: + connection_retry_interval: + description: + - Number of seconds to wait before retrying connection to the CDN if the connection is lost. + type: int + filecache_duration: + description: + - Size in seconds of file cache for streaming outputs. + type: int + media_store_storage_class: + description: + - When set to temporal, output files are stored in non-persistent memory for faster reading and writing. + type: str + choices: ['TEMPORAL'] + num_retries: + description: + - Number of retry attempts that will be made before the Live Event is put into an error state. Applies only if the CDN destination URI begins with "s3" or "mediastore". For other URIs, the value is always 3. + type: int + restart_delay: + description: + - If a streaming output fails, number of seconds to wait until a restart is initiated. A value of 0 means never restart. + type: int + hls_s3_settings: + description: + - Hls S3 Settings + type: dict + suboptions: + canned_acl: + description: + - Specify the canned ACL to apply to each S3 request. Defaults to none. + type: str + choices: ['AUTHENTICATED_READ', 'BUCKET_OWNER_FULL_CONTROL', 'BUCKET_OWNER_READ', 'PUBLIC_READ'] + hls_webdav_settings: + description: + - Hls Webdav Settings + type: dict + suboptions: + connection_retry_interval: + description: + - Number of seconds to wait before retrying connection to the CDN if the connection is lost. + type: int + filecache_duration: + description: + - Size in seconds of file cache for streaming outputs. + type: int + http_transfer_mode: + description: + - Specify whether or not to use chunked transfer encoding to WebDAV. + type: str + choices: ['CHUNKED', 'NON_CHUNKED'] + num_retries: + description: + - Number of retry attempts that will be made before the Live Event is put into an error state. Applies only if the CDN destination URI begins with "s3" or "mediastore". For other URIs, the value is always 3. + type: int + restart_delay: + description: + - If a streaming output fails, number of seconds to wait until a restart is initiated. A value of 0 means never restart. + type: int + hls_id3_segment_tagging: + description: + - State of HLS ID3 Segment Tagging + type: str + choices: ['DISABLED', 'ENABLED'] + i_frame_only_playlists: + description: + - DISABLED - Do not create an I-frame-only manifest, but do create the master and media manifests (according to the Output Selection field). STANDARD - Create an I-frame-only manifest for each output that contains video, as well as the other manifests (according to the Output Selection field). The I-frame manifest contains a #EXT-X-I-FRAMES-ONLY tag to indicate it is I-frame only, and one or more #EXT-X-BYTERANGE entries identifying the I-frame position. For example, #EXT-X-BYTERANGE:160364@1461888" + type: str + choices: ['DISABLED', 'STANDARD'] + incomplete_segment_behavior: + description: + - Specifies whether to include the final (incomplete) segment in the media output when the pipeline stops producing output because of a channel stop, a channel pause or a loss of input to the pipeline. Auto means that MediaLive decides whether to include the final segment, depending on the channel class and the types of output groups. Suppress means to never include the incomplete segment. We recommend you choose Auto and let MediaLive control the behavior. + type: str + choices: ['AUTO', 'SUPPRESS'] + index_n_segments: + description: + - Applies only if Mode field is LIVE. Specifies the maximum number of segments in the media manifest file. After this maximum, older segments are removed from the media manifest. This number must be smaller than the number in the Keep Segments field. + type: int + input_loss_action: + description: + - Parameter that control output group behavior on input loss. + type: str + choices: ['EMIT_OUTPUT', 'PAUSE_OUTPUT'] + iv_in_manifest: + description: + - For use with encryptionType. The IV (Initialization Vector) is a 128-bit number used in conjunction with the key for encrypting blocks. If set to "include", IV is listed in the manifest, otherwise the IV is not in the manifest. + type: str + choices: ['EXCLUDE', 'INCLUDE'] + iv_source: + description: + - For use with encryptionType. The IV (Initialization Vector) is a 128-bit number used in conjunction with the key for encrypting blocks. If this setting is "followsSegmentNumber", it will cause the IV to change every segment (to match the segment number). If this is set to "explicit", you must enter a constantIv value. + type: str + choices: ['EXPLICIT', 'FOLLOWS_SEGMENT_NUMBER'] + keep_segments: + description: + - Applies only if Mode field is LIVE. Specifies the number of media segments to retain in the destination directory. This number should be bigger than indexNSegments (Num segments). We recommend (value = (2 x indexNsegments) + 1). If this "keep segments" number is too low, the following might happen - the player is still reading a media manifest file that lists this segment, but that segment has been removed from the destination directory (as directed by indexNSegments). This situation would result in a 404 HTTP error on the player. + type: int + key_format: + description: + - The value specifies how the key is represented in the resource identified by the URI. If parameter is absent, an implicit value of "identity" is used. A reverse DNS string can also be given. + type: str + key_format_versions: + description: + - Either a single positive integer version value or a slash delimited list of version values (1/2/3). + type: str + key_provider_settings: + description: + - The key provider settings. + type: dict + suboptions: + static_key_settings: + description: + - Static Key Settings + type: dict + suboptions: + key_provider_server: + description: + - The URL of the license server used for protecting content. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + static_key_value: + description: + - Static key value as a 32 character hexadecimal string. + type: str + manifest_compression: + description: + - When set to gzip, compresses HLS playlist. + type: str + choices: ['GZIP', 'NONE'] + manifest_duration_format: + description: + - Indicates whether the output manifest should use floating point values for segment duration. + type: str + choices: ['FLOATING_POINT', 'INTEGER'] + min_segment_length: + description: + - When set, minimumSegmentLength is enforced by looking ahead and back within the specified range for a nearby avail and extending the segment size if needed. + type: int + mode: + description: + - If "vod", all segments are indexed and kept permanently in the destination and manifest. If "live", only the number segments specified in keepSegments and indexNSegments are kept; newer segments replace older segments, which may prevent players from rewinding all the way to the beginning of the event. VOD mode uses HLS EXT-X-PLAYLIST-TYPE of EVENT while the channel is running, converting it to a "VOD" type manifest on completion of the stream. + type: str + choices: ['LIVE', 'VOD'] + output_selection: + description: + - MANIFESTS_AND_SEGMENTS - The master manifest (and media manifests, if created) and all segments are created. SEGMENTS_ONLY - No manifests are created; just segments. VARIANT_MANIFESTS_AND_SEGMENTS - Media manifests and segments are created, but not the master manifest. + type: str + choices: ['MANIFESTS_AND_SEGMENTS', 'SEGMENTS_ONLY', 'VARIANT_MANIFESTS_AND_SEGMENTS'] + program_date_time: + description: + - Includes or excludes the EXT-X-PROGRAM-DATE-TIME tag in .m3u8 manifest files. The value is calculated as follows - either the program date and time are initialized using the input timecode source, or the time is initialized using the input timecode source and the date is initialized using the timestampOffset. + type: str + choices: ['EXCLUDE', 'INCLUDE'] + program_date_time_clock: + description: + - Specifies the source of the program date time. If "systemClock" is selected, the time will be the current time of the system. If "initializeFromOutputTimecode" is selected, the time will be initialized from the first output timecode. If "systemClock" is selected, the time will be the current time of the system. + type: str + choices: ['INITIALIZE_FROM_OUTPUT_TIMECODE', 'SYSTEM_CLOCK'] + program_date_time_period: + description: + - Period of insertion of EXT-X-PROGRAM-DATE-TIME entry, in seconds. + type: int + redundant_manifest: + description: + - ENABLED - The master manifest (.m3u8 file) for each pipeline includes information about both pipelines, first its own media files, then the same for the other pipeline. This feature allows playout device that support stale manifest detection to switch from one manifest to the other, when the current manifest seems to be stale. There are still two destinations and two master manifests, but both master manifests reference the media files from both pipelines. DISABLED - The master manifest (.m3u8 file) for each pipeline includes information about its own pipeline only. For an HLS output group with MediaPackage as the destination, the DISABLED behavior is always followed. MediaPackage regenerates the manifests it serves to players so a redundant manifest from MediaLive is irrelevant. + type: str + choices: ['DISABLED', 'ENABLED'] + segment_length: + description: + - Length of MPEG-2 Transport Stream segments to create (in seconds). Note that segments will end on the next keyframe after this number of seconds, so actual segment length may be longer. + type: int + segmentation_mode: + description: + - useInputSegmentation has been deprecated. The configured segment size is always used. + type: str + choices: ['USE_INPUT_SEGMENTATION', 'USE_SEGMENT_DURATION'] + segments_per_subdirectory: + description: + - Number of segments to write to a subdirectory before starting a new one. directoryStructure must be subdirectoryPerStream for this setting to have an effect. + type: int + stream_inf_resolution: + description: + - Include or exclude RESOLUTION attribute for video in EXT-X-STREAM-INF tag of variant manifest. + type: str + choices: ['EXCLUDE', 'INCLUDE'] + timed_metadata_id3_frame: + description: + - Indicates ID3 frame that has the timecode. + type: str + choices: ['NONE', 'PRIV', 'TDRL'] + timed_metadata_id3_period: + description: + - Timed Metadata interval in seconds. + type: int + timestamp_delta_milliseconds: + description: + - Provides an extra millisecond delta offset to fine tune the timestamps. + type: int + ts_file_mode: + description: + - SEGMENTED_FILES - Emit the program as segments - multiple .ts media files. SINGLE_FILE - Applies only if Mode field is VOD. Emit the program as a single .ts media file. The media manifest includes #EXT-X-BYTERANGE tags to index segments for playback. A typical use for this value is when sending the output to AWS Elemental MediaConvert, which can accept only a single media file. Playback while the channel is running is not guaranteed due to HTTP server caching. + type: str + choices: ['SEGMENTED_FILES', 'SINGLE_FILE'] + media_package_group_settings: + description: + - Media Package Group Settings + type: dict + suboptions: + destination: + description: + - MediaPackage channel destination. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + ms_smooth_group_settings: + description: + - Ms Smooth Group Settings + type: dict + suboptions: + acquisition_point_id: + description: + - The value of the id attribute for the Acquisition Point element. This must match the value of the acquisition point identity attribute in the custom preset file. This is used to associate incoming content with this MS Smooth output. + type: str + audio_only_timecode_control: + description: + - If set to passthrough for an audio-only MS Smooth output, the fragment absolute time will be set to the current timecode. This option does not write timecodes to the audio elementary stream. + type: str + choices: ['PASSTHROUGH', 'USE_CONFIGURED_CLOCK'] + certificate_mode: + description: + - If set to verifyAuthenticity, verify the https certificate chain to a trusted Certificate Authority (CA). This will cause https outputs to self-signed certificates to fail. + type: str + choices: ['SELF_SIGNED', 'VERIFY_AUTHENTICITY'] + connection_retry_interval: + description: + - Number of seconds to wait before retrying connection to the IIS server if the connection is lost. Content will be cached during this time and the cache will be be delivered to the IIS server once the connection is re-established. + type: int + destination: + description: + - Smooth Streaming publish point on an IIS server. Elemental Live acts as a "Push" encoder to IIS. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + event_id: + description: + - MS Smooth event ID to be sent to the IIS server. Should only be specified if eventIdMode is set to useConfigured. + type: str + event_id_mode: + description: + - Specifies whether or not to send an event ID to the IIS server. If no event ID is sent and the same Live Event is used without changing the publishing point, clients might see cached video from the previous run. Options - "useConfigured" - use the value provided in eventId - "useTimestamp" - generate and send an event ID based on the current timestamp - "noEventId" - do not send an event ID to the IIS server. + type: str + choices: ['NO_EVENT_ID', 'USE_CONFIGURED', 'USE_TIMESTAMP'] + event_stop_behavior: + description: + - When set to sendEos, send EOS signal to IIS server when stopping the event + type: str + choices: ['NONE', 'SEND_EOS'] + filecache_duration: + description: + - Size in seconds of file cache for streaming outputs. + type: int + fragment_length: + description: + - Length of mp4 fragments to generate (in seconds). Fragment length must be compatible with GOP size and framerate. + type: int + input_loss_action: + description: + - Parameter that control output group behavior on input loss. + type: str + choices: ['EMIT_OUTPUT', 'PAUSE_OUTPUT'] + num_retries: + description: + - Number of retry attempts. + type: int + restart_delay: + description: + - Number of seconds before initiating a restart due to output failure, due to exhausting the numRetries on one segment, or exceeding filecacheDuration. + type: int + segmentation_mode: + description: + - useInputSegmentation has been deprecated. The configured segment size is always used. + type: str + choices: ['USE_INPUT_SEGMENTATION', 'USE_SEGMENT_DURATION'] + send_delay_ms: + description: + - Number of milliseconds to delay the output from the second pipeline. + type: int + sparse_track_type: + description: + - If set to scte35, use incoming SCTE-35 messages to generate a sparse track in this group of MS-Smooth outputs. + type: str + choices: ['NONE', 'SCTE_35', 'SCTE_35_WITHOUT_SEGMENTATION'] + stream_manifest_behavior: + description: + - When set to send, send stream manifest so publishing point doesn't start until all streams start. + type: str + choices: ['DO_NOT_SEND', 'SEND'] + timestamp_offset: + description: + - Timestamp offset for the event. Only used if timestampOffsetMode is set to useConfiguredOffset. + type: str + timestamp_offset_mode: + description: + - Type of timestamp date offset to use. useEventStartDate - Use the date the event was started as the offset, useConfiguredOffset - Use an explicitly configured date as the offset + type: str + choices: ['USE_CONFIGURED_OFFSET', 'USE_EVENT_START_DATE'] + multiplex_group_settings: + description: + - Multiplex Group Settings + type: dict + rtmp_group_settings: + description: + - Rtmp Group Settings + type: dict + suboptions: + ad_markers: + description: + - Choose the ad marker type for this output group. MediaLive will create a message based on the content of each SCTE-35 message, format it for that marker type, and insert it in the datastream. + type: list + elements: str + choices: ['ON_CUE_POINT_SCTE35'] + AuthenticationScheme: + description: + - Authentication scheme to use when connecting with CDN + type: str + choices: ['AKAMAI', 'COMMON'] + CacheFullBehavior: + description: + - Controls behavior when content cache fills up + type: str + choices: ['DISCONNECT_IMMEDIATELY', 'WAIT_FOR_SERVER'] + CacheLength: + description: + - Cache length, in seconds, is used to calculate buffer size + type: int + CaptionData: + description: + - Controls the types of data that passes to onCaptionInfo outputs. If set to 'all' then 608 and 708 carried DTVCC data will be passed. If set to 'field1AndField2608' then DTVCC data will be stripped out, but 608 data from both fields will be passed. If set to 'field1608' then only the data carried in 608 from field 1 video will be passed. + type: str + choices: ['ALL', 'FIELD1_608', 'FIELD1_AND_FIELD2_608'] + InputLossAction: + description: + - Controls the behavior of this RTMP group if input becomes unavailable. - emitOutput Emit a slate until input returns. - pauseOutput Stop transmitting data until input returns. This does not close the underlying RTMP connection. + type: str + choices: ['EMIT_OUTPUT', 'PAUSE_OUTPUT'] + RestartDelay: + description: + - If a streaming output fails, number of seconds to wait until a restart is initiated. A value of 0 means never restart. + type: int + IncludeFillerNalUnits: + description: + - Applies only to H.264 outputs. Specifies whether to include the filler NAL units in the output. Keep the default value (AUTO) unless you need to reference the filler NAL units in custom downstream systems. INCLUDE - Include filler NAL units in the output. DROP - Remove filler NAL units from the output. AUTO - Remove filler NAL units from the output, unless the output frame rate is 50 or 60 fps, and the output group is either a DASH or HLS group. In that case, include the filler NAL units in the output. + type: str + choices: ['AUTO', 'DROP', 'INCLUDE'] + udp_group_settings: + description: + - Udp Group Settings + type: dict + suboptions: + input_loss_action: + description: + - Specifies behavior of last resort when input video is lost, and no more backup inputs are available. When dropTs is selected the entire transport stream will stop being emitted. When dropProgram is selected only the affected program(s) will stop being emitted. + type: str + choices: ['DROP_PROGRAM', 'DROP_TS', 'EMIT_PROGRAM'] + timed_metadata_id3_frame: + description: + - Indicates ID3 frame that has the timecode. + type: str + choices: ['NONE', 'PRIV', 'TDRL'] + timed_metadata_id3_period: + description: + - Timed Metadata interval in seconds. + type: int + cmaf_ingest_group_settings: + description: + - Cmaf Ingest Group Settings + type: dict + suboptions: + destination: + description: + - A directory or HTTP destination for the CMAF Ingest segments. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + nielsen_id3_behavior: + description: + - If set to passthrough, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + scte35_type: + description: + type: str + choices: ['NONE', 'SCTE_35_WITHOUT_SEGMENTATION'] + segment_length: + description: + - Length of CMAF segments to create (in seconds or milliseconds, depending on the segment length mode). Note that segments will end on the next keyframe after this number of seconds, so actual segment length may be longer. + type: int + segment_length_units: + description: + - Specifies the units for the segment length. + type: str + choices: ['MILLISECONDS', 'SECONDS'] + send_delay_ms: + description: + - Number of milliseconds to delay the output from the second pipeline. + type: int + klv_behavior: + description: + - If set to passthrough, passes any KLV data from the input source to this output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + klv_name_modifier: + description: + - Applies a string to the KLV output file names. + type: str + nielsen_id3_name_modifier: + description: + - Applies a string to the Nielsen ID3 output file names. + type: str + scte35_name_modifier: + description: + - Applies a string to the SCTE-35 output file names. + type: str + id3_behavior: + description: + - If set to passthrough, passes any ID3 metadata from the input source to this output. + type: str + choices: ['DISABLED', 'ENABLED'] + id3_name_modifier: + description: + - Applies a string to the ID3 output file names. + type: str + caption_language_mappings: + description: + - Mapping of up to 4 caption channels to caption languages. Is only meaningful if captionLanguageSetting is set to "insert". + type: list + elements: dict + suboptions: + caption_channel: + description: + - The closed caption channel being described by this CaptionLanguageMapping. Each channel mapping must have a unique channel number (maximum of 4) + type: int + language_code: + description: + - Three character ISO 639-2 language code (see http://www.loc.gov/standards/iso639-2) + type: str + timed_metadata_id3_frame: + description: + - Indicates ID3 frame that has the timecode. + type: str + choices: ['NONE', 'PRIV', 'TDRL'] + timed_metadata_id3_period: + description: + - Timed Metadata interval in seconds. + type: int + timed_metadata_passthrough: + description: + - If set to passthrough, passes any timed metadata from the input source to this output. + type: str + choices: ['DISABLED', 'ENABLED'] + srt_group_settings: + description: + - Srt Group Settings + type: dict + suboptions: + input_loss_action: + description: + - Specifies behavior of last resort when input video is lost, and no more backup inputs are available. When dropTs is selected the entire transport stream will stop being emitted. When dropProgram is selected only the affected program(s) will stop being emitted. + type: str + choices: ['DROP_PROGRAM', 'DROP_TS', 'EMIT_PROGRAM'] + outputs: + description: + - The array of Output specifications + type: list + elements: dict + suboptions: + audio_description_names: + description: + - The names of the AudioDescriptions used as audio sources for this output. + type: list + elements: str + caption_description_names: + description: + - The names of the CaptionDescriptions used as caption sources for this output. + type: list + elements: str + output_name: + description: + - The name used to identify an output. + type: str + output_settings: + description: + - Output Settings + type: dict + suboptions: + archive_output_settings: + description: + - Archive Output Settings + type: dict + suboptions: + container_settings: + description: + - Settings specific to the container type of the file. + type: dict + suboptions: + m2ts_settings: + description: + - M2ts Settings + type: dict + suboptions: + absent_input_audio_behavior: + description: + - When set to drop, output audio streams will be removed from the program if the selected input audio stream is removed from the input. This allows the output audio configuration to dynamically change based on input configuration. If this is set to encodeSilence, all output audio streams will output encoded silence when not connected to an active input stream. + type: str + choices: ['DROP', 'ENCODE_SILENCE'] + arib: + description: + - When set to enabled, uses ARIB standard for DVB subtitles. + type: str + choices: ['DISABLED', 'ENABLED'] + arib_captions_pid: + description: + - Packet Identifier (PID) for ARIB Captions in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + arib_captions_pid_control: + description: + - If set to auto, pid number used for ARIB Captions will be auto-selected from unused pids. If set to useConfigured, ARIB Captions will be on the configured pid number. + type: str + choices: ['AUTO', 'USE_CONFIGURED'] + audio_buffer_model: + description: + - When set to dvb, uses DVB buffer model for Dolby Digital audio. When set to atsc, uses ATSC model. + type: str + choices: ['ATSC', 'DVB'] + audio_frames_per_pes: + description: + - The number of audio frames to insert for each PES packet. + type: int + + audio_pids: + description: + - Packet Identifier (PID) of the elementary audio stream(s) in the transport stream. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + audio_stream_type: + description: + - When set to atsc, uses stream type = 0x81 for AC3 and stream type = 0x87 for EAC3. When set to dvb, uses stream type = 0x06. + type: str + choices: ['ATSC', 'DVB'] + bitrate: + description: + - The output bitrate of the transport stream in bits per second. Setting to 0 lets the muxer automatically determine the appropriate bitrate. + type: int + buffer_model: + description: + - If set to multiplex, use multiplex buffer model for accurate interleaving. Setting to none can lead to lower latency, but low-memory devices may not be able to play back the stream without interruptions. + type: str + choices: ['MULTIPLEX', 'NONE'] + cc_descriptor: + description: + - When set to enabled, generates captionServiceDescriptor in PMT. + type: str + choices: ['DISABLED', 'ENABLED'] + dvb_nit_settings: + description: + - Inserts DVB Network Information Table (NIT) at the specified table repetition interval. + type: dict + suboptions: + network_id: + description: + - The numeric value placed in the Network Information Table (NIT). + type: int + network_name: + description: + - The network name text placed in the networkNameDescriptor inside the Network Information Table (NIT). Maximum length is 256 characters. + type: str + rep_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. + type: int + dvb_sdt_settings: + description: + - Inserts DVB Service Description Table (SDT) at the specified table repetition interval. + type: dict + suboptions: + output_sdt: + description: + - Selects method of SDT insertion. "sdtFollow" sets the SDT to follow the input SDT. "sdtFollowIfPresent" makes the output SDT follow the input SDT when present, otherwise it will fall back on the user-defined values. "sdtManual" means the user will enter the SDT in the provided parameters. "sdtNone" means no SDT will be output. + type: str + choices: ['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE'] + rep_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. + type: int + service_name: + description: + - The service name placed in the serviceDescriptor in the Service Description Table (SDT). Maximum length is 256 characters. + type: str + service_provider_name: + description: + - The service provider name placed in the serviceDescriptor in the Service Description Table (SDT). Maximum length is 256 characters. + type: str + dvb_sub_pids: + description: + - Packet Identifier (PID) for input source DVB Subtitle data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + dvb_tdt_settings: + description: + - Inserts DVB Time and Date Table (TDT) at the specified table repetition interval. + type: dict + suboptions: + rep_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. + type: int + dvb_teletext_pid: + description: + - Packet Identifier (PID) for input source DVB Teletext data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + ebif: + description: + - If set to passthrough, passes any EBIF data from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + ebp_audio_interval: + description: + - When videoAndFixedIntervals is selected, audio EBP markers will be added to partitions 3 and 4. The interval between these additional markers will be fixed, and will be slightly shorter than the video EBP marker interval. Only available when EBP Cablelabs segmentation markers are selected. Partitions 1 and 2 will always follow the video interval. + type: str + choices: ['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL'] + ebp_lookahead_ms: + description: + - When set, enforces that Enhanced Binary Partitioning (EBP) is used for this output. If set to ebpLookaheadMs, partitions will be added to the stream to allow for the downstream system to perform time-shifting (e.g. to delay the stream by 10s), without interrupting the program at the partition. + type: int + ebp_placement: + description: + - Controls placement of EBP on Audio PIDs. If set to videoAndAudioPids, EBP markers will be placed on both video and audio PIDs. If set to videoPid, EBP markers will be placed on only the video PID. + type: str + choices: ['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID'] + ecm_pid: + description: + - This field is unused and deprecated. + type: str + es_rate_in_pes: + description: + - Include or exclude the ES Rate field in the PES header. + type: str + choices: ['EXCLUDE', 'INCLUDE'] + etv_platform_pid: + description: + - Packet Identifier (PID) for input source ETV Platform data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + etv_signal_pid: + description: + - Packet Identifier (PID) for input source ETV Signal data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + fragment_time: + description: + - The length in seconds of each fragment. Only used with EBP markers. + type: float + klv: + description: + - If set to passthrough, passes any KLV data from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + klv_data_pids: + description: + - Packet Identifier (PID) for input source KLV data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + nielsen_id3_behavior: + description: + - If set to passthrough, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + null_packet_bitrate: + description: + - Value in bits per second of extra null packets to insert into the transport stream. This can be used if a downstream encryption system requires specific minimum bitrate. + type: float + pat_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. + type: int + pcr_control: + description: + - When set to pcrEveryPesPacket, a Program Clock Reference value is inserted for every Packetized Elementary Stream (PES) header. This parameter is effective only when the PCR PID is the same as the video or audio elementary stream. + type: str + choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] + pat_period: + description: + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. + type: int + pcr_pid: + description: + - Packet Identifier (PID) of the Program Clock Reference (PCR) in the transport stream. When no value is given, the encoder will assign the same value as the Video PID. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + pmt_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. + type: int + pmt_pid: + description: + - Packet Identifier (PID) for the Program Map Table (PMT) in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + program_num: + description: + - The value of the program number field in the Program Map Table. + type: int + rate_mode: + description: + - When set to vbr, does not insert null packets into transport stream to fill specified bitrate. The bitrate setting acts as the maximum bitrate when vbr is set. + type: str + choices: ['CBR', 'VBR'] + scte27_pids: + description: + - Packet Identifier (PID) for input source SCTE-27 data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + scte35_control: + description: + - Optionally pass SCTE-35 signals from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + scte35_pid: + description: + - Packet Identifier (PID) of the SCTE-35 stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + segmentation_markers: + description: + - Inserts segmentation markers at each segmentation_time period. rai_segstart sets the Random Access Indicator bit in the adaptation field. rai_adapt sets the RAI bit and adds the current timecode in the private data bytes. psi_segstart inserts PAT and PMT tables at the start of segments. ebp adds Encoder Boundary Point information to the adaptation field as per OpenCable specification OC-SP-EBP-I01-130118. ebp_legacy adds Encoder Boundary Point information to the adaptation field using a legacy proprietary format. + type: str + choices: ['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART'] + segmentation_style: + description: + - The segmentation style parameter controls how segmentation markers are inserted into the transport stream. With avails, it is possible that segments may be truncated, which can influence where future segmentation markers are inserted. When a segmentation style of "resetCadence" is selected and a segment is truncated due to an avail, we will reset the segmentation cadence. This means the subsequent segment will have a duration of $segmentationTime seconds. When a segmentation style of "maintainCadence" is selected and a segment is truncated due to an avail, we will not reset the segmentation cadence. This means the subsequent segment will likely be truncated as well. However, all segments after that will have a duration of $segmentationTime seconds. Note that EBP lookahead is a slight exception to this rule. + type: str + choices: ['MAINTAIN_CADENCE', 'RESET_CADENCE'] + segmentation_time: + description: + - The length in seconds of each segment. Required unless markers is set to None_. + type: float + timed_metadata_behavior: + description: + - When set to passthrough, timed metadata will be passed through from input to output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + timed_metadata_pid: + description: + - Packet Identifier (PID) of the timed metadata stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + transport_stream_id: + description: + - The value of the transport stream ID field in the Program Map Table. + type: int + video_pid: + description: + - Packet Identifier (PID) of the elementary video stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + scte35_preroll_pullup_milliseconds: + description: + - Adjusts the timing of SCTE-35 messages to account for delays introduced by ad insertion. Must be a positive number less than 1000. + type: float + raw_settings: + description: + - Raw Settings + type: dict + extension: + description: + - Output file extension. If excluded, this will be auto-selected from the container type. + type: str + name_modifier: + description: + - String concatenated to the end of the destination filename. Required for multiple outputs of the same type. + type: str + frame_capture_output_settings: + description: + - Frame Capture Output Settings + type: dict + suboptions: + name_modifier: + description: + - Required if there are multiple Frame Capture outputs that have the same output path. This modifier forms part of the output file name. + type: str + hls_output_settings: + description: + - Hls Output Settings + type: dict + suboptions: + h265_packaging_type: + description: + - Only applicable when this output is referencing an H.265 video description. Specifies whether MP4 segments should be packaged as HEV1 or HVC1. + type: str + choices: ['HEV1', 'HVC1'] + hls_settings: + description: + - Hls Settings + type: dict + suboptions: + audio_only_hls_settings: + description: + - Audio Only Hls Settings + type: dict + suboptions: + audio_group_id: + description: + - Specifies the group to which the audio Rendition belongs. + type: str + audio_only_image: + description: + - Optional. Specifies an image to display when the stream is playing audio only content. + type: dict + suboptions: + password_param: + description: + - key used to extract the password from EC2 Parameter store + type: str + uri: + description: + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to "rtmp://fmsserver/live". + type: str + username: + description: + - Documentation update needed + type: str + audio_track_type: + description: + - Four types of audio-only tracks are supported. Audio-Only Variant Stream The client can play this audio-only stream instead of video in low-bandwidth scenarios. Represented as an EXT-X-STREAM-INF in the HLS manifest. Alternate Audio, Auto Select, Default Alternate rendition that the client should try to play back by default. Represented as an EXT-X-MEDIA in the HLS manifest with DEFAULT=YES, AUTOSELECT=YES Alternate Audio, Auto Select, Not Default Alternate rendition that the client may try to play back by default. Represented as an EXT-X-MEDIA in the HLS manifest with DEFAULT=NO, AUTOSELECT=YES Alternate Audio, not Auto Select Alternate rendition that the client will not try to play back by default. Represented as an EXT-X-MEDIA in the HLS manifest with DEFAULT=NO, AUTOSELECT=NO + type: str + choices: + - 'ALTERNATE_AUDIO_AUTO_SELECT' + - 'ALTERNATE_AUDIO_AUTO_SELECT_DEFAULT' + - 'ALTERNATE_AUDIO_NOT_AUTO_SELECT' + - 'AUDIO_ONLY_VARIANT_STREAM' + segment_type: + description: + - Specifies the segment type. + type: str + choices: ['AAC', 'FMP4'] + fmp4_hls_settings: + description: + - Fmp4 Hls Settings + type: dict + suboptions: + audio_rendition_sets: + description: + - List all the audio groups that are used with the video output stream. Input all the audio GROUP-IDs that are associated to the video, separate by ','. + type: str + nielsen_id3_behavior: + description: + - If set to passthrough, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + timed_metadata_behavior: + description: + - When set to passthrough, timed metadata is passed through from input to output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + frame_capture_hls_settings: + description: + - Frame Capture Hls Settings + type: dict + standard_hls_settings: + description: + - Standard Hls Settings + type: dict + suboptions: + audio_rendition_sets: + description: + - List all the audio groups that are used with the video output stream. Input all the audio GROUP-IDs that are associated to the video, separate by ','. + type: str + m3u8_settings: + description: + - M3u8 Settings + type: dict + suboptions: + audio_frames_per_pes: + description: + - The number of audio frames to insert for each PES packet. + type: int + audio_pids: + description: + - Packet Identifier (PID) of the elementary audio stream(s) in the transport stream. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + ecm_pid: + description: + - This field is unused and deprecated. + type: str + nielsen_id3_behavior: + description: + - If set to PASSTHROUGH, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + pat_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. A value of "0" writes out the PMT once per segment file. + type: int + pcr_control: + description: + - When set to PCR_EVERY_PES_PACKET, a Program Clock Reference value is inserted for every Packetized Elementary Stream (PES) header. This parameter is effective only when the PCR PID is the same as the video or audio elementary stream. + type: str + choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] + pcr_period: + description: + - Maximum time in milliseconds between Program Clock References (PCRs) inserted into the transport stream. + type: int + pcr_pid: + description: + - Packet Identifier (PID) of the Program Clock Reference (PCR) in the transport stream. When no value is given, the encoder will assign the same value as the Video PID. Can be entered as a decimal or hexadecimal value. + type: str + pmt_interval: + description: + - The number of milliseconds between instances of this table in the output transport stream. A value of "0" writes out the PMT once per segment file. + type: int + pmt_pid: + description: + - Packet Identifier (PID) for the Program Map Table (PMT) in the transport stream. Can be entered as a decimal or hexadecimal value. + type: str + program_num: + description: + - The value of the program number field in the Program Map Table. + type: int + scte35_behavior: + description: + - If set to PASSTHROUGH, passes any SCTE-35 signals from the input source to this output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + scte35_pid: + description: + - Packet Identifier (PID) of the SCTE-35 stream in the transport stream. Can be entered as a decimal or hexadecimal value. + type: str + timed_metadata_behavior: + description: + - Set to PASSTHROUGH to enable ID3 metadata insertion. To include metadata, you configure other parameters in the output group or individual outputs, or you add an ID3 action to the channel schedule. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + timed_metadata_pid: + description: + - Packet Identifier (PID) of the timed metadata stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). + type: str + transport_stream_id: + description: + - The value of the transport stream ID field in the Program Map Table. + type: int + video_pid: + description: + - Packet Identifier (PID) of the elementary video stream in the transport stream. Can be entered as a decimal or hexadecimal value. + type: str + klv_behavior: + description: + - If set to PASSTHROUGH, passes any KLV data from the input source to this output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + klv_data_pids: + description: + - Packet Identifier (PID) for input source KLV data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). + type: str + name_modifier: + description: + - String concatenated to the end of the destination filename. Accepts "Format Identifiers". + type: str + segment_modifier: + description: + - String concatenated to end of segment filenames. + type: str + media_package_output_settings: + description: + - Media Package Output Settings + type: dict + ms_smooth_output_settings: + description: + - Ms Smooth Output Settings + type: dict + suboptions: + h265_packaging_type: + description: + - Only applicable when this output is referencing an H.265 video description. Specifies whether MP4 segments should be packaged as HEV1 or HVC1. + type: str + choices: ['HEV1', 'HVC1'] + name_modifier: + description: + - String concatenated to the end of the destination filename. Required for multiple outputs of the same type. + type: str + multiplex_output_settings: + description: + - Multiplex Output Settings + type: dict + suboptions: + destination: + description: + - Destination is a Multiplex. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + container_settings: + description: + - Multiplex Container Settings + type: dict + suboptions: + multiplex_m2ts_settings: + description: + - Multiplex M2ts Settings + type: dict + suboptions: + + absent_input_audio_behavior: + description: + - When set to DROP, output audio streams will be removed from the program if the selected input audio stream is removed from the input. This allows the output audio configuration to dynamically change based on input configuration. If this is set to ENCODE_SILENCE, all output audio streams will output encoded silence when not connected to an active input stream. + type: str + choices: ['DROP', 'ENCODE_SILENCE'] + arib: + description: + - When set to ENABLED, uses ARIB-compliant field muxing and removes video descriptor. + type: str + choices: ['DISABLED', 'ENABLED'] + audio_buffer_model: + description: + - When set to DVB, uses DVB buffer model for Dolby Digital audio. When set to ATSC, the ATSC model is used. + type: str + choices: ['ATSC', 'DVB'] + audio_frames_per_pes: + description: + - The number of audio frames to insert for each PES packet. + type: int + audio_stream_type: + description: + - When set to ATSC, uses stream type = 0x81 for AC3 and stream type = 0x87 for EAC3. When set to DVB, uses stream type = 0x06. + type: str + choices: ['ATSC', 'DVB'] + cc_descriptor: + description: + - When set to ENABLED, generates captionServiceDescriptor in PMT. + type: str + choices: ['DISABLED', 'ENABLED'] + ebif: + description: + - If set to PASSTHROUGH, passes any EBIF data from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + es_rate_in_pes: + description: + - Include or exclude the ES Rate field in the PES header. + type: str + choices: ['EXCLUDE', 'INCLUDE'] + klv: + description: + - If set to PASSTHROUGH, passes any KLV data from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + nielsen_id3_behavior: + description: + - If set to PASSTHROUGH, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + pcr_control: + description: + - When set to PCR_EVERY_PES_PACKET, a Program Clock Reference value is inserted for every Packetized Elementary Stream (PES) header. This parameter is effective only when the PCR PID is the same as the video or audio elementary stream. + type: str + choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] + pcr_period: + description: + - Maximum time in milliseconds between Program Clock Reference (PCRs) inserted into the transport stream. + type: int + scte35_control: + description: + - Optionally pass SCTE-35 signals from the input source to this output. + type: str + choices: ['NONE', 'PASSTHROUGH'] + scte35_preroll_pullup_milliseconds: + description: + - Defines the amount SCTE-35 preroll will be increased (in milliseconds) on the output. Preroll is the amount of time between the presence of a SCTE-35 indication in a transport stream and the PTS of the video frame it references. Zero means don't add pullup (it doesn't mean set the preroll to zero). Negative pullup is not supported, which means that you can't make the preroll shorter. Be aware that latency in the output will increase by the pullup amount. + type: float + rtmp_output_settings: + description: + - Rtmp Output Settings + type: dict + suboptions: + certificate_mode: + description: + - If set to VERIFY_AUTHENTICITY, verify the tls certificate chain to a trusted Certificate Authority (CA). This will cause rtmps outputs with self-signed certificates to fail. + type: str + choices: ['SELF_SIGNED', 'VERIFY_AUTHENTICITY'] + connection_retry_interval: + description: + - Number of seconds to wait before retrying a connection to the Flash Media server if the connection is lost. + type: int + destination: + description: + - The RTMP endpoint excluding the stream name (eg. rtmp://host/appname). For connection to Akamai, a username and password must be supplied. URI fields accept format identifiers. + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + num_retries: + description: + - Number of retry attempts. + type: int + udp_output_settings: + description: + - Udp Output Settings + type: dict + suboptions: + buffer_msec: + description: + - UDP output buffering in milliseconds. Larger values increase latency through the transcoder but simultaneously assist the transcoder in maintaining a constant, low-jitter UDP/RTP output while accommodating clock recovery, input switching, input disruptions, picture reordering, etc. + type: int + container_settings: + description: + - Udp Container Settings + type: dict + suboptions: + m2ts_settings: + description: + - M2ts Settings + type: dict + suboptions: + absent_input_audio_behavior: + description: + - When set to DROP, output audio streams will be removed from the program if the selected input audio stream is removed from the input. This allows the output audio configuration to dynamically change based on input configuration. If this is set to ENCODE_SILENCE, all output audio streams will output encoded silence when not connected to an active input stream. + type: str + choices: ['DROP', 'ENCODE_SILENCE'] + arib: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + arib_captions_pid: + description: + - TODO + type: str + arib_captions_pid_control: + description: + - TODO + type: str + choices: ['AUTO', 'USE_CONFIGURED'] + audio_buffer_model: + description: + - TODO + type: str + choices: ['ATSC', 'DVB'] + audio_frames_per_pes: + description: + - TODO + type: int + + audio_pids: + description: + - TODO + type: str + audio_stream_type: + description: + - TODO + type: str + choices: ['ATSC', 'DVB'] + bitrate: + description: + - TODO + type: int + buffer_model: + description: + - TODO + type: str + choices: ['MULTIPLEX', 'NONE'] + cc_descriptor: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + dvb_nit_settings: + description: + - TODO + type: dict + suboptions: + network_id: + description: + - TODO + type: int + network_name: + description: + - TODO + type: str + rep_interval: + description: + - TODO + type: int + dvb_sdt_settings: + description: + - TODO + type: dict + suboptions: + output_sdt: + description: + - TODO + type: str + choices: ['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE'] + rep_interval: + description: + - TODO + type: int + service_name: + description: + - TODO + type: str + service_provider_name: + description: + - TODO + type: str + dvb_sub_pids: + description: + - TODO + type: str + dvb_tdt_settings: + description: + - TODO + type: dict + suboptions: + rep_interval: + description: + - TODO + type: int + dvb_teletext_pid: + description: + - TODO + type: str + ebif: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + ebp_audio_interval: + description: + - TODO + type: str + choices: ['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL'] + ebp_lookahead_ms: + description: + - TODO + type: int + ebp_placement: + description: + - TODO + type: str + choices: ['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID'] + ecm_pid: + description: + - TODO + type: str + es_rate_in_pes: + description: + - TODO + type: str + choices: ['EXCLUDE', 'INCLUDE'] + etv_platform_pid: + description: + - TODO + type: str + etv_signal_pid: + description: + - TODO + type: str + fragment_time: + description: + - TODO + type: float + klv: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + klv_data_pids: + description: + - TODO + type: str + nielsen_id3_behavior: + description: + - TODO + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + null_packet_bitrate: + description: + - TODO + type: float + pat_interval: + description: + - TODO + type: int + pcr_control: + description: + - TODO + type: str + choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] + pcr_period: + description: + - TODO + type: int + pcr_pid: + description: + - TODO + type: str + pmt_interval: + description: + - TODO + type: int + pmt_pid: + description: + - TODO + type: str + program_num: + description: + - TODO + type: int + rate_mode: + description: + - TODO + type: str + choices: ['CBR', 'VBR'] + scte27_pids: + description: + - TODO + type: str + scte35_control: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + scte35_pid: + description: + - TODO + type: str + segmentation_markers: + description: + - TODO + type: str + choices: ['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART'] + segmentation_style: + description: + - TODO + type: str + choices: ['MAINTAIN_CADENCE', 'RESET_CADENCE'] + segmentation_time: + description: + - TODO + type: float + timed_metadata_behavior: + description: + - TODO + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + timed_metadata_pid: + description: + - TODO + type: str + transport_stream_id: + description: + - TODO + type: int + video_pid: + description: + - TODO + type: str + scte35_preroll_pullup_milliseconds: + description: + - TODO + type: float + destination: + description: + - Destination address and port number for RTP or UDP packets. Can be unicast or multicast RTP or UDP (eg. rtp://239.10.10.10:5001 or udp://10.100.100.100:5002). + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + fec_output_settings: + description: + - Settings for enabling and adjusting Forward Error Correction on UDP outputs. + type: dict + suboptions: + column_depth: + description: + - Parameter D from SMPTE 2022-1. The height of the FEC protection matrix. The number of transport stream packets per column error correction packet. Must be between 4 and 20, inclusive. + type: int + include_fec: + description: + - Enables column only or column and row based FEC + type: str + choices: ['COLUMN', 'COLUMN_AND_ROW'] + row_length: + description: + - Parameter L from SMPTE 2022-1. The width of the FEC protection matrix. Must be between 1 and 20, inclusive. If only Column FEC is used, then larger values increase robustness. If Row FEC is used, then this is the number of transport stream packets per row error correction packet, and the value must be between 4 and 20, inclusive, if includeFec is COLUMN_AND_ROW. If includeFec is COLUMN, this value must be 1 to 20, inclusive. + type: int + cmaf_ingest_output_settings: + description: + - Cmaf Ingest Output Settings + type: dict + suboptions: + name_modifier: + description: + - String concatenated to the end of the destination filename. Required for multiple outputs of the same type. + type: str + srt_output_settings: + description: + - Srt Output Settings + type: dict + suboptions: + buffer_msec: + description: + - SRT output buffering in milliseconds. A higher value increases latency through the encoder. But the benefits are that it helps to maintain a constant, low-jitter SRT output, and it accommodates clock recovery, input switching, input disruptions, picture reordering, and so on. Range, 0-10000 milliseconds. + type: int + container_settings: + description: + - Udp Container Settings + type: dict + suboptions: + m2ts_settings: + description: + - M2ts Settings + type: dict + suboptions: + absent_input_audio_behavior: + description: + - When set to DROP, output audio streams will be removed from the program if the selected input audio stream is removed from the input. This allows the output audio configuration to dynamically change based on input configuration. If this is set to ENCODE_SILENCE, all output audio streams will output encoded silence when not connected to an active input stream. + type: str + choices: ['DROP', 'ENCODE_SILENCE'] + arib: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + arib_captions_pid: + description: + - TODO + type: str + arib_captions_pid_control: + description: + - TODO + type: str + choices: ['AUTO', 'USE_CONFIGURED'] + audio_buffer_model: + description: + - TODO + type: str + choices: ['ATSC', 'DVB'] + audio_frames_per_pes: + description: + - TODO + type: int + + audio_pids: + description: + - TODO + type: str + audio_stream_type: + description: + - TODO + type: str + choices: ['ATSC', 'DVB'] + bitrate: + description: + - TODO + type: int + buffer_model: + description: + - TODO + type: str + choices: ['MULTIPLEX', 'NONE'] + cc_descriptor: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + dvb_nit_settings: + description: + - TODO + type: dict + suboptions: + network_id: + description: + - TODO + type: int + network_name: + description: + - TODO + type: str + rep_interval: + description: + - TODO + type: int + dvb_sdt_settings: + description: + - TODO + type: dict + suboptions: + output_sdt: + description: + - TODO + type: str + choices: ['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE'] + rep_interval: + description: + - TODO + type: int + service_name: + description: + - TODO + type: str + service_provider_name: + description: + - TODO + type: str + dvb_sub_pids: + description: + - TODO + type: str + dvb_tdt_settings: + description: + - TODO + type: dict + suboptions: + rep_interval: + description: + - TODO + type: int + dvb_teletext_pid: + description: + - TODO + type: str + ebif: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + ebp_audio_interval: + description: + - TODO + type: str + choices: ['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL'] + ebp_lookahead_ms: + description: + - TODO + type: int + ebp_placement: + description: + - TODO + type: str + choices: ['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID'] + ecm_pid: + description: + - TODO + type: str + es_rate_in_pes: + description: + - TODO + type: str + choices: ['EXCLUDE', 'INCLUDE'] + etv_platform_pid: + description: + - TODO + type: str + etv_signal_pid: + description: + - TODO + type: str + fragment_time: + description: + - TODO + type: float + klv: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + klv_data_pids: + description: + - TODO + type: str + nielsen_id3_behavior: + description: + - TODO + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + null_packet_bitrate: + description: + - TODO + type: float + pat_interval: + description: + - TODO + type: int + pcr_control: + description: + - TODO + type: str + choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] + pcr_period: + description: + - TODO + type: int + pcr_pid: + description: + - TODO + type: str + pmt_interval: + description: + - TODO + type: int + pmt_pid: + description: + - TODO + type: str + program_num: + description: + - TODO + type: int + rate_mode: + description: + - TODO + type: str + choices: ['CBR', 'VBR'] + scte27_pids: + description: + - TODO + type: str + scte35_control: + description: + - TODO + type: str + choices: ['NONE', 'PASSTHROUGH'] + scte35_pid: + description: + - TODO + type: str + segmentation_markers: + description: + - TODO + type: str + choices: ['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART'] + segmentation_style: + description: + - TODO + type: str + choices: ['MAINTAIN_CADENCE', 'RESET_CADENCE'] + segmentation_time: + description: + - TODO + type: float + timed_metadata_behavior: + description: + - TODO + type: str + choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] + timed_metadata_pid: + description: + - TODO + type: str + transport_stream_id: + description: + - TODO + type: int + video_pid: + description: + - TODO + type: str + scte35_preroll_pullup_milliseconds: + description: + - TODO + type: float + destination: + description: + - Reference to an OutputDestination ID defined in the channel + type: dict + suboptions: + destination_ref_id: + description: + - Placeholder documentation for __string + type: str + encryption_type: + description: + - The encryption level for the content. Valid values are AES128, AES192, AES256. You and the downstream system should plan how to set this field because the values must not conflict with each other. + type: str + choices: ['AES128', 'AES192', 'AES256'] + latency: + description: + - The latency value, in milliseconds, that is proposed during the SRT connection handshake. SRT will choose the maximum of the values proposed by the sender and receiver. On the sender side, latency is the amount of time a packet is held to give it a chance to be delivered successfully. On the receiver side, latency is the amount of time the packet is held before delivering to the application, aiding in packet recovery and matching as closely as possible the packet timing of the sender. Range, 40-16000 milliseconds. + type: int + video_description_name: + description: + - The name of the VideoDescription used as the source for this output. + type: str + timecode_config: + description: + - Contains settings used to acquire and adjust timecode information from inputs. + type: dict + suboptions: + source: + description: + - Identifies the source for the timecode that will be associated with the events outputs. -EMBEDDED (embedded) Initialize the output timecode with timecode from the the source. If no embedded timecode is detected in the source, the system falls back to using "Start at 0" (zerobased). -System Clock (systemclock) Use the UTC time. -Start at 0 (zerobased) The time of the first frame of the event will be 00:00:00:00. + type: str + choices: ['EMBEDDED', 'SYSTEMCLOCK', 'ZEROBASED'] + sync_threshold: + description: + - Threshold in frames beyond which output timecode is resynchronized to the input timecode. Discrepancies below this threshold are permitted to avoid unnecessary discontinuities in the output timecode. No timecode sync when this is not specified. + type: int + video_descriptions: + description: + - Video settings for this stream. + type: list + elements: dict + suboptions: + codec_settings: + description: + - Video codec settings. + type: dict + suboptions: + frame_capture_settings: + description: + - Frame Capture Settings + type: dict + suboptions: + capture_interval: + description: + - The frequency at which to capture frames for inclusion in the output. May be specified in either seconds or milliseconds, as specified by captureIntervalUnits. + type: int + capture_interval_units: + description: + - Unit for the frame capture interval. + type: str + choices: ['MILLISECONDS', 'SECONDS'] + timecode_burnin_settings: + description: + - TODO + type: dict + suboptions: + font_size: + description: + - TODO + type: str + choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] + position: + description: + - TODO + type: str + choices: + - 'BOTTOM_CENTER' + - 'BOTTOM_LEFT' + - 'BOTTOM_RIGHT' + - 'MIDDLE_CENTER' + - 'MIDDLE_LEFT' + - 'MIDDLE_RIGHT' + - 'TOP_CENTER' + - 'TOP_LEFT' + - 'TOP_RIGHT' + prefix: + description: + - TODO + type: str + h264_settings: + description: + - TODO + type: dict + suboptions: + adaptive_quantization: + description: + - TODO + type: str + choices: ['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF'] + afd_signaling: + description: + - TODO + type: str + choices: ['AUTO', 'FIXED', 'NONE'] + bitrate: + description: + - TODO + type: int + buf_fill_pct: + description: + - TODO + type: int + buf_size: + description: + - TODO + type: int + color_metadata: + description: + - TODO + type: str + choices: ['IGNORE', 'INSERT'] + color_space_settings: + description: + - TODO + type: dict + suboptions: + color_space_passthrough_settings: + description: + - TODO + type: dict + rec601_settings: + description: + - TODO + type: dict + rec709_settings: + description: + - TODO + type: dict + entropy_encoding: + description: + - TODO + type: str + choices: ['CABAC', 'CAVLC'] + filter_settings: + description: + - TODO + type: dict + suboptions: + temporal_filter_settings: + description: + - TODO + type: dict + suboptions: + post_filter_sharpening: + description: + - TODO + type: str + choices: ['AUTO', 'DISABLED', 'ENABLED'] + strength: + description: + - TODO + type: str + choices: + - 'AUTO' + - 'STRENGTH_1' + - 'STRENGTH_2' + - 'STRENGTH_3' + - 'STRENGTH_4' + - 'STRENGTH_5' + - 'STRENGTH_6' + - 'STRENGTH_7' + - 'STRENGTH_8' + - 'STRENGTH_9' + - 'STRENGTH_10' + - 'STRENGTH_11' + - 'STRENGTH_12' + - 'STRENGTH_13' + - 'STRENGTH_14' + - 'STRENGTH_15' + - 'STRENGTH_16' + bandwidth_reduction_filter_settings: + description: + - TODO + type: dict + suboptions: + post_filter_sharpening: + description: + - TODO + type: str + choices: ['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3'] + strength: + description: + - TODO + type: str + choices: ['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4'] + fixed_afd: + description: + - TODO + type: str + choices: + - 'AFD_0000' + - 'AFD_0010' + - 'AFD_0011' + - 'AFD_0100' + - 'AFD_1000' + - 'AFD_1001' + - 'AFD_1010' + - 'AFD_1011' + - 'AFD_1101' + - 'AFD_1110' + - 'AFD_1111' + flicker_aq: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + force_field_pictures: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + framerate_control: + description: + - TODO + type: str + choices: ['INITIALIZE_FROM_SOURCE', 'SPECIFIED'] + framerate_denominator: + description: + - TODO + type: int + framerate_numerator: + description: + - TODO + type: int + gop_b_reference: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + gop_closed_cadence: + description: + - TODO + type: int + gop_num_b_frames: + description: + - TODO + type: int + gop_size: + description: + - TODO + type: float + + gop_size_units: + description: + - TODO + type: str + choices: ['FRAMES', 'SECONDS'] + level: + description: + - TODO + type: str + choices: + - 'H264_LEVEL_1' + - 'H264_LEVEL_1_1' + - 'H264_LEVEL_1_2' + - 'H264_LEVEL_1_3' + - 'H264_LEVEL_2' + - 'H264_LEVEL_2_1' + - 'H264_LEVEL_2_2' + - 'H264_LEVEL_3' + - 'H264_LEVEL_3_1' + - 'H264_LEVEL_3_2' + - 'H264_LEVEL_4' + - 'H264_LEVEL_4_1' + - 'H264_LEVEL_4_2' + - 'H264_LEVEL_5' + - 'H264_LEVEL_5_1' + - 'H264_LEVEL_5_2' + - 'H264_LEVEL_AUTO' + look_ahead_rate_control: + description: + - TODO + type: str + choices: ['HIGH', 'LOW', 'MEDIUM'] + max_bitrate: + description: + - TODO + type: int + min_i_interval: + description: + - TODO + type: int + num_ref_frames: + description: + - TODO + type: int + par_control: + description: + - TODO + type: str + choices: ['INITIALIZE_FROM_SOURCE', 'SPECIFIED'] + par_denominator: + description: + - TODO + type: int + par_numerator: + description: + - TODO + type: int + profile: + description: + - TODO + type: str + choices: ['BASELINE', 'HIGH', 'HIGH_10BIT', 'HIGH_422', 'HIGH_422_10BIT', 'MAIN'] + quality_level: + description: + - TODO + type: str + choices: ['ENHANCED_QUALITY', 'STANDARD_QUALITY'] + qvbr_quality_level: + description: + - TODO + type: int + rate_control_mode: + description: + - TODO + type: str + choices: ['CBR', 'MULTIPLEX', 'QVBR', 'VBR'] + scan_type: + description: + - TODO + type: str + choices: ['INTERLACED', 'PROGRESSIVE'] + scene_change_detect: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + slices: + description: + - TODO + type: int + softness: + description: + - TODO + type: int + spatial_aq: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + subgop_length: + description: + - TODO + type: str + choices: ['DYNAMIC', 'FIXED'] + syntax: + description: + - TODO + type: str + choices: ['DEFAULT', 'RP2027'] + temporal_aq: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + timecode_insertion: + description: + - TODO + type: str + choices: ['DISABLED', 'PIC_TIMING_SEI'] + timecode_burnin_settings: + description: + - TODO + type: dict + suboptions: + font_size: + description: + - TODO + type: str + choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] + position: + description: + - TODO + type: str + choices: + - 'BOTTOM_CENTER' + - 'BOTTOM_LEFT' + - 'BOTTOM_RIGHT' + - 'MIDDLE_CENTER' + - 'MIDDLE_LEFT' + - 'MIDDLE_RIGHT' + - 'TOP_CENTER' + - 'TOP_LEFT' + - 'TOP_RIGHT' + prefix: + description: + - TODO + type: str + min_qp: + description: + - TODO + type: int + h265_settings: + description: + - TODO + type: dict + suboptions: + adaptive_quantization: + description: + - TODO + type: str + choices: ['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF'] + afd_signaling: + description: + - TODO + type: str + choices: ['AUTO', 'FIXED', 'NONE'] + alternative_transfer_function: + description: + - TODO + type: str + choices: ['INSERT', 'OMIT'] + bitrate: + description: + - TODO + type: int + buf_size: + description: + - TODO + type: int + color_metadata: + description: + - TODO + type: str + choices: ['IGNORE', 'INSERT'] + color_space_settings: + description: + - TODO + type: dict + suboptions: + color_space_passthrough_settings: + description: + - TODO + type: dict + dolby_vision81_settings: + description: + - TODO + type: dict + hdr10_settings: + description: + - TODO + type: dict + suboptions: + max_cll: + description: + - TODO + type: int + max_fall: + description: + - TODO + type: int + rec601_settings: + description: + - TODO + type: dict + rec709_settings: + description: + - TODO + type: dict + filter_settings: + description: + - TODO + type: dict + suboptions: + temporal_filter_settings: + description: + - TODO + type: dict + suboptions: + post_filter_sharpening: + description: + - TODO + type: str + choices: ['AUTO', 'DISABLED', 'ENABLED'] + strength: + description: + - TODO + type: str + choices: + - 'AUTO' + - 'STRENGTH_1' + - 'STRENGTH_2' + - 'STRENGTH_3' + - 'STRENGTH_4' + - 'STRENGTH_5' + - 'STRENGTH_6' + - 'STRENGTH_7' + - 'STRENGTH_8' + - 'STRENGTH_9' + - 'STRENGTH_10' + - 'STRENGTH_11' + - 'STRENGTH_12' + - 'STRENGTH_13' + - 'STRENGTH_14' + - 'STRENGTH_15' + - 'STRENGTH_16' + bandwidth_reduction_filter_settings: + description: + - TODO + type: dict + suboptions: + post_filter_sharpening: + description: + - TODO + type: str + choices: ['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3'] + strength: + description: + - TODO + type: str + choices: ['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4'] + fixed_afd: + description: + - TODO + type: str + choices: + - 'AFD_0000' + - 'AFD_0010' + - 'AFD_0011' + - 'AFD_0100' + - 'AFD_1000' + - 'AFD_1001' + - 'AFD_1010' + - 'AFD_1011' + - 'AFD_1101' + - 'AFD_1110' + - 'AFD_1111' + flicker_aq: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + framerate_denominator: + description: + - TODO + type: int + framerate_numerator: + description: + - TODO + type: int + gop_closed_cadence: + description: + - TODO + type: int + gop_size: + description: + - TODO + type: float + + gop_size_units: + description: + - TODO + type: str + choices: ['FRAMES', 'SECONDS'] + level: + description: + - TODO + type: str + choices: + - 'H265_LEVEL_1' + - 'H265_LEVEL_2' + - 'H265_LEVEL_2_1' + - 'H265_LEVEL_3' + - 'H265_LEVEL_3_1' + - 'H265_LEVEL_4' + - 'H265_LEVEL_4_1' + - 'H265_LEVEL_5' + - 'H265_LEVEL_5_1' + - 'H265_LEVEL_5_2' + - 'H265_LEVEL_6' + - 'H265_LEVEL_6_1' + - 'H265_LEVEL_6_2' + - 'H265_LEVEL_AUTO' + look_ahead_rate_control: + description: + - TODO + type: str + choices: ['HIGH', 'LOW', 'MEDIUM'] + max_bitrate: + description: + - TODO + type: int + min_i_interval: + description: + - TODO + type: int + par_denominator: + description: + - TODO + type: int + par_numerator: + description: + - TODO + type: int + profile: + description: + - TODO + type: str + choices: ['MAIN', 'MAIN_10BIT'] + qvbr_quality_level: + description: + - TODO + type: int + rate_control_mode: + description: + - TODO + type: str + choices: ['CBR', 'MULTIPLEX', 'QVBR'] + scan_type: + description: + - TODO + type: str + choices: ['INTERLACED', 'PROGRESSIVE'] + scene_change_detect: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + slices: + description: + - TODO + type: int + tier: + description: + - TODO + type: str + choices: ['HIGH', 'MAIN'] + timecode_insertion: + description: + - TODO + type: str + choices: ['DISABLED', 'PIC_TIMING_SEI'] + timecode_burnin_settings: + description: + - TODO + type: dict + suboptions: + font_size: + description: + - TODO + type: str + choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] + position: + description: + - TODO + type: str + choices: + - 'BOTTOM_CENTER' + - 'BOTTOM_LEFT' + - 'BOTTOM_RIGHT' + - 'MIDDLE_CENTER' + - 'MIDDLE_LEFT' + - 'MIDDLE_RIGHT' + - 'TOP_CENTER' + - 'TOP_LEFT' + - 'TOP_RIGHT' + prefix: + description: + - TODO + type: str + mv_over_picture_boundaries: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + mv_temporal_predictor: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + tile_height: + description: + - TODO + type: int + tile_padding: + description: + - TODO + type: str + choices: ['NONE', 'PADDED'] + tile_width: + description: + - TODO + type: int + treeblock_size: + description: + - TODO + type: str + choices: ['AUTO', 'TREE_SIZE_32X32'] + min_qp: + description: + - TODO + type: int + deblocking: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + mpeg2_settings: + description: + - TODO + type: dict + suboptions: + adaptive_quantization: + description: + - TODO + type: str + choices: ['AUTO', 'HIGH', 'LOW', 'MEDIUM', 'OFF'] + afd_signaling: + description: + - TODO + type: str + choices: ['AUTO', 'FIXED', 'NONE'] + color_metadata: + description: + - TODO + type: str + choices: ['IGNORE', 'INSERT'] + color_space: + description: + - TODO + type: str + choices: ['AUTO', 'PASSTHROUGH'] + display_aspect_ratio: + description: + - TODO + type: str + choices: ['DISPLAYRATIO16X9', 'DISPLAYRATIO4X3'] + filter_settings: + description: + - TODO + type: dict + suboptions: + temporal_filter_settings: + description: + - TODO + type: dict + suboptions: + post_filter_sharpening: + description: + - TODO + type: str + choices: ['AUTO', 'DISABLED', 'ENABLED'] + strength: + description: + - TODO + type: str + choices: + - 'AUTO' + - 'STRENGTH_1' + - 'STRENGTH_2' + - 'STRENGTH_3' + - 'STRENGTH_4' + - 'STRENGTH_5' + - 'STRENGTH_6' + - 'STRENGTH_7' + - 'STRENGTH_8' + - 'STRENGTH_9' + - 'STRENGTH_10' + - 'STRENGTH_11' + - 'STRENGTH_12' + - 'STRENGTH_13' + - 'STRENGTH_14' + - 'STRENGTH_15' + - 'STRENGTH_16' + fixed_afd: + description: + - TODO + type: str + choices: + - 'AFD_0000' + - 'AFD_0010' + - 'AFD_0011' + - 'AFD_0100' + - 'AFD_1000' + - 'AFD_1001' + - 'AFD_1010' + - 'AFD_1011' + - 'AFD_1101' + - 'AFD_1110' + - 'AFD_1111' + framerate_denominator: + description: + - TODO + type: int + framerate_numerator: + description: + - TODO + type: int + gop_closed_cadence: + description: + - TODO + type: int + gop_num_b_frames: + description: + - TODO + type: int + gop_size: + description: + - TODO + type: float + + gop_size_units: + description: + - TODO + type: str + choices: ['FRAMES', 'SECONDS'] + scan_type: + description: + - TODO + type: str + choices: ['INTERLACED', 'PROGRESSIVE'] + subgop_length: + description: + - TODO + type: str + choices: ['DYNAMIC', 'FIXED'] + timecode_insertion: + description: + - TODO + type: str + choices: ['DISABLED', 'GOP_TIMECODE'] + timecode_burnin_settings: + description: + - TODO + type: dict + suboptions: + font_size: + description: + - TODO + type: str + choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] + position: + description: + - TODO + type: str + choices: + - 'BOTTOM_CENTER' + - 'BOTTOM_LEFT' + - 'BOTTOM_RIGHT' + - 'MIDDLE_CENTER' + - 'MIDDLE_LEFT' + - 'MIDDLE_RIGHT' + - 'TOP_CENTER' + - 'TOP_LEFT' + - 'TOP_RIGHT' + prefix: + description: + - TODO + type: str + av1_settings: + description: + - TODO + type: dict + suboptions: + afd_signaling: + description: + - TODO + type: str + choices: ['AUTO', 'FIXED', 'NONE'] + buf_size: + description: + - TODO + type: int + color_space_settings: + description: + - TODO + type: dict + suboptions: + color_space_passthrough_settings: + description: + - TODO + type: dict + hdr10_settings: + description: + - TODO + type: dict + suboptions: + + max_cll: + description: + - TODO + type: int + + max_fall: + description: + - TODO + type: int + rec601_settings: + description: + - TODO + type: dict + rec709_settings: + description: + - TODO + type: dict + fixed_afd: + description: + - TODO + type: str + choices: + - 'AFD_0000' + - 'AFD_0010' + - 'AFD_0011' + - 'AFD_0100' + - 'AFD_1000' + - 'AFD_1001' + - 'AFD_1010' + - 'AFD_1011' + - 'AFD_1101' + - 'AFD_1110' + - 'AFD_1111' + framerate_denominator: + description: + - TODO + type: int + framerate_numerator: + description: + - TODO + type: int + gop_size: + description: + - TODO + type: float + + gop_size_units: + description: + - TODO + type: str + choices: ['FRAMES', 'SECONDS'] + level: + description: + - TODO + type: str + choices: + - 'AV1_LEVEL_2' + - 'AV1_LEVEL_2_1' + - 'AV1_LEVEL_3' + - 'AV1_LEVEL_3_1' + - 'AV1_LEVEL_4' + - 'AV1_LEVEL_4_1' + - 'AV1_LEVEL_5' + - 'AV1_LEVEL_5_1' + - 'AV1_LEVEL_5_2' + - 'AV1_LEVEL_5_3' + - 'AV1_LEVEL_6' + - 'AV1_LEVEL_6_1' + - 'AV1_LEVEL_6_2' + - 'AV1_LEVEL_6_3' + - 'AV1_LEVEL_AUTO' + look_ahead_rate_control: + description: + - TODO + type: str + choices: ['HIGH', 'LOW', 'MEDIUM'] + max_bitrate: + description: + - TODO + type: int + min_i_interval: + description: + - TODO + type: int + par_denominator: + description: + - TODO + type: int + par_numerator: + description: + - TODO + type: int + qvbr_quality_level: + description: + - TODO + type: int + scene_change_detect: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + timecode_burnin_settings: + description: + - TODO + type: dict + suboptions: + font_size: + description: + - TODO + type: str + choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] + position: + description: + - TODO + type: str + choices: + - 'BOTTOM_CENTER' + - 'BOTTOM_LEFT' + - 'BOTTOM_RIGHT' + - 'MIDDLE_CENTER' + - 'MIDDLE_LEFT' + - 'MIDDLE_RIGHT' + - 'TOP_CENTER' + - 'TOP_LEFT' + - 'TOP_RIGHT' + prefix: + description: + - TODO + type: str + height: + description: + - Output video height, in pixels. Must be an even number. For most codecs, you can leave this field and width blank in order to use the height and width (resolution) from the source. Note, however, that leaving blank is not recommended. For the Frame Capture codec, height and width are required. + type: int + name: + description: + - TODO + type: str + respond_to_afd: + description: + - Indicates how MediaLive will respond to the AFD values that might be in the input video. If you do not know what AFD signaling is, or if your downstream system has not given you guidance, choose PASSTHROUGH. RESPOND - MediaLive clips the input video using a formula that uses the AFD values (configured in afdSignaling ), the input display aspect ratio, and the output display aspect ratio. MediaLive also includes the AFD values in the output, unless the codec for this encode is FRAME_CAPTURE. PASSTHROUGH - MediaLive ignores the AFD values and does not clip the video. But MediaLive does include the values in the output. NONE - MediaLive does not clip the input video and does not include the AFD values in the output + type: str + choices: ['NONE', 'PASSTHROUGH', 'RESPOND'] + scaling_behavior: + description: + - STRETCH_TO_OUTPUT configures the output position to stretch the video to the specified output resolution (height and width). This option will override any position value. DEFAULT may insert black boxes (pillar boxes or letter boxes) around the video to provide the specified output resolution. + type: str + choices: ['DEFAULT', 'STRETCH_TO_OUTPUT'] + sharpness: + description: + - Changes the strength of the anti-alias filter used for scaling. 0 is the softest setting, 100 is the sharpest. A setting of 50 is recommended for most content. + type: int + width: + description: + - Output video width, in pixels. Must be an even number. For most codecs, you can leave this field and height blank in order to use the height and width (resolution) from the source. Note, however, that leaving blank is not recommended. For the Frame Capture codec, height and width are required. + type: int + thumbnail_configuration: + description: + - Thumbnail configuration settings. + type: dict + suboptions: + state: + description: + - Enables the thumbnail feature. The feature generates thumbnails of the incoming video in each pipeline in the channel. AUTO turns the feature on, DISABLE turns the feature off. + type: str + choices: ['AUTO', 'DISABLED'] + color_correction_settings: + description: + - Color Correction Settings + type: dict + suboptions: + global_color_corrections: + description: + - An array of colorCorrections that applies when you are using 3D LUT files to perform color conversion on video. Each colorCorrection contains one 3D LUT file (that defines the color mapping for converting an input color space to an output color space), and the input/output combination that this 3D LUT file applies to. MediaLive reads the color space in the input metadata, determines the color space that you have specified for the output, and finds and uses the LUT file that applies to this combination. + type: list + elements: dict + suboptions: + input_color_space: + description: + - The color space of the input. + type: str + choices: ['HDR10', 'HLG_2020', 'REC_601', 'REC_709'] + output_color_space: + description: + - The color space of the output. + type: str + choices: ['HDR10', 'HLG_2020', 'REC_601', 'REC_709'] + uri: + description: + - The URI of the 3D LUT file. The protocol must be 's3:' or 's3ssl:':. + type: str + input_attachments: + description: + - TODO + type: list + elements: dict + suboptions: + automatic_input_failover_settings: + description: + - TODO + type: dict + suboptions: + error_clear_time_msec: + description: + - TODO + type: int + failover_conditions: + description: + - TODO + type: list + elements: dict + suboptions: + failover_condition_settings: + description: + - TODO + type: dict + suboptions: + audio_silence_settings: + description: + - TODO + type: dict + suboptions: + audio_selector_name: + description: + - TODO + type: str + audio_silence_threshold_msec: + description: + - TODO + type: int + input_loss_settings: + description: + - TODO + type: dict + suboptions: + input_loss_threshold_msec: + description: + - TODO + type: int + video_black_settings: + description: + - TODO + type: dict + suboptions: + black_detect_threshold: + description: + - TODO + type: float + video_black_threshold_msec: + description: + - TODO + type: int + input_preference: + description: + - TODO + type: str + choices: ['EQUAL_INPUT_PREFERENCE', 'PRIMARY_INPUT_PREFERRED'] + secondary_input_id: + description: + - TODO + type: str + input_attachment_name: + description: + - TODO + type: str + input_id: + description: + - TODO + type: str + input_settings: + description: + - TODO + type: dict + suboptions: + audio_selectors: + description: + - TODO + type: list + elements: dict + suboptions: + name: + description: + - TODO + type: str + selector_settings: + description: + - TODO + type: dict + suboptions: + audio_hls_rendition_selection: + description: + - TODO + type: dict + suboptions: + group_id: + description: + - TODO + type: str + name: + description: + - TODO + type: str + audio_language_selection: + description: + - TODO + type: dict + suboptions: + language_code: + description: + - TODO + type: str + language_selection_policy: + description: + - TODO + type: str + choices: ['LOOSE', 'STRICT'] + audio_pid_selection: + description: + - TODO + type: dict + suboptions: + pid: + description: + - TODO + type: int + audio_track_selection: + description: + - TODO + type: dict + suboptions: + tracks: + description: + - TODO + type: list + elements: dict + suboptions: + track: + description: + - TODO + type: int + dolby_e_decode: + description: + - TODO + type: dict + suboptions: + program_selection: + description: + - TODO + type: str + choices: ['ALL_CHANNELS', 'PROGRAM_1', 'PROGRAM_2', 'PROGRAM_3', 'PROGRAM_4', 'PROGRAM_5', 'PROGRAM_6', 'PROGRAM_7', 'PROGRAM_8'] + caption_selectors: + description: + - TODO + type: list + elements: dict + suboptions: + language_code: + description: + - TODO + type: str + name: + description: + - TODO + type: str + selector_settings: + description: + - TODO + type: dict + suboptions: + ancillary_source_settings: + description: + - TODO + type: dict + suboptions: + source_ancillary_channel_number: + description: + - TODO + type: int + arib_source_settings: + description: + - TODO + type: dict + dvb_sub_source_settings: + description: + - TODO + type: dict + suboptions: + ocr_language: + description: + - TODO + type: str + choices: ['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA'] + pid: + description: + - TODO + type: int + embedded_source_settings: + description: + - TODO + type: dict + suboptions: + convert608_to708: + description: + - TODO + type: str + choices: ['DISABLED', 'UPCONVERT'] + scte20_detection: + description: + - TODO + type: str + choices: ['AUTO', 'OFF'] + source608_channel_number: + description: + - TODO + type: int + source608_track_number: + description: + - TODO + type: int + scte20_source_settings: + description: + - TODO + type: dict + suboptions: + convert608_to708: + description: + - TODO + type: str + choices: ['DISABLED', 'UPCONVERT'] + source608_channel_number: + description: + - TODO + type: int + scte27_source_settings: + description: + - TODO + type: dict + suboptions: + ocr_language: + description: + - TODO + type: str + choices: ['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA'] + pid: + description: + - TODO + type: int + teletext_source_settings: + description: + - TODO + type: dict + suboptions: + output_rectangle: + description: + - TODO + type: dict + suboptions: + height: + description: + - TODO + type: float + left_offset: + description: + - TODO + type: float + top_offset: + description: + - TODO + type: float + width: + description: + - TODO + type: float + page_number: + description: + - TODO + type: str + deblock_filter: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + denoise_filter: + description: + - TODO + type: str + choices: ['DISABLED', 'ENABLED'] + filter_strength: + description: + - TODO + type: int + input_filter: + description: + - TODO + type: str + choices: ['AUTO', 'DISABLED', 'FORCED'] + network_input_settings: + description: + - TODO + type: dict + suboptions: + hls_input_settings: + description: + - TODO + type: dict + suboptions: + bandwidth: + description: + - TODO + type: int + buffer_segments: + description: + - TODO + type: int + retries: + description: + - TODO + type: int + retry_interval: + description: + - TODO + type: int + scte35_source: + description: + - TODO + type: str + choices: ['MANIFEST', 'SEGMENTS'] + server_validation: + description: + - TODO + type: str + choices: ['CHECK_CRYPTOGRAPHY_AND_VALIDATE_NAME', 'CHECK_CRYPTOGRAPHY_ONLY'] + multicast_input_settings: + description: + - TODO + type: dict + suboptions: + source_ip_address: + description: + - TODO + type: str + scte35_pid: + description: + - TODO + type: int + smpte2038_data_preference: + description: + - TODO + type: str + choices: ['IGNORE', 'PREFER'] + source_end_behavior: + description: + - TODO + type: str + choices: ['CONTINUE', 'LOOP'] + video_selector: + description: + - TODO + type: dict + suboptions: + color_space: + description: + - TODO + type: str + choices: ['FOLLOW', 'HDR10', 'HLG_2020', 'REC_601', 'REC_709'] + color_space_settings: + description: + - TODO + type: dict + suboptions: + hdr10_settings: + description: + - TODO + type: dict + suboptions: + max_cll: + description: + - TODO + type: int + max_fall: + description: + - TODO + type: int + color_space_usage: + description: + - TODO + type: str + choices: ['FALLBACK', 'FORCE'] + selector_settings: + description: + - TODO + type: dict + suboptions: + video_selector_pid: + description: + - TODO + type: dict + suboptions: + pid: + description: + - TODO + type: int + video_selector_program_id: + description: + - TODO + type: dict + suboptions: + program_id: + description: + - TODO + type: int + logical_interface_names: + description: + - A list of logical interface names used for network isolation in MediaLive Anywhere deployments. + type: list + elements: str + input_specification: + description: + - TODO + type: dict + suboptions: + codec: + description: + - TODO + type: str + choices: ['MPEG2', 'AVC', 'HEVC'] + maximum_bitrate: + description: + - TODO + type: str + choices: ['MAX_10_MBPS', 'MAX_20_MBPS', 'MAX_50_MBPS'] + resolution: + description: + - TODO + type: str + choices: ['SD', 'HD', 'UHD'] + log_level: + description: + - TODO + type: str + choices: ['ERROR', 'WARNING', 'INFO', 'DEBUG', 'DISABLED'] + maintenance: + description: + - TODO + type: dict + suboptions: + maintenance_day: + description: + - TODO + type: str + choices: ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'] + maintenance_start_time: + description: + - TODO + type: str + name: + description: + - TODO + type: str + request_id: + description: + - TODO + type: str + reserved: + description: + - TODO + type: str + role_arn: + description: + - TODO + type: str + tags: + description: + - TODO + type: dict + vpc: + description: + - TODO + type: dict + suboptions: + public_address_allocation_ids: + description: + - TODO + type: list + elements: str + security_group_ids: + description: + - TODO + type: list + elements: str + subnet_ids: + description: + - TODO + type: list + elements: str + anywhere_settings: + description: + - TODO + type: dict + suboptions: + channel_placement_group_id: + description: + - TODO + type: str + cluster_id: + description: + - TODO + type: str + channel_engine_version: + description: + - TODO + type: dict + suboptions: + version: + description: + - TODO + type: str + dry_run: + description: + - TODO + type: bool + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 + - amazon.aws.tags +""" + +EXAMPLES = r""" +""" + +RETURN = r""" +""" + +import uuid +from typing import Dict + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveChannelManager: + '''Manage AWS MediaLive Anywhere Channels''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveChannelManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self._channel = {} + self.changed = False + + @property + def channel(self): + return self._channel + + @channel.setter + def channel(self, channel: Dict): + channel = camel_dict_to_snake_dict(channel) + if channel.get('response_metadata'): + del channel['response_metadata'] + if channel.get('id'): + channel['channel_id'] = channel.get('id') + del channel['id'] + self._channel = channel + + + def do_create_channel(self, params): + """ + Create a new MediaLive Channel + + Args: + params: Parameters for Channel creation + """ + allowed_params = [ + 'channel_id', + 'name', + 'cdi_input_specification', + 'channel_class', + 'destinations', + 'encoder_settings', + 'input_attachments', + 'input_specification', + 'log_level', + 'maintenance', + 'reserved', + 'role_arn', + 'vpc', + 'anywhere_settings', + 'channel_engine_version', + 'dry_run', + 'tags', + 'request_id' + ] + required_params = ['name'] + + for param in required_params: + if not params.get(param): + raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new Channel') + + create_params = { k: v for k, v in params.items() if k in allowed_params and v } + create_params = self.clean_up(create_params) + + tags = create_params.get('tags') # To preserve case in tag keys + create_params = snake_dict_to_camel_dict(create_params, capitalize_first=True) + + if tags and create_params: + create_params['Tags'] = tags + + try: + self.channel = self.client.create_channel(**create_params)['Channel'] # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive Channel', + exception=e + ) + + + def do_update_channel(self, params): + """ + Update a new MediaLive Channel + + Args: + params: Parameters for Channel update + """ + if not params.get('channel_id'): + raise MedialiveAnsibleAWSError(message='The channel_id parameter is required during channel update.') + + tags = params.get('tags') + purge_tags = params.get('purge_tags') + del params['tags'] + del params['purge_tags'] + + allowed_params = [ + 'cdi_input_specification', + 'channel_id', + 'destinations', + 'encoder_settings', + 'input_attachments', + 'input_specification', + 'log_level', + 'maintenance', + 'name', + 'role_arn', + 'channel_engine_version', + 'dry_run', + 'anywhere_settings' + ] + + # current_params = { k: v for k, v in self.channel.items() if k in allowed_params } + update_params = { k: v for k, v in params.items() if k in allowed_params and v } + update_params = self.clean_up(update_params) + + current_params = {} + for k, v in self.channel.items(): + if k in allowed_params and k in update_params: + current_params[k] = v + + # Short circuit + if not recursive_diff(current_params, update_params): + return + + try: + if recursive_diff(current_params, update_params): + update_params = snake_dict_to_camel_dict(update_params, capitalize_first=True) + self.channel = self.client.update_channel(**update_params)['Channel'] # type: ignore + self.changed = True + if tags and self._update_tags(tags, purge_tags): + self.channel = self.get_channel_by_id(self.channel['channel_id']) # type: ignore + self.changed = True + + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to update Medialive Channel', + exception=e + ) + + def get_channel_by_name(self, name: str): + """ + Find a Channel by name + + Args: + name: The name of the Channel to find + """ + + try: + paginator = self.client.get_paginator('list_channels') # type: ignore + found = [] + for page in paginator.paginate(): + for channel in page.get('Channels', []): + if channel.get('Name') == name: + found.append(channel.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Channels under the same name') + elif len(found) == 1: + self.get_channel_by_id(found[0]) + + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Channel', + exception=e + ) + + def get_channel_by_id(self, channel_id: str): + """ + Get a Channel by ID + + Args: + channel_id: The ID of the Channel to retrieve + """ + try: + self.channel = self.client.describe_channel(ChannelId=channel_id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.channel = {} + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Channel', + exception=e + ) + + def delete_channel(self, channel_id: str): + """ + Delete a MediaLive Channel + + Args: + channel_id: ID of the Channel to delete + """ + try: + self.client.delete_channel(ChannelId=channel_id) # type: ignore + self.channel = {} + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.channel = {} + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to delete Medialive Channel', + exception=e + ) + + def wait_for(self, want: str, channel_id: str, wait_timeout: int = 60): + """ + Invoke one of the custom waiters and wait + + Args: + want: one of 'channel_created'|'channel_deleted'|'channel_running'|'channel_stopped' + channel_id: the ID of the Channel + wait_timeout: the maximum amount of time to wait in seconds (default: 60) + """ + + try: + waiter = self.client.get_waiter(want) # type: ignore + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + ChannelId=channel_id, + WaiterConfig=config + ) + except WaiterError as e: # type: ignore + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for Channel {channel_id}', + exception=e + ) + + def _update_tags(self, tags: dict, purge: bool) -> bool: + """ + Takes care of updating Channel tags + + Args: + tags: a dict of tags supplied by the user + purge: whether or not to delete existing tags that aren't in the tags dict + Returns: + True if tags were updated, otherwise False + """ + + # Short-circuit + if self.module.check_mode: + return False + + to_add, to_delete = compare_aws_tags(self.channel['tags'], tags, purge) + + if not any((to_add, to_delete)): + return False + + try: + if to_add: + self.client.create_tags(ResourceArn=self.channel['arn'], Tags=to_add) # type: ignore + if to_delete: + self.client.delete_tags(ResourceArn=self.channel['arn'], TagKeys=to_delete) # type: ignore + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to update MediaLive Channel resource Tags', + exception=e + ) + + return True + + @staticmethod + def clean_up(data): + if isinstance(data, dict): + return { + key: MediaLiveChannelManager.clean_up(value) + for key, value in data.items() + if value is not None and MediaLiveChannelManager.clean_up(value) is not None + } + elif isinstance(data, list): + cleaned = [ + MediaLiveChannelManager.clean_up(item) + for item in data + if item is not None and MediaLiveChannelManager.clean_up(item) is not None + ] + # Return None if the list is empty, otherwise return the cleaned list + return cleaned if cleaned else None + else: + return data + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + name=dict(type='str', aliases=['channel_name']), + id=dict(type='str', aliases=['channel_id']), + cdi_input_specification=dict( + type='dict', + options=dict( + resolution=dict( + type='str', + choices=['SD', 'HD', 'FHD', 'UHD'], + ) + ), + ), + channel_class=dict( + type='str', + choices=['STANDARD', 'SINGLE_PIPELINE'], + ), + destinations=dict( + type='list', + elements='dict', + options=dict( + id=dict(type='str'), + media_package_settings=dict( + type='list', + elements='dict', + options=dict( + channel_id=dict(type='str'), + channel_group=dict(type='str'), + channel_name=dict(type='str') + ) + ), + multiplex_settings=dict( + type='dict', + options=dict( + multiplex_id=dict(type='str'), + program_name=dict(type='str') + ) + ), + settings=dict( + type='list', + elements='dict', + options=dict( + password_param=dict(type='str'), + stream_name=dict(type='str'), + url=dict(type='str'), + username=dict(type='str') + ) + ), + srt_settings=dict( + type='list', + elements='dict', + options=dict( + encryption_passphrase_secret_arn=dict(type='str'), + stream_id=dict(type='str'), + url=dict(type='str') + ) + ), + logical_interface_names=dict(type='list', elements='str') + ) + ), + encoder_settings=dict( + type='dict', + options=dict( + audio_descriptions=dict( + type='list', + elements='dict', + options=dict( + audio_normalization_settings=dict( + type='dict', + options=dict( + algorithm=dict(type='str', choices=['ITU_1770_1', 'ITU_1770_2']), + algorithm_control=dict(type='str', choices=['CORRECT_AUDIO']), + target_lkfs=dict(type='float') + ) + ), + audio_selector_name=dict(type='str'), + audio_type=dict( + type='str', + choices=['CLEAN_EFFECTS', 'HEARING_IMPAIRED', 'UNDEFINED', 'VISUAL_IMPAIRED_COMMENTARY'], + ), + audio_type_control=dict(type='str', choices=['FOLLOW_INPUT', 'USE_CONFIGURED']), + audio_watermarking_settings=dict( + type='dict', + options=dict( + nielsen_watermarks_settings=dict( + type='dict', + options=dict( + nielsen_cbet_settings=dict( + type='dict', + options=dict( + cbet_check_digit_string=dict(type='str'), + cbet_stepaside=dict(type='str', choices=['DISABLED', 'ENABLED']), + csid=dict(type='str') + ) + ) + ) + ), + nielsen_distribution_type=dict( + type='str', + choices=['FINAL_DISTRIBUTOR', 'PROGRAM_CONTENT']), + nielsen_naes_ii_nw_settings=dict( + type='dict', + options=dict( + check_digit_string=dict(type='str'), + sid=dict(type='float'), + timezone=dict( + type='str', + choices=[ + 'AMERICA_PUERTO_RICO', + 'US_ALASKA', + 'US_ARIZONA', + 'US_CENTRAL', + 'US_EASTERN', + 'US_HAWAII', + 'US_MOUNTAIN', + 'US_PACIFIC', + 'US_SAMOA', + 'UTC', + ] + ) + ) + ) + ) + ), + codec_settings=dict( + type='dict', + options=dict( + aac_settings=dict( + type='dict', + options=dict( + bitrate=dict(type='float'), + coding_mode=dict( + type='str', + choices=[ + 'AD_RECEIVER_MIX', + 'CODING_MODE_1_0', + 'CODING_MODE_1_1', + 'CODING_MODE_2_0', + 'CODING_MODE_5_1', + ] + ), + input_type=dict(type='str', choices=['BROADCASTER_MIXED_AD', 'NORMAL']), + profile=dict(type='str', choices=['HEV1', 'HEV2', 'LC']), + rate_control_mode=dict(type='str', choices=['CBR', 'VBR']), + raw_format=dict(type='str', choices=['LATM_LOAS', 'NONE']), + sample_rate=dict(type='float'), + spec=dict(type='str', choices=['MPEG2', 'MPEG4']), + vbr_quality=dict(type='str', choices=['HIGH', 'LOW', 'MEDIUM_HIGH', 'MEDIUM_LOW']) + ) + ), + ac3_settings=dict( + type='dict', + options=dict( + bitrate=dict(type='float'), + bitstream_mode=dict( + type='str', + choices=[ + 'COMMENTARY', + 'COMPLETE_MAIN', + 'DIALOGUE', + 'EMERGENCY', + 'HEARING_IMPAIRED', + 'MUSIC_AND_EFFECTS', + 'VISUALLY_IMPAIRED', + 'VOICE_OVER', + ] + ), + coding_mode=dict( + type='str', + choices=[ + 'CODING_MODE_1_0', + 'CODING_MODE_1_1', + 'CODING_MODE_2_0', + 'CODING_MODE_3_2_LFE' + ], + ), + dialnorm=dict(type='float'), + drc_profile=dict(type='str', choices=['FILM_STANDARD', 'NONE']), + lfe_filter=dict(type='str', choices=['DISABLED', 'ENABLED']), + metadata_control=dict(type='str', choices=['FOLLOW_INPUT', 'USE_CONFIGURED']), + attenuation_control=dict(type='str', choices=['ATTENUATE_3_DB', 'NONE']) + ) + ), + eac3_atmos_settings=dict( + type='dict', + options=dict( + bitrate=dict(type='float'), + coding_mode=dict(type='str', choices=['CODING_MODE_5_1_4', 'CODING_MODE_7_1_4', 'CODING_MODE_9_1_6']), + dialnorm=dict(type='int'), + drc_line=dict( + type='str', + choices=[ + 'FILM_LIGHT', + 'FILM_STANDARD', + 'MUSIC_LIGHT', + 'MUSIC_STANDARD', + 'NONE', + 'SPEECH', + ] + ), + drc_rf=dict( + type='str', + choices=[ + 'FILM_LIGHT', + 'FILM_STANDARD', + 'MUSIC_LIGHT', + 'MUSIC_STANDARD', + 'NONE', + 'SPEECH', + ] + ), + height_trim=dict(type='float'), + surround_trim=dict(type='float') + ) + ), + eac3_settings=dict( + type='dict', + options=dict( + attenuation_control=dict(type='str', choices=['ATTENUATE_3_DB', 'NONE']), + bitrate=dict(type='float'), + bitstream_mode=dict( + type='str', + choices=[ + 'COMMENTARY', + 'COMPLETE_MAIN', + 'EMERGENCY', + 'HEARING_IMPAIRED', + 'VISUALLY_IMPAIRED', + ] + ), + coding_mode=dict(type='str', choices=['CODING_MODE_1_0', 'CODING_MODE_2_0', 'CODING_MODE_3_2']), + dc_filter=dict(type='str', choices=['DISABLED', 'ENABLED']), + dialnorm=dict(type='float'), + drc_line=dict( + type='str', + choices=['FILM_LIGHT', 'FILM_STANDARD', 'MUSIC_LIGHT', 'MUSIC_STANDARD', 'NONE', 'SPEECH'] + ), + drc_rf=dict( + type='str', + choices=['FILM_LIGHT', 'FILM_STANDARD', 'MUSIC_LIGHT', 'MUSIC_STANDARD', 'NONE', 'SPEECH'] + ), + lfe_control=dict(type='str', choices=['LFE', 'NO_LFE']), + lfe_filter=dict(type='str', choices=['DISABLED', 'ENABLED']), + lo_ro_center_mix_level=dict(type='float'), + lo_ro_surround_mix_level=dict(type='float'), + lt_rt_center_mix_level=dict(type='float'), + lt_rt_surround_mix_level=dict(type='float'), + metadata_control=dict(type='str', choices=['FOLLOW_INPUT', 'USE_CONFIGURED']), + passthrough_control=dict(type='str', choices=['NO_PASSTHROUGH', 'WHEN_POSSIBLE']), + phase_control=dict(type='str', choices=['NO_SHIFT', 'SHIFT_90_DEGREES']), + stereo_downmix=dict(type='str', choices=['DPL2', 'LO_RO', 'LT_RT', 'NOT_INDICATED']), + surround_ex_mode=dict(type='str', choices=['DISABLED', 'ENABLED', 'NOT_INDICATED']), + surround_mode=dict(type='str', choices=['DISABLED', 'ENABLED', 'NOT_INDICATED']) + ) + ), + mp2_settings=dict( + type='dict', + options=dict( + bitrate=dict(type='float'), + coding_mode=dict(type='str', choices=['CODING_MODE_1_0', 'CODING_MODE_2_0']), + sample_rate=dict(type='float') + ) + ), + pass_through_settings=dict(type='dict'), + wav_settings=dict( + type='dict', + options=dict( + bit_depth=dict(type='float'), + coding_mode=dict(type='str', choices=['CODING_MODE_1_0', 'CODING_MODE_2_0', 'CODING_MODE_4_0', 'CODING_MODE_8_0']), + sample_rate=dict(type='float') + ) + ) + ) + ), + language_code=dict(type='str'), + language_code_control=dict(type='str', choices=['FOLLOW_INPUT', 'USE_CONFIGURED']), + name=dict(type='str'), + remix_settings=dict( + type='dict', + options=dict( + channel_mappings=dict( + type='list', + elements='dict', + options=dict( + input_channel_levels=dict( + type='list', + elements='dict', + options=dict( + gain=dict(type='int'), + input_channel=dict(type='int') + ) + ), + output_channel=dict(type='int') + ) + ), + channels_in=dict(type='int'), + channels_out=dict(type='int') + ) + ), + stream_name=dict(type='str'), + audio_dash_roles=dict( + type='list', + elements='str', + choices=[ + 'ALTERNATE', + 'COMMENTARY', + 'DESCRIPTION', + 'DUB', + 'EMERGENCY', + 'ENHANCED-AUDIO-INTELLIGIBILITY', + 'KARAOKE', + 'MAIN', + 'SUPPLEMENTARY', + ] + ), + dvb_dash_accessibility=dict( + type='str', + choices=[ + 'DVBDASH_1_VISUALLY_IMPAIRED', + 'DVBDASH_2_HARD_OF_HEARING', + 'DVBDASH_3_SUPPLEMENTAL_COMMENTARY', + 'DVBDASH_4_DIRECTORS_COMMENTARY', + 'DVBDASH_5_EDUCATIONAL_NOTES', + 'DVBDASH_6_MAIN_PROGRAM', + 'DVBDASH_7_CLEAN_FEED', + ] + ) + ) + ), + avail_blanking=dict( + type='dict', + options=dict( + avail_blanking_image=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + state=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + avail_configuration=dict( + type='dict', + options=dict( + avail_settings=dict( + type='dict', + options=dict( + esam=dict( + type='dict', + options=dict( + acquisition_point_id=dict(type='str'), + ad_avail_offset=dict(type='int'), + password_param=dict(type='str'), + pois_endpoint=dict(type='str'), + username=dict(type='str'), + zone_identity=dict(type='str') + ) + ), + scte35_splice_insert=dict( + type='dict', + options=dict( + ad_avail_offset=dict(type='int'), + no_regional_blackout_flag=dict(type='str', choices=['FOLLOW', 'IGNORE']), + web_delivery_allowed_flag=dict(type='str', choices=['FOLLOW', 'IGNORE']) + ) + ), + scte35_time_signal_apos=dict( + type='dict', + options=dict( + ad_avail_offset=dict(type='int'), + no_regional_blackout_flag=dict(type='str', choices=['FOLLOW', 'IGNORE']), + web_delivery_allowed_flag=dict(type='str', choices=['FOLLOW', 'IGNORE']) + ) + ) + ) + ), + scte35_segmentation_scope=dict(type='str', choices=['ALL_OUTPUT_GROUPS', 'SCTE35_ENABLED_OUTPUT_GROUPS']) + ) + ), + blackout_slate=dict( + type='dict', + options=dict( + blackout_slate_image=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + network_end_blackout=dict(type='str', choices=['DISABLED', 'ENABLED']), + network_end_blackout_image=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + network_id=dict(type='str'), + state=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + caption_descriptions=dict( + type='list', + elements='dict', + options=dict( + accessibility=dict(type='str', choices=['DOES_NOT_IMPLEMENT_ACCESSIBILITY_FEATURES', 'IMPLEMENTS_ACCESSIBILITY_FEATURES']), + caption_selector_name=dict(type='str'), + destination_settings=dict( + type='dict', + options=dict( + arib_destination_settings=dict(type='dict'), + burn_in_destination_settings=dict( + type='dict', + options=dict( + alignment=dict(type='str', choices=['CENTERED', 'LEFT', 'SMART']), + background_color=dict(type='str', choices=['BLACK', 'NONE', 'WHITE']), + background_opacity=dict(type='int'), + font=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + font_color=dict(type='str', choices=['BLACK', 'BLUE', 'GREEN', 'RED', 'WHITE', 'YELLOW']), + font_opacity=dict(type='int'), + font_resolution=dict(type='int'), + font_size=dict(type='str'), + outline_color=dict(type='str', choices=['BLACK', 'BLUE', 'GREEN', 'RED', 'WHITE', 'YELLOW']), + outline_size=dict(type='int'), + shadow_color=dict(type='str', choices=['BLACK', 'NONE', 'WHITE']), + shadow_opacity=dict(type='int'), + shadow_x_offset=dict(type='int'), + shadow_y_offset=dict(type='int'), + teletext_grid_control=dict(type='str', choices=['FIXED', 'SCALED']), + x_position=dict(type='int'), + y_position=dict(type='int') + ) + ), + dvb_sub_destination_settings=dict( + type='dict', + options=dict( + alignment=dict(type='str', choices=['CENTERED', 'LEFT', 'SMART']), + background_color=dict(type='str', choices=['BLACK', 'NONE', 'WHITE']), + background_opacity=dict(type='int'), + font=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str'))), + font_color=dict( + type='str', + choices=[ + 'BLACK', + 'BLUE', + 'GREEN', + 'RED', + 'WHITE', + 'YELLOW', + ] + ), + font_opacity=dict(type='int'), + font_resolution=dict(type='int'), + font_size=dict(type='str'), + outline_color=dict( + type='str', + choices=[ + 'BLACK', + 'BLUE', + 'GREEN', + 'RED', + 'WHITE', + 'YELLOW', + ] + ), + outline_size=dict(type='int'), + shadow_color=dict(type='str', choices=['BLACK', 'NONE', 'WHITE']), + shadow_opacity=dict(type='int'), + shadow_x_offset=dict(type='int'), + shadow_y_offset=dict(type='int'), + teletext_grid_control=dict(type='str', choices=['FIXED', 'SCALED']), + x_position=dict(type='int'), + y_position=dict(type='int') + ) + ), + ebu_tt_d_destination_settings=dict( + type='dict', + options=dict( + copyright_holder=dict(type='str'), + fill_line_gap=dict(type='str', choices=['DISABLED', 'ENABLED']), + font_family=dict(type='str'), + style_control=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + default_font_size=dict(type='int'), + default_line_height=dict(type='int') + ) + ), + embedded_destination_settings=dict(type='dict'), + embedded_plus_scte20_destination_settings=dict(type='dict'), + rtmp_caption_info_destination_settings=dict(type='dict'), + scte20_plus_embedded_destination_settings=dict(type='dict'), + scte27_destination_settings=dict(type='dict'), + smpte_tt_destination_settings=dict(type='dict'), + teletext_destination_settings=dict(type='dict'), + ttml_destination_settings=dict( + type='dict', + options=dict( + style_control=dict(type='str', choices=['PASSTHROUGH', 'USE_CONFIGURED']) + ) + ), + webvtt_destination_settings=dict( + type='dict', + options=dict( + style_control=dict(type='str', choices=['NO_STYLE_DATA', 'PASSTHROUGH']) + ) + ) + ) + ), + language_code=dict(type='str'), + name=dict(type='str'), + caption_dash_roles=dict( + type='list', + elements='str', + choices=[ + 'ALTERNATE', + 'CAPTION', + 'COMMENTARY', + 'DESCRIPTION', + 'DUB', + 'EASYREADER', + 'EMERGENCY', + 'FORCED-SUBTITLE', + 'KARAOKE', + 'MAIN', + 'METADATA', + 'SUBTITLE', + 'SUPPLEMENTARY', + ] + ), + dvb_dash_accessibility=dict( + type='str', + choices=[ + 'DVBDASH_1_VISUALLY_IMPAIRED', + 'DVBDASH_2_HARD_OF_HEARING', + 'DVBDASH_3_SUPPLEMENTAL_COMMENTARY', + 'DVBDASH_4_DIRECTORS_COMMENTARY', + 'DVBDASH_5_EDUCATIONAL_NOTES', + 'DVBDASH_6_MAIN_PROGRAM', + 'DVBDASH_7_CLEAN_FEED', + ] + ) + ) + ), + feature_activations=dict( + type='dict', + options=dict( + input_prepare_schedule_actions=dict(type='str', choices=['DISABLED', 'ENABLED']), + output_static_image_overlay_schedule_actions=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + global_configuration=dict( + type='dict', + options=dict( + initial_audio_gain=dict(type='int'), + input_end_action=dict(type='str', choices=['NONE', 'SWITCH_AND_LOOP_INPUTS']), + input_loss_behavior=dict( + type='dict', + options=dict( + black_frame_msec=dict(type='int'), + input_loss_image_color=dict(type='str'), + input_loss_image_slate=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + input_loss_image_type=dict(type='str', choices=['COLOR', 'SLATE']), + repeat_frame_msec=dict(type='int') + ) + ), + output_locking_mode=dict(type='str', choices=['EPOCH_LOCKING', 'PIPELINE_LOCKING', 'DISABLED']), + output_timing_source=dict(type='str', choices=['INPUT_CLOCK', 'SYSTEM_CLOCK']), + support_low_framerate_inputs=dict(type='str', choices=['DISABLED', 'ENABLED']), + output_locking_settings=dict( + type='dict', + options=dict( + epoch_locking_settings=dict( + type='dict', + options=dict( + custom_epoch=dict(type='str'), + jam_sync_time=dict(type='str') + ) + ), + pipeline_locking_settings=dict(type='dict') + ) + ) + ) + ), + motion_graphics_configuration=dict( + type='dict', + options=dict( + motion_graphics_insertion=dict(type='str', choices=['DISABLED', 'ENABLED']), + motion_graphics_settings=dict( + type='dict', + options=dict( + html_motion_graphics_settings=dict(type='dict') + ) + ) + ) + ), + nielsen_configuration=dict( + type='dict', + options=dict( + distributor_id=dict(type='str'), + nielsen_pcm_to_id3_tagging=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + output_groups=dict( + type='list', + elements='dict', + options=dict( + name=dict(type='str'), + output_group_settings=dict( + type='dict', + options=dict( + archive_group_settings=dict( + type='dict', + options=dict( + archive_cdn_settings=dict( + type='dict', + options=dict( + archive_s3_settings=dict( + type='dict', + options=dict( + canned_acl=dict( + type='str', + choices=[ + 'AUTHENTICATED_READ', + 'BUCKET_OWNER_FULL_CONTROL', + 'BUCKET_OWNER_READ', + 'PUBLIC_READ', + ] + ) + ) + ) + ) + ), + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + rollover_interval=dict(type='int') + ) + ), + frame_capture_group_settings=dict( + type='dict', + options=dict( + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + frame_capture_cdn_settings=dict( + type='dict', + options=dict( + frame_capture_s3_settings=dict( + type='dict', + options=dict( + canned_acl=dict( + type='str', + choices=[ + 'AUTHENTICATED_READ', + 'BUCKET_OWNER_FULL_CONTROL', + 'BUCKET_OWNER_READ', + 'PUBLIC_READ', + ] + ) + ) + ) + ) + ) + ) + ), + hls_group_settings=dict( + type='dict', + options=dict( + ad_markers=dict(type='list', elements='str', choices=['ADOBE', 'ELEMENTAL', 'ELEMENTAL_SCTE35']), + base_url_content=dict(type='str'), + base_url_content1=dict(type='str'), + base_url_manifest=dict(type='str'), + base_url_manifest1=dict(type='str'), + caption_language_mappings=dict( + type='list', + elements='dict', + options=dict( + caption_channel=dict(type='int'), + language_code=dict(type='str', ) + ) + ), + caption_language_setting=dict(type='str', choices=['INSERT', 'NONE', 'OMIT']), + client_cache=dict(type='str', choices=['DISABLED', 'ENABLED']), + codec_specification=dict(type='str', choices=['RFC_4281', 'RFC_6381']), + constant_iv=dict(type='str'), + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + directory_structure=dict(type='str', choices=['SINGLE_DIRECTORY', 'SUBDIRECTORY_PER_STREAM']), + discontinuity_tags=dict(type='str', choices=['INSERT', 'NEVER_INSERT']), + encryption_type=dict(type='str', choices=['AES128', 'SAMPLE_AES']), + hls_cdn_settings=dict( + type='dict', + options=dict( + hls_akamai_settings=dict( + type='dict', + options=dict( + connection_retry_interval=dict(type='int'), + filecache_duration=dict(type='int'), + http_transfer_mode=dict(type='str', choices=['CHUNKED', 'NON_CHUNKED']), + num_retries=dict(type='int'), + restart_delay=dict(type='int'), + salt=dict(type='str'), + token=dict(type='str') + ) + ), + hls_basic_put_settings=dict( + type='dict', + options=dict( + connection_retry_interval=dict(type='int'), + filecache_duration=dict(type='int'), + num_retries=dict(type='int'), + restart_delay=dict(type='int') + ) + ), + hls_media_store_settings=dict( + type='dict', + options=dict( + connection_retry_interval=dict(type='int'), + filecache_duration=dict(type='int'), + media_store_storage_class=dict(type='str', choices=['TEMPORAL']), + num_retries=dict(type='int'), + restart_delay=dict(type='int') + ) + ), + hls_s3_settings=dict( + type='dict', + options=dict( + canned_acl=dict(type='str', choices=['AUTHENTICATED_READ', 'BUCKET_OWNER_FULL_CONTROL', 'BUCKET_OWNER_READ', 'PUBLIC_READ']) + ) + ), + hls_webdav_settings=dict( + type='dict', + options=dict( + connection_retry_interval=dict(type='int'), + filecache_duration=dict(type='int'), + http_transfer_mode=dict(type='str', choices=['CHUNKED', 'NON_CHUNKED']), + num_retries=dict(type='int'), + restart_delay=dict(type='int') + ) + ) + ) + ), + hls_id3_segment_tagging=dict(type='str', choices=['DISABLED', 'ENABLED']), + i_frame_only_playlists=dict(type='str', choices=['DISABLED', 'STANDARD']), + incomplete_segment_behavior=dict(type='str', choices=['AUTO', 'SUPPRESS']), + index_n_segments=dict(type='int'), + input_loss_action=dict(type='str', choices=['EMIT_OUTPUT', 'PAUSE_OUTPUT']), + iv_in_manifest=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + iv_source=dict(type='str', choices=['EXPLICIT', 'FOLLOWS_SEGMENT_NUMBER']), + keep_segments=dict(type='int'), + key_format=dict(type='str'), + key_format_versions=dict(type='str'), + key_provider_settings=dict( + type='dict', + options=dict( + static_key_settings=dict( + type='dict', + options=dict( + key_provider_server=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + static_key_value=dict(type='str') + ) + ) + ) + ), + manifest_compression=dict(type='str', choices=['GZIP', 'NONE']), + manifest_duration_format=dict(type='str', choices=['FLOATING_POINT', 'INTEGER']), + min_segment_length=dict(type='int'), + mode=dict(type='str', choices=['LIVE', 'VOD']), + output_selection=dict(type='str', choices=['MANIFESTS_AND_SEGMENTS', 'SEGMENTS_ONLY', 'VARIANT_MANIFESTS_AND_SEGMENTS']), + program_date_time=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + program_date_time_clock=dict(type='str', choices=['INITIALIZE_FROM_OUTPUT_TIMECODE', 'SYSTEM_CLOCK']), + program_date_time_period=dict(type='int'), + redundant_manifest=dict(type='str', choices=['DISABLED', 'ENABLED']), + segment_length=dict(type='int'), + segmentation_mode=dict(type='str', choices=['USE_INPUT_SEGMENTATION', 'USE_SEGMENT_DURATION']), + segments_per_subdirectory=dict(type='int'), + stream_inf_resolution=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + timed_metadata_id3_frame=dict(type='str', choices=['NONE', 'PRIV', 'TDRL']), + timed_metadata_id3_period=dict(type='int'), + timestamp_delta_milliseconds=dict(type='int'), + ts_file_mode=dict(type='str', choices=['SEGMENTED_FILES', 'SINGLE_FILE']) + ) + ), + media_package_group_settings=dict( + type='dict', + options=dict( + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ) + ) + ), + ms_smooth_group_settings=dict( + type='dict', + options=dict( + acquisition_point_id=dict(type='str'), + audio_only_timecode_control=dict(type='str', choices=['PASSTHROUGH', 'USE_CONFIGURED_CLOCK']), + certificate_mode=dict(type='str', choices=['SELF_SIGNED', 'VERIFY_AUTHENTICITY']), + connection_retry_interval=dict(type='int'), + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + event_id=dict(type='str'), + event_id_mode=dict(type='str', choices=['NO_EVENT_ID', 'USE_CONFIGURED', 'USE_TIMESTAMP']), + event_stop_behavior=dict(type='str', choices=['NONE', 'SEND_EOS']), + filecache_duration=dict(type='int'), + fragment_length=dict(type='int'), + input_loss_action=dict(type='str', choices=['EMIT_OUTPUT', 'PAUSE_OUTPUT']), + num_retries=dict(type='int'), + restart_delay=dict(type='int'), + segmentation_mode=dict(type='str', choices=['USE_INPUT_SEGMENTATION', 'USE_SEGMENT_DURATION']), + send_delay_ms=dict(type='int'), + sparse_track_type=dict(type='str', choices=['NONE', 'SCTE_35', 'SCTE_35_WITHOUT_SEGMENTATION']), + stream_manifest_behavior=dict(type='str', choices=['DO_NOT_SEND', 'SEND']), + timestamp_offset=dict(type='str'), + timestamp_offset_mode=dict(type='str', choices=['USE_CONFIGURED_OFFSET', 'USE_EVENT_START_DATE']) + ) + ), + multiplex_group_settings=dict(type='dict'), + rtmp_group_settings=dict( + type='dict', + options=dict( + ad_markers=dict(type='list', elements='str', choices=['ON_CUE_POINT_SCTE35']), + AuthenticationScheme=dict(type='str', choices=['AKAMAI', 'COMMON']), + CacheFullBehavior=dict(type='str', choices=['DISCONNECT_IMMEDIATELY', 'WAIT_FOR_SERVER']), + CacheLength=dict(type='int'), + CaptionData=dict(type='str', choices=['ALL', 'FIELD1_608', 'FIELD1_AND_FIELD2_608']), + InputLossAction=dict(type='str', choices=['EMIT_OUTPUT', 'PAUSE_OUTPUT']), + RestartDelay=dict(type='int'), + IncludeFillerNalUnits=dict(type='str', choices=['AUTO', 'DROP', 'INCLUDE']) + ) + ), + udp_group_settings=dict( + type='dict', + options=dict( + input_loss_action=dict(type='str', choices=['DROP_PROGRAM', 'DROP_TS', 'EMIT_PROGRAM']), + timed_metadata_id3_frame=dict(type='str', choices=['NONE', 'PRIV', 'TDRL']), + timed_metadata_id3_period=dict(type='int') + ) + ), + cmaf_ingest_group_settings=dict( + type='dict', + options=dict( + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + scte35_type=dict(type='str', choices=['NONE', 'SCTE_35_WITHOUT_SEGMENTATION']), + segment_length=dict(type='int'), + segment_length_units=dict(type='str', choices=['MILLISECONDS', 'SECONDS']), + send_delay_ms=dict(type='int'), + klv_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + klv_name_modifier=dict(type='str'), + nielsen_id3_name_modifier=dict(type='str'), + scte35_name_modifier=dict(type='str'), + id3_behavior=dict(type='str', choices=['DISABLED', 'ENABLED']), + id3_name_modifier=dict(type='str'), + caption_language_mappings=dict( + type='list', + elements='dict', + options=dict( + caption_channel=dict(type='int'), + language_code=dict(type='str') + ) + ), + timed_metadata_id3_frame=dict(type='str', choices=['NONE', 'PRIV', 'TDRL']), + timed_metadata_id3_period=dict(type='int'), + timed_metadata_passthrough=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + srt_group_settings=dict( + type='dict', + options=dict( + input_loss_action=dict(type='str', choices=['DROP_PROGRAM', 'DROP_TS', 'EMIT_PROGRAM']) + ) + ) + ) + ), + outputs=dict( + type='list', + elements='dict', + options=dict( + audio_description_names=dict(type='list', elements='str'), + caption_description_names=dict(type='list', elements='str'), + output_name=dict(type='str'), + output_settings=dict( + type='dict', + options=dict( + archive_output_settings=dict( + type='dict', + options=dict( + container_settings=dict( + type='dict', + options=dict( + m2ts_settings=dict( + type='dict', + options=dict( + absent_input_audio_behavior=dict(type='str', choices=['DROP', 'ENCODE_SILENCE']), + arib=dict(type='str', choices=['DISABLED', 'ENABLED']), + arib_captions_pid=dict(type='str'), + arib_captions_pid_control=dict(type='str', choices=['AUTO', 'USE_CONFIGURED']), + audio_buffer_model=dict(type='str', choices=['ATSC', 'DVB']), + audio_frames_per_pes=dict(type='int'), + audio_pids=dict(type='str'), + audio_stream_type=dict(type='str', choices=['ATSC', 'DVB']), + bitrate=dict(type='int'), + buffer_model=dict(type='str', choices=['MULTIPLEX', 'NONE']), + cc_descriptor=dict(type='str', choices=['DISABLED', 'ENABLED']), + dvb_nit_settings=dict( + type='dict', + options=dict( + network_id=dict(type='int'), + network_name=dict(type='str'), + rep_interval=dict(type='int') + ) + ), + dvb_sdt_settings=dict( + type='dict', + options=dict( + output_sdt=dict(type='str', choices=['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE']), + rep_interval=dict(type='int'), + service_name=dict(type='str'), + service_provider_name=dict(type='str') + ) + ), + dvb_sub_pids=dict(type='str'), + dvb_tdt_settings=dict( + type='dict', + options=dict( + rep_interval=dict(type='int') + ) + ), + dvb_teletext_pid=dict(type='str'), + ebif=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + ebp_audio_interval=dict(type='str', choices=['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL']), + ebp_lookahead_ms=dict(type='int'), + ebp_placement=dict(type='str', choices=['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID']), + ecm_pid=dict(type='str'), + es_rate_in_pes=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + etv_platform_pid=dict(type='str'), + etv_signal_pid=dict(type='str'), + fragment_time=dict(type='float'), + klv=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + klv_data_pids=dict(type='str'), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + null_packet_bitrate=dict(type='float'), + pat_interval=dict(type='int'), + pcr_control=dict(type='str', choices=['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET']), + pat_period=dict(type='int'), + pcr_pid=dict(type='str'), + pmt_interval=dict(type='int'), + pmt_pid=dict(type='str'), + program_num=dict(type='int'), + rate_mode=dict(type='str', choices=['CBR', 'VBR']), + scte27_pids=dict(type='str'), + scte35_control=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + scte35_pid=dict(type='str'), + segmentation_markers=dict(type='str', choices=['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART']), + segmentation_style=dict(type='str', choices=['MAINTAIN_CADENCE', 'RESET_CADENCE']), + segmentation_time=dict(type='float'), + timed_metadata_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + timed_metadata_pid=dict(type='str'), + transport_stream_id=dict(type='int'), + video_pid=dict(type='str'), + scte35_preroll_pullup_milliseconds=dict(type='float') + ) + ), + raw_settings=dict(type='dict') + ) + ), + extension=dict(type='str'), + name_modifier=dict(type='str') + ) + ), + frame_capture_output_settings=dict( + type='dict', + options=dict( + name_modifier=dict(type='str') + ) + ), + hls_output_settings=dict( + type='dict', + options=dict( + h265_packaging_type=dict(type='str', choices=['HEV1', 'HVC1']), + hls_settings=dict( + type='dict', + options=dict( + audio_only_hls_settings=dict( + type='dict', + options=dict( + audio_group_id=dict(type='str'), + audio_only_image=dict( + type='dict', + options=dict( + password_param=dict(type='str'), + uri=dict(type='str'), + username=dict(type='str') + ) + ), + audio_track_type=dict( + type='str', + choices=[ + 'ALTERNATE_AUDIO_AUTO_SELECT', + 'ALTERNATE_AUDIO_AUTO_SELECT_DEFAULT', + 'ALTERNATE_AUDIO_NOT_AUTO_SELECT', + 'AUDIO_ONLY_VARIANT_STREAM', + ] + ), + segment_type=dict(type='str', choices=['AAC', 'FMP4']) + ) + ), + fmp4_hls_settings=dict( + type='dict', + options=dict( + audio_rendition_sets=dict(type='str'), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + timed_metadata_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']) + ) + ), + frame_capture_hls_settings=dict(type='dict'), + standard_hls_settings=dict( + type='dict', + options=dict( + audio_rendition_sets=dict(type='str'), + m3u8_settings=dict( + type='dict', + options=dict( + audio_frames_per_pes=dict(type='int'), + audio_pids=dict(type='str'), + ecm_pid=dict(type='str'), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + pat_interval=dict(type='int'), + pcr_control=dict(type='str', choices=['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET']), + pcr_period=dict(type='int'), + pcr_pid=dict(type='str'), + pmt_interval=dict(type='int'), + pmt_pid=dict(type='str'), + program_num=dict(type='int'), + scte35_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + scte35_pid=dict(type='str'), + timed_metadata_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + timed_metadata_pid=dict(type='str'), + transport_stream_id=dict(type='int'), + video_pid=dict(type='str'), + klv_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + klv_data_pids=dict(type='str') + ) + ) + ) + ) + ) + ), + name_modifier=dict(type='str'), + segment_modifier=dict(type='str') + ) + ), + media_package_output_settings=dict(type='dict'), + ms_smooth_output_settings=dict( + type='dict', + options=dict( + h265_packaging_type=dict(type='str', choices=['HEV1', 'HVC1']), + name_modifier=dict(type='str') + ) + ), + multiplex_output_settings=dict( + type='dict', + options=dict( + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + container_settings=dict( + type='dict', + options=dict( + multiplex_m2ts_settings=dict( + type='dict', + options=dict( + absent_input_audio_behavior=dict(type='str', choices=['DROP', 'ENCODE_SILENCE']), + arib=dict(type='str', choices=['DISABLED', 'ENABLED']), + audio_buffer_model=dict(type='str', choices=['ATSC', 'DVB']), + audio_frames_per_pes=dict(type='int'), + audio_stream_type=dict(type='str', choices=['ATSC', 'DVB']), + cc_descriptor=dict(type='str', choices=['DISABLED', 'ENABLED']), + ebif=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + es_rate_in_pes=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + klv=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + pcr_control=dict(type='str', choices=['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET']), + pcr_period=dict(type='int'), + scte35_control=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + scte35_preroll_pullup_milliseconds=dict(type='float') + ) + ) + ) + ) + ) + ), + rtmp_output_settings=dict( + type='dict', + options=dict( + certificate_mode=dict(type='str', choices=['SELF_SIGNED', 'VERIFY_AUTHENTICITY']), + connection_retry_interval=dict(type='int'), + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + num_retries=dict(type='int') + ) + ), + udp_output_settings=dict( + type='dict', + options=dict( + buffer_msec=dict(type='int'), + container_settings=dict( + type='dict', + options=dict( + m2ts_settings=dict( + type='dict', + options=dict( + absent_input_audio_behavior=dict(type='str', choices=['DROP', 'ENCODE_SILENCE']), + arib=dict(type='str', choices=['DISABLED', 'ENABLED']), + arib_captions_pid=dict(type='str'), + arib_captions_pid_control=dict(type='str', choices=['AUTO', 'USE_CONFIGURED']), + audio_buffer_model=dict(type='str', choices=['ATSC', 'DVB']), + audio_frames_per_pes=dict(type='int'), + audio_pids=dict(type='str'), + audio_stream_type=dict(type='str', choices=['ATSC', 'DVB']), + bitrate=dict(type='int'), + buffer_model=dict(type='str', choices=['MULTIPLEX', 'NONE']), + cc_descriptor=dict(type='str', choices=['DISABLED', 'ENABLED']), + dvb_nit_settings=dict( + type='dict', + options=dict( + network_id=dict(type='int'), + network_name=dict(type='str'), + rep_interval=dict(type='int') + ) + ), + dvb_sdt_settings=dict( + type='dict', + options=dict( + output_sdt=dict(type='str', choices=['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE']), + rep_interval=dict(type='int'), + service_name=dict(type='str'), + service_provider_name=dict(type='str') + ) + ), + dvb_sub_pids=dict(type='str'), + dvb_tdt_settings=dict( + type='dict', + options=dict( + rep_interval=dict(type='int') + ) + ), + dvb_teletext_pid=dict(type='str'), + ebif=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + ebp_audio_interval=dict(type='str', choices=['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL']), + ebp_lookahead_ms=dict(type='int'), + ebp_placement=dict(type='str', choices=['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID']), + ecm_pid=dict(type='str'), + es_rate_in_pes=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + etv_platform_pid=dict(type='str'), + etv_signal_pid=dict(type='str'), + fragment_time=dict(type='float'), + klv=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + klv_data_pids=dict(type='str'), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + null_packet_bitrate=dict(type='float'), + pat_interval=dict(type='int'), + pcr_control=dict(type='str', choices=['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET']), + pcr_period=dict(type='int'), + pcr_pid=dict(type='str'), + pmt_interval=dict(type='int'), + pmt_pid=dict(type='str'), + program_num=dict(type='int'), + rate_mode=dict(type='str', choices=['CBR', 'VBR']), + scte27_pids=dict(type='str'), + scte35_control=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + scte35_pid=dict(type='str'), + segmentation_markers=dict(type='str', choices=['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART']), + segmentation_style=dict(type='str', choices=['MAINTAIN_CADENCE', 'RESET_CADENCE']), + segmentation_time=dict(type='float'), + timed_metadata_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + timed_metadata_pid=dict(type='str'), + transport_stream_id=dict(type='int'), + video_pid=dict(type='str'), + scte35_preroll_pullup_milliseconds=dict(type='float') + ) + ) + ) + ), + destination=dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + fec_output_settings=dict( + type='dict', + options=dict( + column_depth=dict(type='int'), + include_fec=dict(type='str', choices=['COLUMN', 'COLUMN_AND_ROW']), + row_length=dict(type='int') + ) + ) + ) + ), + cmaf_ingest_output_settings=dict( + type='dict', + options=dict( + name_modifier=dict(type='str') + ) + ), + srt_output_settings=dict( + type='dict', + options=dict( + buffer_msec=dict(type='int'), + container_settings=dict( + type='dict', + options=dict( + m2ts_settings=dict( + type='dict', + options=dict( + absent_input_audio_behavior=dict(type='str', choices=['DROP', 'ENCODE_SILENCE']), + arib=dict(type='str', choices=['DISABLED', 'ENABLED']), + arib_captions_pid=dict(type='str'), + arib_captions_pid_control=dict(type='str', choices=['AUTO', 'USE_CONFIGURED']), + audio_buffer_model=dict(type='str', choices=['ATSC', 'DVB']), + audio_frames_per_pes=dict(type='int'), + audio_pids=dict(type='str'), + audio_stream_type=dict(type='str', choices=['ATSC', 'DVB']), + bitrate=dict(type='int'), + buffer_model=dict(type='str', choices=['MULTIPLEX', 'NONE']), + cc_descriptor=dict(type='str', choices=['DISABLED', 'ENABLED']), + dvb_nit_settings=dict( + type='dict', + options=dict( + network_id=dict(type='int'), + network_name=dict(type='str'), + rep_interval=dict(type='int') + ) + ), + dvb_sdt_settings=dict( + type='dict', + options=dict( + output_sdt=dict(type='str', choices=['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE']), + rep_interval=dict(type='int'), + service_name=dict(type='str'), + service_provider_name=dict(type='str') + ) + ), + dvb_sub_pids=dict(type='str'), + dvb_tdt_settings=dict( + type='dict', + options=dict( + rep_interval=dict(type='int') + ) + ), + dvb_teletext_pid=dict(type='str'), + ebif=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + ebp_audio_interval=dict(type='str', choices=['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL']), + ebp_lookahead_ms=dict(type='int'), + ebp_placement=dict(type='str', choices=['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID']), + ecm_pid=dict(type='str'), + es_rate_in_pes=dict(type='str', choices=['EXCLUDE', 'INCLUDE']), + etv_platform_pid=dict(type='str'), + etv_signal_pid=dict(type='str'), + fragment_time=dict(type='float'), + klv=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + klv_data_pids=dict(type='str'), + nielsen_id3_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + null_packet_bitrate=dict(type='float'), + pat_interval=dict(type='int'), + pcr_control=dict(type='str', choices=['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET']), + pcr_period=dict(type='int'), + pcr_pid=dict(type='str'), + pmt_interval=dict(type='int'), + pmt_pid=dict(type='str'), + program_num=dict(type='int'), + rate_mode=dict(type='str', choices=['CBR', 'VBR']), + scte27_pids=dict(type='str'), + scte35_control=dict(type='str', choices=['NONE', 'PASSTHROUGH']), + scte35_pid=dict(type='str'), + segmentation_markers=dict(type='str', choices=['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART']), + segmentation_style=dict(type='str', choices=['MAINTAIN_CADENCE', 'RESET_CADENCE']), + segmentation_time=dict(type='float'), + timed_metadata_behavior=dict(type='str', choices=['NO_PASSTHROUGH', 'PASSTHROUGH']), + timed_metadata_pid=dict(type='str'), + transport_stream_id=dict(type='int'), + video_pid=dict(type='str'), + scte35_preroll_pullup_milliseconds=dict(type='float') + ) + ) + ) + ), + destination = dict( + type='dict', + options=dict( + destination_ref_id=dict(type='str') + ) + ), + encryption_type = dict(type='str', choices=['AES128', 'AES192', 'AES256']), + latency = dict(type='int') + ) + ) + ) + ), + video_description_name=dict(type='str') + ) + ) + ) + ), + timecode_config=dict( + type='dict', + options=dict( + source=dict(type='str', choices=['EMBEDDED', 'SYSTEMCLOCK', 'ZEROBASED']), + sync_threshold=dict(type='int') + ) + ), + video_descriptions=dict( + type='list', + elements='dict', + options=dict( + codec_settings=dict( + type='dict', + options=dict( + frame_capture_settings=dict( + type='dict', + options=dict( + capture_interval=dict(type='int'), + capture_interval_units=dict(type='str', choices=['MILLISECONDS', 'SECONDS']), + timecode_burnin_settings=dict( + type='dict', + options=dict( + font_size=dict(type='str', choices=['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16']), + position=dict( + type='str', + choices=[ + 'BOTTOM_CENTER', + 'BOTTOM_LEFT', + 'BOTTOM_RIGHT', + 'MIDDLE_CENTER', + 'MIDDLE_LEFT', + 'MIDDLE_RIGHT', + 'TOP_CENTER', + 'TOP_LEFT', + 'TOP_RIGHT', + ] + ), + prefix=dict(type='str') + ) + ) + ) + ), + h264_settings=dict( + type='dict', + options=dict( + adaptive_quantization=dict(type='str', choices=['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF']), + afd_signaling=dict(type='str', choices=['AUTO', 'FIXED', 'NONE']), + bitrate=dict(type='int'), + buf_fill_pct=dict(type='int'), + buf_size=dict(type='int'), + color_metadata=dict(type='str', choices=['IGNORE', 'INSERT']), + color_space_settings=dict( + type='dict', + options=dict( + color_space_passthrough_settings=dict(type='dict'), + rec601_settings=dict(type='dict'), + rec709_settings=dict(type='dict') + ) + ), + entropy_encoding=dict(type='str', choices=['CABAC', 'CAVLC']), + filter_settings=dict( + type='dict', + options=dict( + temporal_filter_settings=dict( + type='dict', + options=dict( + post_filter_sharpening=dict(type='str', choices=['AUTO', 'DISABLED', 'ENABLED']), + strength=dict( + type='str', + choices=[ + 'AUTO', + 'STRENGTH_1', + 'STRENGTH_2', + 'STRENGTH_3', + 'STRENGTH_4', + 'STRENGTH_5', + 'STRENGTH_6', + 'STRENGTH_7', + 'STRENGTH_8', + 'STRENGTH_9', + 'STRENGTH_10', + 'STRENGTH_11', + 'STRENGTH_12', + 'STRENGTH_13', + 'STRENGTH_14', + 'STRENGTH_15', + 'STRENGTH_16', + ] + ) + ) + ), + bandwidth_reduction_filter_settings=dict( + type='dict', + options=dict( + post_filter_sharpening=dict(type='str', choices=['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3']), + strength=dict(type='str', choices=['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4']) + ) + ) + ) + ), + fixed_afd=dict( + type='str', + choices=[ + 'AFD_0000', + 'AFD_0010', + 'AFD_0011', + 'AFD_0100', + 'AFD_1000', + 'AFD_1001', + 'AFD_1010', + 'AFD_1011', + 'AFD_1101', + 'AFD_1110', + 'AFD_1111', + ] + ), + flicker_aq=dict(type='str', choices=['DISABLED', 'ENABLED']), + force_field_pictures=dict(type='str', choices=['DISABLED', 'ENABLED']), + framerate_control=dict(type='str', choices=['INITIALIZE_FROM_SOURCE', 'SPECIFIED']), + framerate_denominator=dict(type='int'), + framerate_numerator=dict(type='int'), + gop_b_reference=dict(type='str', choices=['DISABLED', 'ENABLED']), + gop_closed_cadence=dict(type='int'), + gop_num_b_frames=dict(type='int'), + gop_size=dict(type='float'), + gop_size_units=dict(type='str', choices=['FRAMES', 'SECONDS']), + level=dict( + type='str', + choices=[ + 'H264_LEVEL_1', + 'H264_LEVEL_1_1', + 'H264_LEVEL_1_2', + 'H264_LEVEL_1_3', + 'H264_LEVEL_2', + 'H264_LEVEL_2_1', + 'H264_LEVEL_2_2', + 'H264_LEVEL_3', + 'H264_LEVEL_3_1', + 'H264_LEVEL_3_2', + 'H264_LEVEL_4', + 'H264_LEVEL_4_1', + 'H264_LEVEL_4_2', + 'H264_LEVEL_5', + 'H264_LEVEL_5_1', + 'H264_LEVEL_5_2', + 'H264_LEVEL_AUTO', + ] + ), + look_ahead_rate_control=dict(type='str', choices=['HIGH', 'LOW', 'MEDIUM']), + max_bitrate=dict(type='int'), + min_i_interval=dict(type='int'), + num_ref_frames=dict(type='int'), + par_control=dict(type='str', choices=['INITIALIZE_FROM_SOURCE', 'SPECIFIED']), + par_denominator=dict(type='int'), + par_numerator=dict(type='int'), + profile=dict(type='str', choices=['BASELINE', 'HIGH', 'HIGH_10BIT', 'HIGH_422', 'HIGH_422_10BIT', 'MAIN']), + quality_level=dict(type='str', choices=['ENHANCED_QUALITY', 'STANDARD_QUALITY']), + qvbr_quality_level=dict(type='int'), + rate_control_mode=dict(type='str', choices=['CBR', 'MULTIPLEX', 'QVBR', 'VBR']), + scan_type=dict(type='str', choices=['INTERLACED', 'PROGRESSIVE']), + scene_change_detect=dict(type='str', choices=['DISABLED', 'ENABLED']), + slices=dict(type='int'), + softness=dict(type='int'), + spatial_aq=dict(type='str', choices=['DISABLED', 'ENABLED']), + subgop_length=dict(type='str', choices=['DYNAMIC', 'FIXED']), + syntax=dict(type='str', choices=['DEFAULT', 'RP2027']), + temporal_aq=dict(type='str', choices=['DISABLED', 'ENABLED']), + timecode_insertion=dict(type='str', choices=['DISABLED', 'PIC_TIMING_SEI']), + timecode_burnin_settings=dict( + type='dict', + options=dict( + font_size=dict(type='str', choices=['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16']), + position=dict( + type='str', + choices=[ + 'BOTTOM_CENTER', + 'BOTTOM_LEFT', + 'BOTTOM_RIGHT', + 'MIDDLE_CENTER', + 'MIDDLE_LEFT', + 'MIDDLE_RIGHT', + 'TOP_CENTER', + 'TOP_LEFT', + 'TOP_RIGHT', + ] + ), + prefix=dict(type='str') + ) + ), + min_qp=dict(type='int') + ) + ), + h265_settings=dict( + type='dict', + options=dict( + adaptive_quantization=dict(type='str', choices=['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF']), + afd_signaling=dict(type='str', choices=['AUTO', 'FIXED', 'NONE']), + alternative_transfer_function=dict(type='str', choices=['INSERT', 'OMIT']), + bitrate=dict(type='int'), + buf_size=dict(type='int'), + color_metadata=dict(type='str', choices=['IGNORE', 'INSERT']), + color_space_settings=dict( + type='dict', + options=dict( + color_space_passthrough_settings=dict(type='dict'), + dolby_vision81_settings=dict(type='dict'), + hdr10_settings=dict( + type='dict', + options=dict( + max_cll=dict(type='int'), + max_fall=dict(type='int') + ) + ), + rec601_settings=dict(type='dict'), + rec709_settings=dict(type='dict') + ) + ), + filter_settings=dict( + type='dict', + options=dict( + temporal_filter_settings=dict( + type='dict', + options=dict( + post_filter_sharpening=dict(type='str', choices=['AUTO', 'DISABLED', 'ENABLED']), + strength=dict( + type='str', + choices=[ + 'AUTO', + 'STRENGTH_1', + 'STRENGTH_2', + 'STRENGTH_3', + 'STRENGTH_4', + 'STRENGTH_5', + 'STRENGTH_6', + 'STRENGTH_7', + 'STRENGTH_8', + 'STRENGTH_9', + 'STRENGTH_10', + 'STRENGTH_11', + 'STRENGTH_12', + 'STRENGTH_13', + 'STRENGTH_14', + 'STRENGTH_15', + 'STRENGTH_16', + ] + ) + ) + ), + bandwidth_reduction_filter_settings=dict( + type='dict', + options=dict( + post_filter_sharpening=dict(type='str', choices=['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3']), + strength=dict(type='str', choices=['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4']) + ) + ) + ) + ), + fixed_afd=dict( + type='str', + choices=[ + 'AFD_0000', + 'AFD_0010', + 'AFD_0011', + 'AFD_0100', + 'AFD_1000', + 'AFD_1001', + 'AFD_1010', + 'AFD_1011', + 'AFD_1101', + 'AFD_1110', + 'AFD_1111', + ] + ), + flicker_aq=dict(type='str', choices=['DISABLED', 'ENABLED']), + framerate_denominator=dict(type='int'), + framerate_numerator=dict(type='int'), + gop_closed_cadence=dict(type='int'), + gop_size=dict(type='float'), + gop_size_units=dict(type='str', choices=['FRAMES', 'SECONDS']), + level=dict( + type='str', + choices=[ + 'H265_LEVEL_1', + 'H265_LEVEL_2', + 'H265_LEVEL_2_1', + 'H265_LEVEL_3', + 'H265_LEVEL_3_1', + 'H265_LEVEL_4', + 'H265_LEVEL_4_1', + 'H265_LEVEL_5', + 'H265_LEVEL_5_1', + 'H265_LEVEL_5_2', + 'H265_LEVEL_6', + 'H265_LEVEL_6_1', + 'H265_LEVEL_6_2', + 'H265_LEVEL_AUTO', + ] + ), + look_ahead_rate_control=dict(type='str', choices=['HIGH', 'LOW', 'MEDIUM']), + max_bitrate=dict(type='int'), + min_i_interval=dict(type='int'), + par_denominator=dict(type='int'), + par_numerator=dict(type='int'), + profile=dict(type='str', choices=['MAIN', 'MAIN_10BIT']), + qvbr_quality_level=dict(type='int'), + rate_control_mode=dict(type='str', choices=['CBR', 'MULTIPLEX', 'QVBR']), + scan_type=dict(type='str', choices=['INTERLACED', 'PROGRESSIVE']), + scene_change_detect=dict(type='str', choices=['DISABLED', 'ENABLED']), + slices=dict(type='int'), + tier=dict(type='str', choices=['HIGH', 'MAIN']), + timecode_insertion=dict(type='str', choices=['DISABLED', 'PIC_TIMING_SEI']), + timecode_burnin_settings=dict( + type='dict', + options=dict( + font_size=dict(type='str', choices=['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16']), + position=dict( + type='str', + choices=[ + 'BOTTOM_CENTER', + 'BOTTOM_LEFT', + 'BOTTOM_RIGHT', + 'MIDDLE_CENTER', + 'MIDDLE_LEFT', + 'MIDDLE_RIGHT', + 'TOP_CENTER', + 'TOP_LEFT', + 'TOP_RIGHT', + ] + ), + prefix=dict(type='str') + ) + ), + mv_over_picture_boundaries=dict(type='str', choices=['DISABLED', 'ENABLED']), + mv_temporal_predictor=dict(type='str', choices=['DISABLED', 'ENABLED']), + tile_height=dict(type='int'), + tile_padding=dict(type='str', choices=['NONE', 'PADDED']), + tile_width=dict(type='int'), + treeblock_size=dict(type='str', choices=['AUTO', 'TREE_SIZE_32X32']), + min_qp=dict(type='int'), + deblocking=dict(type='str', choices=['DISABLED', 'ENABLED']) + ) + ), + mpeg2_settings=dict( + type='dict', + options=dict( + adaptive_quantization=dict(type='str', choices=['AUTO', 'HIGH', 'LOW', 'MEDIUM', 'OFF']), + afd_signaling=dict(type='str', choices=['AUTO', 'FIXED', 'NONE']), + color_metadata=dict(type='str', choices=['IGNORE', 'INSERT']), + color_space=dict(type='str', choices=['AUTO', 'PASSTHROUGH']), + display_aspect_ratio=dict(type='str', choices=['DISPLAYRATIO16X9', 'DISPLAYRATIO4X3']), + filter_settings=dict( + type='dict', + options=dict( + temporal_filter_settings=dict( + type='dict', + options=dict( + post_filter_sharpening=dict(type='str', choices=['AUTO', 'DISABLED', 'ENABLED']), + strength=dict( + type='str', + choices=[ + 'AUTO', + 'STRENGTH_1', + 'STRENGTH_2', + 'STRENGTH_3', + 'STRENGTH_4', + 'STRENGTH_5', + 'STRENGTH_6', + 'STRENGTH_7', + 'STRENGTH_8', + 'STRENGTH_9', + 'STRENGTH_10', + 'STRENGTH_11', + 'STRENGTH_12', + 'STRENGTH_13', + 'STRENGTH_14', + 'STRENGTH_15', + 'STRENGTH_16', + ] + ) + ) + ) + ) + ), + fixed_afd=dict( + type='str', + choices=[ + 'AFD_0000', + 'AFD_0010', + 'AFD_0011', + 'AFD_0100', + 'AFD_1000', + 'AFD_1001', + 'AFD_1010', + 'AFD_1011', + 'AFD_1101', + 'AFD_1110', + 'AFD_1111', + ] + ), + framerate_denominator=dict(type='int'), + framerate_numerator=dict(type='int'), + gop_closed_cadence=dict(type='int'), + gop_num_b_frames=dict(type='int'), + gop_size=dict(type='float'), + gop_size_units=dict(type='str', choices=['FRAMES', 'SECONDS']), + scan_type=dict(type='str', choices=['INTERLACED', 'PROGRESSIVE']), + subgop_length=dict(type='str', choices=['DYNAMIC', 'FIXED']), + timecode_insertion=dict(type='str', choices=['DISABLED', 'GOP_TIMECODE']), + timecode_burnin_settings=dict( + type='dict', + options=dict( + font_size=dict(type='str', choices=['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16']), + position=dict( + type='str', + choices=[ + 'BOTTOM_CENTER', + 'BOTTOM_LEFT', + 'BOTTOM_RIGHT', + 'MIDDLE_CENTER', + 'MIDDLE_LEFT', + 'MIDDLE_RIGHT', + 'TOP_CENTER', + 'TOP_LEFT', + 'TOP_RIGHT', + ] + ), + prefix=dict(type='str') + ) + ) + ) + ), + av1_settings=dict( + type='dict', + options=dict( + afd_signaling=dict(type='str', choices=['AUTO', 'FIXED', 'NONE']), + buf_size=dict(type='int'), + color_space_settings=dict( + type='dict', + options=dict( + color_space_passthrough_settings=dict(type='dict'), + hdr10_settings=dict( + type='dict', + options=dict( + + max_cll=dict(type='int'), + max_fall=dict(type='int') + ) + ), + rec601_settings=dict(type='dict'), + rec709_settings=dict(type='dict') + ) + ), + fixed_afd=dict( + type='str', + choices=[ + 'AFD_0000', + 'AFD_0010', + 'AFD_0011', + 'AFD_0100', + 'AFD_1000', + 'AFD_1001', + 'AFD_1010', + 'AFD_1011', + 'AFD_1101', + 'AFD_1110', + 'AFD_1111', + ] + ), + framerate_denominator=dict(type='int'), + framerate_numerator=dict(type='int'), + gop_size=dict(type='float'), + gop_size_units=dict(type='str', choices=['FRAMES', 'SECONDS']), + level=dict( + type='str', + choices=[ + 'AV1_LEVEL_2', + 'AV1_LEVEL_2_1', + 'AV1_LEVEL_3', + 'AV1_LEVEL_3_1', + 'AV1_LEVEL_4', + 'AV1_LEVEL_4_1', + 'AV1_LEVEL_5', + 'AV1_LEVEL_5_1', + 'AV1_LEVEL_5_2', + 'AV1_LEVEL_5_3', + 'AV1_LEVEL_6', + 'AV1_LEVEL_6_1', + 'AV1_LEVEL_6_2', + 'AV1_LEVEL_6_3', + 'AV1_LEVEL_AUTO', + ] + ), + look_ahead_rate_control=dict(type='str', choices=['HIGH', 'LOW', 'MEDIUM']), + max_bitrate=dict(type='int'), + min_i_interval=dict(type='int'), + par_denominator=dict(type='int'), + par_numerator=dict(type='int'), + qvbr_quality_level=dict(type='int'), + scene_change_detect=dict(type='str', choices=['DISABLED', 'ENABLED']), + timecode_burnin_settings=dict( + type='dict', + options=dict( + font_size=dict(type='str', choices=['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16']), + position=dict( + type='str', + choices=[ + 'BOTTOM_CENTER', + 'BOTTOM_LEFT', + 'BOTTOM_RIGHT', + 'MIDDLE_CENTER', + 'MIDDLE_LEFT', + 'MIDDLE_RIGHT', + 'TOP_CENTER', + 'TOP_LEFT', + 'TOP_RIGHT', + ] + ), + prefix=dict(type='str') + ) + ) + ) + ) + ) + ), + height=dict(type='int'), + name=dict(type='str'), + respond_to_afd=dict(type='str', choices=['NONE', 'PASSTHROUGH', 'RESPOND']), + scaling_behavior=dict(type='str', choices=['DEFAULT', 'STRETCH_TO_OUTPUT']), + sharpness=dict(type='int'), + width=dict(type='int') + ) + ), + thumbnail_configuration=dict( + type='dict', + options=dict( + state=dict(type='str', choices=['AUTO', 'DISABLED']) + ) + ), + color_correction_settings=dict( + type='dict', + options=dict( + global_color_corrections=dict( + type='list', + elements='dict', + options=dict( + input_color_space=dict(type='str', choices=['HDR10', 'HLG_2020', 'REC_601', 'REC_709']), + output_color_space=dict(type='str', choices=['HDR10', 'HLG_2020', 'REC_601', 'REC_709']), + uri=dict(type='str') + ) + ) + ) + ) + ) + ), + input_attachments=dict( + type='list', + elements='dict', + options=dict( + automatic_input_failover_settings=dict( + type='dict', + options=dict( + error_clear_time_msec=dict(type='int'), + failover_conditions=dict( + type='list', + elements='dict', + options=dict( + failover_condition_settings=dict( + type='dict', + options=dict( + audio_silence_settings=dict( + type='dict', + options=dict( + audio_selector_name=dict(type='str'), + audio_silence_threshold_msec=dict(type='int') + ) + ), + input_loss_settings=dict( + type='dict', + options=dict( + input_loss_threshold_msec=dict(type='int') + ) + ), + video_black_settings=dict( + type='dict', + options=dict( + black_detect_threshold=dict(type='float'), + video_black_threshold_msec=dict(type='int') + ) + ) + ) + ) + ) + ), + input_preference=dict(type='str', choices=['EQUAL_INPUT_PREFERENCE', 'PRIMARY_INPUT_PREFERRED']), + secondary_input_id=dict(type='str') + ) + ), + input_attachment_name=dict(type='str'), + input_id=dict(type='str'), + input_settings=dict( + type='dict', + options=dict( + audio_selectors=dict( + type='list', + elements='dict', + options=dict( + name=dict(type='str'), + selector_settings=dict( + type='dict', + options=dict( + audio_hls_rendition_selection=dict( + type='dict', + options=dict( + group_id=dict(type='str'), + name=dict(type='str') + ) + ), + audio_language_selection=dict( + type='dict', + options=dict( + language_code=dict(type='str'), + language_selection_policy=dict(type='str', choices=['LOOSE', 'STRICT']) + ) + ), + audio_pid_selection=dict( + type='dict', + options=dict( + pid=dict(type='int') + ) + ), + audio_track_selection=dict( + type='dict', + options=dict( + tracks=dict( + type='list', + elements='dict', + options=dict( + track=dict(type='int') + ) + ), + dolby_e_decode=dict( + type='dict', + options=dict( + program_selection=dict( + type='str', + choices=[ + 'ALL_CHANNELS', + 'PROGRAM_1', + 'PROGRAM_2', + 'PROGRAM_3', + 'PROGRAM_4', + 'PROGRAM_5', + 'PROGRAM_6', + 'PROGRAM_7', + 'PROGRAM_8' + ] + ) + ) + ) + ) + ) + ) + ) + ) + ), + caption_selectors=dict( + type='list', + elements='dict', + options=dict( + language_code=dict(type='str'), + name=dict(type='str'), + selector_settings=dict( + type='dict', + options=dict( + ancillary_source_settings=dict( + type='dict', + options=dict( + source_ancillary_channel_number=dict(type='int') + ) + ), + arib_source_settings=dict(type='dict'), + dvb_sub_source_settings=dict( + type='dict', + options=dict( + ocr_language=dict(type='str', choices=['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA']), + pid=dict(type='int') + ) + ), + embedded_source_settings=dict( + type='dict', + options=dict( + convert608_to708=dict(type='str', choices=['DISABLED', 'UPCONVERT']), + scte20_detection=dict(type='str', choices=['AUTO', 'OFF']), + source608_channel_number=dict(type='int'), + source608_track_number=dict(type='int') + ) + ), + scte20_source_settings=dict( + type='dict', + options=dict( + convert608_to708=dict(type='str', choices=['DISABLED', 'UPCONVERT']), + source608_channel_number=dict(type='int') + ) + ), + scte27_source_settings=dict( + type='dict', + options=dict( + ocr_language=dict(type='str', choices=['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA']), + pid=dict(type='int') + ) + ), + teletext_source_settings=dict( + type='dict', + options=dict( + output_rectangle=dict( + type='dict', + options=dict( + height=dict(type='float'), + left_offset=dict(type='float'), + top_offset=dict(type='float'), + width=dict(type='float') + ) + ), + page_number=dict(type='str') + ) + ) + ) + ) + ) + ), + deblock_filter=dict(type='str', choices=['DISABLED', 'ENABLED']), + denoise_filter=dict(type='str', choices=['DISABLED', 'ENABLED']), + filter_strength=dict(type='int'), + input_filter=dict(type='str', choices=['AUTO', 'DISABLED', 'FORCED']), + network_input_settings=dict( + type='dict', + options=dict( + hls_input_settings=dict( + type='dict', + options=dict( + bandwidth=dict(type='int'), + buffer_segments=dict(type='int'), + retries=dict(type='int'), + retry_interval=dict(type='int'), + scte35_source=dict(type='str', choices=['MANIFEST', 'SEGMENTS']) + ) + ), + server_validation=dict(type='str', choices=['CHECK_CRYPTOGRAPHY_AND_VALIDATE_NAME', 'CHECK_CRYPTOGRAPHY_ONLY']), + multicast_input_settings=dict( + type='dict', + options=dict( + source_ip_address=dict(type='str') + ) + ) + ) + ), + scte35_pid=dict(type='int'), + smpte2038_data_preference=dict(type='str', choices=['IGNORE', 'PREFER']), + source_end_behavior=dict(type='str', choices=['CONTINUE', 'LOOP']), + video_selector=dict( + type='dict', + options=dict( + color_space=dict(type='str', choices=['FOLLOW', 'HDR10', 'HLG_2020', 'REC_601', 'REC_709']), + color_space_settings=dict( + type='dict', + options=dict( + hdr10_settings=dict( + type='dict', + options=dict( + max_cll=dict(type='int'), + max_fall=dict(type='int') + ) + ) + ) + ), + color_space_usage=dict(type='str', choices=['FALLBACK', 'FORCE']), + selector_settings=dict( + type='dict', + options=dict( + video_selector_pid=dict( + type='dict', + options=dict( + pid=dict(type='int') + ) + ), + video_selector_program_id=dict( + type='dict', + options=dict( + program_id=dict(type='int') + ) + ) + ) + ) + ) + ) + ) + ), + logical_interface_names=dict(type='list', elements='str') + ) + ), + input_specification=dict( + type='dict', + options=dict( + codec=dict(type='str', choices=['MPEG2', 'AVC', 'HEVC']), + maximum_bitrate=dict(type='str', choices=['MAX_10_MBPS', 'MAX_20_MBPS', 'MAX_50_MBPS']), + resolution=dict(type='str', choices=['SD', 'HD', 'UHD']) + ) + ), + log_level=dict(type='str', choices=['ERROR', 'WARNING', 'INFO', 'DEBUG', 'DISABLED']), + maintenance=dict( + type='dict', + options=dict( + maintenance_day=dict(type='str', choices=['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY']), + maintenance_start_time=dict(type='str') + ) + ), + reserved=dict(type='str'), + role_arn=dict(type='str'), + vpc=dict( + type='dict', + options=dict( + public_address_allocation_ids=dict(type='list', elements='str'), + security_group_ids=dict(type='list', elements='str'), + subnet_ids=dict(type='list', elements='str') + ) + ), + anywhere_settings=dict( + type='dict', + options=dict( + channel_placement_group_id=dict(type='str'), + cluster_id=dict(type='str') + ) + ), + channel_engine_version=dict( + type='dict', + options=dict( + version=dict(type='str') + ) + ), + state=dict(type='str', default='present', choices=['present', 'absent']), + dry_run=dict(type='bool'), + tags=dict(type='dict'), + purge_tags=dict(type='bool', default=True), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=60), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'channel_id', 'name', 'channel_name')] + ) + + # Extract module parameters + channel_id = get_arg('id', module.params, argument_spec) + channel_name = get_arg('name', module.params, argument_spec) + cdi_input_specification = get_arg('cdi_input_specification', module.params, argument_spec) + channel_class = get_arg('channel_class', module.params, argument_spec) + destinations = get_arg('destinations', module.params, argument_spec) + encoder_settings = get_arg('encoder_settings', module.params, argument_spec) + input_attachments = get_arg('input_attachments', module.params, argument_spec) + input_specification = get_arg('input_specification', module.params, argument_spec) + log_level = get_arg('log_level', module.params, argument_spec) + maintenance = get_arg('maintenance', module.params, argument_spec) + reserved = get_arg('reserved', module.params, argument_spec) + role_arn = get_arg('role_arn', module.params, argument_spec) + vpc = get_arg('vpc', module.params, argument_spec) + anywhere_settings = get_arg('anywhere_settings', module.params, argument_spec) + channel_engine_version = get_arg('channel_engine_version', module.params, argument_spec) + dry_run = get_arg('dry_run', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + tags = get_arg('tags', module.params, argument_spec) + purge_tags = get_arg('purge_tags', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveChannelManager(module) + + # Find the channel by ID or name + if channel_id: + manager.get_channel_by_id(channel_id) + elif channel_name: + manager.get_channel_by_name(channel_name) + channel_id = manager.channel.get('channel_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.channel: + + update_params = { + 'channel_id': channel_id, + 'name': channel_name, + 'cdi_input_specification': cdi_input_specification, + 'destinations': destinations, + 'encoder_settings': encoder_settings, + 'input_attachments': input_attachments, + 'input_specification': input_specification, + 'log_level': log_level, + 'maintenance': maintenance, + 'role_arn': role_arn, + 'channel_engine_version': channel_engine_version, + 'dry_run': dry_run, + 'anywhere_settings': anywhere_settings, + 'tags': tags, + 'purge_tags': purge_tags, + 'request_id': str(uuid.uuid4()) + } + + manager.do_update_channel(update_params) + + # Case create + else: + create_params = { + 'channel_id': channel_id, + 'name': channel_name, + 'cdi_input_specification': cdi_input_specification, + 'channel_class': channel_class, + 'destinations': destinations, + 'encoder_settings': encoder_settings, + 'input_attachments': input_attachments, + 'input_specification': input_specification, + 'log_level': log_level, + 'maintenance': maintenance, + 'reserved': reserved, + 'role_arn': role_arn, + 'vpc': vpc, + 'anywhere_settings': anywhere_settings, + 'channel_engine_version': channel_engine_version, + 'dry_run': dry_run, + 'tags': tags, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_channel(create_params) + channel_id = manager.channel.get('channel_id') + + # Wait for the channel to be created + if wait and channel_id: + manager.wait_for('channel_created', channel_id, wait_timeout) # type: ignore + manager.get_channel_by_id(channel_id) + + # Handle absent state + elif state == 'absent': + if manager.channel: + # Channel exists, delete it + channel_id = manager.channel.get('channel_id') + manager.delete_channel(channel_id) # type: ignore + + # Wait for the channel to be deleted if requested + if wait and channel_id: + manager.wait_for('channel_deleted', channel_id, wait_timeout) # type: ignore + + module.exit_json(changed=manager.changed, channel=manager.channel) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_channel_placement_group.py b/plugins/modules/medialive_channel_placement_group.py new file mode 100644 index 00000000000..75be0546eeb --- /dev/null +++ b/plugins/modules/medialive_channel_placement_group.py @@ -0,0 +1,523 @@ +#!/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_channel_placement_group +short_description: Manage AWS MediaLive Channel Placement Groups +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive Channel Placement Groups. + - This module requires boto3 >= 1.35.17. +author: + - "David Teach" +options: + id: + description: + - The ID of the channel placement group. + - Required when updating or deleting an existing placement group. + required: false + type: str + aliases: ['channel_placement_group_id'] + cluster_id: + description: + - The ID of the cluster. + - Required for all operations. + required: true + type: str + nodes: + description: + - A list of node IDs to associate with the channel placement group. + - Required when creating a new placement group. + type: list + elements: str + required: false + state: + description: + - Create/update or remove the channel placement group. + required: false + choices: ['present', 'absent'] + default: 'present' + type: str + wait: + description: + - Whether to wait for the channel placement group to reach the desired state. + - When I(state=present), wait for the placement group to reach the ACTIVE state. + - When I(state=absent), wait for the placement group to be deleted. + type: bool + required: false + default: true + wait_timeout: + description: + - The maximum time in seconds to wait for the channel placement group to reach the desired state. + - Defaults to 60 seconds. + type: int + required: false + default: 60 + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Create a MediaLive Channel Placement Group +- community.aws.medialive_channel_placement_group: + cluster_id: '123456' + nodes: + - '34598743' + state: present + +# Update a MediaLive Channel Placement Group +- community.aws.medialive_channel_placement_group: + id: '234523' + cluster_id: '123456' + name: 'UpdatedPlacementGroup' + nodes: + - '34598743' + state: present + +# Delete a MediaLive Channel Placement Group +- community.aws.medialive_channel_placement_group: + id: '234523' + cluster_id: '123456' + state: absent +""" + +RETURN = r""" +placement_group: + description: The details of the channel placement group + returned: success + type: dict + contains: + arn: + description: The ARN of the channel placement group + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:channelplacementgroup/cpg-12345" + channel_placement_group_id: + description: The ID of the channel placement group + type: str + returned: success + example: "234523" + cluster_id: + description: The ID of the cluster + type: str + returned: success + example: "123456" + created: + description: When the channel placement group was created + type: str + returned: success + example: "2025-03-31T23:08:55Z" + state: + description: The state of the channel placement group + type: str + returned: success + example: "ACTIVE" +""" + +import uuid +from typing import Dict, Literal + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory + + +class MediaLiveWaiterFactory(BaseWaiterFactory): + '''Custom waiter factory for MediaLive channel placement group resources''' + + @property + def _waiter_model_data(self): + '''Define custom waiters for MediaLive channel placement groups''' + return { + 'channel_placement_group_created': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeChannelPlacementGroup', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ASSIGNED', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'UNASSIGNED', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'CREATE_FAILED', + 'argument': 'State' + }, + { + 'state': 'retry', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + }, + ] + }, + 'channel_placement_group_deleted': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeChannelPlacementGroup', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'DELETED', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'DELETE_FAILED', + 'argument': 'State' + } + ] + } + } + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + + +class MediaLiveChannelPlacementGroupManager: + '''Manage AWS MediaLive Channel Placement Groups''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveChannelPlacementGroupManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self.waiter_factory = MediaLiveWaiterFactory(module, self.client) + self._channel_placement_group = {} + self.changed = False + + @property + def channel_placement_group(self): + return self._channel_placement_group + + @channel_placement_group.setter + def channel_placement_group(self, channel_placement_group: Dict): + if channel_placement_group.get('response_metadata'): + del channel_placement_group['response_metadata'] + if channel_placement_group.get('id'): + channel_placement_group['channel_placement_group_id'] = channel_placement_group.get('id') + del channel_placement_group['id'] + self._channel_placement_group = channel_placement_group + + def do_create_channel_placement_group(self, params): + """ + Create a new MediaLive channel placement group + + Args: + params: Parameters for channel placement group creation + """ + allowed_params = ['cluster_id', 'nodes', 'name', 'request_id'] + + 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_channel_placement_group(**create_params) # type: ignore + self.channel_placement_group = camel_dict_to_snake_dict(response) + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to create MediaLive Channel Placement Group', + exception=e + ) + + def do_update_channel_placement_group(self, params): + """ + Update a MediaLive channel placement group + + Args: + params: Parameters for channel placement group update + """ + if not params.get('channel_placement_group_id'): + raise MedialiveAnsibleAWSError( + message='The channel_placement_group_id parameter is required during placement group update.') + + allowed_params = ['cluster_id', 'channel_placement_group_id', 'nodes', 'name'] + + current_params = {k: v for k, v in self.channel_placement_group.items() if k in allowed_params} + updated_params = {k: v for k, v in params.items() if k in allowed_params and v} + + # Check if params need updating + if not recursive_diff(current_params, updated_params): + return + + # Update nodes if provided + if params.get('nodes'): + try: + # Check if nodes need updating + current_nodes = self.channel_placement_group.get('nodes', []) + if set(current_nodes) != set(params.get('nodes')): + update_params = { + 'ChannelPlacementGroupId': params.get('channel_placement_group_id'), + 'ClusterId': params.get('cluster_id'), + 'Nodes': params.get('nodes') + } + + # Add name if provided + if params.get('name'): + update_params['Name'] = params.get('name') + + self.client.update_channel_placement_group(**update_params) # type: ignore + self.changed = True + + # Refresh placement group data + self.get_channel_placement_group_by_id( + params.get('channel_placement_group_id'), + params.get('cluster_id') + ) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to update nodes for MediaLive Channel Placement Group', + exception=e + ) + # Update name if provided (and nodes not provided) + elif params.get('name') and self.channel_placement_group.get('name') != params.get('name'): + try: + update_params = { + 'ChannelPlacementGroupId': params.get('channel_placement_group_id'), + 'ClusterId': params.get('cluster_id'), + 'Name': params.get('name') + } + + self.client.update_channel_placement_group(**update_params) # type: ignore + self.changed = True + + # Refresh placement group data + self.get_channel_placement_group_by_id( + params.get('channel_placement_group_id'), + params.get('cluster_id') + ) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to update name for MediaLive Channel Placement Group', + exception=e + ) + + def get_channel_placement_group_by_id(self, placement_group_id: str, cluster_id: str): + """ + Get a channel placement group by ID + + Args: + placement_group_id: The ID of the channel placement group to retrieve + cluster_id: The ID of the cluster + """ + try: + response = self.client.describe_channel_placement_group( + ChannelPlacementGroupId=placement_group_id, + ClusterId=cluster_id + ) + self.channel_placement_group = camel_dict_to_snake_dict(response) + except is_boto3_error_code('ResourceNotFoundException'): + self.channel_placement_group = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get MediaLive Channel Placement Group', + exception=e + ) + + def get_channel_placement_group_by_name(self, name: str, cluster_id: str): + """ + Get a channel placement group by name + + Args: + name: The name of the channel placement group to retrieve + cluster_id: The ID of the cluster + """ + try: + response = self.client.list_channel_placement_groups(ClusterId=cluster_id) # type: ignore + if response['ChannelPlacementGroups']: + found = [] + for cpg in response['ChannelPlacementGroups']: + if cpg['Name'] == name: + found.append(camel_dict_to_snake_dict(cpg)) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Channel Placement Groups under the same name') + elif len(found) == 1: + self.channel_placement_group = camel_dict_to_snake_dict(found[0]) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get MediaLive Channel Placement Group', + exception=e + ) + + def delete_channel_placement_group(self, placement_group_id: str, cluster_id: str): + """ + Delete a MediaLive channel placement group + + Args: + placement_group_id: ID of the channel placement group to delete + cluster_id: ID of the cluster + """ + try: + self.client.delete_channel_placement_group( + ChannelPlacementGroupId=placement_group_id, + ClusterId=cluster_id + ) + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.channel_placement_group = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to delete MediaLive Channel Placement Group', + exception=e + ) + + def wait_for( + self, + want: Literal['channel_placement_group_created', 'channel_placement_group_deleted'], + placement_group_id: str, + cluster_id: str, + wait_timeout: int = 60) -> None: + """ + Wait for a channel placement group to reach the desired state + + Args: + want: The desired state to wait for + placement_group_id: The ID of the channel placement group + cluster_id: The ID of the cluster + wait_timeout: Maximum time to wait in seconds + """ + try: + waiter = self.waiter_factory.get_waiter(want) + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + ChannelPlacementGroupId=placement_group_id, + ClusterId=cluster_id, + WaiterConfig=config + ) + except WaiterError as e: + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for channel placement group {placement_group_id} to be {want.lower()}', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['channel_placement_group_id']), + cluster_id=dict(type='str', required=True), + nodes=dict(type='list', elements='str', required=False), + name=dict(type='str', required=False), + state=dict(type='str', default='present', choices=['present', 'absent']), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=60), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'channel_placement_group_id', 'name')], + required_if=[ + ('state', 'absent', ['id', 'channel_placement_group_id'], True), + ], + ) + + # Extract module parameters + placement_group_id = get_arg('id', module.params, argument_spec) + cluster_id = get_arg('cluster_id', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + nodes = get_arg('nodes', module.params, argument_spec) + name = get_arg('name', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveChannelPlacementGroupManager(module) + + # Find the placement group by ID if provided + if placement_group_id: + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + elif name: + manager.get_channel_placement_group_by_name(name, cluster_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.channel_placement_group: + if not placement_group_id: + placement_group_id = manager.channel_placement_group.get('channel_placement_group_id') + update_params = { + 'channel_placement_group_id': placement_group_id, + 'cluster_id': cluster_id, + 'nodes': nodes, + 'name': name + } + + manager.do_update_channel_placement_group(update_params) + + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + + # Case create + else: + create_params = { + 'cluster_id': cluster_id, + 'nodes': nodes, + 'name': name, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_channel_placement_group(create_params) + placement_group_id = manager.channel_placement_group.get('channel_placement_group_id') + + # Wait for the placement group to be created + if wait and placement_group_id: + manager.wait_for('channel_placement_group_created', placement_group_id, cluster_id, wait_timeout) + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + + # Handle absent state + elif state == 'absent': + if manager.channel_placement_group.get('state') != 'DELETED': + # Placement group exists, delete it + manager.delete_channel_placement_group(placement_group_id, cluster_id) + + # Wait for the placement group to be deleted if requested + if wait and placement_group_id: + manager.wait_for('channel_placement_group_deleted', placement_group_id, cluster_id, wait_timeout) + + module.exit_json(changed=manager.changed, channel_placement_group=manager.channel_placement_group) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_channel_placement_group_info.py b/plugins/modules/medialive_channel_placement_group_info.py new file mode 100644 index 00000000000..a0ccb7e361c --- /dev/null +++ b/plugins/modules/medialive_channel_placement_group_info.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# Copyright: (c) 2025, Your Name +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: medialive_channel_placement_group_info +short_description: Get information about an AWS MediaLive Channel Placement Group +version_added: 10.1.0 +description: + - Get information about an AWS MediaLive Channel Placement Group. + - This module allows retrieving detailed information about a specific channel placement group. +author: + - "David Teach" +options: + channel_placement_group_id: + description: + - The ID of the Channel Placement Group. + required: true + type: str + cluster_id: + description: + - The ID of the cluster. + required: true + type: str + aws_access_key: + description: + - AWS access key ID. + type: str + aws_secret_key: + description: + - AWS secret access key. + type: str + region: + description: + - The AWS region to use. + required: true + type: str + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +''' + +EXAMPLES = r''' +# Get information about a specific channel placement group +- name: Get channel placement group info + aws_medialive_channel_placement_group_info: + channel_placement_group_id: "cpg-12345" + cluster_id: "cluster-67890" + region: "us-west-2" +''' + +RETURN = r''' +placement_group: + description: The channel placement group information + type: dict + returned: always + contains: + arn: + description: The ARN of the channel placement group + type: str + returned: always + channel_placement_group_id: + description: The ID of the channel placement group + type: str + returned: always + cluster_id: + description: The ID of the cluster + type: str + returned: always + created: + description: When the channel placement group was created + type: str + returned: always + state: + description: The state of the channel placement group + type: str + returned: always +''' + +try: + import botocore +except ImportError: + pass # Handled by AnsibleAWSModule + +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.ec2 import AWSRetry + + +def get_channel_placement_group(client, module): + """ + Get the channel placement group information + """ + cpgs = [] + try: + if not module.params['channel_placement_group_id']: + response = client.list_channel_placement_groups( + ClusterId=module.params['cluster_id'] + ) + if response['ChannelPlacementGroups']: + cpgs = response['ChannelPlacementGroups'] + else: + response = client.describe_channel_placement_group( + ChannelPlacementGroupId=module.params['channel_placement_group_id'], + ClusterId=module.params['cluster_id'] + ) + if response.get('Arn', None): + cpgs.append(response) + if len(cpgs) == 0: + return cpgs + results = [camel_dict_to_snake_dict(cpg) for cpg in cpgs] + return results + except client.exceptions.NotFoundException: + return cpgs + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg="Failed to get channel placement group information") + + +def main(): + argument_spec = dict( + cluster_id=dict(required=True, type='str'), + channel_placement_group_id=dict(required=False, type='str'), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True + ) + + try: + client = module.client('medialive', retry_decorator=AWSRetry.exponential_backoff()) + cpgs = get_channel_placement_group(client, module) + module.exit_json(changed=False, channel_placement_groups=cpgs) + except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + module.fail_json_aws(e, msg='Failed to connect to AWS') + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_cluster.py b/plugins/modules/medialive_cluster.py new file mode 100644 index 00000000000..630c3768edd --- /dev/null +++ b/plugins/modules/medialive_cluster.py @@ -0,0 +1,608 @@ +#!/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_cluster +short_description: Manage AWS MediaLive Anywhere clusters +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive Anywhere clusters. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the cluster. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['cluster_id'] + name: + description: + - The name of the cluster. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['cluster_name'] + state: + description: + - Create/update or remove the cluster. + required: false + choices: ['present', 'absent'] + default: 'present' + type: str + cluster_type: + description: + - The hardware type for the cluster. + - Currently only 'ON_PREMISES' is supported. + - Required when creating a new cluster. + type: str + required: false + default: 'ON_PREMISES' + choices: ['ON_PREMISES'] + instance_role_arn: + description: + - The ARN of the IAM role for the Node in this Cluster. + - The role must include all the operations that you expect these Nodes to perform. + - Required when creating a new cluster. + type: str + required: false + network_settings: + description: + - Network settings that connect the Nodes in the Cluster to one or more of the Networks. + - Required when creating a new cluster. + type: dict + required: false + suboptions: + default_route: + description: + - The network interface that is the default route for traffic to and from the node. + - This should match one of the logical interface names defined in interface_mappings. + type: str + required: true + interface_mappings: + description: + - An array of interface mapping objects for this Cluster. + - Each mapping logically connects one interface on the nodes with one Network. + type: list + elements: dict + required: true + suboptions: + logical_interface_name: + description: + - The logical name for one interface that handles a specific type of traffic. + type: str + required: true + network_id: + description: + - The ID of the network to connect to the specified logical interface name. + type: str + required: true + wait: + description: + - Whether to wait for the cluster to reach the desired state. + - When I(state=present), wait for the cluster to reach the ACTIVE state. + - When I(state=absent), wait for the cluster to be deleted. + type: bool + required: false + default: true + wait_timeout: + description: + - The maximum time in seconds to wait for the cluster to reach the desired state. + - Defaults to 600 seconds. + type: int + required: false + default: 600 + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Create a MediaLive Anywhere cluster +- community.aws.medialive_cluster: + name: 'ExampleCluster' + state: present + cluster_type: 'ON_PREMISES' + instance_role_arn: 'arn:aws:iam::123456789012:role/MediaLiveAnywhereNodeRole' + network_settings: + default_route: 'management' + interface_mappings: + - logical_interface_name: 'management' + network_id: 'network-1234abcd' + - logical_interface_name: 'input' + network_id: 'network-5678efgh' + tags: + Environment: 'Production' + Project: 'MediaLive' + +# Delete a MediaLive Anywhere cluster +- community.aws.medialive_cluster: + name: 'ExampleCluster' + state: absent +""" + +RETURN = r""" +cluster: + description: The details of the cluster + returned: success + type: dict + contains: + arn: + description: The ARN of the cluster. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:cluster/1234abcd-12ab-34cd-56ef-1234567890ab" + channel_ids: + description: The IDs of channels associated with the cluster. + type: list + elements: str + returned: success + example: ["channel-1234abcd"] + cluster_type: + description: The hardware type for the cluster. + type: str + returned: success + example: "ON_PREMISES" + id: + description: The ID of the cluster. + type: str + returned: success + example: "1234abcd-12ab-34cd-56ef-1234567890ab" + instance_role_arn: + description: The ARN of the IAM role for the Node in this Cluster. + type: str + returned: success + example: "arn:aws:iam::123456789012:role/MediaLiveAnywhereNodeRole" + name: + description: The name of the cluster. + type: str + returned: success + example: "ExampleCluster" + network_settings: + description: Network settings that connect the Nodes in the Cluster to Networks. + type: dict + returned: success + contains: + default_route: + description: The network interface that is the default route for traffic. + type: str + returned: success + example: "management" + interface_mappings: + description: An array of interface mapping objects for this Cluster. + type: list + elements: dict + returned: success + contains: + logical_interface_name: + description: The logical name for one interface. + type: str + returned: success + example: "management" + network_id: + description: The ID of the network connected to the interface. + type: str + returned: success + example: "network-1234abcd" + state: + description: The state of the cluster. + type: str + returned: success + example: "ACTIVE" + tags: + description: The tags assigned to the cluster. + type: dict + returned: success + example: {"Environment": "Production", "Project": "MediaLive"} +""" + +import uuid +from typing import Dict + +try: + from botocore.exceptions import WaiterError, 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 + +from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory + + +class MediaLiveWaiterFactory(BaseWaiterFactory): + """Custom waiter factory for MediaLive resources""" + + @property + def _waiter_model_data(self): + """Define custom waiters for MediaLive clusters""" + return { + 'create_cluster': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeCluster', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ACTIVE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IDLE', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'CREATE_FAILED', + 'argument': 'State' + }, + { + 'state': 'retry', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + }, + ] + }, + 'update_cluster': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeCluster', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ACTIVE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IN_USE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IDLE', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'CREATE_FAILED', + 'argument': 'State' + }, + ] + }, + 'delete_cluster': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeCluster', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'DELETED', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'DELETE_FAILED', + 'argument': 'State' + }, + ] + } + } + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + + +class MediaLiveClusterManager: + """Manage AWS MediaLive Anywhere clusters""" + + def __init__(self, module: AnsibleAWSModule): + """ + Initialize the MediaLiveClusterManager + + Args: + module: AnsibleAWSModule instance + """ + self.module = module + self.client = module.client('medialive') + self.waiter_factory = MediaLiveWaiterFactory(module, self.client) + self._cluster = {} + self.changed = False + + @property + def cluster(self): + return self._cluster + + @cluster.setter + def cluster(self, cluster: Dict): + cluster = camel_dict_to_snake_dict(cluster) + if cluster.get('response_metadata'): + del cluster['response_metadata'] + if cluster.get('id'): + cluster['cluster_id'] = cluster.get('id') + del cluster['id'] + self._cluster = cluster + + def do_create_cluster(self, params): + """ + Create a new MediaLive cluster + + Args: + params: Parameters for cluster creation + """ + allowed_params = ['cluster_type', 'instance_role_arn', 'name', 'network_settings', 'request_id'] + required_params = ['instance_role_arn', 'name', 'network_settings'] + + for param in required_params: + if not params.get(param): + raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new cluster') + + 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_cluster(**create_params) # type: ignore + self.cluster = camel_dict_to_snake_dict(response) + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive Cluster', + exception=e + ) + + def do_update_cluster(self, params): + """ + Update a new MediaLive cluster + + Args: + params: Parameters for cluster update + """ + if not params.get('cluster_id'): + raise MedialiveAnsibleAWSError(message='The cluster_id parameter is required during cluster update.') + + allowed_params = ['cluster_id', 'name', 'network_settings'] + + + current_params = { k: v for k, v in self.cluster.items() if k in allowed_params } + 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_cluster(**update_params) # type: ignore + self.cluster = camel_dict_to_snake_dict(response) + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to update Medialive Cluster', + exception=e + ) + + def get_cluster_by_name(self, name: str): + """ + Find a cluster by name + + Args: + name: The name of the cluster to find + """ + try: + paginator = self.client.get_paginator('list_clusters') # type: ignore + found = [] + for page in paginator.paginate(): + for cluster in page.get('Clusters', []): + if cluster.get('Name') == name: + found.append(cluster.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Clusters under the same name') + elif len(found) == 1: + self.get_cluster_by_id(found[0]) + + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get MediaLive Cluster', + exception=e + ) + + def get_cluster_by_id(self, id: str): + """ + Get a cluster by ID + + Args: + id: The ID of the cluster to retrieve + """ + try: + self.cluster = self.client.describe_cluster(ClusterId=id) # type: ignore + return True + except is_boto3_error_code('ResourceNotFoundException'): + self.cluster = {} + + def delete_cluster(self, cluster_id: str): + """ + Delete a MediaLive cluster + + Args: + cluster_id: ID of the cluster to delete + """ + try: + self.client.delete_cluster(ClusterId=cluster_id) # type: ignore + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.cluster = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to delete Medialive Cluster', + exception=e + ) + + def wait_for(self, want: str, cluster_id: str, wait_timeout: int = 60): + """ + Invoke one of the custom waiters and wait + + Args: + want: the name of the waiter + cluster_id: the ID of the cluster + wait_timeout: the maximum amount of time to wait in seconds (default: 60) + """ + + try: + waiter = self.waiter_factory.get_waiter(want) + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + ClusterId=cluster_id, + WaiterConfig=config + ) + except WaiterError as e: + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for cluster {cluster_id}', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['cluster_id']), + name=dict(type='str', required=False, aliases=['cluster_name']), + state=dict(type='str', default='present', choices=['present', 'absent']), + cluster_type=dict(type='str', required=False, default='ON_PREMISES', choices=['ON_PREMISES']), + instance_role_arn=dict(type='str', required=False), + network_settings=dict( + type='dict', + required=False, + options=dict( + default_route=dict(type='str', required=True), + interface_mappings=dict( + type='list', + elements='dict', + required=True, + options=dict( + logical_interface_name=dict(type='str', required=True), + network_id=dict(type='str', required=True), + ) + ) + ) + ), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=600), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'cluster_id', 'name', 'cluster_name')] + ) + + # Extract module parameters + cluster_id = get_arg('id', module.params, argument_spec) + cluster_name = get_arg('name', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + cluster_type = get_arg('cluster_type', module.params, argument_spec) + instance_role_arn = get_arg('instance_role_arn', module.params, argument_spec) + network_settings = get_arg('network_settings', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveClusterManager(module) + + # Find the cluster by ID or name + # Update manager.cluster with the details + if cluster_id: + manager.get_cluster_by_id(cluster_id) + elif cluster_name: + manager.get_cluster_by_name(cluster_name) + cluster_id = manager.cluster.get('cluster_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.cluster: + update_params = { + 'name': cluster_name, + 'network_settings': network_settings, + 'cluster_id': cluster_id + } + + manager.do_update_cluster(update_params) + + # Wait for the cluster to be updated + if wait and cluster_id: + manager.wait_for('update_cluster', cluster_id, wait_timeout) # type: ignore + manager.get_cluster_by_id(cluster_id) + + # Case create + else: + create_params = { + 'name': cluster_name, + 'cluster_type': cluster_type, + 'instance_role_arn': instance_role_arn, + 'network_settings': network_settings, + 'cluster_id': cluster_id, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_cluster(create_params) + cluster_id = manager.cluster.get('cluster_id') + + # Wait for the cluster to be created + if wait and cluster_id: + manager.wait_for('create_cluster', cluster_id, wait_timeout) # type: ignore + manager.get_cluster_by_id(cluster_id) + + # Handle absent state + elif state == 'absent': + if manager.cluster: + # Cluster exists, delete it + cluster_id = manager.cluster.get('cluster_id') + manager.delete_cluster(cluster_id) # type: ignore + + # Wait for the cluster to be deleted if requested + if wait and cluster_id: + manager.wait_for('delete_cluster', cluster_id, wait_timeout) # type: ignore + + module.exit_json(changed=manager.changed, cluster=manager.cluster) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_cluster_info.py b/plugins/modules/medialive_cluster_info.py new file mode 100644 index 00000000000..230721ed66f --- /dev/null +++ b/plugins/modules/medialive_cluster_info.py @@ -0,0 +1,231 @@ +#!/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_cluster +short_description: Gather MediaLive Anywhere cluster info +version_added: 10.1.0 +description: + - Get details about a AWS MediaLive Anywhere cluster. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the cluster. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['cluster_id'] + name: + description: + - The name of the cluster. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['cluster_name'] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Find a MediaLive Anywhere cluster by ID +- community.aws.medialive_cluster_info: + id: '1234567' + register: found_cluster + +# Find a MediaLive Anywhere cluster by name +- community.aws.medialive_cluster_info: + name: 'ExampleCluster' + register: found_cluster +""" + +RETURN = r""" +cluster: + description: The details of the cluster + returned: success + type: dict + contains: + arn: + description: The ARN of the cluster. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:cluster/1234abcd-12ab-34cd-56ef-1234567890ab" + channel_ids: + description: The IDs of channels associated with the cluster. + type: list + elements: str + returned: success + example: ["channel-1234abcd"] + cluster_type: + description: The hardware type for the cluster. + type: str + returned: success + example: "ON_PREMISES" + id: + description: The ID of the cluster. + type: str + returned: success + example: "1234abcd-12ab-34cd-56ef-1234567890ab" + instance_role_arn: + description: The ARN of the IAM role for the Node in this Cluster. + type: str + returned: success + example: "arn:aws:iam::123456789012:role/MediaLiveAnywhereNodeRole" + name: + description: The name of the cluster. + type: str + returned: success + example: "ExampleCluster" + network_settings: + description: Network settings that connect the Nodes in the Cluster to Networks. + type: dict + returned: success + contains: + default_route: + description: The network interface that is the default route for traffic. + type: str + returned: success + example: "management" + interface_mappings: + description: An array of interface mapping objects for this Cluster. + type: list + elements: dict + returned: success + contains: + logical_interface_name: + description: The logical name for one interface. + type: str + returned: success + example: "management" + network_id: + description: The ID of the network connected to the interface. + type: str + returned: success + example: "network-1234abcd" + state: + description: The state of the cluster. + type: str + returned: success + example: "ACTIVE" +""" + +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 MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveClusterGetter: + '''Look up AWS MediaLive Anywhere clusters''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveClusterGetter + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self._cluster = {} + + @property + def cluster(self): + return self._cluster + + @cluster.setter + def cluster(self, cluster: Dict): + cluster = camel_dict_to_snake_dict(cluster) + if cluster.get('response_metadata'): + del cluster['response_metadata'] + if cluster.get('id'): + cluster['cluster_id'] = cluster.get('id') + del cluster['id'] + self._cluster = cluster + + def get_cluster_by_name(self, name: str): + """ + Find a cluster by name + + Args: + name: The name of the cluster to find + """ + try: + paginator = self.client.get_paginator('list_clusters') # type: ignore + for page in paginator.paginate(): + found = [] + for cluster in page.get('Clusters', []): + if cluster.get('Name') == name: + found.append(cluster.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Clusters under the same name') + elif len(found) == 1: + self.get_cluster_by_id(found[0]) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get MediaLive Cluster', + exception=e + ) + + def get_cluster_by_id(self, id: str): + """ + Get a cluster by ID + + Args: + id: The ID of the cluster to retrieve + """ + try: + self.cluster = self.client.describe_cluster(ClusterId=id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.cluster = {} + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['cluster_id']), + name=dict(type='str', required=False, aliases=['cluster_name']), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'cluster_id', 'name', 'cluster_name')] + ) + + # Extract module parameters + cluster_id = get_arg('id', module.params, argument_spec) + cluster_name = get_arg('name', module.params, argument_spec) + + # Initialize the manager + getter = MediaLiveClusterGetter(module) + + # Find the cluster by ID or name + if cluster_id: + getter.get_cluster_by_id(cluster_id) + elif cluster_name: + getter.get_cluster_by_name(cluster_name) + + module.exit_json(changed=False, cluster=getter.cluster) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_input.py b/plugins/modules/medialive_input.py new file mode 100644 index 00000000000..cae1ee8a706 --- /dev/null +++ b/plugins/modules/medialive_input.py @@ -0,0 +1,964 @@ +#!/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_input +short_description: Manage AWS MediaLive Anywhere inputs +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive inputs. + - Requires boto3 >= 1.37.30 +author: + - "Sergey Papyan" +options: + name: + description: + - Name of the input + - Mutually exclusive with O(id) + type: str + id: + description: + - ID of the input + - Mutually exclusive with O(name) + type: str + destinations: + description: + - A list of destination settings for PUSH type inputs + - Required if O(type=UDP_PUSH) + - Required if O(type=RTP_PUSH) + - Required if O(type=RTMP_PUSH) + type: list + elements: dict + suboptions: + stream_name: + description: + - A unique name for the location the RTMP stream is being pushed to + required: true + type: str + network: + description: + - The ID of the attached network + - Required if O(input_network_location=ON_PREMISES) + type: str + network_routes: + description: + - The route of the input on the customer local network + - Required if O(input_network_location=ON_PREMISES) + type: list + elements: dict + suboptions: + cidr: + description: + - The CIDR of the route + required: true + type: str + gateway: + description: + - An optional gateway for the route + type: str + static_ip_address: + description: + - IP address of the input on the customer local network + - Optional if O(input_network_location=ON_PREMISES) + type: str + input_devices: + description: + - Settings for the input devices + - Required if O(type=INPUT_DEVICE) + type: list + elements: dict + suboptions: + id: + description: + - The unique ID for the device + type: str + required: true + input_security_groups: + description: + - A list of security groups referenced by IDs to attach to the input + - Mutually exclusive with O(input_vpc_request) + type: list + elements: str + media_connect_flows: + description: + - A list of the MediaConnect Flows that you want to use in this input + - You can specify as few as one Flow and presently, as many as two + - | + The only requirement is when you have more than one is that + each Flow is in a separate Availability Zone as this ensures + your EML input is redundant to AZ issues. + - Required if O(type=MEDIACONNECT) + type: list + elements: dict + suboptions: + flow_arn: + description: + - The ARN of the MediaConnect Flow that you want to use as a source + type: str + required: true + role_arn: + description: + - The Amazon Resource Name (ARN) of the role this input assumes during and after creation + - Required if O(vpc) + type: str + sources: + description: + - The source URLs for PULL type inputs + - Two sources must be provided + - If identical, the input_class of the resulting Input will be SINGLE_PIPELINE + - If not idential, the input_class of the resulting Input will be STANDARD + - Required if O(type=RTMP_PULL) + - Required if O(type=URL_PULL) + - Required if O(type=MP4_FILE) + - Required if O(type=TS_FILE) + type: list + elements: dict + suboptions: + password_param: + description: + - The key used to extract the password from SSM Parameter store + type: str + url: + description: + - This represents the customer’s source URL where stream is pulled from + required: true + type: str + username: + description: + - The username for the input source + type: str + type: + description: + - The type of input to create + - Required if O(state=present) + type: str + choices: + - UDP_PUSH + - RTP_PUSH + - RTMP_PUSH + - RTMP_PULL + - URL_PULL + - MP4_FILE + - MEDIACONNECT + - INPUT_DEVICE + - AWS_CDI + - TS_FILE + - SRT_CALLER + - MULTICAST + - SMPTE_2110_RECEIVER_GROUP + - SDI + vpc: + description: + - Settings for a private VPC Input + - When this property is specified, the input destination addresses will be created in a VPC rather than with public Internet addresses + - Mutually exclusive with O(input_security_groups) + type: dict + suboptions: + security_group_ids: + description: + - A list of up to 5 EC2 VPC security group IDs to attach to the Input VPC network interfaces + - If none are specified then the VPC default security group will be used + type: list + elements: str + subnet_ids: + description: + - A list of 2 VPC subnet IDs from the same VPC + - Subnet IDs must be mapped to two unique availability zones (AZ) + type: list + required: true + elements: str + srt_settings: + description: + - The settings associated with an SRT input + type: dict + suboptions: + srt_caller_sources: + description: + - A list of connection configurations for sources that use SRT as the connection protocol + - In terms of establishing the connection, MediaLive is always the caller and the upstream system is always the listener + - In terms of transmission of the source content, MediaLive is always the receiver and the upstream system is always the sender + type: list + elements: dict + suboptions: + decryption: + description: + - Decryption configuration + - Required only if the content is encrypted + type: dict + suboptions: + algorithm: + description: + - The algorithm used to encrypt content + type: str + passphrase_secret_arn: + description: + - The ARN for the secret in Secrets Manager + - The secret holds the passphrase that MediaLive will use to decrypt the source content + - The secret must be created in advance + type: str + minimum_latency: + description: + - The preferred latency (in milliseconds) for implementing packet loss and recovery + - Packet recovery is a key feature of SRT + - Obtain this value from the operator at the upstream system + type: int + srt_listener_address: + description: + - The IP address at the upstream system (the listener) that MediaLive (the caller) will connect to + type: str + srt_listener_port: + description: + - The port at the upstream system (the listener) that MediaLive (the caller) will connect to + type: str + stream_id: + description: + - | + This value is required if the upstream system uses this identifier because without it, + the SRT handshake between MediaLive (the caller) and the upstream system (the listener) might fail + type: str + input_network_location: + description: + - The location of this input + - AWS for an input existing in the AWS Cloud + - ON_PREMISES for an input in a customer network + - Required if O(type=RTP) + - Required if O(type=RTMP_PUSH) + - Must be AWS if O(type=URL_PULL) + - Must be ON_PREMISES if O(type=SDI) + type: str + choices: ['AWS', 'ON_PREMISES'] + multicast_settings: + description: + - Multicast Input settings + - Required if O(type=MULTICAST) + type: dict + suboptions: + sources: + description: + - List of pairs of multicast urls and source ip addresses that make up a multicast source + type: list + elements: dict + suboptions: + source_ip: + description: + - This represents the ip address of the device sending the multicast stream + type: str + url: + description: + - This represents the ip address of the device sending the multicast stream + type: str + required: true + smpte_2110_receiver_group_settings: + description: + - Settings to identify the stream sources + - Required if O(type=SMPTE_2110_RECEIVER_GROUP) + type: dict + suboptions: + smpte_2110_receiver_groups: + description: + - List of receiver groups + - A receiver group is a collection of video, audio, and ancillary streams that you want to group together and attach to one input + type: list + elements: dict + suboptions: + sdp_settings: + description: + - The single dict of settings that identify the video, audio, and ancillary streams for this receiver group + type: dict + suboptions: + ancillary_sdps: + description: + - A list of input SDP locations + - Each item in the list specifies the SDP file and index for one ancillary SMPTE 2110 stream + - Each stream encapsulates one captions stream (out of any number you can include) or the single SCTE 35 stream that you can include + type: list + elements: dict + suboptions: + media_index: + description: + - The index of the media stream in the SDP file for one SMPTE 2110 stream + type: int + sdp_url: + description: + - The URL of the SDP file for one SMPTE 2110 stream + type: str + audio_sdps: + description: + - A list of input SDP locations + - Each item in the list specifies the SDP file and index for one audio SMPTE 2110 stream in a receiver group + type: list + elements: dict + suboptions: + media_index: + description: + - The index of the media stream in the SDP file for one SMPTE 2110 stream + type: int + sdp_url: + description: + - The URL of the SDP file for one SMPTE 2110 stream + type: str + video_sdp: + description: + - A dict that specifies the SDP file and index for the single video SMPTE 2110 stream for this 2110 input + type: dict + suboptions: + media_index: + description: + - The index of the media stream in the SDP file for one SMPTE 2110 stream + type: int + sdp_url: + description: + - The URL of the SDP file for one SMPTE 2110 stream + type: str + sdi_sources: + description: + - SDI Sources for this Input + - Must contain a single item - the ID of the SDI source + - Required if O(type=SDI) + type: list + elements: str + state: + description: + - Create/update or remove the input + choices: ['present', 'absent'] + default: 'present' + type: str + wait: + description: + - Whether to wait for the input to reach the desired state + - When I(state=present), wait for the input to reach the DETACHED state + - When I(state=absent), wait for the input to reach the DELETED state + type: bool + default: true + wait_timeout: + description: + - The maximum time in seconds to wait for the input to reach the desired state + - Defaults to 60 seconds + type: int + default: 60 + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 + - amazon.aws.tags +""" + +EXAMPLES = r""" +""" + +RETURN = r""" +""" + +from typing import Dict, List, Literal + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + """A personalized exception for this module""" + pass + +class MediaLiveInputManager: + """Manage AWS MediaLive Anywhere inputs""" + + def __init__(self, module: AnsibleAWSModule): + """ + Initialize the MediaLiveInputManager + + Args: + module: AnsibleAWSModule instance + """ + self.module = module + self.client = self.module.client('medialive') + self._input = {} + self.changed = False + + @property + def input(self): + """Simple getter for input""" + return self._input + + @input.setter + def input(self, input: Dict): + """Setter for input that takes care of normalizing raw API responses""" + tags = input.get('Tags') # To preserve case in tag keys + input = camel_dict_to_snake_dict(input) + if 'response_metadata' in input.keys(): + del input['response_metadata'] + if input.get('id'): + input['input_id'] = input.get('id') + del input['id'] + if tags: + input['tags'] = tags + self._input = input + + def validate_sdi_source(self, sources: List[str], check_use=False): + """ + Validates the following: + * the list of sdi_sources provided by the user contains only a single item + * the SDI Source exists + * if check_use=True then makes sure the state of the SDI Source is not IN_USE + """ + # Though still undocumented, if you pass more than one SDI Input Source ID + # to the CreateInput API you get the following BadRequest error + # "The SDI sources for an SDI input must specify exactly one SDI source" + if len(sources) != 1: + raise MedialiveAnsibleAWSError(message='The sdi_sources list must contain a single element') + + # Make sure the SDI source exists and is in IDLE state + try: + response = self.client.describe_sdi_source(SdiSourceId=sources[0]) # type: ignore + if check_use and response['SdiSource']['State'] == 'IN_USE': + raise MedialiveAnsibleAWSError(message='The provided sdi_source is already in use') + + except is_boto3_error_code('ResourceNotFoundException'): + raise MedialiveAnsibleAWSError(message='The provided sdi_source does not exist') + + def validate_input_network_location(self, location: str, input_type: str): + """ + Validates the following: + * if input_network_location + """ + pass + + def get_input_by_name(self, name: str): + """ + Find a input by name + + Args: + name: The name of the input to find + """ + + try: + paginator = self.client.get_paginator('list_inputs') # type: ignore + for page in paginator.paginate(): + for input in page.get('Inputs', []): + if input.get('Name') == name: + self.get_input_by_id(input.get('Id')) + return + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Input', + exception=e + ) + + def get_input_by_id(self, input_id: str): + """ + Get an input by ID + + Args: + input_id: The ID of the input to retrieve + """ + try: + self.input = self.client.describe_input(InputId=input_id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.input = {} + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Input', + exception=e + ) + + def wait_for(self, + want: Literal['input_attached','input_deleted','input_detached'], + input_id: str, + wait_timeout: int = 60): + """ + Wait for an Input to reach the wanted state + + Args: + want: the waiter to invoke + input_id: the ID of the Input + wait_timeout: the maximum amount of time to wait in seconds (default: 60) + """ + + try: + waiter = self.client.get_waiter(want) # type: ignore + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + InputId=input_id, + WaiterConfig=config + ) + except WaiterError as e: # type: ignore + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for Input {input_id}', + exception=e + ) + + def do_create_input(self, params): + """ + Create a new MediaLive input + + Args: + params: Parameters for input creation + """ + allowed_params = [ + 'destinations', + 'input_devices', + 'input_security_groups', + 'media_connect_flows', + 'name', + 'role_arn', + 'sources', + 'type', + 'vpc', + 'srt_settings', + 'input_network_location', + 'multicast_settings', + 'smpte_2110_receiver_group_settings', + 'sdi_sources', + 'tags' + ] + + create_params = { k: v for k, v in params.items() if k in allowed_params and v } + + # Do some extra validation + sdi_sources = create_params.get('sdi_sources') + if sdi_sources: + self.validate_sdi_source(sdi_sources) + + tags = create_params.get('tags') # To preserve case in tag keys + create_params = snake_dict_to_camel_dict(create_params, capitalize_first=True) + + if tags and create_params: + create_params['Tags'] = tags + + try: + self.input = self.client.create_input(**create_params)['Input'] # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive Input', + exception=e + ) + + def do_update_input(self, params): + """ + Update a new MediaLive input + + Args: + params: Parameters for input update + """ + if not params.get('input_id'): + raise MedialiveAnsibleAWSError(message='The input_id parameter is required during input update.') + + tags = params.get('tags') + purge_tags = params.get('purge_tags') + del params['tags'] + del params['purge_tags'] + + allowed_params = [ + 'destinations', + 'input_devices', + 'input_id', + 'input_security_groups', + 'media_connect_flows', + 'name', + 'role_arn', + 'sources', + 'srt_settings', + 'multicast_settings', + 'smpte_2110_receiver_group_settings', + 'sdi_sources' + ] + + update_params = { k: v for k, v in params.items() if k in allowed_params and v } + + current_params = {} + for k, v in self.input.items(): + if k in allowed_params and k in update_params: + current_params[k] = v + + try: + if recursive_diff(current_params, update_params): + update_params = snake_dict_to_camel_dict(update_params, capitalize_first=True) + self.input = self.client.update_input(**update_params)['Input'] # type: ignore + self.changed = True + if tags and self._update_tags(tags, purge_tags): + self.input = self.get_input_by_id(self.input['input_id']) # type: ignore + self.changed = True + + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to update Medialive Input', + exception=e + ) + + def delete_input(self, input_id: str): + """ + Delete a MediaLive input + + Args: + input_id: ID of the input to delete + """ + try: + self.client.delete_input(InputId=input_id) # type: ignore + self.input = {} + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.input = {} + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to delete Medialive Input', + exception=e + ) + + def _update_tags(self, tags: dict, purge: bool) -> bool: + """ + Takes care of updating Input tags + + Args: + tags: a dict of tags supplied by the user + purge: whether or not to delete existing tags that aren't in the tags dict + Returns: + True if tags were updated, otherwise False + """ + + # Short-circuit + if self.module.check_mode: + return False + + to_add, to_delete = compare_aws_tags(self.input['tags'], tags, purge) + + if not any((to_add, to_delete)): + return False + + try: + if to_add: + self.client.create_tags(ResourceArn=self.input['arn'], Tags=to_add) # type: ignore + if to_delete: + self.client.delete_tags(ResourceArn=self.input['arn'], TagKeys=to_delete) # type: ignore + except (ClientError, BotoCoreError) as e: # type: ignore + raise MedialiveAnsibleAWSError( + message='Unable to update MediaLive Input resource Tags', + exception=e + ) + + return True + + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', aliases=['input_id']), + name=dict(type='str', aliases=['input_name']), + state=dict(type='str', default='present', choices=['present', 'absent']), + request_id=dict(type='str'), + destinations=dict( + type='list', + elements='dict', + options=dict( + stream_name=dict(type='str', required=True), + network=dict(type='str'), + network_routes=dict( + type='list', + elements='dict', + options=dict( + cidr=dict(type='str', required=True), + gateway=dict(type='str') + ) + ), + static_ip_address=dict(type='str') # TODO: validate in code that input_network_location is ON_PREMISES + + ), + required_if=[('input_network_location', 'ON_PREMISES', ['network', 'network_routes'])] + ), + input_devices=dict( + type='list', + elements='dict', + options=dict( + id=dict(type='str', required=True) + ) + ), + input_security_groups=dict(type='list', elements='str'), + media_connect_flows=dict( + type='list', + elements='dict', + options=dict( + flow_arn=dict(type='str', required=True) + ) + ), + role_arn=dict(type='str'), + sources=dict( + type='list', + elements='dict', + options=dict( + password_param=dict(type='str'), + url=dict(type='str', required=True), + username=dict(type='str') + ) + ), + type=dict( + type='str', + choices=[ + 'UDP_PUSH', + 'RTP_PUSH', + 'RTMP_PUSH', + 'RTMP_PULL', + 'URL_PULL', + 'MP4_FILE', + 'MEDIACONNECT', + 'INPUT_DEVICE', + 'AWS_CDI', + 'TS_FILE', + 'SRT_CALLER', + 'MULTICAST', + 'SMPTE_2110_RECEIVER_GROUP', + 'SDI', + ] + ), + vpc=dict( + type='dict', + options=dict( + security_group_ids=dict(type='list', elements='str'), + subnet_ids=dict(type='list', elements='str', required=True) + ) + ), + srt_settings=dict( + type='dict', + options=dict( + srt_caller_sources=dict( + type='list', + elements='dict', + options=dict( + decryption=dict( + type='dict', + options=dict( + algorithm=dict(type='str'), + passphrase_secret_arn=dict(type='str') + ) + ), + minimum_latency=dict(type='int'), + srt_listener_address=dict(type='str'), + srt_listener_port=dict(type='str'), + stream_id=dict(type='str') + ) + ) + ) + ), + input_network_location=dict( + type='str', + choices=['AWS', 'ON_PREMISES'] # TODO: validate in code: Must be AWS if O(type=URL_PULL), ON_PREMISES if O(type=SDI) + ), + multicast_settings=dict( + type='dict', + options=dict( + sources=dict( + type='list', + elements='dict', + options=dict( + type='dict', + options=dict( + source_ip=dict(type='str'), + url=dict(type='str', required=True) + ) + ) + ) + ) + ), + smpte_2110_receiver_group_settings=dict( + type='dict', + options=dict( + smpte_2110_receiver_groups=dict( + type='list', + elements='dict', + options=dict( + sdp_settings=dict( + type='dict', + options=dict( + ancillary_sdps=dict( + type='list', + elements='dict', + options=dict( + meta_index=dict(type='int'), + sdp_url=dict(type='str') + ) + ), + audio_sdps=dict( + type='list', + elements='dict', + options=dict( + meta_index=dict(type='int'), + sdp_url=dict(type='str') + ) + ), + video_sdp=dict( + type='list', + elements='dict', + options=dict( + meta_index=dict(type='int'), + sdp_url=dict(type='str') + ) + ) + ) + ) + ) + ) + ) + ), + sdi_sources=dict( + type='list', + elements='str' + ), + tags=dict(type='dict'), + purge_tags=dict(type="bool", default=True), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=60), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + mutually_exclusive=[ + ('id', 'input_id', 'name', 'input_name'), + ('vpc', 'input_security_groups') + ], + required_one_of=[('id', 'input_id', 'name', 'input_name')], + required_if=[ + ('state', 'present', ['type']), + ('type', 'UDP_PUSH', ['destinations']), + ('type', 'RTP_PUSH', ['destinations']), + ('type', 'RTMP_PUSH', ['destinations', 'input_network_location']), + ('type', 'INPUT_DEVICE', ['input_devices']), + ('type', 'MEDIACONNECT', ['media_connect_flows']), + ('type', 'RTMP_PULL', ['sources']), + ('type', 'URL_PULL', ['sources', 'input_network_location']), + ('type', 'MP4_FILE', ['sources']), + ('type', 'TS_FILE', ['sources']), + ('type', 'RTP', ['input_network_location']), + ('type', 'SDI', ['input_network_location', 'sdi_sources']), + ('type', 'MULTICAST', ['multicast_settings']), + ('type', 'SMPTE_2110_RECEIVER_GROUP', ['smpte_2110_receiver_group_settings']), + ], + required_by={ + 'vpc': ('role_arn') + } + ) + + # Extract module arguments + input_id = get_arg('id', module.params, argument_spec) + input_name = get_arg('name', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + destinations = get_arg('destinations', module.params, argument_spec) + input_devices = get_arg('input_devices', module.params, argument_spec) + input_security_groups = get_arg('input_security_groups', module.params, argument_spec) + media_connect_flows = get_arg('media_connect_flows', module.params, argument_spec) + role_arn = get_arg('role_arn', module.params, argument_spec) + sources = get_arg('sources', module.params, argument_spec) + input_type = get_arg('type', module.params, argument_spec) + vpc = get_arg('vpc', module.params, argument_spec) + srt_settings = get_arg('srt_settings', module.params, argument_spec) + input_network_location = get_arg('input_network_location', module.params, argument_spec) + multicast_settings = get_arg('multicast_settings', module.params, argument_spec) + smpte_2110_receiver_group_settings = get_arg('smpte_2110_receiver_group_settings', module.params, argument_spec) + sdi_sources = get_arg('sdi_sources', module.params, argument_spec) + tags = get_arg('tags', module.params, argument_spec) + purge_tags = get_arg('purge_tags', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveInputManager(module) + + # Find the input by ID or name + if input_id: + manager.get_input_by_id(input_id) + elif input_name: + manager.get_input_by_name(input_name) + input_id = manager.input.get('input_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.input: + + update_params = { + 'destinations': destinations, + 'input_devices': input_devices, + 'input_id': input_id, + 'input_security_groups': input_security_groups, + 'media_connect_flows': media_connect_flows, + 'name': input_name, + 'role_arn': role_arn, + 'sources': sources, + 'srt_settings': srt_settings, + 'multicast_settings': multicast_settings, + 'smpte_2110_receiver_group_settings': smpte_2110_receiver_group_settings, + 'sdi_sources': sdi_sources, + 'tags': tags, + 'purge_tags': purge_tags + } + + manager.do_update_input(update_params) + + # Case create + else: + create_params = { + 'destinations': destinations, + 'input_devices': input_devices, + 'input_security_groups': input_security_groups, + 'media_connect_flows': media_connect_flows, + 'name': input_name, + 'role_arn': role_arn, + 'sources': sources, + 'type': input_type, + 'vpc': vpc, + 'srt_settings': srt_settings, + 'input_network_location': input_network_location, + 'multicast_settings': multicast_settings, + 'smpte_2110_receiver_group_settings': smpte_2110_receiver_group_settings, + 'sdi_sources': sdi_sources, + 'tags': tags + } + + manager.do_create_input(create_params) + input_id = manager.input.get('input_id') + + # Wait for the input to be created + if wait and input_id: + manager.wait_for('input_detached', input_id, wait_timeout) # type: ignore + manager.get_input_by_id(input_id) + + # Handle absent state + elif state == 'absent': + if manager.input: + # Network exists, delete it + input_id = manager.input.get('input_id') + manager.delete_input(input_id) # type: ignore + + # Wait for the input to be deleted + if wait and input_id: + manager.wait_for('input_deleted', input_id, wait_timeout) # type: ignore + + module.exit_json(changed=manager.changed, input=manager.input) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_input_info.py b/plugins/modules/medialive_input_info.py new file mode 100644 index 00000000000..622e9ca5738 --- /dev/null +++ b/plugins/modules/medialive_input_info.py @@ -0,0 +1,180 @@ +#!/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_input_info +short_description: Gather MediaLive Anywhere input info +version_added: 10.1.0 +description: + - A module for gathering information about AWS MediaLive inputs + - Requires boto3 >= 1.37.30 +author: + - "Sergey Papyan" +options: + name: + description: + - Name of the input + - At least this or id must be provided + type: str + id: + description: + - ID of the input + - At least this or name must be provided + type: str + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Find a MediaLive Anywhere Input by ID +- community.aws.medialive_input_info: + id: '1234567' + register: found_input + +# Find a MediaLive Anywhere Input by name +- community.aws.medialive_input_info: + name: 'ExampleInput' + register: found_input +""" + +RETURN = r""" +""" + +from typing import Dict, List + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags +from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + """A personalized exception for this module""" + pass + +class MediaLiveInputGetter: + """Gather info about AWS MediaLive Anywhere Inputs""" + + def __init__(self, module: AnsibleAWSModule): + """ + Initialize the MediaLiveInputGetter + + Args: + module: AnsibleAWSModule instance + """ + self.module = module + self.client = self.module.client('medialive') + self._input = {} + + @property + def input(self): + """Simple getter for input""" + return self._input + + @input.setter + def input(self, input: Dict): + """Setter for input that takes care of normalizing raw API responses""" + tags = input.get('Tags') # To preserve case in tag keys + input = camel_dict_to_snake_dict(input) + if 'response_metadata' in input.keys(): + del input['response_metadata'] # Unneeded + if input.get('id'): + input['input_id'] = input.get('id') + del input['id'] + if tags: + input['tags'] = tags + self._input = input + + def find_input_by_name(self, name: str): + """ + Find an Input by name + + Args: + name: The name of the input to find + """ + + try: + paginator = self.client.get_paginator('list_inputs') # type: ignore + found = [] + for page in paginator.paginate(): + for input in page.get('Inputs', []): + if input.get('Name') == name: + found.append(input.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Inputs under the same name') + elif len(found) == 1: + self.get_input_by_id(found[0]) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Input', + exception=e + ) + + def get_input_by_id(self, input_id: str): + """ + Get an input by ID + + Args: + input_id: The ID of the input to retrieve + """ + try: + self.input = self.client.describe_input(InputId=input_id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.input = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Input', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', aliases=['input_id']), + name=dict(type='str', aliases=['input_name']), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'input_id', 'name', 'input_name')] + ) + + # Extract module arguments + input_id = get_arg('id', module.params, argument_spec) + input_name = get_arg('name', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveInputGetter(module) + + # Find the input by ID or name + if input_id: + manager.get_input_by_id(input_id) + elif input_name: + manager.find_input_by_name(input_name) + input_id = manager.input.get('input_id') + + module.exit_json(changed=False, input=manager.input) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_network.py b/plugins/modules/medialive_network.py new file mode 100644 index 00000000000..41970e9488f --- /dev/null +++ b/plugins/modules/medialive_network.py @@ -0,0 +1,565 @@ +#!/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_network +short_description: Manage AWS MediaLive Anywhere networks +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive Anywhere networks. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the network. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['network_id'] + name: + description: + - The name of the network. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ['network_name'] + state: + description: + - Create/update or remove the network. + required: false + choices: ['present', 'absent'] + default: 'present' + type: str + ip_pools: + description: + - A list of IP pools to associate with the network. + - Required when creating a new network. + type: list + elements: dict + required: false + suboptions: + cidr: + description: + - The CIDR block for the IP pool. + type: str + required: true + routes: + description: + - A list of routes to associate with the network. + type: list + elements: dict + required: false + suboptions: + cidr: + description: + - The CIDR block for the route. + type: str + required: true + gateway: + description: + - The gateway for the route. + type: str + required: true + wait: + description: + - Whether to wait for the network to reach the desired state. + - When I(state=present), wait for the network to reach the IDLE or ACTIVE states. + - When I(state=absent), wait for the network to reach the DELETED state. + type: bool + required: false + default: true + wait_timeout: + description: + - The maximum time in seconds to wait for the network to reach the desired state. + - Defaults to 60 seconds. + type: int + required: false + default: 60 + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Create a MediaLive Anywhere network +- community.aws.medialive_network: + name: 'ExampleNetwork' + state: present + ip_pools: + - cidr: '10.0.0.0/24' + routes: + - cidr: '0.0.0.0/0' + gateway: '10.0.0.1' + +# Delete a MediaLive Anywhere network +- community.aws.medialive_network: + name: 'ExampleNetwork' + state: absent +""" + +RETURN = r""" +network: + description: The details of the network + returned: success + type: dict + contains: + arn: + description: The ARN of the network. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:network/1234abcd-12ab-34cd-56ef-1234567890ab" + associated_cluster_ids: + description: The IDs of clusters associated with the network. + type: list + elements: str + returned: success + example: ["cluster-1234abcd"] + network_id: + description: The ID of the network. + type: str + returned: success + example: "1234abcd-12ab-34cd-56ef-1234567890ab" + ip_pools: + description: The IP pools associated with the network. + type: list + elements: dict + returned: success + contains: + cidr: + description: The CIDR block for the IP pool. + type: str + returned: success + example: "10.0.0.0/24" + name: + description: The name of the network. + type: str + returned: success + example: "ExampleNetwork" + routes: + description: The routes associated with the network. + type: list + elements: dict + returned: success + contains: + cidr: + description: The CIDR block for the route. + type: str + returned: success + example: "0.0.0.0/0" + gateway: + description: The gateway for the route. + type: str + returned: success + example: "10.0.0.1" + state: + description: The state of the network. + type: str + returned: success + example: "ACTIVE" +""" + +import uuid +from typing import Dict + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory + + +class MediaLiveWaiterFactory(BaseWaiterFactory): + '''Custom waiter factory for MediaLive resources''' + + @property + def _waiter_model_data(self): + '''Define custom waiters for MediaLive networks''' + return { + 'create_network': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNetwork', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ACTIVE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IDLE', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'CREATE_FAILED', + 'argument': 'State' + }, + { + 'state': 'retry', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + } + ] + }, + 'update_network': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNetwork', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ACTIVE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IN_USE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IDLE', + 'argument': 'State' + } + ] + }, + 'delete_network': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNetwork', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'DELETED', + 'argument': 'State' + }, + { + 'state': 'failure', + 'matcher': 'path', + 'expected': 'DELETE_FAILED', + 'argument': 'State' + } + ] + } + } + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveNetworkManager: + '''Manage AWS MediaLive Anywhere networks''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveNetworkManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self.waiter_factory = MediaLiveWaiterFactory(module, self.client) + self._network = {} + self.changed = False + + @property + def network(self): + return self._network + + @network.setter + def network(self, network: Dict): + network = camel_dict_to_snake_dict(network) + if network.get('response_metadata'): + del network['response_metadata'] + if network.get('id'): + network['network_id'] = network.get('id') + del network['id'] + self._network = network + + + def do_create_network(self, params): + """ + Create a new MediaLive network + + Args: + params: Parameters for network creation + """ + allowed_params = ['ip_pools', 'name', 'routes', 'request_id'] + required_params = ['ip_pools', 'routes'] + + for param in required_params: + if not params.get(param): + raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new Network') + + 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: + self.network = self.client.create_network(**create_params) # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive Network', + exception=e + ) + + + def do_update_network(self, params): + """ + Update a new MediaLive network + + Args: + params: Parameters for network update + """ + if not params.get('network_id'): + raise MedialiveAnsibleAWSError(message='The network_id parameter is required during network update.') + + allowed_params = ['ip_pools', 'name', 'routes', 'network_id'] + + + current_params = { k: v for k, v in self.network.items() if k in allowed_params } + 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: + self.network = self.client.update_network(**update_params) # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to update Medialive Network', + exception=e + ) + + def get_network_by_name(self, name: str): + """ + Find a network by name + + Args: + name: The name of the network to find + """ + + try: + paginator = self.client.get_paginator('list_networks') # type: ignore + found = [] + for page in paginator.paginate(): + for network in page.get('Networks', []): + if network.get('Name') == name: + found.append(network.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Networks under the same name') + elif len(found) == 1: + self.get_network_by_id(found[0]) + + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Network', + exception=e + ) + + def get_network_by_id(self, network_id: str): + """ + Get a network by ID + + Args: + network_id: The ID of the network to retrieve + """ + try: + self.network = self.client.describe_network(NetworkId=network_id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.network = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Network', + exception=e + ) + + def delete_network(self, network_id: str): + """ + Delete a MediaLive network + + Args: + network_id: ID of the network to delete + """ + try: + self.client.delete_network(NetworkId=network_id) # type: ignore + self.network = {} + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.network = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to delete Medialive Network', + exception=e + ) + + def wait_for(self, want: str, network_id: str, wait_timeout: int = 60): + """ + Invoke one of the custom waiters and wait + + Args: + want: the name of the waiter + network_id: the ID of the network + wait_timeout: the maximum amount of time to wait in seconds (default: 60) + """ + + try: + waiter = self.waiter_factory.get_waiter(want) + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + NetworkId=network_id, + WaiterConfig=config + ) + except WaiterError as e: + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for network {network_id}', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['network_id']), + name=dict(type='str', required=False, aliases=['network_name']), + state=dict(type='str', default='present', choices=['present', 'absent']), + ip_pools=dict( + type='list', + elements='dict', + required=False, + options=dict(cidr=dict(type='str', required=True), + )), + routes=dict( + type='list', + elements='dict', + required=False, + options=dict( + cidr=dict(type='str', required=True), + gateway=dict(type='str', required=True), + ) + ), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=60), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'network_id', 'name', 'network_name')] + ) + + # Extract module parameters + network_id = get_arg('id', module.params, argument_spec) + network_name = get_arg('name', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + ip_pools = get_arg('ip_pools', module.params, argument_spec) + routes = get_arg('routes', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveNetworkManager(module) + + # Find the network by ID or name + if network_id: + manager.get_network_by_id(network_id) + elif network_name: + manager.get_network_by_name(network_name) + network_id = manager.network.get('network_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.network: + + update_params = { + 'name': network_name, + 'ip_pools': ip_pools, + 'routes': routes, + 'network_id': network_id + } + + manager.do_update_network(update_params) + + # Wait for the network to be updated + if wait and network_id: + manager.wait_for('update_network', network_id, wait_timeout) # type: ignore + manager.get_network_by_id(network_id) + + # Case create + else: + create_params = { + 'name': network_name, + 'ip_pools': ip_pools, + 'routes': routes, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_network(create_params) + network_id = manager.network.get('network_id') + + # Wait for the network to be created + if wait and network_id: + manager.wait_for('create_network', network_id, wait_timeout) # type: ignore + manager.get_network_by_id(network_id) + + # Handle absent state + elif state == 'absent': + if manager.network: + # Network exists, delete it + network_id = manager.network.get('network_id') + manager.delete_network(network_id) # type: ignore + + # Wait for the network to be deleted if requested + if wait and network_id: + manager.wait_for('delete_network', network_id, wait_timeout) # type: ignore + + module.exit_json(changed=manager.changed, network=manager.network) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_network_info.py b/plugins/modules/medialive_network_info.py new file mode 100644 index 00000000000..f8ca30d8e4f --- /dev/null +++ b/plugins/modules/medialive_network_info.py @@ -0,0 +1,234 @@ +#!/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_network +short_description: Gather MediaLive Anywhere network info +version_added: 10.1.0 +description: + - Get details about a AWS MediaLive Anywhere network. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the network. + - At least this or name must be provided + required: false + type: str + aliases: ['network_id'] + name: + description: + - The name of the network. + - At least this or id must be provided + required: false + type: str + aliases: ['network_name'] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Find a MediaLive Anywhere network by ID +- community.aws.medialive_network_info: + id: '1234567' + register: found_network + +# Find a MediaLive Anywhere network by name +- community.aws.medialive_network_info: + name: 'ExampleNetwork' + register: found_network +""" + +RETURN = r""" +network: + description: The details of the network + returned: success + type: dict + contains: + arn: + description: The ARN of the network. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:network:1234567" + associated_cluster_ids: + description: The IDs of clusters associated with the network. + type: list + elements: str + returned: success + example: ["cluster-1234abcd"] + id: + description: The ID of the network. + type: str + returned: success + example: "1234567" + ip_pools: + description: The IP pools associated with the network. + type: list + elements: dict + returned: success + contains: + cidr: + description: The CIDR block for the IP pool. + type: str + returned: success + example: "10.0.0.0/24" + name: + description: The name of the network. + type: str + returned: success + example: "ExampleNetwork" + routes: + description: The routes associated with the network. + type: list + elements: dict + returned: success + contains: + cidr: + description: The CIDR block for the route. + type: str + returned: success + example: "0.0.0.0/0" + gateway: + description: The gateway for the route. + type: str + returned: success + example: "10.0.0.1" + state: + description: The state of the network. + type: str + returned: success + example: "ACTIVE" +""" + +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 +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 MedialiveAnsibleAWSError(AnsibleAWSError): + pass + + +class MediaLiveNetworkManager: + '''Manage AWS MediaLive Anywhere networks''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveNetworkManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self._network = {} + + @property + def network(self): + return self._network + + @network.setter + def network(self, network: Dict): + network = camel_dict_to_snake_dict(network) + if network.get('response_metadata'): + del network['response_metadata'] + if network.get('id'): + network['network_id'] = network.get('id') + del network['id'] + self._network = network + + def find_network_by_name(self, name: str): + """ + Find a network by name + + Args: + name: The name of the network to find + """ + + try: + paginator = self.client.get_paginator('list_networks') # type: ignore + found = [] + for page in paginator.paginate(): + for network in page.get('Networks', []): + if network.get('Name') == name: + found.append(network.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Networks under the same name') + elif len(found) == 1: + self.get_network_by_id(found[0]) + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Network', + exception=e + ) + + def get_network_by_id(self, id: str): + """ + Get a network by ID + + Args: + id: The ID of the network to retrieve + """ + try: + self.network = self.client.describe_network(NetworkId=id) # type: ignore + except is_boto3_error_code('ResourceNotFoundException'): + self.network = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Network', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['network_id']), + name=dict(type='str', required=False, aliases=['network_name']), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'network_id', 'name', 'network_name')] + ) + + # Extract module parameters + network_id = get_arg('id', module.params, argument_spec) + network_name = get_arg('name', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveNetworkManager(module) + + # Find the network by ID or name + if network_id: + manager.get_network_by_id(network_id) + elif network_name: + manager.find_network_by_name(network_name) + + module.exit_json(changed=False, network=manager.network) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_node.py b/plugins/modules/medialive_node.py new file mode 100644 index 00000000000..f7034881cbf --- /dev/null +++ b/plugins/modules/medialive_node.py @@ -0,0 +1,665 @@ +#!/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_node +short_description: Manage AWS MediaLive Anywhere nodes +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive Anywhere nodes. + - This module requires boto3 >= 1.37.30. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the Node to manage. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ["node_id"] + name: + description: + - The user-specified name of the Node to be created. + - Name should include at least one number or letter. The allowed special characters are - space, at-sign, hyphen, underscore, period, comma, apostrophe and semicolon. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ["node_name"] + cluster_id: + description: + - The ID of the cluster. + required: true + type: str + aliases: ["cluster"] + node_interface_mappings: + description: + - A list of logical interface to physical interface mappings + required: false + type: list + elements: dict + suboptions: + logical_interface_name: + description: + - one of the logicalInterfaceNames in the Cluster that this node belongs to + type: str + required: true + physical_interface_name: + description: + - the physical interface name that corresponds to the logical interface name + type: str + required: true + network_interface_mode: + description: + - The style of the network – NAT or BRIDGE. + type: str + choices: ["NAT", "BRIDGE"] + required: true + role: + description: + - The initial role of the Node in the Cluster. + - ACTIVE means the Node is available for encoding. + - BACKUP means the Node is a redundant Node and might get used if an ACTIVE Node fails. + required: false + type: str + choices: ["ACTIVE", "BACKUP"] + sdi_source_mappings: + description: + - The mappings of a SDI capture card port to a logical SDI data stream + - Can only be applied to an existing node, not on node creation. + required: false + type: list + elements: dict + suboptions: + card_number: + description: + - A number that uniquely identifies the SDI card on the node hardware. + - For information about how physical cards are identified on your node hardware, see the documentation for your node hardware. + - The numbering always starts at 1. + type: int + required: true + channel_number: + description: + - A number that uniquely identifies a port on the card. + - This must be an SDI port (not a timecode port, for example). + - For information about how ports are identified on physical cards, see the documentation for your node hardware. + type: int + required: true + sdi_source: + description: + - The ID of a SDI source streaming on the given SDI capture card port. + required: true + type: str + wait: + description: + - Whether to wait for the node to reach the desired state. + - When I(state=present), wait for the node to reach one of the ["CREATED", "ACTIVE", "READY", "IN_USE"] states. + - When I(state=absent), wait for the node to be deleted. + type: bool + required: false + default: true + wait_timeout: + description: + - The maximum time in seconds to wait for the node to reach the desired state. + - Defaults to 60 seconds. + type: int + required: false + default: 60 + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Create a MediaLive Anywhere node +- community.aws.medialive_node: + name: 'ExampleNode' + cluster_id: '1234567' + node_interface_mappings: + - logical_interface_name: input-if-1 + physical_interface_name: eth1 + network_interface_mode: NAT + role: 'ACTIVE' + state: present + +# Update an existing MediaLive Anywhere node with SDI mappings +- community.aws.medialive_node: + name: 'ExampleNode' + cluster_id: '1234567' + sdi_source_mappings: + - card_number: 123 + channel_number: 123 + sdi_source: 'string' + state: present + +# Delete a MediaLive Anywhere node +- community.aws.medialive_node: + id: '1234567' + cluster_id: '7654321' + state: absent +""" + +RETURN = r""" +node: + description: The details of the node + returned: success + type: dict + contains: + arn: + description: The ARN of the node. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:node:1234567/7654321" + channel_placement_groups: + description: An array of IDs. Each ID is one channel_placement_group that is associated with this Node. Empty if the Node is not yet associated with any groups. + type: list + elements: str + returned: success + example: ["1234567", "7654321"] + cluster_id: + description: The ID of the cluster that the node belongs to. + type: str + returned: success + example: "1234567" + connection_state: + description: The connection state of the node. Can be CONNECTED or DISCONNECTED. + type: str + returned: success + example: "CONNECTED" + node_id: + description: The unique ID of the node. Unique in the cluster. The ID is the resource-id portion of the ARN. + type: str + returned: success + example: "1234567" + instance_arn: + description: The ARN of the EC2/SSM Managed instance hosting the Node. + type: str + returned: success + example: "arn:aws:ssm:us-east-1:123456789012:managed-instance/mi-abcdefgh12345678" + name: + description: The name of the node. + type: str + returned: success + example: "ExampleNode" + node_interface_mappings: + description: A mapping that’s used to pair a logical network interface name on a node with the physical interface name exposed in the operating system. + type: list + elements: dict + returned: success + contains: + logical_interface_name: + description: A uniform logical interface name to address in a MediaLive channel configuration. + type: str + returned: success + example: "input-if-1" + physical_interface_name: + description: The name of the physical interface on the hardware that will be running AWS MediaLive anywhere. + type: str + returned: success + example: "eth1" + network_interface_mode: + description: The style of the network – NAT or BRIDGE. + type: str + returned: success + example: "NAT" + role: + description: The role of the node in the cluster. Can be ACTIVE or BACKUP. + type: str + returned: success + example: "ACTIVE" + sdi_source_mappings: + description: + - An array of SDI source mappings. + - Each mapping connects one logical SdiSource to the physical SDI card and port that the physical SDI source uses. + type: list + elements: dict + returned: success + contains: + card_number: + description: + - A number that uniquely identifies the SDI card on the node hardware. + - For information about how physical cards are identified on your node hardware, see the documentation for your node hardware. + - The numbering always starts at 1. + type: int + required: true + channel_number: + description: + - A number that uniquely identifies a port on the card. + - This must be an SDI port (not a timecode port, for example). + - For information about how ports are identified on physical cards, see the documentation for your node hardware. + type: int + required: true + sdi_source: + description: + - The ID of a SDI source streaming on the given SDI capture card port. + required: true + type: str + state: + description: > + The current state of the node. + Possible values: + - CREATED + - REGISTERING + - READY_TO_ACTIVATE + - REGISTRATION_FAILED + - ACTIVATION_FAILED + - ACTIVE + - READY + - IN_USE + - DEREGISTERING + - DRAINING + - DEREGISTRATION_FAILED + - DEREGISTERED + type: str + returned: success + example: "ACTIVE" +""" + +import uuid +from typing import Dict + +try: + from botocore.exceptions import WaiterError, ClientError, BotoCoreError +except ImportError: + pass # caught by AnsibleAWSModule + +from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 +from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory + + +class MediaLiveWaiterFactory(BaseWaiterFactory): + '''Custom waiter factory for MediaLive resources''' + + @property + def _waiter_model_data(self): + '''Define custom waiters for MediaLive Anywhere nodes''' + return { + 'create_node': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNode', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'CREATED', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'REGISTERING', + 'argument': 'State' + }, + { + 'state': 'retry', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + } + ] + }, + 'update_node': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNode', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'CREATED', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'READY_TO_ACTIVATE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'READY', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'ACTIVE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'IN_USE', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'REGISTERING', + 'argument': 'State' + }, + ] + }, + 'delete_node': { + 'delay': 5, + 'maxAttempts': 120, + 'operation': 'DescribeNode', + 'acceptors': [ + { + 'state': 'success', + 'matcher': 'path', + 'expected': 'DEREGISTERED', + 'argument': 'State' + }, + { + 'state': 'success', + 'matcher': 'error', + 'expected': 'ResourceNotFoundException' + } + ] + } + } + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveNodeManager: + '''Manage AWS MediaLive Anywhere node''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveNodeManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self.waiter_factory = MediaLiveWaiterFactory(module, self.client) + self._node = {} + self.changed = False + + @property + def node(self): + return self._node + + @node.setter + def node(self, node: Dict): + node = camel_dict_to_snake_dict(node) + if node.get('response_metadata'): + del node['response_metadata'] + if node.get('id'): + node['node_id'] = node.get('id') + del node['id'] + self._node = node + + def do_create_node(self, params): + """ + Create a new MediaLive node + + Args: + params: Parameters for node creation + """ + allowed_params = ['cluster_id', 'name', 'node_interface_mappings', 'role', 'request_id'] + required_params = ['cluster_id', 'name', 'node_interface_mappings', 'role'] + + for param in required_params: + if not params.get(param): + raise MedialiveAnsibleAWSError( + message=f'The {", ".join(required_params)} parameters are required when creating a new node' + ) + + 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: + self.node = self.client.create_node(**create_params) # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive node', + exception=e + ) + + def do_update_node(self, params: dict): + """ + Update a new MediaLive node + + Args: + params: Parameters for node update + """ + + allowed_params = ['cluster_id', 'node_id', 'name', 'role'] + + current_params = { k: v for k, v in self.node.items() if k in allowed_params } + update_params = { k: v for k, v in params.items() if k in allowed_params and v } + + update_params['cluster_id'] = self.node.get('cluster_id') + update_params['node_id'] = self.node.get('node_id') + if params.get('sdi_source_mappings'): + update_params['sdi_source_mappings'] = params.get('sdi_source_mappings') + + # 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_node(**update_params) # type: ignore + self.node = camel_dict_to_snake_dict(response) + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to update Medialive node', + exception=e + ) + + def delete_node(self): + """ + Delete a MediaLive node + """ + try: + self.client.delete_node(ClusterId=self.node.get('cluster_id'), NodeId=self.node.get('node_id')) # type: ignore + self.changed = True + except is_boto3_error_code('ResourceNotFoundException'): + self.node = {} + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to delete Medialive node', + exception=e + ) + + def get_node_by_name(self, cluster_id: str, name: str): + """ + Find a node by name + + Args: + cluster_id: The id of the cluster to which the node belongs + name: The name of the node to find + """ + try: + paginator = self.client.get_paginator('list_nodes') # type: ignore + found = [] + for page in paginator.paginate(ClusterId=cluster_id): + for node in page.get('Nodes', []): + if node.get('Name') == name: + found.append(node.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Nodes under the same name') + elif len(found) == 1: + self.get_node_by_id(cluster_id, found[0]) + + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Node', + exception=e + ) + + def get_node_by_id(self, cluster_id: str, node_id: str): + """ + Get a node by ID + + Args: + cluster_id: The id of the cluster to which the node belongs + node_id: The ID of the node to retrieve + """ + try: + self.node = self.client.describe_node(ClusterId=cluster_id, NodeId=node_id) # type: ignore + return True + except is_boto3_error_code('ResourceNotFoundException'): + self.node = {} + + def wait_for(self, want: str, wait_timeout: int = 60): + """ + Invoke one of the custom waiters and wait + + Args: + want: the name of the waiter + wait_timeout: the maximum amount of time to wait in seconds (default: 60) + """ + cluster_id = self.node.get('cluster_id') + node_id = self.node.get('node_id') + + try: + waiter = self.waiter_factory.get_waiter(want) + config = { + 'Delay': min(5, wait_timeout), + 'MaxAttempts': wait_timeout // 5 + } + waiter.wait( + ClusterId=cluster_id, + NodeId=node_id, + WaiterConfig=config + ) + self.get_node_by_id(cluster_id, node_id) # type: ignore + except WaiterError as e: + raise MedialiveAnsibleAWSError( + message=f'Timeout waiting for node {node_id} in cluster {cluster_id}', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['node_id']), + name=dict(type='str', required=False, aliases=['node_name']), + cluster_id=dict(type='str', required=True), + node_interface_mappings=dict( + type='list', + elements='dict', + required=False, + options=dict( + logical_interface_name=dict(type='str', required=True), + physical_interface_name=dict(type='str', required=True), + network_interface_mode=dict(type='str', required=True, choices=['NAT', 'BRIDGE']), + ) + ), + role=dict(type='str', required=False, choices=["ACTIVE", "BACKUP"]), + sdi_source_mappings=dict( + type='list', + elements='dict', + required=False, + options=dict( + card_number=dict(type='int', required=True), + channel_number=dict(type='int', required=True), + sdi_source=dict(type='str', required=True), + ) + ), + state=dict(type='str', default='present', choices=['present', 'absent']), + wait=dict(type='bool', default=True), + wait_timeout=dict(type='int', default=60), + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'node_id', 'name', 'node_name')] + ) + + # Extract module parameters + node_id = get_arg('id', module.params, argument_spec) + node_name = get_arg('name', module.params, argument_spec) + cluster_id = get_arg('cluster_id', module.params, argument_spec) + node_interface_mappings = get_arg('node_interface_mappings', module.params, argument_spec) + role = get_arg('role', module.params, argument_spec) + sdi_source_mappings = get_arg('sdi_source_mappings', module.params, argument_spec) + state = get_arg('state', module.params, argument_spec) + wait = get_arg('wait', module.params, argument_spec) + wait_timeout = get_arg('wait_timeout', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveNodeManager(module) + + # Find the node by ID or name + # Update manager.node with the details + if node_id: + manager.get_node_by_id(cluster_id, node_id) # type: ignore + elif node_name: + manager.get_node_by_name(cluster_id, node_name) # type: ignore + + # Do nothing in check mode + if module.check_mode: + module.exit_json(changed=True) + + # Handle present state + if state == 'present': + + # Case update + if manager.node: + update_params = { + 'name': node_name, + 'role': role, + 'sdi_source_mappings': sdi_source_mappings + } + + manager.do_update_node(update_params) + + # Wait for the node to be updated + if wait: + manager.wait_for('update_node', wait_timeout) # type: ignore + manager.get_node_by_id(cluster_id, manager.node.get('node_id')) # type: ignore + + # Case create + else: + create_params = { + 'cluster_id': cluster_id, + 'name': node_name, + 'node_interface_mappings': node_interface_mappings, + 'role': role, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_node(create_params) + + # Wait for the node to be created + if wait: + manager.wait_for('create_node', wait_timeout) # type: ignore + manager.get_node_by_id(cluster_id, manager.node.get('node_id')) # type: ignore + + # Handle absent state + elif state == 'absent': + if manager.node: + # Node exists, delete it + manager.delete_node() + + module.exit_json(changed=manager.changed, node=manager.node) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_node_info.py b/plugins/modules/medialive_node_info.py new file mode 100644 index 00000000000..fadf017d28d --- /dev/null +++ b/plugins/modules/medialive_node_info.py @@ -0,0 +1,265 @@ +#!/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_node +short_description: Gather AWS MediaLive Anywhere node info +version_added: 10.1.0 +description: + - Get details about a AWS MediaLive Anywhere node. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the Node. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ["node_id"] + name: + description: + - The name of the Node. + - Exactly one of I(id) or I(name) must be provided. + required: false + type: str + aliases: ["node_name"] + cluster_id: + description: + - The ID of the cluster. + required: true + type: str + aliases: ["cluster"] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Find a MediaLive Anywhere node by ID +- community.aws.medialive_node_info: + id: '1234567' + cluster_id: '7654321' + register: found_node + +# Find a MediaLive Anywhere node by name +- community.aws.medialive_node_info: + name: 'ExampleNode' + cluster_id: '7654321' + register: found_node +""" + +RETURN = r""" +node: + description: The details of the node + returned: success + type: dict + contains: + arn: + description: The ARN of the node. + type: str + returned: success + example: "arn:aws:medialive:us-east-1:123456789012:node:1234567/7654321" + channel_placement_groups: + description: An array of IDs. Each ID is one channel_placement_group that is associated with this Node. Empty if the Node is not yet associated with any groups. + type: list + elements: str + returned: success + example: ["1234567", "7654321"] + cluster_id: + description: The ID of the cluster that the node belongs to. + type: str + returned: success + example: "1234567" + connection_state: + description: The connection state of the node. Can be CONNECTED or DISCONNECTED. + type: str + returned: success + example: "CONNECTED" + node_id: + description: The unique ID of the node. Unique in the cluster. The ID is the resource-id portion of the ARN. + type: str + returned: success + example: "1234567" + instance_arn: + description: The ARN of the EC2/SSM Managed instance hosting the Node. + type: str + returned: success + example: "arn:aws:ssm:us-east-1:123456789012:managed-instance/mi-abcdefgh12345678" + name: + description: The name of the node. + type: str + returned: success + example: "ExampleNode" + node_interface_mappings: + description: A mapping that’s used to pair a logical network interface name on a node with the physical interface name exposed in the operating system. + type: list + elements: dict + returned: success + contains: + logical_interface_name: + description: A uniform logical interface name to address in a MediaLive channel configuration. + type: str + returned: success + example: "input-if-1" + physical_interface_name: + description: The name of the physical interface on the hardware that will be running AWS MediaLive anywhere. + type: str + returned: success + example: "eth1" + network_interface_mode: + description: The style of the network – NAT or BRIDGE. + type: str + returned: success + example: "NAT" + role: + description: The role of the node in the cluster. Can be ACTIVE or BACKUP. + type: str + returned: success + example: "ACTIVE" + state: + description: > + The current state of the node. + Possible values: + - CREATED + - REGISTERING + - READY_TO_ACTIVATE + - REGISTRATION_FAILED + - ACTIVATION_FAILED + - ACTIVE + - READY + - IN_USE + - DEREGISTERING + - DRAINING + - DEREGISTRATION_FAILED + - DEREGISTERED + type: str + returned: success + example: "ACTIVE" +""" + +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 MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveNodeGetter: + '''Look up AWS MediaLive Anywhere nodes''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveNodeGetter + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self._node = {} + + @property + def node(self): + return self._node + + @node.setter + def node(self, node: Dict): + node = camel_dict_to_snake_dict(node) + if node.get('response_metadata'): + del node['response_metadata'] + if node.get('id'): + node['node_id'] = node.get('id') + del node['id'] + self._node = node + + def get_node_by_name(self, cluster_id: str, name: str): + """ + Find a node by name + + Args: + cluster_id: The id of the cluster to which the node belongs + name: The name of the node to find + """ + try: + paginator = self.client.get_paginator('list_nodes') # type: ignore + found = [] + for page in paginator.paginate(ClusterId=cluster_id): + for node in page.get('Nodes', []): + if node.get('Name') == name: + found.append(node.get('Id')) + if len(found) > 1: + raise MedialiveAnsibleAWSError(message='Found more than one Nodes under the same name') + elif len(found) == 1: + self.get_node_by_id(cluster_id, found[0]) + + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to get Medialive Node', + exception=e + ) + + def get_node_by_id(self, cluster_id: str, node_id: str): + """ + Get a node by ID + + Args: + cluster_id: The id of the cluster to which the node belongs + node_id: The ID of the node to retrieve + """ + try: + self.node = self.client.describe_node(ClusterId=cluster_id, NodeId=node_id) # type: ignore + return True + except is_boto3_error_code('ResourceNotFoundException'): + self.node = {} + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=False, aliases=['node_id']), + name=dict(type='str', required=False, aliases=['node_name']), + cluster_id=dict(type='str', required=True) + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + required_one_of=[('id', 'node_id', 'name', 'node_name')], + ) + + # Extract module parameters + node_id = get_arg('id', module.params, argument_spec) + node_name = get_arg('name', module.params, argument_spec) + cluster_id = get_arg('cluster_id', module.params, argument_spec) + + # Initialize the getter + getter = MediaLiveNodeGetter(module) + + # Find the node by ID or name + # Update getter.node with the details + if node_id: + getter.get_node_by_id(cluster_id, node_id) # type: ignore + elif node_name: + getter.get_node_by_name(cluster_id, node_name) # type: ignore + + module.exit_json(changed=False, node=getter.node) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_node_registration.py b/plugins/modules/medialive_node_registration.py new file mode 100644 index 00000000000..06751d00266 --- /dev/null +++ b/plugins/modules/medialive_node_registration.py @@ -0,0 +1,177 @@ +#!/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_node_registration +short_description: Manage AWS MediaLive Anywhere nodes +version_added: 10.1.0 +description: + - A module for creating, updating and deleting AWS MediaLive Anywhere nodes. + - This module requires boto3 >= 1.35.17. +author: + - "Sergey Papyan" +options: + id: + description: + - The ID of the Node to create registration script for. + required: true + type: str + aliases: ["node_id"] + cluster_id: + description: + - The ID of the cluster where the node lives. + required: true + type: str + aliases: ["cluster"] + role: + description: + - The initial role of the Node in the Cluster. + - ACTIVE means the Node is available for encoding. + - BACKUP means the Node is a redundant Node and might get used if an ACTIVE Node fails. + required: true + type: str + choices: ["ACTIVE", "BACKUP"] + +extends_documentation_fragment: + - amazon.aws.common.modules + - amazon.aws.region.modules + - amazon.aws.boto3 +""" + +EXAMPLES = r""" +# Request a MediaLive Anywhere node registration script +- community.aws.medialive_node_registration: + cluster_id: '1234567' + node_id: '7654321' + role: 'ACTIVE' + register: response +""" + +RETURN = r""" +node_registration_script: + description: A script that can be run on a Bring Your Own Device Elemental Anywhere system to create a node in a cluster. + returned: success + type: str +""" + +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 snake_dict_to_camel_dict, camel_dict_to_snake_dict +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + +class MediaLiveNodeRegistrationScriptManager: + '''Requests AWS MediaLive Anywhere node registration script''' + + def __init__(self, module: AnsibleAWSModule): + ''' + Initialize the MediaLiveNodeRegistrationScriptManager + + Args: + module: AnsibleAWSModule instance + ''' + self.module = module + self.client = self.module.client('medialive') + self._script = {} + self.changed = False + + @property + def script(self): + return self._script + + @script.setter + def script(self, script: Dict): + script = camel_dict_to_snake_dict(script) + if script.get('response_metadata'): + del script['response_metadata'] + self._script = script + + # TODO: this needs to be updated once the API and the docs are fixed + def do_create_registration_script(self, params): + """ + Create a new MediaLive node registration script + + Args: + params: Parameters for node registration script creation + """ + # NOTE: The API docs don't match the reality as of today + allowed_params = ['cluster_id', 'id', 'name', 'node_interface_mappings', 'request_id', 'role'] + required_params = ['cluster_id', 'id', 'role'] + + for param in required_params: + if not params.get(param): + raise MedialiveAnsibleAWSError( + message=f'The {", ".join(required_params)} parameters are required when creating a new node registration script' + ) + + 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: + self.script = self.client.create_node_registration_script(**create_params) # type: ignore + self.changed = True + except (ClientError, BotoCoreError) as e: + raise MedialiveAnsibleAWSError( + message='Unable to create Medialive node registration script', + exception=e + ) + +def get_arg(arg:str, params:dict, spec:dict): + if arg in spec.keys(): + aliases = spec[arg].get('aliases', []) + for k, v in params.items(): + if k in [arg, *aliases] and v: + return v + +def main(): + """Main entry point for the module""" + argument_spec = dict( + id=dict(type='str', required=True, aliases=['node_id']), + cluster_id=dict(type='str', required=True), + role=dict(type='str', required=True, choices=["ACTIVE", "BACKUP"]) + ) + + module = AnsibleAWSModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + # Extract module parameters + node_id = get_arg('id', module.params, argument_spec) + cluster_id = get_arg('cluster_id', module.params, argument_spec) + role = get_arg('role', module.params, argument_spec) + + # Initialize the manager + manager = MediaLiveNodeRegistrationScriptManager(module) + + # Do nothing in check mode + if module.check_mode: + module.exit_json(changed=True) + + # Create the script + create_params = { + 'cluster_id': cluster_id, + 'id': node_id, + 'role': role, + 'request_id': str(uuid.uuid4()) + } + + manager.do_create_registration_script(create_params) + + module.exit_json(changed=manager.changed, node=manager.script) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/medialive_sdi_source.py b/plugins/modules/medialive_sdi_source.py new file mode 100644 index 00000000000..efdc891bbf7 --- /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" +""" + +from typing import Dict + +from botocore.exceptions import ClientError, BotoCoreError +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 +import uuid + + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass + + +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') + + 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 MedialiveAnsibleAWSError(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: + raise MedialiveAnsibleAWSError( + 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 MedialiveAnsibleAWSError(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: + raise MedialiveAnsibleAWSError( + 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 MedialiveAnsibleAWSError( + 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: + raise MedialiveAnsibleAWSError( + 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: + raise MedialiveAnsibleAWSError( + 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..c54c16d6c6e --- /dev/null +++ b/plugins/modules/medialive_sdi_source_info.py @@ -0,0 +1,212 @@ +#!/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.30. +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 MedialiveAnsibleAWSError(AnsibleAWSError): + pass + + +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') + + 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 MedialiveAnsibleAWSError( + 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: + raise MedialiveAnsibleAWSError( + 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_channel_placement_group/aliases b/tests/integration/targets/medialive_channel_placement_group/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_channel_placement_group/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_channel_placement_group/meta/main.yml b/tests/integration/targets/medialive_channel_placement_group/meta/main.yml new file mode 100644 index 00000000000..16c24927b55 --- /dev/null +++ b/tests/integration/targets/medialive_channel_placement_group/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: setup_botocore_pip + vars: + botocore_version: "1.35.17" diff --git a/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml b/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml new file mode 100644 index 00000000000..daaa58a8539 --- /dev/null +++ b/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml @@ -0,0 +1,33 @@ +--- +- name: Cleanup - ensure channel placement group is deleted + community.aws.medialive_channel_placement_group: + id: "{{ cpg_id }}" + cluster_id: "{{ cluster_id }}" + state: absent + wait: true + ignore_errors: true + when: cpg_id is defined + +- name: Cleanup - ensure cluster is deleted + community.aws.medialive_cluster: + id: "{{ cluster_id }}" + state: absent + wait: true + ignore_errors: true + when: cluster_id is defined + +- name: Cleanup - ensure network is deleted + community.aws.medialive_network: + id: "{{ network_id }}" + state: absent + wait: true + ignore_errors: true + when: network_id is defined + +- name: Cleanup - ensure role is deleted + amazon.aws.iam_role: + name: '{{ instance_role_name }}' + state: absent + wait: true + ignore_errors: true + when: instance_role_arn is defined diff --git a/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml b/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml new file mode 100644 index 00000000000..7e9433f4101 --- /dev/null +++ b/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml @@ -0,0 +1,268 @@ +--- +# Integration tests for medialive_channel_placement_group module +- name: Wrap MediaLive CPG 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: + cluster_name: "{{ resource_prefix }}-cluster" + network_name: "{{ resource_prefix }}-network" + instance_role_name: "{{ resource_prefix }}-role" + cpg_name: "{{ resource_prefix }}-cpg" + ip_pool_cidr: "10.21.21.0/24" + route_cidr: "0.0.0.0/0" + route_gateway: "10.21.21.1" + wait_timeout: 60 + + # Create a network first for the cluster to use + - name: Create a MediaLive Anywhere network for the cluster + community.aws.medialive_network: + name: "{{ network_name }}" + state: present + ip_pools: + - cidr: "{{ ip_pool_cidr }}" + routes: + - cidr: "{{ route_cidr }}" + gateway: "{{ route_gateway }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: network_result + retries: 3 + delay: 5 + until: network_result is not failed or 'TooManyRequestsException' not in (network_result.msg | default('')) + + # Create a simple IAM role for MediaLive Anywhere nodes + - name: Create MediaLive Anywhere node IAM role + amazon.aws.iam_role: + name: '{{ instance_role_name }}' + assume_role_policy_document: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "medialive.amazonaws.com", + "ssm.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + } + state: present + create_instance_profile: false + register: iam_role + + - name: Capture network_id and instance_role_arn + ansible.builtin.set_fact: + network_id: "{{ network_result.network.network_id }}" + instance_role_arn: "{{ iam_role.iam_role.arn }}" + when: + - network_result is defined + - network_result.changed is defined + - iam_role is defined + - iam_role.changed is defined + + # Create a cluster first to host the channel placement group + - name: Create a MediaLive Anywhere cluster + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: present + cluster_type: "ON_PREMISES" + instance_role_arn: "{{ instance_role_arn }}" + network_settings: + default_route: "management" + interface_mappings: + - logical_interface_name: "management" + network_id: "{{ network_id }}" + - logical_interface_name: "input" + network_id: "{{ network_id }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: cluster_result + retries: 3 + delay: 5 + until: cluster_result is not failed or 'TooManyRequestsException' not in (cluster_result.msg | default('')) + when: network_id is defined + + - name: Capture cluster_id + ansible.builtin.set_fact: + cluster_id: '{{ cluster_result.cluster.cluster_id }}' + + # Test creating a channel placement group in the cluster + - name: Create a MediaLive Anywhere channel placement group + community.aws.medialive_channel_placement_group: + cluster_id: '{{ cluster_id }}' + name: '{{ cpg_name }}' + state: present + wait: true + wait_timeout: "{{ wait_timeout }}" + register: create_result + retries: 3 + delay: 5 + until: create_result is not failed or 'TooManyRequestsException' not in (create_result.msg | default('')) + when: cluster_id is defined + + - name: Assert channel placement group was created successfully + ansible.builtin.assert: + that: + - create_result is changed + - create_result.channel_placement_group.arn is defined + - create_result.channel_placement_group.arn != '' + - create_result.channel_placement_group.channels is defined + - create_result.channel_placement_group.channels == [] + - create_result.channel_placement_group.cluster_id is defined + - create_result.channel_placement_group.cluster_id != '' + - create_result.channel_placement_group.channel_placement_group_id is defined + - create_result.channel_placement_group.channel_placement_group_id != '' + - create_result.channel_placement_group.name is defined + - create_result.channel_placement_group.name != '' + - create_result.channel_placement_group.nodes is defined + - create_result.channel_placement_group.nodes == [] + - create_result.channel_placement_group.state is defined + - create_result.channel_placement_group.state == 'UNASSIGNED' + fail_msg: "Channel placement group creation failed or returned unexpected data" + success_msg: "Channel placement group created successfully with expected properties" + when: + - create_result is defined + - create_result.channel_placement_group is defined + + - name: Capture cpg_id + ansible.builtin.set_fact: + cpg_id: '{{ create_result.channel_placement_group.channel_placement_group_id }}' + + # Test idempotency + - name: Create the same channel placement group again (idempotency check) + community.aws.medialive_channel_placement_group: + cluster_id: '{{ cluster_id }}' + name: '{{ cpg_name }}' + state: present + wait: true + wait_timeout: "{{ wait_timeout }}" + register: idempotency_result + when: cluster_id is defined + + - name: Assert idempotency + ansible.builtin.assert: + that: + - not idempotency_result.changed + - idempotency_result.channel_placement_group.channel_placement_group_id == cpg_id + fail_msg: "Idempotency check failed - module made changes when none were expected" + success_msg: "Idempotency check passed" + when: + - idempotency_result is defined + - idempotency_result.channel_placement_group is defined + + # Test retrieval by ID + - name: Get channel placement group by ID + community.aws.medialive_channel_placement_group_info: + channel_placement_group_id: "{{ cpg_id }}" + cluster_id: "{{ cluster_id }}" + register: get_by_id_result + when: + - cpg_id is defined + - cluster_id is defined + + - name: Assert channel placement group was retrieved by ID + ansible.builtin.assert: + that: + - not get_by_id_result.changed + - get_by_id_result.channel_placement_group.channel_placement_group_id == cpg_id + - get_by_id_result.channel_placement_group.name == cpg_name + fail_msg: "Failed to retrieve channel placement group by ID" + success_msg: "Successfully retrieved channel placement group by ID" + when: + - get_by_id_result is defined + - get_by_id_result.channel_placement_group is defined + + # Test check mode + - name: Test check mode - attempt to delete channel placement group + community.aws.medialive_channel_placement_group: + channel_placement_group_id: "{{ cpg_id }}" + cluster_id: "{{ cluster_id }}" + state: absent + check_mode: true + register: check_mode_result + when: cpg_id is defined + + - name: Assert check mode works correctly + ansible.builtin.assert: + that: + - check_mode_result is changed + - (check_mode_result.channel_placement_group | default({})) == {} + fail_msg: "Check mode did not work as expected" + success_msg: "Check mode correctly simulated deletion" + when: check_mode_result is defined + + + # Test channel placement group deletion + - name: Delete the channel placement group + community.aws.medialive_channel_placement_group: + channel_placement_group_id: "{{ cpg_id }}" + cluster_id: "{{ cluster_id }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + register: delete_result + retries: 3 + delay: 5 + until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + when: cpg_id is defined + + - name: Assert channel placement group was deleted + ansible.builtin.assert: + that: + - delete_result is changed + fail_msg: "Channel placement group deletion failed" + success_msg: "Channel placement group deleted successfully" + when: delete_result is defined + + # Test deletion idempotency + - name: Try to delete the channel placement group again (idempotency check) + community.aws.medialive_channel_placement_group: + id: "{{ cpg_id }}" + cluster_id: "{{ cluster_id }}" + state: absent + register: delete_idempotency_result + when: cpg_id is defined + + - name: Assert delete idempotency + ansible.builtin.assert: + that: + - not delete_idempotency_result.changed + fail_msg: "Delete idempotency check failed" + success_msg: "Delete idempotency check passed" + when: delete_idempotency_result 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 channel placement group 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 + diff --git a/tests/integration/targets/medialive_cluster/aliases b/tests/integration/targets/medialive_cluster/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_cluster/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_cluster/meta/main.yml b/tests/integration/targets/medialive_cluster/meta/main.yml new file mode 100644 index 00000000000..16c24927b55 --- /dev/null +++ b/tests/integration/targets/medialive_cluster/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: setup_botocore_pip + vars: + botocore_version: "1.35.17" diff --git a/tests/integration/targets/medialive_cluster/tasks/main.yml b/tests/integration/targets/medialive_cluster/tasks/main.yml new file mode 100644 index 00000000000..0523faf065d --- /dev/null +++ b/tests/integration/targets/medialive_cluster/tasks/main.yml @@ -0,0 +1,300 @@ +--- +# Integration tests for medialive_cluster module +- name: Wrap MediaLive Cluster 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 + set_fact: + cluster_name: "{{ resource_prefix }}-cluster" + network_name: "{{ resource_prefix }}-network" + ip_pool_cidr: "10.21.21.0/24" + route_cidr: "0.0.0.0/0" + route_gateway: "10.21.21.1" + wait_timeout: 30 + + # Create the required IAM role for MediaLive Anywhere nodes + - name: Create MediaLive Anywhere node IAM role + amazon.aws.iam_role: + name: MLANodeRole-Test + assume_role_policy_document: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "medialive.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + } + state: present + create_instance_profile: false + managed_policies: + - arn:aws:iam::aws:policy/AWSElementalMediaLiveFullAccess + tags: + Environment: Test + Purpose: Integration Testing + register: iam_role + + - name: Set IAM role ARN + set_fact: + instance_role_arn: "{{ iam_role.iam_role.arn }}" + + # Create a network first for the cluster to use + - name: Create a MediaLive Anywhere network for the cluster + community.aws.medialive_network: + name: "{{ network_name }}" + state: present + ip_pools: + - cidr: "{{ ip_pool_cidr }}" + routes: + - cidr: "{{ route_cidr }}" + gateway: "{{ route_gateway }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: network_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: network_result is not failed or 'TooManyRequestsException' not in (network_result.msg | default('')) + + - name: Store network ID + set_fact: + network_id: "{{ network_result.network.network_id }}" + when: network_result is defined and network_result.changed is defined + + # Test cluster creation + - name: Create a MediaLive Anywhere cluster + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: present + cluster_type: "ON_PREMISES" + instance_role_arn: "{{ instance_role_arn }}" + network_settings: + default_route: "management" + interface_mappings: + - logical_interface_name: "management" + network_id: "{{ network_id }}" + - logical_interface_name: "input" + network_id: "{{ network_id }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: create_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: create_result is not failed or 'TooManyRequestsException' not in (create_result.msg | default('')) + when: network_id is defined + + - name: Assert cluster was created successfully + assert: + that: + - create_result is changed + - create_result.cluster is defined + - create_result.cluster.name == cluster_name + - create_result.cluster.state in ['ACTIVE', 'IDLE', 'IN_USE'] + - create_result.cluster.cluster_type == "ON_PREMISES" + - create_result.cluster.instance_role_arn == instance_role_arn + - create_result.cluster.network_settings.default_route == "management" + - create_result.cluster.network_settings.interface_mappings | length == 2 + - create_result.cluster.cluster_id is defined + - create_result.cluster.arn is defined + fail_msg: "Cluster creation failed or returned unexpected data" + success_msg: "Cluster created successfully with expected properties" + when: create_result is defined and create_result.cluster is defined + + # Store cluster ID for later use + - name: Store cluster ID + set_fact: + cluster_id: "{{ create_result.cluster.cluster_id }}" + when: create_result is defined and create_result.cluster is defined + + # Test idempotency + - name: Create the same cluster again (idempotency check) + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: present + cluster_type: "ON_PREMISES" + instance_role_arn: "{{ instance_role_arn }}" + network_settings: + default_route: "management" + interface_mappings: + - logical_interface_name: "management" + network_id: "{{ network_id }}" + - logical_interface_name: "input" + network_id: "{{ network_id }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: idempotency_result + when: cluster_id is defined + + - name: Assert idempotency + assert: + that: + - not idempotency_result.changed + - idempotency_result.cluster is defined + - idempotency_result.cluster.cluster_id == cluster_id + fail_msg: "Idempotency check failed - module made changes when none were expected" + success_msg: "Idempotency check passed" + when: idempotency_result is defined and idempotency_result.cluster is defined + + - name: Get cluster by ID + community.aws.medialive_cluster_info: + id: "{{ cluster_id }}" + register: get_by_id_result + when: cluster_id is defined + + - name: Assert cluster was retrieved by ID + assert: + that: + - not get_by_id_result.changed + - get_by_id_result.cluster is defined + - get_by_id_result.cluster.cluster_id == cluster_id + - get_by_id_result.cluster.name == cluster_name + fail_msg: "Failed to retrieve cluster by ID" + success_msg: "Successfully retrieved cluster by ID" + when: get_by_id_result is defined and get_by_id_result.cluster is defined + + - name: Get cluster by name + community.aws.medialive_cluster_info: + name: "{{ cluster_name }}" + register: get_by_name_result + when: cluster_name is defined + + - name: Assert cluster was retrieved by name + assert: + that: + - not get_by_name_result.changed + - get_by_name_result.cluster is defined + - get_by_name_result.cluster.cluster_id == cluster_id + - get_by_name_result.cluster.name == cluster_name + fail_msg: "Failed to retrieve cluster by name" + success_msg: "Successfully retrieved cluster by name" + when: get_by_name_result is defined and get_by_name_result.cluster is defined + + # Test check mode + - name: Test check mode - attempt to delete cluster + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + check_mode: true + register: check_mode_result + when: cluster_id is defined + + - name: Assert check mode works correctly + assert: + that: + - check_mode_result is changed + - (check_mode_result.cluster | default({})) == {} + fail_msg: "Check mode did not work as expected" + success_msg: "Check mode correctly simulated deletion" + when: check_mode_result is defined + + # Test cluster deletion + - name: Delete the cluster + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + register: delete_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + when: cluster_id is defined + + - name: Assert cluster was deleted + assert: + that: + - delete_result is changed + fail_msg: "Cluster deletion failed" + success_msg: "Cluster deleted successfully" + when: delete_result is defined + + # Test deletion idempotency + - name: Try to delete the cluster again (idempotency check) + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + register: delete_idempotency_result + when: cluster_id is defined + + - name: Assert delete idempotency + assert: + that: + - not delete_idempotency_result.changed + fail_msg: "Delete idempotency check failed" + success_msg: "Delete idempotency check passed" + when: delete_idempotency_result is defined + + # Clean up the network + - name: Delete the network + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + when: network_id is defined + ignore_errors: true + + # Error handling and cleanup + rescue: + - name: Capture error + set_fact: + test_failed: true + + - name: Cleanup - ensure cluster is deleted + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + wait: true + ignore_errors: true + when: cluster_id is defined + + - name: Cleanup - ensure network is deleted + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + ignore_errors: true + when: network_id is defined + + - name: Fail with useful error message + fail: + msg: "MediaLive Cluster integration tests failed. See previous errors for details." + when: test_failed | default(false) + + always: + - name: Ensure all test resources are cleaned up (cluster) + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + wait: true + ignore_errors: true + when: cluster_id is defined + + - name: Ensure all test resources are cleaned up (network) + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + ignore_errors: true + when: network_id is defined + + - name: Ensure all test resources are cleaned up (role) + amazon.aws.iam_role: + name: "{{ iam_role.iam_role.role_name }}" + state: absent + wait: true + ignore_errors: true + when: instance_role_arn is defined + diff --git a/tests/integration/targets/medialive_input/aliases b/tests/integration/targets/medialive_input/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_input/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_input/meta/main.yml b/tests/integration/targets/medialive_input/meta/main.yml new file mode 100644 index 00000000000..9cf91cb1cc2 --- /dev/null +++ b/tests/integration/targets/medialive_input/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: setup_botocore_pip + vars: + botocore_version: "1.37.30" diff --git a/tests/integration/targets/medialive_input/tasks/main.yml b/tests/integration/targets/medialive_input/tasks/main.yml new file mode 100644 index 00000000000..75b90e2ce06 --- /dev/null +++ b/tests/integration/targets/medialive_input/tasks/main.yml @@ -0,0 +1,217 @@ +--- +# Integration tests for medialive_input module +- name: Wrap MediaLive Input 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: + input_name: "{{ resource_prefix }}-input" + sdi_source: + name_single: "{{ resource_prefix }}-sdi-source-single" + name_alt: "{{ resource_prefix }}-alt-sdi-source" + name_quad: "{{ resource_prefix }}-sdi-source-quad" + # sdi_source_mode: "QUADRANT" + # sdi_source_mode_interleave: "INTERLEAVE" + # quad_source_type: "QUAD" + # single_source_type: "SINGLE" + wait_timeout: 30 + + # Create MediaLive SDI source + - name: Create a single MediaLive SDI source + community.aws.medialive_sdi_source: + name: '{{ sdi_source.name_single }}' + type: SINGLE + state: present + register: sdi_source_result + + # Test Input creation: SDI source + - name: Create a MediaLive Anywhere Input with SDI source + community.aws.medialive_input: + name: "{{ input_name }}" + state: present + type: SDI + input_network_location: ON_PREMISES + sdi_sources: + - '{{ sdi_source_result.sdi_source.sdi_source_id }}' + tags: + AnsibleTest: '{{ resource_prefix }}-sdi_source' + wait: true + wait_timeout: "{{ wait_timeout }}" + register: create_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: create_result is not failed or 'TooManyRequestsException' not in (create_result.msg | default('')) + + - name: Assert Input was created successfully + ansible.builtin.assert: + that: + - create_result is changed + - create_result.input is defined + - create_result.input.name == input_name + - create_result.input.input_id is defined + - create_result.input.arn is defined + - create_result.input.state == 'DETACHED' + - create_result.input.type == 'SDI' + - create_result.input.input_network_location == 'ON_PREMISES' + - create_result.input.sdi_sources | length == 1 + - create_result.input.sdi_sources[0] == sdi_source_result.sdi_source.sdi_source_id + - create_result.input.tags.AnsibleTest is defined + - create_result.input.tags.AnsibleTest == resource_prefix + '-sdi_source' + fail_msg: "Input creation failed or returned unexpected data" + success_msg: "Input created successfully with expected properties" + + # Store Input ID for later use + - name: Store Input ID + ansible.builtin.set_fact: + input_id: "{{ create_result.input.input_id }}" + + # Test idempotency + - name: Create the same Input again (idempotency check) + community.aws.medialive_input: + name: "{{ input_name }}" + state: present + type: SDI + input_network_location: ON_PREMISES + sdi_sources: + - '{{ sdi_source_result.sdi_source.sdi_source_id }}' + tags: + AnsibleTest: '{{ resource_prefix }}-sdi_source' + wait: true + wait_timeout: "{{ wait_timeout }}" + register: idempotency_result + + - name: Assert idempotency + ansible.builtin.assert: + that: + - not idempotency_result.changed + - idempotency_result.input is defined + - idempotency_result.input.input_id == input_id + fail_msg: "Idempotency check failed - module made changes when none were expected" + success_msg: "Idempotency check passed" + + # Test retrieval by ID + - name: Get Input by ID + community.aws.medialive_input_info: + id: "{{ input_id }}" + register: get_by_id_result + + - name: Assert Input was retrieved by ID + ansible.builtin.assert: + that: + - not get_by_id_result.changed + - get_by_id_result.input is defined + - get_by_id_result.input.input_id == input_id + - get_by_id_result.input.name == input_name + fail_msg: "Failed to retrieve Input by ID" + success_msg: "Successfully retrieved Input by ID" + + # Test retrieval by name + - name: Get Input by name + community.aws.medialive_input_info: + name: "{{ input_name }}" + register: get_by_name_result + + - name: Assert Input was retrieved by name + ansible.builtin.assert: + that: + - not get_by_name_result.changed + - get_by_name_result.input is defined + - get_by_name_result.input.input_id == input_id + - get_by_name_result.input.name == input_name + fail_msg: "Failed to retrieve Input by name" + success_msg: "Successfully retrieved Input by name" + + # Test check mode + - name: Test check mode - attempt to delete Input + community.aws.medialive_input: + name: "{{ input_name }}" + state: absent + check_mode: true + register: check_mode_result + + - name: Assert check mode works correctly + ansible.builtin.assert: + that: + - check_mode_result is changed + - (check_mode_result.input | default({})) == {} + fail_msg: "Check mode did not work as expected" + success_msg: "Check mode correctly simulated deletion" + + # # Test INPUT deletion + # - name: Delete the INPUT + # community.aws.medialive_INPUT: + # name: "{{ INPUT_name }}" + # state: absent + # wait: true + # wait_timeout: "{{ wait_timeout }}" + # register: delete_result + # retries: 3 # Add retries for API throttling resilience + # delay: 5 + # until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + + # - name: Assert INPUT was deleted + # ansible.builtin.assert: + # that: + # - delete_result is changed + # fail_msg: "INPUT deletion failed" + # success_msg: "INPUT deleted successfully" + + # # Test deletion idempotency + # - name: Try to delete the INPUT again (idempotency check) + # community.aws.medialive_INPUT: + # name: "{{ INPUT_name }}" + # state: absent + # register: delete_idempotency_result + + # - name: Assert delete idempotency + # ansible.builtin.assert: + # that: + # - not delete_idempotency_result.changed + # fail_msg: "Delete idempotency check failed" + # success_msg: "Delete idempotency check passed" + + # Error handling and cleanup + rescue: + - name: Capture error + ansible.builtin.set_fact: + test_failed: true + + - name: Cleanup - ensure Input is deleted + community.aws.medialive_input: + name: "{{ input_name }}" + state: absent + wait: true + ignore_errors: true + when: input_id is defined + + - name: Fail with useful error message + ansible.builtin.fail: + msg: "MediaLive Input integration tests failed. See previous errors for details." + when: test_failed | default(false) + + always: + - name: Ensure all test resources are cleaned up + block: + - name: Clean up Input + community.aws.medialive_input: + name: "{{ input_name }}" + state: absent + wait: false # Don't wait in cleanup to speed up test completion + ignore_errors: true + when: input_id is defined + + - name: Clean up SDI source + community.aws.medialive_sdi_source: + name: '{{ sdi_source.name_single }}' + state: absent + ignore_errors: true + when: sdi_source_result is defined diff --git a/tests/integration/targets/medialive_network/aliases b/tests/integration/targets/medialive_network/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_network/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_network/meta/main.yml b/tests/integration/targets/medialive_network/meta/main.yml new file mode 100644 index 00000000000..16c24927b55 --- /dev/null +++ b/tests/integration/targets/medialive_network/meta/main.yml @@ -0,0 +1,4 @@ +dependencies: + - role: setup_botocore_pip + vars: + botocore_version: "1.35.17" diff --git a/tests/integration/targets/medialive_network/tasks/main.yml b/tests/integration/targets/medialive_network/tasks/main.yml new file mode 100644 index 00000000000..e52b4dc59b0 --- /dev/null +++ b/tests/integration/targets/medialive_network/tasks/main.yml @@ -0,0 +1,192 @@ +--- +# Integration tests for medialive_network module +- name: Wrap MediaLive Network 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: + network_name: "{{ resource_prefix }}-network" + ip_pool_cidr: "10.21.21.0/24" + route_cidr: "0.0.0.0/0" + route_gateway: "10.21.21.1" + wait_timeout: 30 + + # Test network creation + - name: Create a MediaLive Anywhere network + community.aws.medialive_network: + name: "{{ network_name }}" + state: present + ip_pools: + - cidr: "{{ ip_pool_cidr }}" + routes: + - cidr: "{{ route_cidr }}" + gateway: "{{ route_gateway }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: create_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: create_result is not failed or 'TooManyRequestsException' not in (create_result.msg | default('')) + + - name: Assert network was created successfully + ansible.builtin.assert: + that: + - create_result is changed + - create_result.network is defined + - create_result.network.name == network_name + - create_result.network.state in ['ACTIVE', 'IDLE', 'IN_USE'] + - create_result.network.ip_pools | length == 1 + - create_result.network.ip_pools[0].cidr == ip_pool_cidr + - create_result.network.routes | length == 1 + - create_result.network.routes[0].cidr == route_cidr + - create_result.network.routes[0].gateway == route_gateway + - create_result.network.network_id is defined + - create_result.network.arn is defined + fail_msg: "Network creation failed or returned unexpected data" + success_msg: "Network created successfully with expected properties" + + # Store network ID for later use + - name: Store network ID + ansible.builtin.set_fact: + network_id: "{{ create_result.network.network_id }}" + + # Test idempotency + - name: Create the same network again (idempotency check) + community.aws.medialive_network: + name: "{{ network_name }}" + state: present + ip_pools: + - cidr: "{{ ip_pool_cidr }}" + routes: + - cidr: "{{ route_cidr }}" + gateway: "{{ route_gateway }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: idempotency_result + + - name: Assert idempotency + ansible.builtin.assert: + that: + - not idempotency_result.changed + - idempotency_result.network is defined + - idempotency_result.network.network_id == network_id + fail_msg: "Idempotency check failed - module made changes when none were expected" + success_msg: "Idempotency check passed" + + # Test retrieval by ID + - name: Get network by ID + community.aws.medialive_network_info: + id: "{{ network_id }}" + register: get_by_id_result + + - name: Assert network was retrieved by ID + ansible.builtin.assert: + that: + - not get_by_id_result.changed + - get_by_id_result.network is defined + - get_by_id_result.network.network_id == network_id + - get_by_id_result.network.name == network_name + fail_msg: "Failed to retrieve network by ID" + success_msg: "Successfully retrieved network by ID" + + # Test retrieval by name + - name: Get network by name + community.aws.medialive_network_info: + name: "{{ network_name }}" + register: get_by_name_result + + - name: Assert network was retrieved by name + ansible.builtin.assert: + that: + - not get_by_name_result.changed + - get_by_name_result.network is defined + - get_by_name_result.network.network_id == network_id + - get_by_name_result.network.name == network_name + fail_msg: "Failed to retrieve network by name" + success_msg: "Successfully retrieved network by name" + + # Test check mode + - name: Test check mode - attempt to delete network + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + check_mode: true + register: check_mode_result + + - name: Assert check mode works correctly + ansible.builtin.assert: + that: + - check_mode_result is changed + - (check_mode_result.network | default({})) == {} + fail_msg: "Check mode did not work as expected" + success_msg: "Check mode correctly simulated deletion" + + # Test network deletion + - name: Delete the network + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + register: delete_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + + - name: Assert network was deleted + ansible.builtin.assert: + that: + - delete_result is changed + fail_msg: "Network deletion failed" + success_msg: "Network deleted successfully" + + # Test deletion idempotency + - name: Try to delete the network again (idempotency check) + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + register: delete_idempotency_result + + - name: Assert delete idempotency + ansible.builtin.assert: + that: + - not delete_idempotency_result.changed + fail_msg: "Delete idempotency check failed" + success_msg: "Delete idempotency check passed" + + # Error handling and cleanup + rescue: + - name: Capture error + ansible.builtin.set_fact: + test_failed: true + + - name: Cleanup - ensure network is deleted + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + ignore_errors: true + when: network_id is defined + + - name: Fail with useful error message + ansible.builtin.fail: + msg: "MediaLive Network integration tests failed. See previous errors for details." + when: test_failed | default(false) + + always: + - name: Ensure all test resources are cleaned up + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: false # Don't wait in cleanup to speed up test completion + ignore_errors: true + when: network_id is defined diff --git a/tests/integration/targets/medialive_node/aliases b/tests/integration/targets/medialive_node/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/medialive_node/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/medialive_node/meta/main.yml b/tests/integration/targets/medialive_node/meta/main.yml new file mode 100644 index 00000000000..42b16d695fd --- /dev/null +++ b/tests/integration/targets/medialive_node/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_node/tasks/cleanup.yml b/tests/integration/targets/medialive_node/tasks/cleanup.yml new file mode 100644 index 00000000000..0c2646ba42c --- /dev/null +++ b/tests/integration/targets/medialive_node/tasks/cleanup.yml @@ -0,0 +1,41 @@ +--- +- name: Cleanup - ensure node is deleted + community.aws.medialive_node: + name: "{{ node_name }}" + cluster_id: "{{ cluster_id }}" + state: absent + wait: true + ignore_errors: true + when: node_id is defined + +- name: Cleanup - ensure cluster is deleted + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: absent + wait: true + ignore_errors: true + when: cluster_id is defined + +- name: Cleanup - ensure network is deleted + community.aws.medialive_network: + name: "{{ network_name }}" + state: absent + wait: true + ignore_errors: true + when: network_id is defined + +- name: Cleanup - ensure role is deleted + amazon.aws.iam_role: + name: '{{ instance_role_name }}' + state: absent + wait: true + ignore_errors: true + when: instance_role_arn is defined + +- name: Cleanup - ensure role policy is deleted + amazon.aws.iam_managed_policy: + name: '{{ instance_role_policy_name }}' + state: absent + wait: true + ignore_errors: true + when: instance_role_policy_arn is defined diff --git a/tests/integration/targets/medialive_node/tasks/main.yml b/tests/integration/targets/medialive_node/tasks/main.yml new file mode 100644 index 00000000000..2ecefd63e1a --- /dev/null +++ b/tests/integration/targets/medialive_node/tasks/main.yml @@ -0,0 +1,376 @@ +--- +# Integration tests for medialive_node module +- name: Wrap MediaLive node 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: + cluster_name: "{{ resource_prefix }}-cluster" + network_name: "{{ resource_prefix }}-network" + node_name: "{{ resource_prefix }}-node" + instance_role_name: "{{ resource_prefix }}-role" + instance_role_policy_name: "{{ resource_prefix }}-policy" + ip_pool_cidr: "10.21.21.0/24" + route_cidr: "0.0.0.0/0" + route_gateway: "10.21.21.1" + wait_timeout: 60 + + # Look up Account ID to use in ARN construction + - name: Look up Account ID + amazon.aws.aws_caller_info: + register: caller_info + + # Create a policy for the required IAM role for MediaLive Anywhere nodes + - name: Create MediaLive Anywhere node IAM role policy + amazon.aws.iam_managed_policy: + name: '{{ instance_role_policy_name }}' + state: present + policy: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "medialive:SubmitAnywhereStateChange", + "medialive:PollAnywhere" + ], + "Resource": "arn:aws:medialive:*:{{ caller_info.account }}:cluster:*" + }, + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam::{{ caller_info.account }}:role/MediaLiveAccessRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": [ + "medialive.amazonaws.com" + ] + } + } + } + ] + } + register: instance_role_policy + + # Create the required IAM role for MediaLive Anywhere nodes + - name: Create MediaLive Anywhere node IAM role + amazon.aws.iam_role: + name: '{{ instance_role_name }}' + assume_role_policy_document: | + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "medialive.amazonaws.com", + "ssm.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + } + state: present + create_instance_profile: false + managed_policies: + - arn:aws:iam::aws:policy/AWSElementalMediaLiveFullAccess + - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore + - arn:aws:iam::aws:policy/CloudWatchFullAccessV2 + - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role + - '{{ instance_role_policy.policy.arn }}' + tags: + Environment: Test + Purpose: Integration Testing + register: iam_role + + - name: Create an IAM Instance Profile + amazon.aws.iam_instance_profile: + name: '{{ instance_role_name }}' + role: '{{ instance_role_name }}' + state: present + register: instance_profile + + # Create a network first for the cluster to use + - name: Create a MediaLive Anywhere network for the cluster + community.aws.medialive_network: + name: "{{ network_name }}" + state: present + ip_pools: + - cidr: "{{ ip_pool_cidr }}" + routes: + - cidr: "{{ route_cidr }}" + gateway: "{{ route_gateway }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: network_result + retries: 3 + delay: 5 + until: network_result is not failed or 'TooManyRequestsException' not in (network_result.msg | default('')) + + - name: Capture network_id, instance_role_arn and instance_profile_arn + ansible.builtin.set_fact: + network_id: "{{ network_result.network.network_id }}" + instance_role_arn: "{{ iam_role.iam_role.arn }}" + instance_profile_arn: "{{ instance_profile.iam_instance_profile.arn }}" + when: + - network_result is defined and network_result.changed is defined + - iam_role is defined and iam_role.changed is defined + - instance_profile is defined and instance_profile.changed is defined + + # Create a cluster first to host the node + - name: Create a MediaLive Anywhere cluster + community.aws.medialive_cluster: + name: "{{ cluster_name }}" + state: present + cluster_type: "ON_PREMISES" + instance_role_arn: "{{ instance_role_arn }}" + network_settings: + default_route: "management" + interface_mappings: + - logical_interface_name: "management" + network_id: "{{ network_id }}" + - logical_interface_name: "input" + network_id: "{{ network_id }}" + wait: true + wait_timeout: "{{ wait_timeout }}" + register: cluster_result + retries: 3 + delay: 5 + until: cluster_result is not failed or 'TooManyRequestsException' not in (cluster_result.msg | default('')) + when: network_id is defined + + - name: Capture cluster_id + ansible.builtin.set_fact: + cluster_id: '{{ cluster_result.cluster.cluster_id }}' + + # Test creating a node in the cluster + - name: Create a MediaLive Anywhere node + community.aws.medialive_node: + cluster_id: '{{ cluster_id }}' + name: '{{ node_name }}' + state: present + node_interface_mappings: + - logical_interface_name: logical-0 + physical_interface_name: eth0 + network_interface_mode: NAT + role: ACTIVE + wait: true + wait_timeout: "{{ wait_timeout }}" + register: create_result + retries: 3 + delay: 5 + until: create_result is not failed or 'TooManyRequestsException' not in (create_result.msg | default('')) + when: cluster_id is defined + + - name: Assert node was created successfully + ansible.builtin.assert: + that: + - create_result is changed + - create_result.node is defined + - create_result.node.name == node_name + - create_result.node.state == "REGISTERING" + - create_result.node.connection_state == "DISCONNECTED" + - create_result.node.role == "ACTIVE" + - create_result.node.channel_placement_groups == [] + - create_result.node.instance_arn == "" + - create_result.node.node_interface_mappings | length == 1 + - create_result.node.node_interface_mappings[0].logical_interface_name == "logical-0" + - create_result.node.node_interface_mappings[0].physical_interface_name == "eth0" + - create_result.node.node_interface_mappings[0].network_interface_mode == "NAT" + - create_result.node.cluster_id is defined + - create_result.node.arn is defined + fail_msg: "Node creation failed or returned unexpected data" + success_msg: "Node created successfully with expected properties" + when: create_result is defined and create_result.node is defined + + - name: Capture node_id + ansible.builtin.set_fact: + node_id: '{{ create_result.node.node_id }}' + + # Test idempotency + - name: Create the same node again (idempotency check) + community.aws.medialive_node: + cluster_id: '{{ cluster_id }}' + name: '{{ node_name }}' + state: present + node_interface_mappings: + - logical_interface_name: logical-0 + physical_interface_name: eth0 + network_interface_mode: NAT + role: ACTIVE + wait: true + wait_timeout: "{{ wait_timeout }}" + register: idempotency_result + when: node_id is defined + + - name: Assert idempotency + ansible.builtin.assert: + that: + - not idempotency_result.changed + - idempotency_result.node is defined + - idempotency_result.node.node_id == node_id + fail_msg: "Idempotency check failed - module made changes when none were expected" + success_msg: "Idempotency check passed" + when: idempotency_result is defined and idempotency_result.node is defined + + # Test retrieval by ID + - name: Get node by ID + community.aws.medialive_node_info: + id: "{{ node_id }}" + cluster_id: "{{ cluster_id }}" + register: get_by_id_result + when: + - node_id is defined + - cluster_id is defined + + - name: Assert node was retrieved by ID + ansible.builtin.assert: + that: + - not get_by_id_result.changed + - get_by_id_result.node is defined + - get_by_id_result.node.node_id == node_id + - get_by_id_result.node.name == node_name + fail_msg: "Failed to retrieve node by ID" + success_msg: "Successfully retrieved node by ID" + when: + - get_by_id_result is defined + - get_by_id_result.node is defined + + # Test retrieval by name + - name: Get node by name + community.aws.medialive_node_info: + name: "{{ node_name }}" + cluster_id: "{{ cluster_id }}" + register: get_by_name_result + when: + - node_name is defined + - cluster_id is defined + + - name: Assert node was retrieved by name + ansible.builtin.assert: + that: + - not get_by_name_result.changed + - get_by_name_result.node is defined + - get_by_name_result.node.node_id == node_id + - get_by_name_result.node.name == node_name + fail_msg: "Failed to retrieve node by name" + success_msg: "Successfully retrieved node by name" + when: + - get_by_name_result is defined + - get_by_name_result.node is defined + + # Test node registration script creation + - name: Create a node registration script + community.aws.medialive_node_registration: + node_id: "{{ node_id }}" + cluster_id: "{{ cluster_id }}" + role: ACTIVE + register: node_registration_result + when: + - node_id is defined + - cluster_id is defined + + - name: Assert registration script was created + ansible.builtin.assert: + that: + - node_registration_result is changed + - node_registration_result.node is defined + - (node_registration_result.node | default({})) != {} + - node_registration_result.node.node_registration_script is defined + - node_registration_result.node.node_registration_script != '' + fail_msg: "Node registration script creation did not work as expected" + success_msg: "Node registration script was successfully created" + when: node_registration_result is defined + + # Test check mode + - name: Test check mode - attempt to delete node + community.aws.medialive_node: + name: "{{ node_name }}" + cluster_id: "{{ cluster_id }}" + state: absent + check_mode: true + register: check_mode_result + when: node_id is defined + + - name: Assert check mode works correctly + ansible.builtin.assert: + that: + - check_mode_result is changed + - (check_mode_result.node | default({})) == {} + fail_msg: "Check mode did not work as expected" + success_msg: "Check mode correctly simulated deletion" + when: check_mode_result is defined + + + # Test node deletion + - name: Delete the node + community.aws.medialive_node: + name: "{{ node_name }}" + cluster_id: "{{ cluster_id }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + register: delete_result + retries: 3 + delay: 5 + until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + when: node_id is defined + + - name: Assert node was deleted + ansible.builtin.assert: + that: + - delete_result is changed + fail_msg: "Node deletion failed" + success_msg: "Node deleted successfully" + when: delete_result is defined + + # Test deletion idempotency + - name: Try to delete the node again (idempotency check) + community.aws.medialive_node: + name: "{{ node_name }}" + cluster_id: "{{ cluster_id }}" + state: absent + register: delete_idempotency_result + when: node_id is defined + + - name: Assert delete idempotency + ansible.builtin.assert: + that: + - not delete_idempotency_result.changed + fail_msg: "Delete idempotency check failed" + success_msg: "Delete idempotency check passed" + when: delete_idempotency_result 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 node 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 + 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 From b6abffed923375da9796e097e2a8d81acbc9ebd4 Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Fri, 18 Jul 2025 15:29:45 -0700 Subject: [PATCH 02/11] Fix return documentation for medialive_node --- plugins/modules/medialive_node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/modules/medialive_node.py b/plugins/modules/medialive_node.py index f7034881cbf..e6458965189 100644 --- a/plugins/modules/medialive_node.py +++ b/plugins/modules/medialive_node.py @@ -225,18 +225,18 @@ - For information about how physical cards are identified on your node hardware, see the documentation for your node hardware. - The numbering always starts at 1. type: int - required: true + returned: success channel_number: description: - A number that uniquely identifies a port on the card. - This must be an SDI port (not a timecode port, for example). - For information about how ports are identified on physical cards, see the documentation for your node hardware. type: int - required: true + returned: success sdi_source: description: - The ID of a SDI source streaming on the given SDI capture card port. - required: true + returned: success type: str state: description: > From d7ec0ac81307efeeff2f1f4c4142b9b4df18c2eb Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Fri, 18 Jul 2025 15:49:18 -0700 Subject: [PATCH 03/11] Remove reference to missing input_vpc_request --- plugins/modules/medialive_input.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/modules/medialive_input.py b/plugins/modules/medialive_input.py index cae1ee8a706..b79402cf5d5 100644 --- a/plugins/modules/medialive_input.py +++ b/plugins/modules/medialive_input.py @@ -79,7 +79,6 @@ input_security_groups: description: - A list of security groups referenced by IDs to attach to the input - - Mutually exclusive with O(input_vpc_request) type: list elements: str media_connect_flows: From e4c66cd259fb3ace9bce608526694d420e44ac58 Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Fri, 18 Jul 2025 15:59:38 -0700 Subject: [PATCH 04/11] Remove duplicate aws_access_key and aws_secret_key from medialive channel placement group documentation --- plugins/modules/medialive_channel_placement_group_info.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/plugins/modules/medialive_channel_placement_group_info.py b/plugins/modules/medialive_channel_placement_group_info.py index a0ccb7e361c..9f6046d2f6b 100644 --- a/plugins/modules/medialive_channel_placement_group_info.py +++ b/plugins/modules/medialive_channel_placement_group_info.py @@ -26,14 +26,6 @@ - The ID of the cluster. required: true type: str - aws_access_key: - description: - - AWS access key ID. - type: str - aws_secret_key: - description: - - AWS secret access key. - type: str region: description: - The AWS region to use. From a0d881e51757c2e118f0847d60b0c528045fe407 Mon Sep 17 00:00:00 2001 From: Brenton Buxell Date: Tue, 22 Jul 2025 10:40:35 -0700 Subject: [PATCH 05/11] Update IAM Role name to be ansible-test-* --- tests/integration/targets/medialive_cluster/tasks/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/targets/medialive_cluster/tasks/main.yml b/tests/integration/targets/medialive_cluster/tasks/main.yml index 0523faf065d..44429e69f4e 100644 --- a/tests/integration/targets/medialive_cluster/tasks/main.yml +++ b/tests/integration/targets/medialive_cluster/tasks/main.yml @@ -24,7 +24,7 @@ # Create the required IAM role for MediaLive Anywhere nodes - name: Create MediaLive Anywhere node IAM role amazon.aws.iam_role: - name: MLANodeRole-Test + name: ansible-test-MLANodeRole assume_role_policy_document: | { "Version": "2012-10-17", From adac42e7b555dc583c31078227aa09c75f1c3acd Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Fri, 15 Aug 2025 13:25:46 -0700 Subject: [PATCH 06/11] No need for changelogs for new modules --- changelogs/fragments/medialive_cluster.yml | 3 --- changelogs/fragments/medialive_network.yml | 3 --- changelogs/fragments/medialive_sdi_source.yml | 3 --- 3 files changed, 9 deletions(-) delete mode 100644 changelogs/fragments/medialive_cluster.yml delete mode 100644 changelogs/fragments/medialive_network.yml delete mode 100644 changelogs/fragments/medialive_sdi_source.yml diff --git a/changelogs/fragments/medialive_cluster.yml b/changelogs/fragments/medialive_cluster.yml deleted file mode 100644 index 35773eb2fe2..00000000000 --- a/changelogs/fragments/medialive_cluster.yml +++ /dev/null @@ -1,3 +0,0 @@ -minor_changes: - - medialive_cluster_info - New module for gathering info about AWS MediaLive Anywhere clusters - - medialive_cluster - New module for managing AWS MediaLive Anywhere clusters diff --git a/changelogs/fragments/medialive_network.yml b/changelogs/fragments/medialive_network.yml deleted file mode 100644 index 2cc89cf87d2..00000000000 --- a/changelogs/fragments/medialive_network.yml +++ /dev/null @@ -1,3 +0,0 @@ -minor_changes: - - medialive_network_info - New module for gathering info about AWS MediaLive Anywhere networks - - medialive_network - New module for managing AWS MediaLive Anywhere networks diff --git a/changelogs/fragments/medialive_sdi_source.yml b/changelogs/fragments/medialive_sdi_source.yml deleted file mode 100644 index 5b9aff55d75..00000000000 --- a/changelogs/fragments/medialive_sdi_source.yml +++ /dev/null @@ -1,3 +0,0 @@ -minor_changes: - - medialive_sdi_source - New module for managing AWS MediaLive Anywhere SDI sources - - medialive_sdi_source_info - New module for gathering info about AWS MediaLive Anywhere SDI sources From 2638ce76729d79594b2a1797809a95d2ee26c95f Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Fri, 15 Aug 2025 13:31:51 -0700 Subject: [PATCH 07/11] Minor documentation fixes --- plugins/modules/medialive_channel.py | 77 ++++++++++++++-------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/plugins/modules/medialive_channel.py b/plugins/modules/medialive_channel.py index fab2d0873e7..d2ff7b71052 100644 --- a/plugins/modules/medialive_channel.py +++ b/plugins/modules/medialive_channel.py @@ -18,12 +18,12 @@ options: cdi_input_specification: description: - - Specification of CDI inputs for this channel + - Specification of CDI inputs for this channel. type: dict suboptions: resolution: description: - - Maximum CDI input resolution + - Maximum CDI input resolution. type: str choices: ['SD', 'HD', 'FHD', 'UHD'] channel_class: @@ -80,19 +80,19 @@ suboptions: password_param: description: - - key used to extract the password from EC2 Parameter store + - key used to extract the password from EC2 Parameter store. type: str stream_name: description: - - Stream name for RTMP destinations (URLs of type rtmp://) + - Stream name for RTMP destinations (URLs of type rtmp://). type: str url: description: - - A URL specifying a destination + - A URL specifying a destination. type: str username: description: - - username for destination + - username for destination. type: str srt_settings: description: @@ -102,15 +102,15 @@ suboptions: encryption_passphrase_secret_arn: description: - - Arn used to extract the password from Secrets Manager + - Arn used to extract the password from Secrets Manager. type: str stream_id: description: - - Stream id for SRT destinations (URLs of type srt://) + - Stream id for SRT destinations (URLs of type srt://). type: str url: description: - - A URL specifying a destination + - A URL specifying a destination. type: str logical_interface_names: description: @@ -120,12 +120,12 @@ elements: str encoder_settings: description: - - Encoder Settings + - Encoder Settings. type: dict suboptions: audio_descriptions: description: - - Placeholder documentation for __listOfAudioDescription + - Placeholder documentation for __listOfAudioDescription. type: list elements: dict suboptions: @@ -168,17 +168,17 @@ choices: ['FOLLOW_INPUT', 'USE_CONFIGURED'] audio_watermarking_settings: description: - - Settings to configure one or more solutions that insert audio watermarks in the audio encode + - Settings to configure one or more solutions that insert audio watermarks in the audio encode. type: dict suboptions: nielsen_watermarks_settings: description: - - Settings to configure Nielsen Watermarks in the audio encode + - Settings to configure Nielsen Watermarks in the audio encode. type: dict suboptions: nielsen_cbet_settings: description: - - Complete these fields only if you want to insert watermarks of type Nielsen CBET + - Complete these fields only if you want to insert watermarks of type Nielsen CBET. type: dict suboptions: cbet_check_digit_string: @@ -192,11 +192,12 @@ choices: ['DISABLED', 'ENABLED'] csid: description: - - Enter the CBET Source ID (CSID) to use in the watermark + - Enter the CBET Source ID (CSID) to use in the watermark. type: str nielsen_distribution_type: description: - - Choose the distribution types that you want to assign to the watermarks - PROGRAM_CONTENT or FINAL_DISTRIBUTOR + - Choose the distribution types that you want to assign to the watermarks. + - PROGRAM_CONTENT or FINAL_DISTRIBUTOR. type: str choices: ['FINAL_DISTRIBUTOR', 'PROGRAM_CONTENT'] nielsen_naes_ii_nw_settings: @@ -206,15 +207,15 @@ suboptions: check_digit_string: description: - - Enter the check digit string for the watermark + - Enter the check digit string for the watermark. type: str sid: description: - - Enter the Nielsen Source ID (SID) to include in the watermark + - Enter the Nielsen Source ID (SID) to include in the watermark. type: float timezone: description: - - Choose the timezone for the time stamps in the watermark. If not provided, the timestamps will be in Coordinated Universal Time (UTC) + - Choose the timezone for the time stamps in the watermark. If not provided, the timestamps will be in Coordinated Universal Time (UTC). type: str choices: - 'AMERICA_PUERTO_RICO' @@ -451,7 +452,7 @@ - 'SPEECH' lfe_control: description: - - When encoding 3/2 audio, setting to lfe enables the LFE channel + - When encoding 3/2 audio, setting to lfe enables the LFE channel. type: str choices: ['LFE', 'NO_LFE'] lfe_filter: @@ -525,11 +526,11 @@ type: float pass_through_settings: description: - - Pass Through Settings + - Pass Through Settings. type: dict wav_settings: description: - - Wav Settings + - Wav Settings. type: dict suboptions: bit_depth: @@ -638,7 +639,7 @@ suboptions: password_param: description: - - key used to extract the password from EC2 Parameter store + - key used to extract the password from EC2 Parameter store. type: str uri: description: @@ -678,7 +679,7 @@ type: int password_param: description: - - Documentation update needed + - Documentation update needed. type: str pois_endpoint: description: @@ -686,7 +687,7 @@ type: str username: description: - - Documentation update needed + - Documentation update needed. type: str zone_identity: description: @@ -703,12 +704,12 @@ type: int no_regional_blackout_flag: description: - - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates + - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates. type: str choices: ['FOLLOW', 'IGNORE'] web_delivery_allowed_flag: description: - - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates + - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates. type: str choices: ['FOLLOW', 'IGNORE'] scte35_time_signal_apos: @@ -722,12 +723,12 @@ type: int no_regional_blackout_flag: description: - - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates + - When set to ignore, Segment Descriptors with noRegionalBlackoutFlag set to 0 will no longer trigger blackouts or Ad Avail slates. type: str choices: ['FOLLOW', 'IGNORE'] web_delivery_allowed_flag: description: - - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates + - When set to ignore, Segment Descriptors with webDeliveryAllowedFlag set to 0 will no longer trigger blackouts or Ad Avail slates. type: str choices: ['FOLLOW', 'IGNORE'] scte35_segmentation_scope: @@ -749,7 +750,7 @@ suboptions: password_param: description: - - key used to extract the password from EC2 Parameter store + - key used to extract the password from EC2 Parameter store. type: str uri: description: @@ -757,7 +758,7 @@ type: str username: description: - - Documentation update needed + - Documentation update needed. type: str network_end_blackout: description: @@ -771,7 +772,7 @@ suboptions: password_param: description: - - key used to extract the password from EC2 Parameter store + - key used to extract the password from EC2 Parameter store. type: str uri: description: @@ -792,7 +793,7 @@ choices: ['DISABLED', 'ENABLED'] caption_descriptions: description: - - Settings for caption decriptions + - Settings for caption decriptions. type: list elements: dict suboptions: @@ -812,11 +813,11 @@ suboptions: arib_destination_settings: description: - - Arib Destination Settings + - Arib Destination Settings. type: dict burn_in_destination_settings: description: - - Burn In Destination Settings + - Burn In Destination Settings. type: dict suboptions: alignment: @@ -840,7 +841,7 @@ suboptions: password_param: description: - - key used to extract the password from EC2 Parameter store + - key used to extract the password from EC2 Parameter store. type: str uri: description: @@ -1148,7 +1149,7 @@ - 'DVBDASH_7_CLEAN_FEED' feature_activations: description: - - Feature Activations + - Feature Activations. type: dict suboptions: input_prepare_schedule_actions: @@ -1182,7 +1183,7 @@ suboptions: black_frame_msec: description: - - Documentation update needed + - Documentation update needed. type: int input_loss_image_color: description: From b2410b127b1833acbc3a0783a0f86472b8defca4 Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Fri, 15 Aug 2025 15:53:55 -0700 Subject: [PATCH 08/11] Get rid of custom waiters in favor of official ones --- plugins/module_utils/medialive.py | 9 + plugins/modules/medialive_channel.py | 18 +- .../medialive_channel_placement_group.py | 110 +++-------- .../medialive_channel_placement_group_info.py | 7 +- plugins/modules/medialive_cluster.py | 137 ++------------ plugins/modules/medialive_cluster_info.py | 9 +- plugins/modules/medialive_input.py | 19 +- plugins/modules/medialive_input_info.py | 14 +- plugins/modules/medialive_network.py | 174 +---------------- plugins/modules/medialive_network_info.py | 12 +- plugins/modules/medialive_node.py | 176 +----------------- plugins/modules/medialive_node_info.py | 9 +- .../modules/medialive_node_registration.py | 9 +- plugins/modules/medialive_sdi_source.py | 24 +-- plugins/modules/medialive_sdi_source_info.py | 12 +- .../tasks/cleanup.yml | 1 - .../tasks/main.yml | 2 - .../targets/medialive_cluster/tasks/main.yml | 6 - .../targets/medialive_input/meta/main.yml | 2 +- .../targets/medialive_input/tasks/main.yml | 64 +++---- .../targets/medialive_network/tasks/main.yml | 9 - .../targets/medialive_node/tasks/cleanup.yml | 2 - .../targets/medialive_node/tasks/main.yml | 9 - 23 files changed, 153 insertions(+), 681 deletions(-) create mode 100644 plugins/module_utils/medialive.py diff --git a/plugins/module_utils/medialive.py b/plugins/module_utils/medialive.py new file mode 100644 index 00000000000..8780b88262b --- /dev/null +++ b/plugins/module_utils/medialive.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +# Copyright: Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError + +class MedialiveAnsibleAWSError(AnsibleAWSError): + pass diff --git a/plugins/modules/medialive_channel.py b/plugins/modules/medialive_channel.py index d2ff7b71052..33a1541c7ec 100644 --- a/plugins/modules/medialive_channel.py +++ b/plugins/modules/medialive_channel.py @@ -14,7 +14,7 @@ - This module includes basic functionality for managing channels, but does not include input validation - Requires boto3 >= 1.37.30 author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: cdi_input_specification: description: @@ -4833,7 +4833,7 @@ """ import uuid -from typing import Dict +from typing import Dict, Literal try: from botocore.exceptions import WaiterError, ClientError, BotoCoreError @@ -4843,13 +4843,10 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass - class MediaLiveChannelManager: '''Manage AWS MediaLive Anywhere Channels''' @@ -5053,12 +5050,17 @@ def delete_channel(self, channel_id: str): exception=e ) - def wait_for(self, want: str, channel_id: str, wait_timeout: int = 60): + def wait_for( + self, + want: Literal['channel_created','channel_deleted','channel_running','channel_stopped'], + channel_id: str, + wait_timeout = 60 + ): """ Invoke one of the custom waiters and wait Args: - want: one of 'channel_created'|'channel_deleted'|'channel_running'|'channel_stopped' + want: the state to wait for channel_id: the ID of the Channel wait_timeout: the maximum amount of time to wait in seconds (default: 60) """ diff --git a/plugins/modules/medialive_channel_placement_group.py b/plugins/modules/medialive_channel_placement_group.py index 75be0546eeb..46d26652cf2 100644 --- a/plugins/modules/medialive_channel_placement_group.py +++ b/plugins/modules/medialive_channel_placement_group.py @@ -132,70 +132,7 @@ 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 -from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory - - -class MediaLiveWaiterFactory(BaseWaiterFactory): - '''Custom waiter factory for MediaLive channel placement group resources''' - - @property - def _waiter_model_data(self): - '''Define custom waiters for MediaLive channel placement groups''' - return { - 'channel_placement_group_created': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeChannelPlacementGroup', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ASSIGNED', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'UNASSIGNED', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'CREATE_FAILED', - 'argument': 'State' - }, - { - 'state': 'retry', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - }, - ] - }, - 'channel_placement_group_deleted': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeChannelPlacementGroup', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'DELETED', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'DELETE_FAILED', - 'argument': 'State' - } - ] - } - } - - -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveChannelPlacementGroupManager: @@ -210,7 +147,6 @@ def __init__(self, module: AnsibleAWSModule): ''' self.module = module self.client = self.module.client('medialive') - self.waiter_factory = MediaLiveWaiterFactory(module, self.client) self._channel_placement_group = {} self.changed = False @@ -243,7 +179,7 @@ def do_create_channel_placement_group(self, params): response = self.client.create_channel_placement_group(**create_params) # type: ignore self.channel_placement_group = camel_dict_to_snake_dict(response) self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to create MediaLive Channel Placement Group', exception=e @@ -293,7 +229,7 @@ def do_update_channel_placement_group(self, params): params.get('channel_placement_group_id'), params.get('cluster_id') ) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update nodes for MediaLive Channel Placement Group', exception=e @@ -315,7 +251,7 @@ def do_update_channel_placement_group(self, params): params.get('channel_placement_group_id'), params.get('cluster_id') ) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update name for MediaLive Channel Placement Group', exception=e @@ -330,14 +266,14 @@ def get_channel_placement_group_by_id(self, placement_group_id: str, cluster_id: cluster_id: The ID of the cluster """ try: - response = self.client.describe_channel_placement_group( + response = self.client.describe_channel_placement_group( # type: ignore ChannelPlacementGroupId=placement_group_id, ClusterId=cluster_id ) self.channel_placement_group = camel_dict_to_snake_dict(response) except is_boto3_error_code('ResourceNotFoundException'): self.channel_placement_group = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get MediaLive Channel Placement Group', exception=e @@ -362,7 +298,7 @@ def get_channel_placement_group_by_name(self, name: str, cluster_id: str): raise MedialiveAnsibleAWSError(message='Found more than one Channel Placement Groups under the same name') elif len(found) == 1: self.channel_placement_group = camel_dict_to_snake_dict(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get MediaLive Channel Placement Group', exception=e @@ -377,25 +313,25 @@ def delete_channel_placement_group(self, placement_group_id: str, cluster_id: st cluster_id: ID of the cluster """ try: - self.client.delete_channel_placement_group( + self.client.delete_channel_placement_group( # type: ignore ChannelPlacementGroupId=placement_group_id, ClusterId=cluster_id ) self.changed = True except is_boto3_error_code('ResourceNotFoundException'): self.channel_placement_group = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to delete MediaLive Channel Placement Group', exception=e ) def wait_for( - self, - want: Literal['channel_placement_group_created', 'channel_placement_group_deleted'], - placement_group_id: str, - cluster_id: str, - wait_timeout: int = 60) -> None: + self, + want: Literal['channel_placement_group_assigned', 'channel_placement_group_deleted'], + placement_group_id: str, + cluster_id: str, + wait_timeout = 60) -> None: """ Wait for a channel placement group to reach the desired state @@ -406,7 +342,7 @@ def wait_for( wait_timeout: Maximum time to wait in seconds """ try: - waiter = self.waiter_factory.get_waiter(want) + waiter = self.client.get_waiter(want) # type: ignore config = { 'Delay': min(5, wait_timeout), 'MaxAttempts': wait_timeout // 5 @@ -416,7 +352,7 @@ def wait_for( ClusterId=cluster_id, WaiterConfig=config ) - except WaiterError as e: + except WaiterError as e: # type: ignore raise MedialiveAnsibleAWSError( message=f'Timeout waiting for channel placement group {placement_group_id} to be {want.lower()}', exception=e @@ -464,9 +400,9 @@ def main(): # Find the placement group by ID if provided if placement_group_id: - manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) # type: ignore elif name: - manager.get_channel_placement_group_by_name(name, cluster_id) + manager.get_channel_placement_group_by_name(name, cluster_id) # type: ignore # Do nothing in check mode if module.check_mode: @@ -487,7 +423,7 @@ def main(): manager.do_update_channel_placement_group(update_params) - manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) # type: ignore # Case create else: @@ -503,18 +439,18 @@ def main(): # Wait for the placement group to be created if wait and placement_group_id: - manager.wait_for('channel_placement_group_created', placement_group_id, cluster_id, wait_timeout) - manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) + manager.wait_for('channel_placement_group_assigned', placement_group_id, cluster_id, wait_timeout) # type: ignore + manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) # type: ignore # Handle absent state elif state == 'absent': if manager.channel_placement_group.get('state') != 'DELETED': # Placement group exists, delete it - manager.delete_channel_placement_group(placement_group_id, cluster_id) + manager.delete_channel_placement_group(placement_group_id, cluster_id) # type: ignore # Wait for the placement group to be deleted if requested if wait and placement_group_id: - manager.wait_for('channel_placement_group_deleted', placement_group_id, cluster_id, wait_timeout) + manager.wait_for('channel_placement_group_deleted', placement_group_id, cluster_id, wait_timeout) # type: ignore module.exit_json(changed=manager.changed, channel_placement_group=manager.channel_placement_group) diff --git a/plugins/modules/medialive_channel_placement_group_info.py b/plugins/modules/medialive_channel_placement_group_info.py index 9f6046d2f6b..a7f42877d75 100644 --- a/plugins/modules/medialive_channel_placement_group_info.py +++ b/plugins/modules/medialive_channel_placement_group_info.py @@ -13,6 +13,7 @@ description: - Get information about an AWS MediaLive Channel Placement Group. - This module allows retrieving detailed information about a specific channel placement group. + - This module requires boto3 >= 1.35.17. author: - "David Teach" options: @@ -76,7 +77,7 @@ ''' try: - import botocore + from botocore.exceptions import ClientError, BotoCoreError except ImportError: pass # Handled by AnsibleAWSModule @@ -110,7 +111,7 @@ def get_channel_placement_group(client, module): return results except client.exceptions.NotFoundException: return cpgs - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore module.fail_json_aws(e, msg="Failed to get channel placement group information") @@ -129,7 +130,7 @@ def main(): client = module.client('medialive', retry_decorator=AWSRetry.exponential_backoff()) cpgs = get_channel_placement_group(client, module) module.exit_json(changed=False, channel_placement_groups=cpgs) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore module.fail_json_aws(e, msg='Failed to connect to AWS') diff --git a/plugins/modules/medialive_cluster.py b/plugins/modules/medialive_cluster.py index 630c3768edd..87066d76f64 100644 --- a/plugins/modules/medialive_cluster.py +++ b/plugins/modules/medialive_cluster.py @@ -12,7 +12,7 @@ - A module for creating, updating and deleting AWS MediaLive Anywhere clusters. - This module requires boto3 >= 1.35.17. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -204,7 +204,7 @@ """ import uuid -from typing import Dict +from typing import Dict, Literal try: from botocore.exceptions import WaiterError, ClientError, BotoCoreError @@ -214,107 +214,7 @@ 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 - -from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory - - -class MediaLiveWaiterFactory(BaseWaiterFactory): - """Custom waiter factory for MediaLive resources""" - - @property - def _waiter_model_data(self): - """Define custom waiters for MediaLive clusters""" - return { - 'create_cluster': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeCluster', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ACTIVE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IDLE', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'CREATE_FAILED', - 'argument': 'State' - }, - { - 'state': 'retry', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - }, - ] - }, - 'update_cluster': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeCluster', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ACTIVE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IN_USE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IDLE', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'CREATE_FAILED', - 'argument': 'State' - }, - ] - }, - 'delete_cluster': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeCluster', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'DELETED', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'DELETE_FAILED', - 'argument': 'State' - }, - ] - } - } - -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveClusterManager: @@ -329,7 +229,6 @@ def __init__(self, module: AnsibleAWSModule): """ self.module = module self.client = module.client('medialive') - self.waiter_factory = MediaLiveWaiterFactory(module, self.client) self._cluster = {} self.changed = False @@ -368,7 +267,7 @@ def do_create_cluster(self, params): response = self.client.create_cluster(**create_params) # type: ignore self.cluster = camel_dict_to_snake_dict(response) self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to create Medialive Cluster', exception=e @@ -400,7 +299,7 @@ def do_update_cluster(self, params): response = self.client.update_cluster(**update_params) # type: ignore self.cluster = camel_dict_to_snake_dict(response) self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update Medialive Cluster', exception=e @@ -425,7 +324,7 @@ def get_cluster_by_name(self, name: str): elif len(found) == 1: self.get_cluster_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get MediaLive Cluster', exception=e @@ -456,13 +355,18 @@ def delete_cluster(self, cluster_id: str): self.changed = True except is_boto3_error_code('ResourceNotFoundException'): self.cluster = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to delete Medialive Cluster', exception=e ) - def wait_for(self, want: str, cluster_id: str, wait_timeout: int = 60): + def wait_for( + self, + want: Literal['cluster_created', 'cluster_deleted'], + cluster_id: str, + wait_timeout = 60 + ): """ Invoke one of the custom waiters and wait @@ -473,7 +377,7 @@ def wait_for(self, want: str, cluster_id: str, wait_timeout: int = 60): """ try: - waiter = self.waiter_factory.get_waiter(want) + waiter = self.client.get_waiter(want) # type: ignore config = { 'Delay': min(5, wait_timeout), 'MaxAttempts': wait_timeout // 5 @@ -482,9 +386,9 @@ def wait_for(self, want: str, cluster_id: str, wait_timeout: int = 60): ClusterId=cluster_id, WaiterConfig=config ) - except WaiterError as e: + except WaiterError as e: # type: ignore raise MedialiveAnsibleAWSError( - message=f'Timeout waiting for cluster {cluster_id}', + message=f'Timeout waiting for cluster state to become {cluster_id}', exception=e ) @@ -567,11 +471,6 @@ def main(): manager.do_update_cluster(update_params) - # Wait for the cluster to be updated - if wait and cluster_id: - manager.wait_for('update_cluster', cluster_id, wait_timeout) # type: ignore - manager.get_cluster_by_id(cluster_id) - # Case create else: create_params = { @@ -588,7 +487,7 @@ def main(): # Wait for the cluster to be created if wait and cluster_id: - manager.wait_for('create_cluster', cluster_id, wait_timeout) # type: ignore + manager.wait_for('cluster_created', cluster_id, wait_timeout) # type: ignore manager.get_cluster_by_id(cluster_id) # Handle absent state @@ -600,7 +499,7 @@ def main(): # Wait for the cluster to be deleted if requested if wait and cluster_id: - manager.wait_for('delete_cluster', cluster_id, wait_timeout) # type: ignore + manager.wait_for('cluster_deleted', cluster_id, wait_timeout) # type: ignore module.exit_json(changed=manager.changed, cluster=manager.cluster) diff --git a/plugins/modules/medialive_cluster_info.py b/plugins/modules/medialive_cluster_info.py index 230721ed66f..25344c5202f 100644 --- a/plugins/modules/medialive_cluster_info.py +++ b/plugins/modules/medialive_cluster_info.py @@ -12,7 +12,7 @@ - Get details about a AWS MediaLive Anywhere cluster. - This module requires boto3 >= 1.35.17. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -122,12 +122,9 @@ 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 +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass - class MediaLiveClusterGetter: '''Look up AWS MediaLive Anywhere clusters''' @@ -174,7 +171,7 @@ def get_cluster_by_name(self, name: str): raise MedialiveAnsibleAWSError(message='Found more than one Clusters under the same name') elif len(found) == 1: self.get_cluster_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get MediaLive Cluster', exception=e diff --git a/plugins/modules/medialive_input.py b/plugins/modules/medialive_input.py index b79402cf5d5..8cf71529ac1 100644 --- a/plugins/modules/medialive_input.py +++ b/plugins/modules/medialive_input.py @@ -10,9 +10,9 @@ version_added: 10.1.0 description: - A module for creating, updating and deleting AWS MediaLive inputs. - - Requires boto3 >= 1.37.30 + - Requires boto3 >= 1.37.34 author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: name: description: @@ -362,14 +362,10 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - """A personalized exception for this module""" - pass - class MediaLiveInputManager: """Manage AWS MediaLive Anywhere inputs""" @@ -426,13 +422,6 @@ def validate_sdi_source(self, sources: List[str], check_use=False): except is_boto3_error_code('ResourceNotFoundException'): raise MedialiveAnsibleAWSError(message='The provided sdi_source does not exist') - def validate_input_network_location(self, location: str, input_type: str): - """ - Validates the following: - * if input_network_location - """ - pass - def get_input_by_name(self, name: str): """ Find a input by name @@ -448,7 +437,7 @@ def get_input_by_name(self, name: str): if input.get('Name') == name: self.get_input_by_id(input.get('Id')) return - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Input', exception=e diff --git a/plugins/modules/medialive_input_info.py b/plugins/modules/medialive_input_info.py index 622e9ca5738..2fd44f5ff07 100644 --- a/plugins/modules/medialive_input_info.py +++ b/plugins/modules/medialive_input_info.py @@ -10,9 +10,9 @@ version_added: 10.1.0 description: - A module for gathering information about AWS MediaLive inputs - - Requires boto3 >= 1.37.30 + - Requires boto3 >= 1.37.34 author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: name: description: @@ -53,18 +53,12 @@ except ImportError: pass # caught by AnsibleAWSModule -from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_dict, recursive_diff +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 -from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags -from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - """A personalized exception for this module""" - pass - class MediaLiveInputGetter: """Gather info about AWS MediaLive Anywhere Inputs""" diff --git a/plugins/modules/medialive_network.py b/plugins/modules/medialive_network.py index 41970e9488f..a62dcbb1078 100644 --- a/plugins/modules/medialive_network.py +++ b/plugins/modules/medialive_network.py @@ -12,7 +12,7 @@ - A module for creating, updating and deleting AWS MediaLive Anywhere networks. - This module requires boto3 >= 1.35.17. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -65,21 +65,6 @@ - The gateway for the route. type: str required: true - wait: - description: - - Whether to wait for the network to reach the desired state. - - When I(state=present), wait for the network to reach the IDLE or ACTIVE states. - - When I(state=absent), wait for the network to reach the DELETED state. - type: bool - required: false - default: true - wait_timeout: - description: - - The maximum time in seconds to wait for the network to reach the desired state. - - Defaults to 60 seconds. - type: int - required: false - default: 60 extends_documentation_fragment: - amazon.aws.common.modules @@ -166,111 +151,17 @@ """ import uuid -from typing import Dict +from typing import Dict, Literal try: - from botocore.exceptions import WaiterError, ClientError, BotoCoreError + from botocore.exceptions import ClientError, BotoCoreError except ImportError: pass # caught by AnsibleAWSModule from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 -from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory - - -class MediaLiveWaiterFactory(BaseWaiterFactory): - '''Custom waiter factory for MediaLive resources''' - - @property - def _waiter_model_data(self): - '''Define custom waiters for MediaLive networks''' - return { - 'create_network': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNetwork', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ACTIVE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IDLE', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'CREATE_FAILED', - 'argument': 'State' - }, - { - 'state': 'retry', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - } - ] - }, - 'update_network': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNetwork', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ACTIVE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IN_USE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IDLE', - 'argument': 'State' - } - ] - }, - 'delete_network': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNetwork', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'DELETED', - 'argument': 'State' - }, - { - 'state': 'failure', - 'matcher': 'path', - 'expected': 'DELETE_FAILED', - 'argument': 'State' - } - ] - } - } - - -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveNetworkManager: '''Manage AWS MediaLive Anywhere networks''' @@ -284,7 +175,6 @@ def __init__(self, module: AnsibleAWSModule): ''' self.module = module self.client = self.module.client('medialive') - self.waiter_factory = MediaLiveWaiterFactory(module, self.client) self._network = {} self.changed = False @@ -323,7 +213,7 @@ def do_create_network(self, params): try: self.network = self.client.create_network(**create_params) # type: ignore self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to create Medialive Network', exception=e @@ -355,7 +245,7 @@ def do_update_network(self, params): try: self.network = self.client.update_network(**update_params) # type: ignore self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update Medialive Network', exception=e @@ -381,7 +271,7 @@ def get_network_by_name(self, name: str): elif len(found) == 1: self.get_network_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Network', exception=e @@ -398,7 +288,7 @@ def get_network_by_id(self, network_id: str): self.network = self.client.describe_network(NetworkId=network_id) # type: ignore except is_boto3_error_code('ResourceNotFoundException'): self.network = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Network', exception=e @@ -417,38 +307,12 @@ def delete_network(self, network_id: str): self.changed = True except is_boto3_error_code('ResourceNotFoundException'): self.network = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to delete Medialive Network', exception=e ) - def wait_for(self, want: str, network_id: str, wait_timeout: int = 60): - """ - Invoke one of the custom waiters and wait - - Args: - want: the name of the waiter - network_id: the ID of the network - wait_timeout: the maximum amount of time to wait in seconds (default: 60) - """ - - try: - waiter = self.waiter_factory.get_waiter(want) - config = { - 'Delay': min(5, wait_timeout), - 'MaxAttempts': wait_timeout // 5 - } - waiter.wait( - NetworkId=network_id, - WaiterConfig=config - ) - except WaiterError as e: - raise MedialiveAnsibleAWSError( - message=f'Timeout waiting for network {network_id}', - exception=e - ) - def get_arg(arg:str, params:dict, spec:dict): if arg in spec.keys(): aliases = spec[arg].get('aliases', []) @@ -476,9 +340,7 @@ def main(): cidr=dict(type='str', required=True), gateway=dict(type='str', required=True), ) - ), - wait=dict(type='bool', default=True), - wait_timeout=dict(type='int', default=60), + ) ) module = AnsibleAWSModule( @@ -493,8 +355,6 @@ def main(): state = get_arg('state', module.params, argument_spec) ip_pools = get_arg('ip_pools', module.params, argument_spec) routes = get_arg('routes', module.params, argument_spec) - wait = get_arg('wait', module.params, argument_spec) - wait_timeout = get_arg('wait_timeout', module.params, argument_spec) # Initialize the manager manager = MediaLiveNetworkManager(module) @@ -525,11 +385,6 @@ def main(): manager.do_update_network(update_params) - # Wait for the network to be updated - if wait and network_id: - manager.wait_for('update_network', network_id, wait_timeout) # type: ignore - manager.get_network_by_id(network_id) - # Case create else: create_params = { @@ -542,11 +397,6 @@ def main(): manager.do_create_network(create_params) network_id = manager.network.get('network_id') - # Wait for the network to be created - if wait and network_id: - manager.wait_for('create_network', network_id, wait_timeout) # type: ignore - manager.get_network_by_id(network_id) - # Handle absent state elif state == 'absent': if manager.network: @@ -554,10 +404,6 @@ def main(): network_id = manager.network.get('network_id') manager.delete_network(network_id) # type: ignore - # Wait for the network to be deleted if requested - if wait and network_id: - manager.wait_for('delete_network', network_id, wait_timeout) # type: ignore - module.exit_json(changed=manager.changed, network=manager.network) diff --git a/plugins/modules/medialive_network_info.py b/plugins/modules/medialive_network_info.py index f8ca30d8e4f..e62ae9b6196 100644 --- a/plugins/modules/medialive_network_info.py +++ b/plugins/modules/medialive_network_info.py @@ -12,7 +12,7 @@ - Get details about a AWS MediaLive Anywhere network. - This module requires boto3 >= 1.35.17. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -118,11 +118,7 @@ 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 MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveNetworkManager: @@ -172,7 +168,7 @@ def find_network_by_name(self, name: str): raise MedialiveAnsibleAWSError(message='Found more than one Networks under the same name') elif len(found) == 1: self.get_network_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Network', exception=e @@ -189,7 +185,7 @@ def get_network_by_id(self, id: str): self.network = self.client.describe_network(NetworkId=id) # type: ignore except is_boto3_error_code('ResourceNotFoundException'): self.network = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Network', exception=e diff --git a/plugins/modules/medialive_node.py b/plugins/modules/medialive_node.py index e6458965189..1695eec9e68 100644 --- a/plugins/modules/medialive_node.py +++ b/plugins/modules/medialive_node.py @@ -9,9 +9,9 @@ version_added: 10.1.0 description: - A module for creating, updating and deleting AWS MediaLive Anywhere nodes. - - This module requires boto3 >= 1.37.30. + - This module requires boto3 >= 1.37.34. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -92,21 +92,6 @@ - The ID of a SDI source streaming on the given SDI capture card port. required: true type: str - wait: - description: - - Whether to wait for the node to reach the desired state. - - When I(state=present), wait for the node to reach one of the ["CREATED", "ACTIVE", "READY", "IN_USE"] states. - - When I(state=absent), wait for the node to be deleted. - type: bool - required: false - default: true - wait_timeout: - description: - - The maximum time in seconds to wait for the node to reach the desired state. - - Defaults to 60 seconds. - type: int - required: false - default: 60 extends_documentation_fragment: - amazon.aws.common.modules @@ -260,7 +245,7 @@ """ import uuid -from typing import Dict +from typing import Dict, Literal try: from botocore.exceptions import WaiterError, ClientError, BotoCoreError @@ -270,108 +255,9 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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 -from ansible_collections.community.aws.plugins.module_utils.base import BaseWaiterFactory - - -class MediaLiveWaiterFactory(BaseWaiterFactory): - '''Custom waiter factory for MediaLive resources''' - - @property - def _waiter_model_data(self): - '''Define custom waiters for MediaLive Anywhere nodes''' - return { - 'create_node': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNode', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'CREATED', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'REGISTERING', - 'argument': 'State' - }, - { - 'state': 'retry', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - } - ] - }, - 'update_node': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNode', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'CREATED', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'READY_TO_ACTIVATE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'READY', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'ACTIVE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'IN_USE', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'REGISTERING', - 'argument': 'State' - }, - ] - }, - 'delete_node': { - 'delay': 5, - 'maxAttempts': 120, - 'operation': 'DescribeNode', - 'acceptors': [ - { - 'state': 'success', - 'matcher': 'path', - 'expected': 'DEREGISTERED', - 'argument': 'State' - }, - { - 'state': 'success', - 'matcher': 'error', - 'expected': 'ResourceNotFoundException' - } - ] - } - } +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass - class MediaLiveNodeManager: '''Manage AWS MediaLive Anywhere node''' @@ -384,7 +270,6 @@ def __init__(self, module: AnsibleAWSModule): ''' self.module = module self.client = self.module.client('medialive') - self.waiter_factory = MediaLiveWaiterFactory(module, self.client) self._node = {} self.changed = False @@ -424,7 +309,7 @@ def do_create_node(self, params): try: self.node = self.client.create_node(**create_params) # type: ignore self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to create Medialive node', exception=e @@ -458,7 +343,7 @@ def do_update_node(self, params: dict): response = self.client.update_node(**update_params) # type: ignore self.node = camel_dict_to_snake_dict(response) self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update Medialive node', exception=e @@ -473,7 +358,7 @@ def delete_node(self): self.changed = True except is_boto3_error_code('ResourceNotFoundException'): self.node = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to delete Medialive node', exception=e @@ -499,7 +384,7 @@ def get_node_by_name(self, cluster_id: str, name: str): elif len(found) == 1: self.get_node_by_id(cluster_id, found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive Node', exception=e @@ -519,35 +404,6 @@ def get_node_by_id(self, cluster_id: str, node_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.node = {} - def wait_for(self, want: str, wait_timeout: int = 60): - """ - Invoke one of the custom waiters and wait - - Args: - want: the name of the waiter - wait_timeout: the maximum amount of time to wait in seconds (default: 60) - """ - cluster_id = self.node.get('cluster_id') - node_id = self.node.get('node_id') - - try: - waiter = self.waiter_factory.get_waiter(want) - config = { - 'Delay': min(5, wait_timeout), - 'MaxAttempts': wait_timeout // 5 - } - waiter.wait( - ClusterId=cluster_id, - NodeId=node_id, - WaiterConfig=config - ) - self.get_node_by_id(cluster_id, node_id) # type: ignore - except WaiterError as e: - raise MedialiveAnsibleAWSError( - message=f'Timeout waiting for node {node_id} in cluster {cluster_id}', - exception=e - ) - def get_arg(arg:str, params:dict, spec:dict): if arg in spec.keys(): aliases = spec[arg].get('aliases', []) @@ -582,9 +438,7 @@ def main(): sdi_source=dict(type='str', required=True), ) ), - state=dict(type='str', default='present', choices=['present', 'absent']), - wait=dict(type='bool', default=True), - wait_timeout=dict(type='int', default=60), + state=dict(type='str', default='present', choices=['present', 'absent']) ) module = AnsibleAWSModule( @@ -601,8 +455,6 @@ def main(): role = get_arg('role', module.params, argument_spec) sdi_source_mappings = get_arg('sdi_source_mappings', module.params, argument_spec) state = get_arg('state', module.params, argument_spec) - wait = get_arg('wait', module.params, argument_spec) - wait_timeout = get_arg('wait_timeout', module.params, argument_spec) # Initialize the manager manager = MediaLiveNodeManager(module) @@ -631,11 +483,6 @@ def main(): manager.do_update_node(update_params) - # Wait for the node to be updated - if wait: - manager.wait_for('update_node', wait_timeout) # type: ignore - manager.get_node_by_id(cluster_id, manager.node.get('node_id')) # type: ignore - # Case create else: create_params = { @@ -648,11 +495,6 @@ def main(): manager.do_create_node(create_params) - # Wait for the node to be created - if wait: - manager.wait_for('create_node', wait_timeout) # type: ignore - manager.get_node_by_id(cluster_id, manager.node.get('node_id')) # type: ignore - # Handle absent state elif state == 'absent': if manager.node: diff --git a/plugins/modules/medialive_node_info.py b/plugins/modules/medialive_node_info.py index fadf017d28d..916009c096c 100644 --- a/plugins/modules/medialive_node_info.py +++ b/plugins/modules/medialive_node_info.py @@ -10,9 +10,9 @@ version_added: 10.1.0 description: - Get details about a AWS MediaLive Anywhere node. - - This module requires boto3 >= 1.35.17. + - This module requires boto3 >= 1.37.34. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -149,12 +149,9 @@ 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 +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass - class MediaLiveNodeGetter: '''Look up AWS MediaLive Anywhere nodes''' diff --git a/plugins/modules/medialive_node_registration.py b/plugins/modules/medialive_node_registration.py index 06751d00266..887202e932d 100644 --- a/plugins/modules/medialive_node_registration.py +++ b/plugins/modules/medialive_node_registration.py @@ -10,9 +10,9 @@ version_added: 10.1.0 description: - A module for creating, updating and deleting AWS MediaLive Anywhere nodes. - - This module requires boto3 >= 1.35.17. + - This module requires boto3 >= 1.37.34. author: - - "Sergey Papyan" + - Sergey Papyan (@r363x) options: id: description: @@ -67,12 +67,9 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_dict from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass - class MediaLiveNodeRegistrationScriptManager: '''Requests AWS MediaLive Anywhere node registration script''' diff --git a/plugins/modules/medialive_sdi_source.py b/plugins/modules/medialive_sdi_source.py index efdc891bbf7..10d56b03311 100644 --- a/plugins/modules/medialive_sdi_source.py +++ b/plugins/modules/medialive_sdi_source.py @@ -122,18 +122,18 @@ example: "SINGLE" """ +import uuid from typing import Dict -from botocore.exceptions import ClientError, BotoCoreError +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 -import uuid - - -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveSdiSourceManager: @@ -163,7 +163,7 @@ def sdi_source(self, sdi_source: Dict): # Handle nested sdi_source if sdi_source.get('sdi_source'): - sdi_source = 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') @@ -191,7 +191,7 @@ def do_create_sdi_source(self, params): response = self.client.create_sdi_source(**create_params) # type: ignore self.sdi_source = response self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to create Medialive SDI Source', exception=e @@ -226,7 +226,7 @@ def do_update_sdi_source(self, params): response = self.client.update_sdi_source(**update_params) # type: ignore self.sdi_source = response self.changed = True - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to update Medialive SDI Source', exception=e @@ -253,7 +253,7 @@ def get_sdi_source_by_name(self, name: str): elif len(found) == 1: self.get_sdi_source_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive SDI Source', exception=e @@ -284,7 +284,7 @@ def delete_sdi_source_by_id(self, sdi_source_id: str): self.changed = True except is_boto3_error_code('ResourceNotFoundException'): self.sdi_source = {} - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to delete Medialive SDI source', exception=e diff --git a/plugins/modules/medialive_sdi_source_info.py b/plugins/modules/medialive_sdi_source_info.py index c54c16d6c6e..a9d9760e1e6 100644 --- a/plugins/modules/medialive_sdi_source_info.py +++ b/plugins/modules/medialive_sdi_source_info.py @@ -11,7 +11,7 @@ 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.30. + - This module requires boto3 >= 1.37.34. author: - "Brenton Buxell (@bbuxell)" options: @@ -96,11 +96,7 @@ 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 MedialiveAnsibleAWSError(AnsibleAWSError): - pass +from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveSdiSourceGetter: @@ -129,7 +125,7 @@ def sdi_source(self, sdi_source: Dict): # Handle nested sdi_source if sdi_source.get('sdi_source'): - sdi_source = 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') @@ -157,7 +153,7 @@ def get_sdi_source_by_name(self, name: str): elif len(found) == 1: self.get_sdi_source_by_id(found[0]) - except (ClientError, BotoCoreError) as e: + except (ClientError, BotoCoreError) as e: # type: ignore raise MedialiveAnsibleAWSError( message='Unable to get Medialive SDI Source', exception=e diff --git a/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml b/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml index daaa58a8539..b66233d7b6e 100644 --- a/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml +++ b/tests/integration/targets/medialive_channel_placement_group/tasks/cleanup.yml @@ -20,7 +20,6 @@ community.aws.medialive_network: id: "{{ network_id }}" state: absent - wait: true ignore_errors: true when: network_id is defined diff --git a/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml b/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml index 7e9433f4101..a248fedd208 100644 --- a/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml +++ b/tests/integration/targets/medialive_channel_placement_group/tasks/main.yml @@ -33,8 +33,6 @@ routes: - cidr: "{{ route_cidr }}" gateway: "{{ route_gateway }}" - wait: true - wait_timeout: "{{ wait_timeout }}" register: network_result retries: 3 delay: 5 diff --git a/tests/integration/targets/medialive_cluster/tasks/main.yml b/tests/integration/targets/medialive_cluster/tasks/main.yml index 44429e69f4e..a3a4951370a 100644 --- a/tests/integration/targets/medialive_cluster/tasks/main.yml +++ b/tests/integration/targets/medialive_cluster/tasks/main.yml @@ -61,8 +61,6 @@ routes: - cidr: "{{ route_cidr }}" gateway: "{{ route_gateway }}" - wait: true - wait_timeout: "{{ wait_timeout }}" register: network_result retries: 3 # Add retries for API throttling resilience delay: 5 @@ -241,8 +239,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true - wait_timeout: "{{ wait_timeout }}" when: network_id is defined ignore_errors: true @@ -264,7 +260,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true ignore_errors: true when: network_id is defined @@ -286,7 +281,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true ignore_errors: true when: network_id is defined diff --git a/tests/integration/targets/medialive_input/meta/main.yml b/tests/integration/targets/medialive_input/meta/main.yml index 9cf91cb1cc2..e34d7fe10c7 100644 --- a/tests/integration/targets/medialive_input/meta/main.yml +++ b/tests/integration/targets/medialive_input/meta/main.yml @@ -1,4 +1,4 @@ dependencies: - role: setup_botocore_pip vars: - botocore_version: "1.37.30" + botocore_version: "1.37.34" diff --git a/tests/integration/targets/medialive_input/tasks/main.yml b/tests/integration/targets/medialive_input/tasks/main.yml index 75b90e2ce06..7b13d580d1a 100644 --- a/tests/integration/targets/medialive_input/tasks/main.yml +++ b/tests/integration/targets/medialive_input/tasks/main.yml @@ -146,38 +146,38 @@ fail_msg: "Check mode did not work as expected" success_msg: "Check mode correctly simulated deletion" - # # Test INPUT deletion - # - name: Delete the INPUT - # community.aws.medialive_INPUT: - # name: "{{ INPUT_name }}" - # state: absent - # wait: true - # wait_timeout: "{{ wait_timeout }}" - # register: delete_result - # retries: 3 # Add retries for API throttling resilience - # delay: 5 - # until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) - - # - name: Assert INPUT was deleted - # ansible.builtin.assert: - # that: - # - delete_result is changed - # fail_msg: "INPUT deletion failed" - # success_msg: "INPUT deleted successfully" - - # # Test deletion idempotency - # - name: Try to delete the INPUT again (idempotency check) - # community.aws.medialive_INPUT: - # name: "{{ INPUT_name }}" - # state: absent - # register: delete_idempotency_result - - # - name: Assert delete idempotency - # ansible.builtin.assert: - # that: - # - not delete_idempotency_result.changed - # fail_msg: "Delete idempotency check failed" - # success_msg: "Delete idempotency check passed" + # Test INPUT deletion + - name: Delete the INPUT + community.aws.medialive_INPUT: + name: "{{ INPUT_name }}" + state: absent + wait: true + wait_timeout: "{{ wait_timeout }}" + register: delete_result + retries: 3 # Add retries for API throttling resilience + delay: 5 + until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) + + - name: Assert INPUT was deleted + ansible.builtin.assert: + that: + - delete_result is changed + fail_msg: "INPUT deletion failed" + success_msg: "INPUT deleted successfully" + + # Test deletion idempotency + - name: Try to delete the INPUT again (idempotency check) + community.aws.medialive_INPUT: + name: "{{ INPUT_name }}" + state: absent + register: delete_idempotency_result + + - name: Assert delete idempotency + ansible.builtin.assert: + that: + - not delete_idempotency_result.changed + fail_msg: "Delete idempotency check failed" + success_msg: "Delete idempotency check passed" # Error handling and cleanup rescue: diff --git a/tests/integration/targets/medialive_network/tasks/main.yml b/tests/integration/targets/medialive_network/tasks/main.yml index e52b4dc59b0..e174e0da2d9 100644 --- a/tests/integration/targets/medialive_network/tasks/main.yml +++ b/tests/integration/targets/medialive_network/tasks/main.yml @@ -18,7 +18,6 @@ ip_pool_cidr: "10.21.21.0/24" route_cidr: "0.0.0.0/0" route_gateway: "10.21.21.1" - wait_timeout: 30 # Test network creation - name: Create a MediaLive Anywhere network @@ -30,8 +29,6 @@ routes: - cidr: "{{ route_cidr }}" gateway: "{{ route_gateway }}" - wait: true - wait_timeout: "{{ wait_timeout }}" register: create_result retries: 3 # Add retries for API throttling resilience delay: 5 @@ -69,8 +66,6 @@ routes: - cidr: "{{ route_cidr }}" gateway: "{{ route_gateway }}" - wait: true - wait_timeout: "{{ wait_timeout }}" register: idempotency_result - name: Assert idempotency @@ -135,8 +130,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true - wait_timeout: "{{ wait_timeout }}" register: delete_result retries: 3 # Add retries for API throttling resilience delay: 5 @@ -173,7 +166,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true ignore_errors: true when: network_id is defined @@ -187,6 +179,5 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: false # Don't wait in cleanup to speed up test completion ignore_errors: true when: network_id is defined diff --git a/tests/integration/targets/medialive_node/tasks/cleanup.yml b/tests/integration/targets/medialive_node/tasks/cleanup.yml index 0c2646ba42c..11d20e1c896 100644 --- a/tests/integration/targets/medialive_node/tasks/cleanup.yml +++ b/tests/integration/targets/medialive_node/tasks/cleanup.yml @@ -4,7 +4,6 @@ name: "{{ node_name }}" cluster_id: "{{ cluster_id }}" state: absent - wait: true ignore_errors: true when: node_id is defined @@ -20,7 +19,6 @@ community.aws.medialive_network: name: "{{ network_name }}" state: absent - wait: true ignore_errors: true when: network_id is defined diff --git a/tests/integration/targets/medialive_node/tasks/main.yml b/tests/integration/targets/medialive_node/tasks/main.yml index 2ecefd63e1a..5e9c95a949f 100644 --- a/tests/integration/targets/medialive_node/tasks/main.yml +++ b/tests/integration/targets/medialive_node/tasks/main.yml @@ -22,7 +22,6 @@ ip_pool_cidr: "10.21.21.0/24" route_cidr: "0.0.0.0/0" route_gateway: "10.21.21.1" - wait_timeout: 60 # Look up Account ID to use in ARN construction - name: Look up Account ID @@ -115,8 +114,6 @@ routes: - cidr: "{{ route_cidr }}" gateway: "{{ route_gateway }}" - wait: true - wait_timeout: "{{ wait_timeout }}" register: network_result retries: 3 delay: 5 @@ -169,8 +166,6 @@ physical_interface_name: eth0 network_interface_mode: NAT role: ACTIVE - wait: true - wait_timeout: "{{ wait_timeout }}" register: create_result retries: 3 delay: 5 @@ -213,8 +208,6 @@ physical_interface_name: eth0 network_interface_mode: NAT role: ACTIVE - wait: true - wait_timeout: "{{ wait_timeout }}" register: idempotency_result when: node_id is defined @@ -323,8 +316,6 @@ name: "{{ node_name }}" cluster_id: "{{ cluster_id }}" state: absent - wait: true - wait_timeout: "{{ wait_timeout }}" register: delete_result retries: 3 delay: 5 From 88d7540c2530ecd751477b058b71f056263b7893 Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Mon, 18 Aug 2025 11:27:22 -0700 Subject: [PATCH 09/11] Fix integration tests --- .../medialive_channel_placement_group.py | 4 ++-- .../targets/medialive_input/tasks/main.yml | 20 +++++++++---------- .../targets/medialive_node/tasks/main.yml | 7 +++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/plugins/modules/medialive_channel_placement_group.py b/plugins/modules/medialive_channel_placement_group.py index 46d26652cf2..dbe78e2b39a 100644 --- a/plugins/modules/medialive_channel_placement_group.py +++ b/plugins/modules/medialive_channel_placement_group.py @@ -328,7 +328,7 @@ def delete_channel_placement_group(self, placement_group_id: str, cluster_id: st def wait_for( self, - want: Literal['channel_placement_group_assigned', 'channel_placement_group_deleted'], + want: Literal['channel_placement_group_unassigned', 'channel_placement_group_deleted'], placement_group_id: str, cluster_id: str, wait_timeout = 60) -> None: @@ -439,7 +439,7 @@ def main(): # Wait for the placement group to be created if wait and placement_group_id: - manager.wait_for('channel_placement_group_assigned', placement_group_id, cluster_id, wait_timeout) # type: ignore + manager.wait_for('channel_placement_group_unassigned', placement_group_id, cluster_id, wait_timeout) # type: ignore manager.get_channel_placement_group_by_id(placement_group_id, cluster_id) # type: ignore # Handle absent state diff --git a/tests/integration/targets/medialive_input/tasks/main.yml b/tests/integration/targets/medialive_input/tasks/main.yml index 7b13d580d1a..988d474cb44 100644 --- a/tests/integration/targets/medialive_input/tasks/main.yml +++ b/tests/integration/targets/medialive_input/tasks/main.yml @@ -146,10 +146,10 @@ fail_msg: "Check mode did not work as expected" success_msg: "Check mode correctly simulated deletion" - # Test INPUT deletion - - name: Delete the INPUT - community.aws.medialive_INPUT: - name: "{{ INPUT_name }}" + # Test Input deletion + - name: Delete the Input + community.aws.medialive_input: + name: "{{ input_name }}" state: absent wait: true wait_timeout: "{{ wait_timeout }}" @@ -158,17 +158,17 @@ delay: 5 until: delete_result is not failed or 'TooManyRequestsException' not in (delete_result.msg | default('')) - - name: Assert INPUT was deleted + - name: Assert Input was deleted ansible.builtin.assert: that: - delete_result is changed - fail_msg: "INPUT deletion failed" - success_msg: "INPUT deleted successfully" + fail_msg: "Input deletion failed" + success_msg: "Input deleted successfully" # Test deletion idempotency - - name: Try to delete the INPUT again (idempotency check) - community.aws.medialive_INPUT: - name: "{{ INPUT_name }}" + - name: Try to delete the Input again (idempotency check) + community.aws.medialive_input: + name: "{{ input_name }}" state: absent register: delete_idempotency_result diff --git a/tests/integration/targets/medialive_node/tasks/main.yml b/tests/integration/targets/medialive_node/tasks/main.yml index 5e9c95a949f..47970903719 100644 --- a/tests/integration/targets/medialive_node/tasks/main.yml +++ b/tests/integration/targets/medialive_node/tasks/main.yml @@ -22,6 +22,7 @@ ip_pool_cidr: "10.21.21.0/24" route_cidr: "0.0.0.0/0" route_gateway: "10.21.21.1" + wait_timeout: 30 # Look up Account ID to use in ARN construction - name: Look up Account ID @@ -137,11 +138,9 @@ cluster_type: "ON_PREMISES" instance_role_arn: "{{ instance_role_arn }}" network_settings: - default_route: "management" + default_route: "logical-0" interface_mappings: - - logical_interface_name: "management" - network_id: "{{ network_id }}" - - logical_interface_name: "input" + - logical_interface_name: "logical-0" network_id: "{{ network_id }}" wait: true wait_timeout: "{{ wait_timeout }}" From 4ffb578a9f8e21c68b843b837076d9fd698eab45 Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Mon, 18 Aug 2025 14:00:06 -0700 Subject: [PATCH 10/11] Replace remaining TODO values with actual documentation strings --- plugins/modules/medialive_channel.py | 898 +++++++++++++-------------- 1 file changed, 449 insertions(+), 449 deletions(-) diff --git a/plugins/modules/medialive_channel.py b/plugins/modules/medialive_channel.py index 33a1541c7ec..7bf6a8a0751 100644 --- a/plugins/modules/medialive_channel.py +++ b/plugins/modules/medialive_channel.py @@ -12,7 +12,7 @@ description: - A module for creating, updating and deleting AWS MediaLive Channels. - This module includes basic functionality for managing channels, but does not include input validation - - Requires boto3 >= 1.37.30 + - Requires boto3 >= 1.37.34 author: - Sergey Papyan (@r363x) options: @@ -864,20 +864,20 @@ - 'YELLOW' font_opacity: description: - - TODO + - Specifies the opacity of the burned-in captions. 255 is opaque; 0 is transparent. All burn-in and DVB-Sub font settings must match. type: int font_resolution: description: - - TODO + - Font resolution in DPI (dots per inch); default is 96 dpi. All burn-in and DVB-Sub font settings must match. type: int font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str outline_color: description: - - TODO + - Specifies font outline color. This option is not valid for source captions that are either 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. type: str choices: - 'BLACK' @@ -888,77 +888,77 @@ - 'YELLOW' outline_size: description: - - TODO + - Specifies font outline size in pixels. This option is not valid for source captions that are either 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. type: int shadow_color: description: - - TODO + - Specifies the color of the shadow cast by the captions. All burn-in and DVB-Sub font settings must match. type: str choices: ['BLACK', 'NONE', 'WHITE'] shadow_opacity: description: - - TODO + - Specifies the opacity of the shadow. 255 is opaque; 0 is transparent. Leaving this parameter out is equivalent to setting it to 0 (transparent). All burn-in and DVB-Sub font settings must match. type: int shadow_x_offset: description: - - TODO + - Specifies the horizontal offset of the shadow relative to the captions in pixels. A value of -2 would result in a shadow offset 2 pixels to the left. All burn-in and DVB-Sub font settings must match. type: int shadow_y_offset: description: - - TODO + - Specifies the vertical offset of the shadow relative to the captions in pixels. A value of -2 would result in a shadow offset 2 pixels above the text. All burn-in and DVB-Sub font settings must match. type: int teletext_grid_control: description: - - TODO + - Controls whether a fixed grid size will be used to generate the output subtitles bitmap. Only applicable for Teletext inputs and DVB-Sub/Burn-in outputs. type: str choices: ['FIXED', 'SCALED'] x_position: description: - - TODO + - Specifies the horizontal position of the caption relative to the left side of the output in pixels. A value of 10 would result in the captions starting 10 pixels from the left of the output. If no explicit xPosition is provided, the horizontal caption position will be determined by the alignment parameter. All burn-in and DVB-Sub font settings must match. type: int y_position: description: - - TODO + - Specifies the vertical position of the caption relative to the top of the output in pixels. A value of 10 would result in the captions starting 10 pixels from the top of the output. If no explicit yPosition is provided, the caption will be positioned towards the bottom of the output. All burn-in and DVB-Sub font settings must match. type: int dvb_sub_destination_settings: description: - - TODO + - Dvb Sub Destination Settings. type: dict suboptions: alignment: description: - - TODO + - If no explicit xPosition or yPosition is provided, setting alignment to centered will place the captions at the bottom center of the output. Similarly, setting a left alignment will align captions to the bottom left of the output. If x and y positions are given in conjunction with the alignment parameter, the font will be justified (either left or centered) relative to those coordinates. Selecting “smart” justification will left-justify live subtitles and center-justify pre-recorded subtitles. All burn-in and DVB-Sub font settings must match. type: str choices: ['CENTERED', 'LEFT', 'SMART'] background_color: description: - - TODO + - Specifies the color of the rectangle behind the captions. All burn-in and DVB-Sub font settings must match. type: str choices: ['BLACK', 'NONE', 'WHITE'] background_opacity: description: - - TODO + - Specifies the opacity of the background rectangle. 255 is opaque; 0 is transparent. Leaving this parameter out is equivalent to setting it to 0 (transparent). All burn-in and DVB-Sub font settings must match. type: int font: description: - - TODO + - External font file used for caption burn-in. File extension must be ‘ttf’ or ‘tte’. Although the user can select output fonts for many different types of input captions, embedded, STL and teletext sources use a strict grid system. Using external fonts with these caption sources could cause unexpected display of proportional fonts. All burn-in and DVB-Sub font settings must match. type: dict suboptions: password_param: description: - - TODO + - key used to extract the password from EC2 Parameter store. type: str uri: description: - - TODO + - Uniform Resource Identifier - This should be a path to a file accessible to the Live system (eg. a http:// URI) depending on the output type. For example, a RTMP destination should have a uri simliar to “rtmp://fmsserver/live”. type: str username: description: - - TODO + - username for destination. type: str font_color: description: - - TODO + - Specifies the color of the burned-in captions. This option is not valid for source captions that are STL, 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. type: str choices: - 'BLACK' @@ -969,20 +969,20 @@ - 'YELLOW' font_opacity: description: - - TODO + - Specifies the opacity of the burned-in captions. 255 is opaque; 0 is transparent. All burn-in and DVB-Sub font settings must match. type: int font_resolution: description: - - TODO + - Font resolution in DPI (dots per inch); default is 96 dpi. All burn-in and DVB-Sub font settings must match. type: int font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str outline_color: description: - - TODO + - Specifies font outline color. This option is not valid for source captions that are either 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. type: str choices: - 'BLACK' @@ -993,132 +993,132 @@ - 'YELLOW' outline_size: description: - - TODO + - Specifies font outline size in pixels. This option is not valid for source captions that are either 608/embedded or teletext. These source settings are already pre-defined by the caption stream. All burn-in and DVB-Sub font settings must match. type: int shadow_color: description: - - TODO + - Specifies the color of the shadow cast by the captions. All burn-in and DVB-Sub font settings must match. type: str choices: ['BLACK', 'NONE', 'WHITE'] shadow_opacity: description: - - TODO + - Specifies the opacity of the shadow. 255 is opaque; 0 is transparent. Leaving this parameter out is equivalent to setting it to 0 (transparent). All burn-in and DVB-Sub font settings must match. type: int shadow_x_offset: description: - - TODO + - Specifies the horizontal offset of the shadow relative to the captions in pixels. A value of -2 would result in a shadow offset 2 pixels to the left. All burn-in and DVB-Sub font settings must match. type: int shadow_y_offset: description: - - TODO + - Specifies the vertical offset of the shadow relative to the captions in pixels. A value of -2 would result in a shadow offset 2 pixels above the text. All burn-in and DVB-Sub font settings must match. type: int teletext_grid_control: description: - - TODO + - Controls whether a fixed grid size will be used to generate the output subtitles bitmap. Only applicable for Teletext inputs and DVB-Sub/Burn-in outputs. type: str choices: ['FIXED', 'SCALED'] x_position: description: - - TODO + - Specifies the horizontal position of the caption relative to the left side of the output in pixels. A value of 10 would result in the captions starting 10 pixels from the left of the output. If no explicit xPosition is provided, the horizontal caption position will be determined by the alignment parameter. All burn-in and DVB-Sub font settings must match. type: int y_position: description: - - TODO + - Specifies the vertical position of the caption relative to the top of the output in pixels. A value of 10 would result in the captions starting 10 pixels from the top of the output. If no explicit yPosition is provided, the caption will be positioned towards the bottom of the output. All burn-in and DVB-Sub font settings must match. type: int ebu_tt_d_destination_settings: description: - - TODO + - Ebu Tt DDestination Settings. type: dict suboptions: copyright_holder: description: - - TODO + - Complete this field if you want to include the name of the copyright holder in the copyright tag in the captions metadata. type: str fill_line_gap: description: - - TODO + - 'Specifies how to handle the gap between the lines (in multi-line captions). ENABLED: Fill with the captions background color (as specified in the input captions). DISABLED: Leave the gap unfilled.' type: str choices: ['DISABLED', 'ENABLED'] font_family: description: - - TODO + - Specifies the font family to include in the font data attached to the EBU-TT captions. Valid only if style_control is set to include. (If style_control is set to exclude, the font family is always set to monospaced.) Enter a list of font families, as a comma-separated list of font names, in order of preference. The name can be a font family (such as Arial), or a generic font family (such as serif), or default (to let the downstream player choose the font). Or leave blank to set the family to monospace. Note that you can specify only the font family. All other style information (color, bold, position and so on) is copied from the input captions. The size is always set to 100% to allow the downstream player to choose the size. type: str style_control: description: - - TODO + - 'Specifies the style information to include in the font data that is attached to the EBU-TT captions. INCLUDE: Take the style information from the source captions and include that information in the font data attached to the EBU-TT captions. This option is valid only if the source captions are Embedded or Teletext. EXCLUDE: Set the font family to monospaced. Do not include any other style information.' type: str choices: ['EXCLUDE', 'INCLUDE'] default_font_size: description: - - TODO + - Specifies the default font size as a percentage of the computed cell size. Valid only if the defaultLineHeight is also set. If you leave this field empty, the default font size is 80% of the cell size. type: int default_line_height: description: - - TODO + - Documentation update needed. type: int embedded_destination_settings: description: - - TODO + - Embedded Destination Settings. type: dict embedded_plus_scte20_destination_settings: description: - - TODO + - Embedded Plus Scte20 Destination Settings. type: dict rtmp_caption_info_destination_settings: description: - - TODO + - Rtmp Caption Info Destination Settings. type: dict scte20_plus_embedded_destination_settings: description: - - TODO + - Scte20 Plus Embedded Destination Settings. type: dict scte27_destination_settings: description: - - TODO + - Scte27 Destination Settings. type: dict smpte_tt_destination_settings: description: - - TODO + - Smpte Tt Destination Settings. type: dict teletext_destination_settings: description: - - TODO + - Teletext Destination Settings. type: dict ttml_destination_settings: description: - - TODO + - Ttml Destination Settings. type: dict suboptions: style_control: description: - - TODO + - 'Specifies the style information to include in the font data that is attached to the EBU-TT captions. INCLUDE: Take the style information from the source captions and include that information in the font data attached to the EBU-TT captions. This option is valid only if the source captions are Embedded or Teletext. EXCLUDE: Set the font family to monospaced. Do not include any other style information.' type: str choices: ['PASSTHROUGH', 'USE_CONFIGURED'] webvtt_destination_settings: description: - - TODO + - Webvtt Destination Settings. type: dict suboptions: style_control: description: - - TODO + - 'Specifies the style information to include in the font data that is attached to the EBU-TT captions. INCLUDE: Take the style information from the source captions and include that information in the font data attached to the EBU-TT captions. This option is valid only if the source captions are Embedded or Teletext. EXCLUDE: Set the font family to monospaced. Do not include any other style information.' type: str choices: ['NO_STYLE_DATA', 'PASSTHROUGH'] language_code: description: - - TODO + - RFC 5646 language code representing the language of the audio output track. Only used if languageControlMode is useConfigured, or there is no ISO 639 language code specified in the input. type: str language_description: description: - - TODO + - Human readable information to indicate captions available for players (eg. English, or Spanish). type: str name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str caption_dash_roles: description: - - TODO + - Identifies the DASH roles to assign to this captions output. Applies only when the captions output is configured for DVB DASH accessibility signaling. Dash Role Caption. type: list elements: str choices: @@ -1137,7 +1137,7 @@ - 'SUPPLEMENTARY' dvb_dash_accessibility: description: - - TODO + - Identifies DVB DASH accessibility signaling in this audio output. Used in Microsoft Smooth Streaming outputs to signal accessibility information to packagers. type: str choices: - 'DVBDASH_1_VISUALLY_IMPAIRED' @@ -1344,7 +1344,7 @@ suboptions: destination: description: - - The destination for the frame capture files. Either the URI for an Amazon S3 bucket and object, plus a file name prefix (for example, s3ssl://sportsDelivery/highlights/20180820/curling-) or the URI for a MediaStore container, plus a file name prefix (for example, mediastoressl://sportsDelivery/20180820/curling-). The final file names consist of the prefix from the destination field (for example, "curling-") + name modifier + the counter (5 digits, starting from 00001) + extension (which is always .jpg). For example, curling-low.00001.jpg + - The destination for the frame capture files. Either the URI for an Amazon S3 bucket and object, plus a file name prefix (for example, s3ssl://sportsDelivery/highlights/20180820/curling-) or the URI for a MediaStore container, plus a file name prefix (for example, mediastoressl://sportsDelivery/20180820/curling-). The final file names consist of the prefix from the destination field (for example, "curling-") + name modifier + the counter (5 digits, starting from 00001) + extension (which is always.jpg). For example, curling-low.00001.jpg type: dict suboptions: destination_ref_id: @@ -2665,246 +2665,246 @@ choices: ['DROP', 'ENCODE_SILENCE'] arib: description: - - TODO + - When set to enabled, uses ARIB-compliant field muxing and removes video descriptor. type: str choices: ['DISABLED', 'ENABLED'] arib_captions_pid: description: - - TODO + - Packet Identifier (PID) for ARIB Captions in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str arib_captions_pid_control: description: - - TODO + - If set to auto, pid number used for ARIB Captions will be auto-selected from unused pids. If set to useConfigured, ARIB Captions will be on the configured pid number. type: str choices: ['AUTO', 'USE_CONFIGURED'] audio_buffer_model: description: - - TODO + - When set to dvb, uses DVB buffer model for Dolby Digital audio. When set to atsc, the ATSC model is used. type: str choices: ['ATSC', 'DVB'] audio_frames_per_pes: description: - - TODO + - The number of audio frames to insert for each PES packet. type: int audio_pids: description: - - TODO + - Packet Identifier (PID) of the elementary audio stream(s) in the transport stream. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str audio_stream_type: description: - - TODO + - When set to atsc, uses stream type = 0x81 for AC3 and stream type = 0x87 for EAC3. When set to dvb, uses stream type = 0x06. type: str choices: ['ATSC', 'DVB'] bitrate: description: - - TODO + - Average bitrate in bits/second. Valid values depend on rate control mode and profile. type: int buffer_model: description: - - TODO + - Controls the timing accuracy for output network traffic. Leave as MULTIPLEX to ensure accurate network packet timing. Or set to NONE, which might result in lower latency but will result in more variability in output network packet timing. This variability might cause interruptions, jitter, or bursty behavior in your playback or receiving devices. type: str choices: ['MULTIPLEX', 'NONE'] cc_descriptor: description: - - TODO + - When set to enabled, generates captionServiceDescriptor in PMT. type: str choices: ['DISABLED', 'ENABLED'] dvb_nit_settings: description: - - TODO + - Inserts DVB Network Information Table (NIT) at the specified table repetition interval. type: dict suboptions: network_id: description: - - TODO + - Provides Network ID that matches EIDR ID format (e.g., “10.XXXX/XXXX-XXXX-XXXX-XXXX-XXXX-C”). type: int network_name: description: - - TODO + - The network name text placed in the networkNameDescriptor inside the Network Information Table. Maximum length is 256 characters. type: str rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int dvb_sdt_settings: description: - - TODO + - Inserts DVB Service Description Table (SDT) at the specified table repetition interval. type: dict suboptions: output_sdt: description: - - TODO + - Selects method of inserting SDT information into output stream. The sdtFollow setting copies SDT information from input stream to output stream. The sdtFollowIfPresent setting copies SDT information from input stream to output stream if SDT information is present in the input, otherwise it will fall back on the user-defined values. The sdtManual setting means user will enter the SDT information. The sdtNone setting means output stream will not contain SDT information. type: str choices: ['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE'] rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int service_name: description: - - TODO + - The service name placed in the serviceDescriptor in the Service Description Table. Maximum length is 256 characters. type: str service_provider_name: description: - - TODO + - The service provider name placed in the serviceDescriptor in the Service Description Table. Maximum length is 256 characters. type: str dvb_sub_pids: description: - - TODO + - Packet Identifier (PID) for input source DVB Subtitle data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str dvb_tdt_settings: description: - - TODO + - Inserts DVB Time and Date Table (TDT) at the specified table repetition interval. type: dict suboptions: rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int dvb_teletext_pid: description: - - TODO + - Packet Identifier (PID) for input source DVB Teletext data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str ebif: description: - - TODO + - If set to passthrough, passes any EBIF data from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] ebp_audio_interval: description: - - TODO + - When videoAndFixedIntervals is selected, audio EBP markers will be added to partitions 3 and 4. The interval between these additional markers will be fixed, and will be slightly shorter than the video EBP marker interval. Only available when EBP Cablelabs segmentation markers are selected. Partitions 1 and 2 will always follow the video interval. type: str choices: ['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL'] ebp_lookahead_ms: description: - - TODO + - When set, enforces that Encoder Boundary Points do not come within the specified time interval of each other by looking ahead at input video. If another EBP is going to come in within the specified time interval, the current EBP is not emitted, and the segment is “stretched” to the next marker. The lookahead value does not add latency to the system. The Live Event must be configured elsewhere to create sufficient latency to make the lookahead accurate. type: int ebp_placement: description: - - TODO + - Controls placement of EBP on Audio PIDs. If set to videoAndAudioPids, EBP markers will be placed on the video PID and all audio PIDs. If set to videoPid, EBP markers will be placed on only the video PID. type: str choices: ['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID'] ecm_pid: description: - - TODO + - This field is unused and deprecated. type: str es_rate_in_pes: description: - - TODO + - Include or exclude the ES Rate field in the PES header. type: str choices: ['EXCLUDE', 'INCLUDE'] etv_platform_pid: description: - - TODO + - Packet Identifier (PID) for input source ETV Platform data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str etv_signal_pid: description: - - TODO + - Packet Identifier (PID) for input source ETV Signal data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str fragment_time: description: - - TODO + - The length in seconds of each fragment. Only used with EBP markers. type: float klv: description: - - TODO + - If set to passthrough, passes any KLV data from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] klv_data_pids: description: - - TODO + - Packet Identifier (PID) for input source KLV data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str nielsen_id3_behavior: description: - - TODO + - If set to passthrough, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. type: str choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] null_packet_bitrate: description: - - TODO + - Value in bits per second of extra null packets to insert into the transport stream. This can be used if a downstream encryption system requires periodic null packets. type: float pat_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. type: int pcr_control: description: - - TODO + - When set to pcrEveryPesPacket, a Program Clock Reference value is inserted for every Packetized Elementary Stream (PES) header. This parameter is effective only when the PCR PID is the same as the video or audio elementary stream. type: str choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] pcr_period: description: - - TODO + - Maximum time in milliseconds between Program Clock Reference (PCRs) inserted into the transport stream. type: int pcr_pid: description: - - TODO + - Packet Identifier (PID) of the Program Clock Reference (PCR) in the transport stream. When no value is given, the encoder will assign the same value as the Video PID. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str pmt_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. type: int pmt_pid: description: - - TODO + - Packet Identifier (PID) for the Program Map Table (PMT) in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str program_num: description: - - TODO + - The value of the program number field in the Program Map Table. type: int rate_mode: description: - - TODO + - When vbr, does not insert null packets into transport stream to fill specified bitrate. The bitrate setting acts as the maximum bitrate when vbr is set. type: str choices: ['CBR', 'VBR'] scte27_pids: description: - - TODO + - Packet Identifier (PID) for input source SCTE-27 data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str scte35_control: description: - - TODO + - Optionally pass SCTE-35 signals from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] scte35_pid: description: - - TODO + - Packet Identifier (PID) of the SCTE-35 stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str segmentation_markers: description: - - TODO + - Inserts segmentation markers at each segmentationTime period. raiSegstart sets the Random Access Indicator bit in the adaptation field. raiAdapt sets the RAI bit and adds the current timecode in the private data bytes. psiSegstart inserts PAT and PMT tables at the start of segments. ebp adds Encoder Boundary Point information to the adaptation field as per OpenCable specification OC-SP-EBP-I01-130118. ebpLegacy adds Encoder Boundary Point information to the adaptation field using a legacy proprietary format. type: str choices: ['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART'] segmentation_style: description: - - TODO + - The segmentation style parameter controls how segmentation markers are inserted into the transport stream. With avails, it is possible that segments may be truncated, which can influence where future segmentation markers are inserted. When a segmentation style of “resetCadence” is selected and a segment is truncated due to an avail, we will reset the segmentation cadence. This means the subsequent segment will have a duration of $segmentationTime seconds. When a segmentation style of “maintainCadence” is selected and a segment is truncated due to an avail, we will not reset the segmentation cadence. This means the subsequent segment will likely be truncated as well. However, all segments after that will have a duration of $segmentationTime seconds. Note that EBP lookahead is a slight exception to this rule. type: str choices: ['MAINTAIN_CADENCE', 'RESET_CADENCE'] segmentation_time: description: - - TODO + - The length in seconds of each segment. Required unless markers is set to _none_. type: float timed_metadata_behavior: description: - - TODO + - When set to passthrough, timed metadata will be passed through from input to output. type: str choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] timed_metadata_pid: description: - - TODO + - Packet Identifier (PID) of the timed metadata stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str transport_stream_id: description: - - TODO + - The value of the transport stream ID field in the Program Map Table. type: int video_pid: description: - - TODO + - Packet Identifier (PID) of the elementary video stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str scte35_preroll_pullup_milliseconds: description: - - TODO + - Defines the amount SCTE-35 preroll will be increased (in milliseconds) on the output. Preroll is the amount of time between the presence of a SCTE-35 indication in a transport stream and the PTS of the video frame it references. Zero means don’t add pullup (it doesn’t mean set the preroll to zero). Negative pullup is not supported, which means that you can’t make the preroll shorter. Be aware that latency in the output will increase by the pullup amount. type: float destination: description: @@ -2968,246 +2968,246 @@ choices: ['DROP', 'ENCODE_SILENCE'] arib: description: - - TODO + - When set to enabled, uses ARIB-compliant field muxing and removes video descriptor. type: str choices: ['DISABLED', 'ENABLED'] arib_captions_pid: description: - - TODO + - Packet Identifier (PID) for ARIB Captions in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str arib_captions_pid_control: description: - - TODO + - If set to auto, pid number used for ARIB Captions will be auto-selected from unused pids. If set to useConfigured, ARIB Captions will be on the configured pid number. type: str choices: ['AUTO', 'USE_CONFIGURED'] audio_buffer_model: description: - - TODO + - When set to dvb, uses DVB buffer model for Dolby Digital audio. When set to atsc, the ATSC model is used. type: str choices: ['ATSC', 'DVB'] audio_frames_per_pes: description: - - TODO + - The number of audio frames to insert for each PES packet. type: int audio_pids: description: - - TODO + - Packet Identifier (PID) of the elementary audio stream(s) in the transport stream. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str audio_stream_type: description: - - TODO + - When set to atsc, uses stream type = 0x81 for AC3 and stream type = 0x87 for EAC3. When set to dvb, uses stream type = 0x06. type: str choices: ['ATSC', 'DVB'] bitrate: description: - - TODO + - Average bitrate in bits/second. Valid values depend on rate control mode and profile. type: int buffer_model: description: - - TODO + - Controls the timing accuracy for output network traffic. Leave as MULTIPLEX to ensure accurate network packet timing. Or set to NONE, which might result in lower latency but will result in more variability in output network packet timing. This variability might cause interruptions, jitter, or bursty behavior in your playback or receiving devices. type: str choices: ['MULTIPLEX', 'NONE'] cc_descriptor: description: - - TODO + - When set to enabled, generates captionServiceDescriptor in PMT. type: str choices: ['DISABLED', 'ENABLED'] dvb_nit_settings: description: - - TODO + - Inserts DVB Network Information Table (NIT) at the specified table repetition interval. type: dict suboptions: network_id: description: - - TODO + - Provides Network ID that matches EIDR ID format (e.g., “10.XXXX/XXXX-XXXX-XXXX-XXXX-XXXX-C”). type: int network_name: description: - - TODO + - The network name text placed in the networkNameDescriptor inside the Network Information Table. Maximum length is 256 characters. type: str rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int dvb_sdt_settings: description: - - TODO + - Inserts DVB Service Description Table (SDT) at the specified table repetition interval. type: dict suboptions: output_sdt: description: - - TODO + - Selects method of inserting SDT information into output stream. The sdtFollow setting copies SDT information from input stream to output stream. The sdtFollowIfPresent setting copies SDT information from input stream to output stream if SDT information is present in the input, otherwise it will fall back on the user-defined values. The sdtManual setting means user will enter the SDT information. The sdtNone setting means output stream will not contain SDT information. type: str choices: ['SDT_FOLLOW', 'SDT_FOLLOW_IF_PRESENT', 'SDT_MANUAL', 'SDT_NONE'] rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int service_name: description: - - TODO + - The service name placed in the serviceDescriptor in the Service Description Table. Maximum length is 256 characters. type: str service_provider_name: description: - - TODO + - The service provider name placed in the serviceDescriptor in the Service Description Table. Maximum length is 256 characters. type: str dvb_sub_pids: description: - - TODO + - Packet Identifier (PID) for input source DVB Subtitle data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str dvb_tdt_settings: description: - - TODO + - Inserts DVB Time and Date Table (TDT) at the specified table repetition interval. type: dict suboptions: rep_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. type: int dvb_teletext_pid: description: - - TODO + - Packet Identifier (PID) for input source DVB Teletext data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str ebif: description: - - TODO + - If set to passthrough, passes any EBIF data from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] ebp_audio_interval: description: - - TODO + - When videoAndFixedIntervals is selected, audio EBP markers will be added to partitions 3 and 4. The interval between these additional markers will be fixed, and will be slightly shorter than the video EBP marker interval. Only available when EBP Cablelabs segmentation markers are selected. Partitions 1 and 2 will always follow the video interval. type: str choices: ['VIDEO_AND_FIXED_INTERVALS', 'VIDEO_INTERVAL'] ebp_lookahead_ms: description: - - TODO + - When set, enforces that Encoder Boundary Points do not come within the specified time interval of each other by looking ahead at input video. If another EBP is going to come in within the specified time interval, the current EBP is not emitted, and the segment is “stretched” to the next marker. The lookahead value does not add latency to the system. The Live Event must be configured elsewhere to create sufficient latency to make the lookahead accurate. type: int ebp_placement: description: - - TODO + - Controls placement of EBP on Audio PIDs. If set to videoAndAudioPids, EBP markers will be placed on the video PID and all audio PIDs. If set to videoPid, EBP markers will be placed on only the video PID. type: str choices: ['VIDEO_AND_AUDIO_PIDS', 'VIDEO_PID'] ecm_pid: description: - - TODO + - This field is unused and deprecated. type: str es_rate_in_pes: description: - - TODO + - Include or exclude the ES Rate field in the PES header. type: str choices: ['EXCLUDE', 'INCLUDE'] etv_platform_pid: description: - - TODO + - Packet Identifier (PID) for input source ETV Platform data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str etv_signal_pid: description: - - TODO + - Packet Identifier (PID) for input source ETV Signal data to this output. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str fragment_time: description: - - TODO + - The length in seconds of each fragment. Only used with EBP markers. type: float klv: description: - - TODO + - If set to passthrough, passes any KLV data from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] klv_data_pids: description: - - TODO + - Packet Identifier (PID) for input source KLV data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str nielsen_id3_behavior: description: - - TODO + - If set to passthrough, Nielsen inaudible tones for media tracking will be detected in the input audio and an equivalent ID3 tag will be inserted in the output. type: str choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] null_packet_bitrate: description: - - TODO + - Value in bits per second of extra null packets to insert into the transport stream. This can be used if a downstream encryption system requires periodic null packets. type: float pat_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. type: int pcr_control: description: - - TODO + - When set to pcrEveryPesPacket, a Program Clock Reference value is inserted for every Packetized Elementary Stream (PES) header. This parameter is effective only when the PCR PID is the same as the video or audio elementary stream. type: str choices: ['CONFIGURED_PCR_PERIOD', 'PCR_EVERY_PES_PACKET'] pcr_period: description: - - TODO + - Maximum time in milliseconds between Program Clock Reference (PCRs) inserted into the transport stream. type: int pcr_pid: description: - - TODO + - Packet Identifier (PID) of the Program Clock Reference (PCR) in the transport stream. When no value is given, the encoder will assign the same value as the Video PID. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str pmt_interval: description: - - TODO + - The number of milliseconds between instances of this table in the output transport stream. Valid values are 0, 10..1000. type: int pmt_pid: description: - - TODO + - Packet Identifier (PID) for the Program Map Table (PMT) in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str program_num: description: - - TODO + - The value of the program number field in the Program Map Table. type: int rate_mode: description: - - TODO + - When vbr, does not insert null packets into transport stream to fill specified bitrate. The bitrate setting acts as the maximum bitrate when vbr is set. type: str choices: ['CBR', 'VBR'] scte27_pids: description: - - TODO + - Packet Identifier (PID) for input source SCTE-27 data to this output. Multiple values are accepted, and can be entered in ranges and/or by comma separation. Can be entered as decimal or hexadecimal values. Each PID specified must be in the range of 32 (or 0x20)..8182 (or 0x1ff6). type: str scte35_control: description: - - TODO + - Optionally pass SCTE-35 signals from the input source to this output. type: str choices: ['NONE', 'PASSTHROUGH'] scte35_pid: description: - - TODO + - Packet Identifier (PID) of the SCTE-35 stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str segmentation_markers: description: - - TODO + - Inserts segmentation markers at each segmentationTime period. raiSegstart sets the Random Access Indicator bit in the adaptation field. raiAdapt sets the RAI bit and adds the current timecode in the private data bytes. psiSegstart inserts PAT and PMT tables at the start of segments. ebp adds Encoder Boundary Point information to the adaptation field as per OpenCable specification OC-SP-EBP-I01-130118. ebpLegacy adds Encoder Boundary Point information to the adaptation field using a legacy proprietary format. type: str choices: ['EBP', 'EBP_LEGACY', 'NONE', 'PSI_SEGSTART', 'RAI_ADAPT', 'RAI_SEGSTART'] segmentation_style: description: - - TODO + - The segmentation style parameter controls how segmentation markers are inserted into the transport stream. With avails, it is possible that segments may be truncated, which can influence where future segmentation markers are inserted. When a segmentation style of “resetCadence” is selected and a segment is truncated due to an avail, we will reset the segmentation cadence. This means the subsequent segment will have a duration of $segmentationTime seconds. When a segmentation style of “maintainCadence” is selected and a segment is truncated due to an avail, we will not reset the segmentation cadence. This means the subsequent segment will likely be truncated as well. However, all segments after that will have a duration of $segmentationTime seconds. Note that EBP lookahead is a slight exception to this rule. type: str choices: ['MAINTAIN_CADENCE', 'RESET_CADENCE'] segmentation_time: description: - - TODO + - The length in seconds of each segment. Required unless markers is set to _none_. type: float timed_metadata_behavior: description: - - TODO + - When set to passthrough, timed metadata will be passed through from input to output. type: str choices: ['NO_PASSTHROUGH', 'PASSTHROUGH'] timed_metadata_pid: description: - - TODO + - Packet Identifier (PID) of the timed metadata stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str transport_stream_id: description: - - TODO + - The value of the transport stream ID field in the Program Map Table. type: int video_pid: description: - - TODO + - Packet Identifier (PID) of the elementary video stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: str scte35_preroll_pullup_milliseconds: description: - - TODO + - Defines the amount SCTE-35 preroll will be increased (in milliseconds) on the output. Preroll is the amount of time between the presence of a SCTE-35 indication in a transport stream and the PTS of the video frame it references. Zero means don’t add pullup (it doesn’t mean set the preroll to zero). Negative pullup is not supported, which means that you can’t make the preroll shorter. Be aware that latency in the output will increase by the pullup amount. type: float destination: description: @@ -3272,17 +3272,17 @@ choices: ['MILLISECONDS', 'SECONDS'] timecode_burnin_settings: description: - - TODO + - Timecode burn-in settings. type: dict suboptions: font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] position: description: - - TODO + - Choose a timecode burn-in output position. type: str choices: - 'BOTTOM_CENTER' @@ -3296,80 +3296,80 @@ - 'TOP_RIGHT' prefix: description: - - TODO + - Create a timecode burn-in prefix (optional). type: str h264_settings: description: - - TODO + - H264 Settings. type: dict suboptions: adaptive_quantization: description: - - TODO + - 'Enables or disables adaptive quantization, which is a technique MediaLive can apply to video on a frame-by-frame basis to produce more compression without losing quality. There are three types of adaptive quantization: flicker, spatial, and temporal. Set the field in one of these ways: Set to Auto. Recommended. For each type of AQ, MediaLive will determine if AQ is needed, and if so, the appropriate strength. Set a strength (a value other than Auto or Disable). This strength will apply to any of the AQ fields that you choose to enable. Set to Disabled to disable all types of adaptive quantization.' type: str choices: ['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF'] afd_signaling: description: - - TODO + - Indicates that AFD values will be written into the output stream. If afdSignaling is “auto”, the system will try to preserve the input AFD value (in cases where multiple AFD values are valid). If set to “fixed”, the AFD value will be the value configured in the fixedAfd parameter. type: str choices: ['AUTO', 'FIXED', 'NONE'] bitrate: description: - - TODO + - Average bitrate in bits/second. Valid values depend on rate control mode and profile. type: int buf_fill_pct: description: - - TODO + - Percentage of the buffer that should initially be filled (HRD buffer model). type: int buf_size: description: - - TODO + - Size of buffer (HRD buffer model) in bits. type: int color_metadata: description: - - TODO + - Includes colorspace metadata in the output. type: str choices: ['IGNORE', 'INSERT'] color_space_settings: description: - - TODO + - Color Space settings. type: dict suboptions: color_space_passthrough_settings: description: - - TODO + - Passthrough applies no color space conversion to the output. type: dict rec601_settings: description: - - TODO + - Rec601 Settings. type: dict rec709_settings: description: - - TODO + - Rec709 Settings. type: dict entropy_encoding: description: - - TODO + - Entropy encoding mode. Use cabac (must be in Main or High profile) or cavlc. type: str choices: ['CABAC', 'CAVLC'] filter_settings: description: - - TODO + - Optional. Both filters reduce bandwidth by removing imperceptible details. You can enable one of the filters. We recommend that you try both filters and observe the results to decide which one to use. The Temporal Filter reduces bandwidth by removing imperceptible details in the content. It combines perceptual filtering and motion compensated temporal filtering (MCTF). It operates independently of the compression level. The Bandwidth Reduction filter is a perceptual filter located within the encoding loop. It adapts to the current compression level to filter imperceptible signals. This filter works only when the resolution is 1080p or lower. type: dict suboptions: temporal_filter_settings: description: - - TODO + - Temporal Filter Settings. type: dict suboptions: post_filter_sharpening: description: - - TODO + - 'If you enable this filter, the results are the following: - If the source content is noisy (it contains excessive digital artifacts), the filter cleans up the source. - If the source content is already clean, the filter tends to decrease the bitrate, especially when the rate control mode is QVBR.' type: str choices: ['AUTO', 'DISABLED', 'ENABLED'] strength: description: - - TODO + - Choose a filter strength. We recommend a strength of 1 or 2. A higher strength might take out good information, resulting in an image that is overly soft. type: str choices: - 'AUTO' @@ -3391,22 +3391,22 @@ - 'STRENGTH_16' bandwidth_reduction_filter_settings: description: - - TODO + - Bandwidth Reduction Filter Settings. type: dict suboptions: post_filter_sharpening: description: - - TODO + - 'If you enable this filter, the results are the following: - If the source content is noisy (it contains excessive digital artifacts), the filter cleans up the source. - If the source content is already clean, the filter tends to decrease the bitrate, especially when the rate control mode is QVBR.' type: str choices: ['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3'] strength: description: - - TODO + - Choose a filter strength. We recommend a strength of 1 or 2. A higher strength might take out good information, resulting in an image that is overly soft. type: str choices: ['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4'] fixed_afd: description: - - TODO + - Four bit AFD value to write on all frames of video in the output stream. Only valid when afdSignaling is set to ‘Fixed’. type: str choices: - 'AFD_0000' @@ -3422,53 +3422,53 @@ - 'AFD_1111' flicker_aq: description: - - TODO + - 'Flicker AQ makes adjustments within each frame to reduce flicker or ‘pop’ on I-frames. The value to enter in this field depends on the value in the Adaptive quantization field: If you have set the Adaptive quantization field to Auto, MediaLive ignores any value in this field. MediaLive will determine if flicker AQ is appropriate and will apply the appropriate strength. If you have set the Adaptive quantization field to a strength, you can set this field to Enabled or Disabled. Enabled: MediaLive will apply flicker AQ using the specified strength. Disabled: MediaLive won’t apply flicker AQ. If you have set the Adaptive quantization to Disabled, MediaLive ignores any value in this field and doesn’t apply flicker AQ.' type: str choices: ['DISABLED', 'ENABLED'] force_field_pictures: description: - - TODO + - 'This setting applies only when scan type is “interlaced.” It controls whether coding is performed on a field basis or on a frame basis. (When the video is progressive, the coding is always performed on a frame basis.) enabled: Force MediaLive to code on a field basis, so that odd and even sets of fields are coded separately. disabled: Code the two sets of fields separately (on a field basis) or together (on a frame basis using PAFF), depending on what is most appropriate for the content.' type: str choices: ['DISABLED', 'ENABLED'] framerate_control: description: - - TODO + - This field indicates how the output video frame rate is specified. If “specified” is selected then the output video frame rate is determined by framerateNumerator and framerateDenominator, else if “initializeFromSource” is selected then the output video frame rate will be set equal to the input video frame rate of the first input. type: str choices: ['INITIALIZE_FROM_SOURCE', 'SPECIFIED'] framerate_denominator: description: - - TODO + - Framerate denominator. type: int framerate_numerator: description: - - TODO + - Framerate numerator - framerate is a fraction, e.g. 24000 / 1001 = 23.976 fps. type: int gop_b_reference: description: - - TODO + - Documentation update needed. type: str choices: ['DISABLED', 'ENABLED'] gop_closed_cadence: description: - - TODO + - Frequency of closed GOPs. In streaming applications, it is recommended that this be set to 1 so a decoder joining mid-stream will receive an IDR frame as quickly as possible. Setting this value to 0 will break output segmenting. type: int gop_num_b_frames: description: - - TODO + - Number of B-frames between reference frames. type: int gop_size: description: - - TODO + - GOP size (keyframe interval) in units of either frames or seconds per gopSizeUnits. If gopSizeUnits is frames, gopSize must be an integer and must be greater than or equal to 1. If gopSizeUnits is seconds, gopSize must be greater than 0, but need not be an integer. type: float gop_size_units: description: - - TODO + - Indicates if the gopSize is specified in frames or seconds. If seconds the system will convert the gopSize into a frame count at run time. type: str choices: ['FRAMES', 'SECONDS'] level: description: - - TODO + - H.264 Level. type: str choices: - 'H264_LEVEL_1' @@ -3490,109 +3490,109 @@ - 'H264_LEVEL_AUTO' look_ahead_rate_control: description: - - TODO + - Amount of lookahead. A value of low can decrease latency and memory usage, while high can produce better quality for certain content. type: str choices: ['HIGH', 'LOW', 'MEDIUM'] max_bitrate: description: - - TODO + - 'For QVBR: See the tooltip for Quality level For VBR: Set the maximum bitrate in order to accommodate expected spikes in the complexity of the video.' type: int min_i_interval: description: - - TODO + - Only meaningful if sceneChangeDetect is set to enabled. Defaults to 5 if multiplex rate control is used. Enforces separation between repeated (cadence) I-frames and I-frames inserted by Scene Change Detection. If a scene change I-frame is within I-interval frames of a cadence I-frame, the GOP is shrunk and/or stretched to the scene change I-frame. GOP stretch requires enabling lookahead as well as setting I-interval. The normal cadence resumes for the next GOP. Note: Maximum GOP stretch = GOP size + Min-I-interval - 1. type: int num_ref_frames: description: - - TODO + - Number of reference frames to use. The encoder may use more than requested if using B-frames and/or interlaced encoding. type: int par_control: description: - - TODO + - This field indicates how the output pixel aspect ratio is specified. If “specified” is selected then the output video pixel aspect ratio is determined by parNumerator and parDenominator, else if “initializeFromSource” is selected then the output pixsel aspect ratio will be set equal to the input video pixel aspect ratio of the first input. type: str choices: ['INITIALIZE_FROM_SOURCE', 'SPECIFIED'] par_denominator: description: - - TODO + - Pixel Aspect Ratio denominator. type: int par_numerator: description: - - TODO + - Pixel Aspect Ratio numerator. type: int profile: description: - - TODO + - AAC Profile. type: str choices: ['BASELINE', 'HIGH', 'HIGH_10BIT', 'HIGH_422', 'HIGH_422_10BIT', 'MAIN'] quality_level: description: - - TODO + - 'Leave as STANDARD_QUALITY or choose a different value (which might result in additional costs to run the channel). - ENHANCED_QUALITY: Produces a slightly better video quality without an increase in the bitrate. Has an effect only when the Rate control mode is QVBR or CBR. If this channel is in a MediaLive multiplex, the value must be ENHANCED_QUALITY. - STANDARD_QUALITY: Valid for any Rate control mode.' type: str choices: ['ENHANCED_QUALITY', 'STANDARD_QUALITY'] qvbr_quality_level: description: - - TODO + - 'Controls the target quality for the video encode. Applies only when the rate control mode is QVBR. You can set a target quality or you can let MediaLive determine the best quality. To set a target quality, enter values in the QVBR quality level field and the Max bitrate field. Enter values that suit your most important viewing devices. Recommended values are: - Primary screen: Quality level: 8 to 10. Max bitrate: 4M - PC or tablet: Quality level: 7. Max bitrate: 1.5M to 3M - Smartphone: Quality level: 6. Max bitrate: 1M to 1.5M To let MediaLive decide, leave the QVBR quality level field empty, and in Max bitrate enter the maximum rate you want in the video. For more information, see the section called “Video - rate control mode” in the MediaLive user guide.' type: int rate_control_mode: description: - - TODO + - Rate Control Mode. type: str choices: ['CBR', 'MULTIPLEX', 'QVBR', 'VBR'] scan_type: description: - - TODO + - Sets the scan type of the output to progressive or top-field-first interlaced. type: str choices: ['INTERLACED', 'PROGRESSIVE'] scene_change_detect: description: - - TODO + - 'Scene change detection. - On: inserts I-frames when scene change is detected. - Off: does not force an I-frame when scene change is detected.' type: str choices: ['DISABLED', 'ENABLED'] slices: description: - - TODO + - Number of slices per picture. Must be less than or equal to the number of macroblock rows for progressive pictures, and less than or equal to half the number of macroblock rows for interlaced pictures. This field is optional; when no value is specified the encoder will choose the number of slices based on encode resolution. type: int softness: description: - - TODO + - Softness. Selects quantizer matrix, larger values reduce high-frequency content in the encoded image. If not set to zero, must be greater than 15. type: int spatial_aq: description: - - TODO + - 'Spatial AQ makes adjustments within each frame based on spatial variation of content complexity. The value to enter in this field depends on the value in the Adaptive quantization field: If you have set the Adaptive quantization field to Auto, MediaLive ignores any value in this field. MediaLive will determine if spatial AQ is appropriate and will apply the appropriate strength. If you have set the Adaptive quantization field to a strength, you can set this field to Enabled or Disabled. Enabled: MediaLive will apply spatial AQ using the specified strength. Disabled: MediaLive won’t apply spatial AQ. If you have set the Adaptive quantization to Disabled, MediaLive ignores any value in this field and doesn’t apply spatial AQ.' type: str choices: ['DISABLED', 'ENABLED'] subgop_length: description: - - TODO + - If set to fixed, use gopNumBFrames B-frames per sub-GOP. If set to dynamic, optimize the number of B-frames used for each sub-GOP to improve visual quality. type: str choices: ['DYNAMIC', 'FIXED'] syntax: description: - - TODO + - Produces a bitstream compliant with SMPTE RP-2027. type: str choices: ['DEFAULT', 'RP2027'] temporal_aq: description: - - TODO + - 'Temporal makes adjustments within each frame based on temporal variation of content complexity. The value to enter in this field depends on the value in the Adaptive quantization field: If you have set the Adaptive quantization field to Auto, MediaLive ignores any value in this field. MediaLive will determine if temporal AQ is appropriate and will apply the appropriate strength. If you have set the Adaptive quantization field to a strength, you can set this field to Enabled or Disabled. Enabled: MediaLive will apply temporal AQ using the specified strength. Disabled: MediaLive won’t apply temporal AQ. If you have set the Adaptive quantization to Disabled, MediaLive ignores any value in this field and doesn’t apply temporal AQ.' type: str choices: ['DISABLED', 'ENABLED'] timecode_insertion: description: - - TODO + - 'Determines how timecodes should be inserted into the video elementary stream. - ‘disabled’: Do not include timecodes - ‘picTimingSei’: Pass through picture timing SEI messages from the source specified in Timecode Config.' type: str choices: ['DISABLED', 'PIC_TIMING_SEI'] timecode_burnin_settings: description: - - TODO + - Timecode burn-in settings. type: dict suboptions: font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] position: description: - - TODO + - Choose a timecode burn-in output position. type: str choices: - 'BOTTOM_CENTER' @@ -3606,97 +3606,97 @@ - 'TOP_RIGHT' prefix: description: - - TODO + - Create a timecode burn-in prefix (optional). type: str min_qp: description: - - TODO + - Sets the minimum QP. If you aren’t familiar with quantization adjustment, leave the field empty. MediaLive will apply an appropriate value. type: int h265_settings: description: - - TODO + - H265 Settings. type: dict suboptions: adaptive_quantization: description: - - TODO + - 'Enables or disables adaptive quantization, which is a technique MediaLive can apply to video on a frame-by-frame basis to produce more compression without losing quality. There are three types of adaptive quantization: flicker, spatial, and temporal. Set the field in one of these ways: Set to Auto. Recommended. For each type of AQ, MediaLive will determine if AQ is needed, and if so, the appropriate strength. Set a strength (a value other than Auto or Disable). This strength will apply to any of the AQ fields that you choose to enable. Set to Disabled to disable all types of adaptive quantization.' type: str choices: ['AUTO', 'HIGH', 'HIGHER', 'LOW', 'MAX', 'MEDIUM', 'OFF'] afd_signaling: description: - - TODO + - Indicates that AFD values will be written into the output stream. If afdSignaling is “auto”, the system will try to preserve the input AFD value (in cases where multiple AFD values are valid). If set to “fixed”, the AFD value will be the value configured in the fixedAfd parameter. type: str choices: ['AUTO', 'FIXED', 'NONE'] alternative_transfer_function: description: - - TODO + - Whether or not EML should insert an Alternative Transfer Function SEI message to support backwards compatibility with non-HDR decoders and displays. type: str choices: ['INSERT', 'OMIT'] bitrate: description: - - TODO + - Average bitrate in bits/second. Valid values depend on rate control mode and profile. type: int buf_size: description: - - TODO + - Size of buffer (HRD buffer model) in bits. type: int color_metadata: description: - - TODO + - Includes colorspace metadata in the output. type: str choices: ['IGNORE', 'INSERT'] color_space_settings: description: - - TODO + - Color Space settings. type: dict suboptions: color_space_passthrough_settings: description: - - TODO + - Passthrough applies no color space conversion to the output. type: dict dolby_vision81_settings: description: - - TODO + - Dolby Vision81 Settings. type: dict hdr10_settings: description: - - TODO + - Hdr10 Settings. type: dict suboptions: max_cll: description: - - TODO + - Maximum Content Light Level An integer metadata value defining the maximum light level, in nits, of any single pixel within an encoded HDR video stream or file. type: int max_fall: description: - - TODO + - Maximum Frame Average Light Level An integer metadata value defining the maximum average light level, in nits, for any single frame within an encoded HDR video stream or file. type: int rec601_settings: description: - - TODO + - Rec601 Settings. type: dict rec709_settings: description: - - TODO + - Rec709 Settings. type: dict filter_settings: description: - - TODO + - Optional. Both filters reduce bandwidth by removing imperceptible details. You can enable one of the filters. We recommend that you try both filters and observe the results to decide which one to use. The Temporal Filter reduces bandwidth by removing imperceptible details in the content. It combines perceptual filtering and motion compensated temporal filtering (MCTF). It operates independently of the compression level. The Bandwidth Reduction filter is a perceptual filter located within the encoding loop. It adapts to the current compression level to filter imperceptible signals. This filter works only when the resolution is 1080p or lower. type: dict suboptions: temporal_filter_settings: description: - - TODO + - Temporal Filter Settings. type: dict suboptions: post_filter_sharpening: description: - - TODO + - 'If you enable this filter, the results are the following: - If the source content is noisy (it contains excessive digital artifacts), the filter cleans up the source. - If the source content is already clean, the filter tends to decrease the bitrate, especially when the rate control mode is QVBR.' type: str choices: ['AUTO', 'DISABLED', 'ENABLED'] strength: description: - - TODO + - Choose a filter strength. We recommend a strength of 1 or 2. A higher strength might take out good information, resulting in an image that is overly soft. type: str choices: - 'AUTO' @@ -3718,22 +3718,22 @@ - 'STRENGTH_16' bandwidth_reduction_filter_settings: description: - - TODO + - Bandwidth Reduction Filter Settings. type: dict suboptions: post_filter_sharpening: description: - - TODO + - 'If you enable this filter, the results are the following: - If the source content is noisy (it contains excessive digital artifacts), the filter cleans up the source. - If the source content is already clean, the filter tends to decrease the bitrate, especially when the rate control mode is QVBR.' type: str choices: ['DISABLED', 'SHARPENING_1', 'SHARPENING_2', 'SHARPENING_3'] strength: description: - - TODO + - Choose a filter strength. We recommend a strength of 1 or 2. A higher strength might take out good information, resulting in an image that is overly soft. type: str choices: ['AUTO', 'STRENGTH_1', 'STRENGTH_2', 'STRENGTH_3', 'STRENGTH_4'] fixed_afd: description: - - TODO + - Four bit AFD value to write on all frames of video in the output stream. Only valid when afdSignaling is set to ‘Fixed’. type: str choices: - 'AFD_0000' @@ -3749,34 +3749,34 @@ - 'AFD_1111' flicker_aq: description: - - TODO + - 'Flicker AQ makes adjustments within each frame to reduce flicker or ‘pop’ on I-frames. The value to enter in this field depends on the value in the Adaptive quantization field: If you have set the Adaptive quantization field to Auto, MediaLive ignores any value in this field. MediaLive will determine if flicker AQ is appropriate and will apply the appropriate strength. If you have set the Adaptive quantization field to a strength, you can set this field to Enabled or Disabled. Enabled: MediaLive will apply flicker AQ using the specified strength. Disabled: MediaLive won’t apply flicker AQ. If you have set the Adaptive quantization to Disabled, MediaLive ignores any value in this field and doesn’t apply flicker AQ.' type: str choices: ['DISABLED', 'ENABLED'] framerate_denominator: description: - - TODO + - Framerate denominator. type: int framerate_numerator: description: - - TODO + - Framerate numerator - framerate is a fraction, e.g. 24000 / 1001 = 23.976 fps. type: int gop_closed_cadence: description: - - TODO + - Frequency of closed GOPs. In streaming applications, it is recommended that this be set to 1 so a decoder joining mid-stream will receive an IDR frame as quickly as possible. Setting this value to 0 will break output segmenting. type: int gop_size: description: - - TODO + - GOP size (keyframe interval) in units of either frames or seconds per gopSizeUnits. If gopSizeUnits is frames, gopSize must be an integer and must be greater than or equal to 1. If gopSizeUnits is seconds, gopSize must be greater than 0, but need not be an integer. type: float gop_size_units: description: - - TODO + - Indicates if the gopSize is specified in frames or seconds. If seconds the system will convert the gopSize into a frame count at run time. type: str choices: ['FRAMES', 'SECONDS'] level: description: - - TODO + - H.264 Level. type: str choices: - 'H265_LEVEL_1' @@ -3795,76 +3795,76 @@ - 'H265_LEVEL_AUTO' look_ahead_rate_control: description: - - TODO + - Amount of lookahead. A value of low can decrease latency and memory usage, while high can produce better quality for certain content. type: str choices: ['HIGH', 'LOW', 'MEDIUM'] max_bitrate: description: - - TODO + - 'For QVBR: See the tooltip for Quality level For VBR: Set the maximum bitrate in order to accommodate expected spikes in the complexity of the video.' type: int min_i_interval: description: - - TODO + - Only meaningful if sceneChangeDetect is set to enabled. Defaults to 5 if multiplex rate control is used. Enforces separation between repeated (cadence) I-frames and I-frames inserted by Scene Change Detection. If a scene change I-frame is within I-interval frames of a cadence I-frame, the GOP is shrunk and/or stretched to the scene change I-frame. GOP stretch requires enabling lookahead as well as setting I-interval. The normal cadence resumes for the next GOP. Note: Maximum GOP stretch = GOP size + Min-I-interval - 1. type: int par_denominator: description: - - TODO + - Pixel Aspect Ratio denominator. type: int par_numerator: description: - - TODO + - Pixel Aspect Ratio numerator. type: int profile: description: - - TODO + - AAC Profile. type: str choices: ['MAIN', 'MAIN_10BIT'] qvbr_quality_level: description: - - TODO + - 'Controls the target quality for the video encode. Applies only when the rate control mode is QVBR. You can set a target quality or you can let MediaLive determine the best quality. To set a target quality, enter values in the QVBR quality level field and the Max bitrate field. Enter values that suit your most important viewing devices. Recommended values are: - Primary screen: Quality level: 8 to 10. Max bitrate: 4M - PC or tablet: Quality level: 7. Max bitrate: 1.5M to 3M - Smartphone: Quality level: 6. Max bitrate: 1M to 1.5M To let MediaLive decide, leave the QVBR quality level field empty, and in Max bitrate enter the maximum rate you want in the video. For more information, see the section called “Video - rate control mode” in the MediaLive user guide.' type: int rate_control_mode: description: - - TODO + - Rate Control Mode. type: str choices: ['CBR', 'MULTIPLEX', 'QVBR'] scan_type: description: - - TODO + - Sets the scan type of the output to progressive or top-field-first interlaced. type: str choices: ['INTERLACED', 'PROGRESSIVE'] scene_change_detect: description: - - TODO + - 'Scene change detection. - On: inserts I-frames when scene change is detected. - Off: does not force an I-frame when scene change is detected.' type: str choices: ['DISABLED', 'ENABLED'] slices: description: - - TODO + - Number of slices per picture. Must be less than or equal to the number of macroblock rows for progressive pictures, and less than or equal to half the number of macroblock rows for interlaced pictures. This field is optional; when no value is specified the encoder will choose the number of slices based on encode resolution. type: int tier: description: - - TODO + - H.265 Tier. type: str choices: ['HIGH', 'MAIN'] timecode_insertion: description: - - TODO + - 'Determines how timecodes should be inserted into the video elementary stream. - ‘disabled’: Do not include timecodes - ‘picTimingSei’: Pass through picture timing SEI messages from the source specified in Timecode Config.' type: str choices: ['DISABLED', 'PIC_TIMING_SEI'] timecode_burnin_settings: description: - - TODO + - Timecode burn-in settings. type: dict suboptions: font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] position: description: - - TODO + - Choose a timecode burn-in output position. type: str choices: - 'BOTTOM_CENTER' @@ -3878,93 +3878,93 @@ - 'TOP_RIGHT' prefix: description: - - TODO + - Create a timecode burn-in prefix (optional). type: str mv_over_picture_boundaries: description: - - TODO + - If you are setting up the picture as a tile, you must set this to “disabled”. In all other configurations, you typically enter “enabled”. type: str choices: ['DISABLED', 'ENABLED'] mv_temporal_predictor: description: - - TODO + - If you are setting up the picture as a tile, you must set this to “disabled”. In other configurations, you typically enter “enabled”. type: str choices: ['DISABLED', 'ENABLED'] tile_height: description: - - TODO + - Set this field to set up the picture as a tile. You must also set tileWidth. The tile height must result in 22 or fewer rows in the frame. The tile width must result in 20 or fewer columns in the frame. And finally, the product of the column count and row count must be 64 of less. If the tile width and height are specified, MediaLive will override the video codec slices field with a value that MediaLive calculates. type: int tile_padding: description: - - TODO + - Set to “padded” to force MediaLive to add padding to the frame, to obtain a frame that is a whole multiple of the tile size. If you are setting up the picture as a tile, you must enter “padded”. In all other configurations, you typically enter “none”. type: str choices: ['NONE', 'PADDED'] tile_width: description: - - TODO + - Set this field to set up the picture as a tile. See tileHeight for more information. type: int treeblock_size: description: - - TODO + - Select the tree block size used for encoding. If you enter “auto”, the encoder will pick the best size. If you are setting up the picture as a tile, you must set this to 32x32. In all other configurations, you typically enter “auto”. type: str choices: ['AUTO', 'TREE_SIZE_32X32'] min_qp: description: - - TODO + - Sets the minimum QP. If you aren’t familiar with quantization adjustment, leave the field empty. MediaLive will apply an appropriate value. type: int deblocking: description: - - TODO + - Enable or disable the deblocking filter for this codec. The filter reduces blocking artifacts at block boundaries, which improves overall video quality. If the filter is disabled, visible block edges might appear in the output, especially at lower bitrates. type: str choices: ['DISABLED', 'ENABLED'] mpeg2_settings: description: - - TODO + - Mpeg2 Settings. type: dict suboptions: adaptive_quantization: description: - - TODO + - 'Enables or disables adaptive quantization, which is a technique MediaLive can apply to video on a frame-by-frame basis to produce more compression without losing quality. There are three types of adaptive quantization: flicker, spatial, and temporal. Set the field in one of these ways: Set to Auto. Recommended. For each type of AQ, MediaLive will determine if AQ is needed, and if so, the appropriate strength. Set a strength (a value other than Auto or Disable). This strength will apply to any of the AQ fields that you choose to enable. Set to Disabled to disable all types of adaptive quantization.' type: str choices: ['AUTO', 'HIGH', 'LOW', 'MEDIUM', 'OFF'] afd_signaling: description: - - TODO + - Indicates that AFD values will be written into the output stream. If afdSignaling is “auto”, the system will try to preserve the input AFD value (in cases where multiple AFD values are valid). If set to “fixed”, the AFD value will be the value configured in the fixedAfd parameter. type: str choices: ['AUTO', 'FIXED', 'NONE'] color_metadata: description: - - TODO + - Includes colorspace metadata in the output. type: str choices: ['IGNORE', 'INSERT'] color_space: description: - - TODO + - Choose the type of color space conversion to apply to the output. For detailed information on setting up both the input and the output to obtain the desired color space in the output, see the section on "MediaLive Features - Video - color space" in the MediaLive User Guide. PASSTHROUGH: Keep the color space of the input content - do not convert it. AUTO:Convert all content that is SD to rec 601, and convert all content that is HD to rec 709. type: str choices: ['AUTO', 'PASSTHROUGH'] display_aspect_ratio: description: - - TODO + - Sets the pixel aspect ratio for the encode. type: str choices: ['DISPLAYRATIO16X9', 'DISPLAYRATIO4X3'] filter_settings: description: - - TODO + - Optional. Both filters reduce bandwidth by removing imperceptible details. You can enable one of the filters. We recommend that you try both filters and observe the results to decide which one to use. The Temporal Filter reduces bandwidth by removing imperceptible details in the content. It combines perceptual filtering and motion compensated temporal filtering (MCTF). It operates independently of the compression level. The Bandwidth Reduction filter is a perceptual filter located within the encoding loop. It adapts to the current compression level to filter imperceptible signals. This filter works only when the resolution is 1080p or lower. type: dict suboptions: temporal_filter_settings: description: - - TODO + - Temporal Filter Settings. type: dict suboptions: post_filter_sharpening: description: - - TODO + - 'If you enable this filter, the results are the following: - If the source content is noisy (it contains excessive digital artifacts), the filter cleans up the source. - If the source content is already clean, the filter tends to decrease the bitrate, especially when the rate control mode is QVBR.' type: str choices: ['AUTO', 'DISABLED', 'ENABLED'] strength: description: - - TODO + - Choose a filter strength. We recommend a strength of 1 or 2. A higher strength might take out good information, resulting in an image that is overly soft. type: str choices: - 'AUTO' @@ -3986,7 +3986,7 @@ - 'STRENGTH_16' fixed_afd: description: - - TODO + - Four bit AFD value to write on all frames of video in the output stream. Only valid when afdSignaling is set to ‘Fixed’. type: str choices: - 'AFD_0000' @@ -4002,58 +4002,58 @@ - 'AFD_1111' framerate_denominator: description: - - TODO + - Framerate denominator. type: int framerate_numerator: description: - - TODO + - Framerate numerator - framerate is a fraction, e.g. 24000 / 1001 = 23.976 fps. type: int gop_closed_cadence: description: - - TODO + - Frequency of closed GOPs. In streaming applications, it is recommended that this be set to 1 so a decoder joining mid-stream will receive an IDR frame as quickly as possible. Setting this value to 0 will break output segmenting. type: int gop_num_b_frames: description: - - TODO + - Number of B-frames between reference frames. type: int gop_size: description: - - TODO + - GOP size (keyframe interval) in units of either frames or seconds per gopSizeUnits. If gopSizeUnits is frames, gopSize must be an integer and must be greater than or equal to 1. If gopSizeUnits is seconds, gopSize must be greater than 0, but need not be an integer. type: float gop_size_units: description: - - TODO + - Indicates if the gopSize is specified in frames or seconds. If seconds the system will convert the gopSize into a frame count at run time. type: str choices: ['FRAMES', 'SECONDS'] scan_type: description: - - TODO + - Sets the scan type of the output to progressive or top-field-first interlaced. type: str choices: ['INTERLACED', 'PROGRESSIVE'] subgop_length: description: - - TODO + - If set to fixed, use gopNumBFrames B-frames per sub-GOP. If set to dynamic, optimize the number of B-frames used for each sub-GOP to improve visual quality. type: str choices: ['DYNAMIC', 'FIXED'] timecode_insertion: description: - - TODO + - 'Determines how timecodes should be inserted into the video elementary stream. - ‘disabled’: Do not include timecodes - ‘picTimingSei’: Pass through picture timing SEI messages from the source specified in Timecode Config.' type: str choices: ['DISABLED', 'GOP_TIMECODE'] timecode_burnin_settings: description: - - TODO + - Timecode burn-in settings. type: dict suboptions: font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] position: description: - - TODO + - Choose a timecode burn-in output position. type: str choices: - 'BOTTOM_CENTER' @@ -4067,57 +4067,57 @@ - 'TOP_RIGHT' prefix: description: - - TODO + - Create a timecode burn-in prefix (optional). type: str av1_settings: description: - - TODO + - Av1 Settings. type: dict suboptions: afd_signaling: description: - - TODO + - Indicates that AFD values will be written into the output stream. If afdSignaling is “auto”, the system will try to preserve the input AFD value (in cases where multiple AFD values are valid). If set to “fixed”, the AFD value will be the value configured in the fixedAfd parameter. type: str choices: ['AUTO', 'FIXED', 'NONE'] buf_size: description: - - TODO + - Size of buffer (HRD buffer model) in bits. type: int color_space_settings: description: - - TODO + - Color Space settings. type: dict suboptions: color_space_passthrough_settings: description: - - TODO + - Passthrough applies no color space conversion to the output. type: dict hdr10_settings: description: - - TODO + - Hdr10 Settings. type: dict suboptions: max_cll: description: - - TODO + - Maximum Content Light Level An integer metadata value defining the maximum light level, in nits, of any single pixel within an encoded HDR video stream or file. type: int max_fall: description: - - TODO + - Maximum Frame Average Light Level An integer metadata value defining the maximum average light level, in nits, for any single frame within an encoded HDR video stream or file. type: int rec601_settings: description: - - TODO + - Rec601 Settings. type: dict rec709_settings: description: - - TODO + - Rec709 Settings. type: dict fixed_afd: description: - - TODO + - Four bit AFD value to write on all frames of video in the output stream. Only valid when afdSignaling is set to ‘Fixed’. type: str choices: - 'AFD_0000' @@ -4133,25 +4133,25 @@ - 'AFD_1111' framerate_denominator: description: - - TODO + - Framerate denominator. type: int framerate_numerator: description: - - TODO + - Framerate numerator - framerate is a fraction, e.g. 24000 / 1001 = 23.976 fps. type: int gop_size: description: - - TODO + - GOP size (keyframe interval) in units of either frames or seconds per gopSizeUnits. If gopSizeUnits is frames, gopSize must be an integer and must be greater than or equal to 1. If gopSizeUnits is seconds, gopSize must be greater than 0, but need not be an integer. type: float gop_size_units: description: - - TODO + - Indicates if the gopSize is specified in frames or seconds. If seconds the system will convert the gopSize into a frame count at run time. type: str choices: ['FRAMES', 'SECONDS'] level: description: - - TODO + - H.264 Level. type: str choices: - 'AV1_LEVEL_2' @@ -4171,47 +4171,47 @@ - 'AV1_LEVEL_AUTO' look_ahead_rate_control: description: - - TODO + - Amount of lookahead. A value of low can decrease latency and memory usage, while high can produce better quality for certain content. type: str choices: ['HIGH', 'LOW', 'MEDIUM'] max_bitrate: description: - - TODO + - 'For QVBR: See the tooltip for Quality level For VBR: Set the maximum bitrate in order to accommodate expected spikes in the complexity of the video.' type: int min_i_interval: description: - - TODO + - Only meaningful if sceneChangeDetect is set to enabled. Defaults to 5 if multiplex rate control is used. Enforces separation between repeated (cadence) I-frames and I-frames inserted by Scene Change Detection. If a scene change I-frame is within I-interval frames of a cadence I-frame, the GOP is shrunk and/or stretched to the scene change I-frame. GOP stretch requires enabling lookahead as well as setting I-interval. The normal cadence resumes for the next GOP. Note: Maximum GOP stretch = GOP size + Min-I-interval - 1. type: int par_denominator: description: - - TODO + - Pixel Aspect Ratio denominator. type: int par_numerator: description: - - TODO + - Pixel Aspect Ratio numerator. type: int qvbr_quality_level: description: - - TODO + - 'Controls the target quality for the video encode. Applies only when the rate control mode is QVBR. You can set a target quality or you can let MediaLive determine the best quality. To set a target quality, enter values in the QVBR quality level field and the Max bitrate field. Enter values that suit your most important viewing devices. Recommended values are: - Primary screen: Quality level: 8 to 10. Max bitrate: 4M - PC or tablet: Quality level: 7. Max bitrate: 1.5M to 3M - Smartphone: Quality level: 6. Max bitrate: 1M to 1.5M To let MediaLive decide, leave the QVBR quality level field empty, and in Max bitrate enter the maximum rate you want in the video. For more information, see the section called “Video - rate control mode” in the MediaLive user guide.' type: int scene_change_detect: description: - - TODO + - 'Scene change detection. - On: inserts I-frames when scene change is detected. - Off: does not force an I-frame when scene change is detected.' type: str choices: ['DISABLED', 'ENABLED'] timecode_burnin_settings: description: - - TODO + - Timecode burn-in settings. type: dict suboptions: font_size: description: - - TODO + - When set to ‘auto’ fontSize will scale depending on the size of the output. Giving a positive integer will specify the exact font size in points. All burn-in and DVB-Sub font settings must match. type: str choices: ['EXTRA_SMALL_10', 'LARGE_48', 'MEDIUM_32', 'SMALL_16'] position: description: - - TODO + - Choose a timecode burn-in output position. type: str choices: - 'BOTTOM_CENTER' @@ -4225,7 +4225,7 @@ - 'TOP_RIGHT' prefix: description: - - TODO + - Create a timecode burn-in prefix (optional). type: str height: description: @@ -4233,7 +4233,7 @@ type: int name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str respond_to_afd: description: @@ -4290,423 +4290,423 @@ type: str input_attachments: description: - - TODO + - List of input attachments for channel. type: list elements: dict suboptions: automatic_input_failover_settings: description: - - TODO + - User-specified settings for defining what the conditions are for declaring the input unhealthy and failing over to a different input. type: dict suboptions: error_clear_time_msec: description: - - TODO + - This clear time defines the requirement a recovered input must meet to be considered healthy. The input must have no failover conditions for this length of time. Enter a time in milliseconds. This value is particularly important if the input_preference for the failover pair is set to PRIMARY_INPUT_PREFERRED, because after this time, MediaLive will switch back to the primary input. type: int failover_conditions: description: - - TODO + - A list of failover conditions. If any of these conditions occur, MediaLive will perform a failover to the other input. type: list elements: dict suboptions: failover_condition_settings: description: - - TODO + - Failover condition type-specific settings. type: dict suboptions: audio_silence_settings: description: - - TODO + - MediaLive will perform a failover if the specified audio selector is silent for the specified period. type: dict suboptions: audio_selector_name: description: - - TODO + - The name of the AudioSelector used as the source for this AudioDescription. type: str audio_silence_threshold_msec: description: - - TODO + - The amount of time (in milliseconds) that the active input must be silent before automatic input failover occurs. Silence is defined as audio loss or audio quieter than -50 dBFS. type: int input_loss_settings: description: - - TODO + - MediaLive will perform a failover if content is not detected in this input for the specified period. type: dict suboptions: input_loss_threshold_msec: description: - - TODO + - The amount of time (in milliseconds) that no input is detected. After that time, an input failover will occur. type: int video_black_settings: description: - - TODO + - MediaLive will perform a failover if content is considered black for the specified period. type: dict suboptions: black_detect_threshold: description: - - TODO + - 'A value used in calculating the threshold below which MediaLive considers a pixel to be ‘black’. For the input to be considered black, every pixel in a frame must be below this threshold. The threshold is calculated as a percentage (expressed as a decimal) of white. Therefore.1 means 10% white (or 90% black). Note how the formula works for any color depth. For example, if you set this field to 0.1 in 10-bit color depth: (1023*0.1=102.3), which means a pixel value of 102 or less is ‘black’. If you set this field to.1 in an 8-bit color depth: (255*0.1=25.5), which means a pixel value of 25 or less is ‘black’. The range is 0.0 to 1.0, with any number of decimal places.' type: float video_black_threshold_msec: description: - - TODO + - The amount of time (in milliseconds) that the active input must be black before automatic input failover occurs. type: int input_preference: description: - - TODO + - Input preference when deciding which input to make active when a previously failed input has recovered. type: str choices: ['EQUAL_INPUT_PREFERENCE', 'PRIMARY_INPUT_PREFERRED'] secondary_input_id: description: - - TODO + - The input ID of the secondary input in the automatic input failover pair. type: str input_attachment_name: description: - - TODO + - User-specified name for the attachment. This is required if the user wants to use this input in an input switch action. type: str input_id: description: - - TODO + - The ID of the input. type: str input_settings: description: - - TODO + - Settings of an input (caption selector, etc.). type: dict suboptions: audio_selectors: description: - - TODO + - Used to select the audio stream to decode for inputs that have multiple available. type: list elements: dict suboptions: name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str selector_settings: description: - - TODO + - The audio selector settings. type: dict suboptions: audio_hls_rendition_selection: description: - - TODO + - Audio Hls Rendition Selection. type: dict suboptions: group_id: description: - - TODO + - Specifies the GROUP-ID in the #EXT-X-MEDIA tag of the target HLS audio rendition. type: str name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str audio_language_selection: description: - - TODO + - Audio Language Selection. type: dict suboptions: language_code: description: - - TODO + - RFC 5646 language code representing the language of the audio output track. Only used if languageControlMode is useConfigured, or there is no ISO 639 language code specified in the input. type: str language_selection_policy: description: - - TODO + - When set to “strict”, the transport stream demux strictly identifies audio streams by their language descriptor. If a PMT update occurs such that an audio stream matching the initially selected language is no longer present then mute will be encoded until the language returns. If “loose”, then on a PMT update the demux will choose another audio stream in the program with the same stream type if it can’t find one with the same language. type: str choices: ['LOOSE', 'STRICT'] audio_pid_selection: description: - - TODO + - Audio Pid Selection. type: dict suboptions: pid: description: - - TODO + - Selects a specific PID from within a source. type: int audio_track_selection: description: - - TODO + - Audio Track Selection. type: dict suboptions: tracks: description: - - TODO + - Selects one or more unique audio tracks from within a source. type: list elements: dict suboptions: track: description: - - TODO + - 1-based integer value that maps to a specific audio track. type: int dolby_e_decode: description: - - TODO + - Configure decoding options for Dolby E streams - these should be Dolby E frames carried in PCM streams tagged with SMPTE-337. type: dict suboptions: program_selection: description: - - TODO + - Applies only to Dolby E. Enter the program ID (according to the metadata in the audio) of the Dolby E program to extract from the specified track. One program extracted per audio selector. To select multiple programs, create multiple selectors with the same Track and different Program numbers. “All channels” means to ignore the program IDs and include all the channels in this selector; useful if metadata is known to be incorrect. type: str choices: ['ALL_CHANNELS', 'PROGRAM_1', 'PROGRAM_2', 'PROGRAM_3', 'PROGRAM_4', 'PROGRAM_5', 'PROGRAM_6', 'PROGRAM_7', 'PROGRAM_8'] caption_selectors: description: - - TODO + - Used to select the caption input to use for inputs that have multiple available. type: list elements: dict suboptions: language_code: description: - - TODO + - RFC 5646 language code representing the language of the audio output track. Only used if languageControlMode is useConfigured, or there is no ISO 639 language code specified in the input. type: str name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str selector_settings: description: - - TODO + - The audio selector settings. type: dict suboptions: ancillary_source_settings: description: - - TODO + - Ancillary Source Settings. type: dict suboptions: source_ancillary_channel_number: description: - - TODO + - Specifies the number (1 to 4) of the captions channel you want to extract from the ancillary captions. If you plan to convert the ancillary captions to another format, complete this field. If you plan to choose Embedded as the captions destination in the output (to pass through all the channels in the ancillary captions), leave this field blank because MediaLive ignores the field. type: int arib_source_settings: description: - - TODO + - Arib Source Settings. type: dict dvb_sub_source_settings: description: - - TODO + - Dvb Sub Source Settings. type: dict suboptions: ocr_language: description: - - TODO + - If you will configure a WebVTT caption description that references this caption selector, use this field to provide the language to consider when translating the image-based source to text. type: str choices: ['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA'] pid: description: - - TODO + - Selects a specific PID from within a source. type: int embedded_source_settings: description: - - TODO + - Embedded Source Settings. type: dict suboptions: convert608_to708: description: - - TODO + - If upconvert, 608 data is both passed through via the “608 compatibility bytes” fields of the 708 wrapper as well as translated into 708. 708 data present in the source content will be discarded. type: str choices: ['DISABLED', 'UPCONVERT'] scte20_detection: description: - - TODO + - Set to “auto” to handle streams with intermittent and/or non-aligned SCTE-20 and Embedded captions. type: str choices: ['AUTO', 'OFF'] source608_channel_number: description: - - TODO + - Specifies the 608/708 channel number within the video track from which to extract captions. Unused for passthrough. type: int source608_track_number: description: - - TODO + - This field is unused and deprecated. type: int scte20_source_settings: description: - - TODO + - Scte20 Source Settings. type: dict suboptions: convert608_to708: description: - - TODO + - If upconvert, 608 data is both passed through via the “608 compatibility bytes” fields of the 708 wrapper as well as translated into 708. 708 data present in the source content will be discarded. type: str choices: ['DISABLED', 'UPCONVERT'] source608_channel_number: description: - - TODO + - Specifies the 608/708 channel number within the video track from which to extract captions. Unused for passthrough. type: int scte27_source_settings: description: - - TODO + - Scte27 Source Settings. type: dict suboptions: ocr_language: description: - - TODO + - If you will configure a WebVTT caption description that references this caption selector, use this field to provide the language to consider when translating the image-based source to text. type: str choices: ['DEU', 'ENG', 'FRA', 'NLD', 'POR', 'SPA'] pid: description: - - TODO + - Selects a specific PID from within a source. type: int teletext_source_settings: description: - - TODO + - Teletext Source Settings. type: dict suboptions: output_rectangle: description: - - TODO + - Optionally defines a region where TTML style captions will be displayed. type: dict suboptions: height: description: - - TODO + - Output video height, in pixels. Must be an even number. For most codecs, you can leave this field and width blank in order to use the height and width (resolution) from the source. Note, however, that leaving blank is not recommended. For the Frame Capture codec, height and width are required. type: float left_offset: description: - - TODO + - Applies only if you plan to convert these source captions to EBU-TT-D or TTML in an output. (Make sure to leave the default if you don’t have either of these formats in the output.) You can define a display rectangle for the captions that is smaller than the underlying video frame. You define the rectangle by specifying the position of the left edge, top edge, bottom edge, and right edge of the rectangle, all within the underlying video frame. The units for the measurements are percentages. If you specify a value for one of these fields, you must specify a value for all of them. For leftOffset, specify the position of the left edge of the rectangle, as a percentage of the underlying frame width, and relative to the left edge of the frame. For example, "10" means the measurement is 10% of the underlying frame width. The rectangle left edge starts at that position from the left edge of the frame. This field corresponds to tts:origin - X in the TTML standard. type: float top_offset: description: - - TODO + - See the description in leftOffset. For topOffset, specify the position of the top edge of the rectangle, as a percentage of the underlying frame height, and relative to the top edge of the frame. For example, "10" means the measurement is 10% of the underlying frame height. The rectangle top edge starts at that position from the top edge of the frame. This field corresponds to tts:origin - Y in the TTML standard. type: float width: description: - - TODO + - Output video width, in pixels. Must be an even number. For most codecs, you can leave this field and height blank in order to use the height and width (resolution) from the source. Note, however, that leaving blank is not recommended. For the Frame Capture codec, height and width are required. type: float page_number: description: - - TODO + - Specifies the teletext page number within the data stream from which to extract captions. Range of 0x100 (256) to 0x8FF (2303). Unused for passthrough. Should be specified as a hexadecimal string with no “0x” prefix. type: str deblock_filter: description: - - TODO + - Enable or disable the deblock filter when filtering. type: str choices: ['DISABLED', 'ENABLED'] denoise_filter: description: - - TODO + - Enable or disable the denoise filter when filtering. type: str choices: ['DISABLED', 'ENABLED'] filter_strength: description: - - TODO + - Adjusts the magnitude of filtering from 1 (minimal) to 5 (strongest). type: int input_filter: description: - - TODO + - Turns on the filter for this input. MPEG-2 inputs have the deblocking filter enabled by default. 1) auto - filtering will be applied depending on input type/quality 2) disabled - no filtering will be applied to the input 3) forced - filtering will be applied regardless of input type. type: str choices: ['AUTO', 'DISABLED', 'FORCED'] network_input_settings: description: - - TODO + - Input settings. type: dict suboptions: hls_input_settings: description: - - TODO + - Specifies HLS input settings when the uri is for a HLS manifest. type: dict suboptions: bandwidth: description: - - TODO + - When specified the HLS stream with the m3u8 BANDWIDTH that most closely matches this value will be chosen, otherwise the highest bandwidth stream in the m3u8 will be chosen. The bitrate is specified in bits per second, as in an HLS manifest. type: int buffer_segments: description: - - TODO + - When specified, reading of the HLS input will begin this many buffer segments from the end (most recently written segment). When not specified, the HLS input will begin with the first segment specified in the m3u8. type: int retries: description: - - TODO + - The number of consecutive times that attempts to read a manifest or segment must fail before the input is considered unavailable. type: int retry_interval: description: - - TODO + - The number of seconds between retries when an attempt to read a manifest or segment fails. type: int scte35_source: description: - - TODO + - Identifies the source for the SCTE-35 messages that MediaLive will ingest. Messages can be ingested from the content segments (in the stream) or from tags in the playlist (the HLS manifest). MediaLive ignores SCTE-35 information in the source that is not selected. type: str choices: ['MANIFEST', 'SEGMENTS'] server_validation: description: - - TODO + - Check HTTPS server certificates. When set to checkCryptographyOnly, cryptography in the certificate will be checked, but not the server’s name. Certain subdomains (notably S3 buckets that use dots in the bucket name) do not strictly match the corresponding certificate’s wildcard pattern and would otherwise cause the event to error. This setting is ignored for protocols that do not use https. type: str choices: ['CHECK_CRYPTOGRAPHY_AND_VALIDATE_NAME', 'CHECK_CRYPTOGRAPHY_ONLY'] multicast_input_settings: description: - - TODO + - Specifies multicast input settings when the uri is for a multicast event. type: dict suboptions: source_ip_address: description: - - TODO + - Optionally, a source ip address to filter by for Source-specific Multicast (SSM). type: str scte35_pid: description: - - TODO + - Packet Identifier (PID) of the SCTE-35 stream in the transport stream. Can be entered as a decimal or hexadecimal value. Valid values are 32 (or 0x20)..8182 (or 0x1ff6). type: int smpte2038_data_preference: description: - - TODO + - 'Specifies whether to extract applicable ancillary data from a SMPTE-2038 source in this input. Applicable data types are captions, timecode, AFD, and SCTE-104 messages. - PREFER: Extract from SMPTE-2038 if present in this input, otherwise extract from another source (if any). - IGNORE: Never extract any ancillary data from SMPTE-2038.' type: str choices: ['IGNORE', 'PREFER'] source_end_behavior: description: - - TODO + - Loop input if it is a file. This allows a file input to be streamed indefinitely. type: str choices: ['CONTINUE', 'LOOP'] video_selector: description: - - TODO + - Informs which video elementary stream to decode for input types that have multiple available. type: dict suboptions: color_space: description: - - TODO + - Choose the type of color space conversion to apply to the output. For detailed information on setting up both the input and the output to obtain the desired color space in the output, see the section on "MediaLive Features - Video - color space" in the MediaLive User Guide. PASSTHROUGH: Keep the color space of the input content - do not convert it. AUTO:Convert all content that is SD to rec 601, and convert all content that is HD to rec 709. type: str choices: ['FOLLOW', 'HDR10', 'HLG_2020', 'REC_601', 'REC_709'] color_space_settings: description: - - TODO + - Color Space settings. type: dict suboptions: hdr10_settings: description: - - TODO + - Hdr10 Settings. type: dict suboptions: max_cll: description: - - TODO + - Maximum Content Light Level An integer metadata value defining the maximum light level, in nits, of any single pixel within an encoded HDR video stream or file. type: int max_fall: description: - - TODO + - Maximum Frame Average Light Level An integer metadata value defining the maximum average light level, in nits, for any single frame within an encoded HDR video stream or file. type: int color_space_usage: description: - - TODO + - Applies only if colorSpace is a value other than follow. This field controls how the value in the colorSpace field will be used. fallback means that when the input does include color space data, that data will be used, but when the input has no color space data, the value in colorSpace will be used. Choose fallback if your input is sometimes missing color space data, but when it does have color space data, that data is correct. force means to always use the value in colorSpace. Choose force if your input usually has no color space data or might have unreliable color space data. type: str choices: ['FALLBACK', 'FORCE'] selector_settings: description: - - TODO + - The audio selector settings. type: dict suboptions: video_selector_pid: description: - - TODO + - Video Selector Pid. type: dict suboptions: pid: description: - - TODO + - Selects a specific PID from within a source. type: int video_selector_program_id: description: - - TODO + - Video Selector Program Id. type: dict suboptions: program_id: description: - - TODO + - Selects a specific program from within a multi-program transport stream. If the program doesn’t exist, the first program within the transport stream will be selected by default. type: int logical_interface_names: description: @@ -4715,108 +4715,108 @@ elements: str input_specification: description: - - TODO + - Specification of network and file inputs for this channel. type: dict suboptions: codec: description: - - TODO + - Input codec. type: str choices: ['MPEG2', 'AVC', 'HEVC'] maximum_bitrate: description: - - TODO + - Maximum input bitrate, categorized coarsely. type: str choices: ['MAX_10_MBPS', 'MAX_20_MBPS', 'MAX_50_MBPS'] resolution: description: - - TODO + - Maximum CDI input resolution. type: str choices: ['SD', 'HD', 'UHD'] log_level: description: - - TODO + - The log level to write to CloudWatch Logs. type: str choices: ['ERROR', 'WARNING', 'INFO', 'DEBUG', 'DISABLED'] maintenance: description: - - TODO + - Maintenance settings for this channel. type: dict suboptions: maintenance_day: description: - - TODO + - Choose one day of the week for maintenance. The chosen day is used for all future maintenance windows. type: str choices: ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'] maintenance_start_time: description: - - TODO + - Choose the hour that maintenance will start. The chosen time is used for all future maintenance windows. type: str name: description: - - TODO + - The name of this AudioDescription. Outputs will use this name to uniquely identify this AudioDescription. Description names should be unique within this Live Event. type: str request_id: description: - - TODO + - Unique request ID to be specified. This is needed to prevent retries from creating multiple resources.This field is autopopulated if not provided. type: str reserved: description: - - TODO + - Deprecated field that’s only usable by whitelisted customers. type: str role_arn: description: - - TODO + - An optional Amazon Resource Name (ARN) of the role to assume when running the Channel. type: str tags: description: - - TODO + - A collection of key-value pairs.. type: dict vpc: description: - - TODO + - Settings for the VPC outputs. type: dict suboptions: public_address_allocation_ids: description: - - TODO + - List of public address allocation ids to associate with ENIs that will be created in Output VPC. Must specify one for SINGLE_PIPELINE, two for STANDARD channels. type: list elements: str security_group_ids: description: - - TODO + - A list of up to 5 EC2 VPC security group IDs to attach to the Output VPC network interfaces. If none are specified then the VPC default security group will be used. type: list elements: str subnet_ids: description: - - TODO + - A list of VPC subnet IDs from the same VPC. If STANDARD channel, subnet IDs must be mapped to two unique availability zones (AZ). type: list elements: str anywhere_settings: description: - - TODO + - The Elemental Anywhere settings for this channel. type: dict suboptions: channel_placement_group_id: description: - - TODO + - The ID of the channel placement group for the channel. type: str cluster_id: description: - - TODO + - The ID of the cluster for the channel. type: str channel_engine_version: description: - - TODO + - The desired engine version for this channel. type: dict suboptions: version: description: - - TODO + - The build identifier of the engine version to use for this channel. Specify ‘DEFAULT’ to reset to the default version. type: str dry_run: description: - - TODO + - Placeholder documentation type: bool extends_documentation_fragment: From af7a502f3426fe0fd0dd892ec2f23bff98bc09d7 Mon Sep 17 00:00:00 2001 From: Sergey Papyan Date: Mon, 18 Aug 2025 14:11:26 -0700 Subject: [PATCH 11/11] Stop using a custom exception class --- plugins/module_utils/medialive.py | 9 ------- plugins/modules/medialive_channel.py | 23 +++++++++--------- .../medialive_channel_placement_group.py | 19 +++++++-------- plugins/modules/medialive_cluster.py | 18 +++++++------- plugins/modules/medialive_cluster_info.py | 6 ++--- plugins/modules/medialive_input.py | 24 +++++++++---------- plugins/modules/medialive_input_info.py | 8 +++---- plugins/modules/medialive_network.py | 19 ++++++++------- plugins/modules/medialive_network_info.py | 8 +++---- plugins/modules/medialive_node.py | 14 +++++------ plugins/modules/medialive_node_info.py | 6 ++--- .../modules/medialive_node_registration.py | 6 ++--- plugins/modules/medialive_sdi_source.py | 16 ++++++------- plugins/modules/medialive_sdi_source_info.py | 6 ++--- 14 files changed, 86 insertions(+), 96 deletions(-) delete mode 100644 plugins/module_utils/medialive.py diff --git a/plugins/module_utils/medialive.py b/plugins/module_utils/medialive.py deleted file mode 100644 index 8780b88262b..00000000000 --- a/plugins/module_utils/medialive.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError - -class MedialiveAnsibleAWSError(AnsibleAWSError): - pass diff --git a/plugins/modules/medialive_channel.py b/plugins/modules/medialive_channel.py index 7bf6a8a0751..8ee84592ed1 100644 --- a/plugins/modules/medialive_channel.py +++ b/plugins/modules/medialive_channel.py @@ -4844,8 +4844,7 @@ 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.tagging import compare_aws_tags -from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError - +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveChannelManager: '''Manage AWS MediaLive Anywhere Channels''' @@ -4908,7 +4907,7 @@ def do_create_channel(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new Channel') + raise AnsibleAWSError(message=f'The {param} parameter is required when creating a new Channel') create_params = { k: v for k, v in params.items() if k in allowed_params and v } create_params = self.clean_up(create_params) @@ -4923,7 +4922,7 @@ def do_create_channel(self, params): self.channel = self.client.create_channel(**create_params)['Channel'] # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive Channel', exception=e ) @@ -4937,7 +4936,7 @@ def do_update_channel(self, params): params: Parameters for Channel update """ if not params.get('channel_id'): - raise MedialiveAnsibleAWSError(message='The channel_id parameter is required during channel update.') + raise AnsibleAWSError(message='The channel_id parameter is required during channel update.') tags = params.get('tags') purge_tags = params.get('purge_tags') @@ -4983,7 +4982,7 @@ def do_update_channel(self, params): self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive Channel', exception=e ) @@ -5004,12 +5003,12 @@ def get_channel_by_name(self, name: str): if channel.get('Name') == name: found.append(channel.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Channels under the same name') + raise AnsibleAWSError(message='Found more than one Channels under the same name') elif len(found) == 1: self.get_channel_by_id(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Channel', exception=e ) @@ -5026,7 +5025,7 @@ def get_channel_by_id(self, channel_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.channel = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Channel', exception=e ) @@ -5045,7 +5044,7 @@ def delete_channel(self, channel_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.channel = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive Channel', exception=e ) @@ -5076,7 +5075,7 @@ def wait_for( WaiterConfig=config ) except WaiterError as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'Timeout waiting for Channel {channel_id}', exception=e ) @@ -5107,7 +5106,7 @@ def _update_tags(self, tags: dict, purge: bool) -> bool: if to_delete: self.client.delete_tags(ResourceArn=self.channel['arn'], TagKeys=to_delete) # type: ignore except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update MediaLive Channel resource Tags', exception=e ) diff --git a/plugins/modules/medialive_channel_placement_group.py b/plugins/modules/medialive_channel_placement_group.py index dbe78e2b39a..47071cc2a5b 100644 --- a/plugins/modules/medialive_channel_placement_group.py +++ b/plugins/modules/medialive_channel_placement_group.py @@ -132,7 +132,6 @@ 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 -from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError class MediaLiveChannelPlacementGroupManager: @@ -180,7 +179,7 @@ def do_create_channel_placement_group(self, params): self.channel_placement_group = camel_dict_to_snake_dict(response) self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create MediaLive Channel Placement Group', exception=e ) @@ -193,7 +192,7 @@ def do_update_channel_placement_group(self, params): params: Parameters for channel placement group update """ if not params.get('channel_placement_group_id'): - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='The channel_placement_group_id parameter is required during placement group update.') allowed_params = ['cluster_id', 'channel_placement_group_id', 'nodes', 'name'] @@ -230,7 +229,7 @@ def do_update_channel_placement_group(self, params): params.get('cluster_id') ) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update nodes for MediaLive Channel Placement Group', exception=e ) @@ -252,7 +251,7 @@ def do_update_channel_placement_group(self, params): params.get('cluster_id') ) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update name for MediaLive Channel Placement Group', exception=e ) @@ -274,7 +273,7 @@ def get_channel_placement_group_by_id(self, placement_group_id: str, cluster_id: except is_boto3_error_code('ResourceNotFoundException'): self.channel_placement_group = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get MediaLive Channel Placement Group', exception=e ) @@ -295,11 +294,11 @@ def get_channel_placement_group_by_name(self, name: str, cluster_id: str): if cpg['Name'] == name: found.append(camel_dict_to_snake_dict(cpg)) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Channel Placement Groups under the same name') + raise AnsibleAWSError(message='Found more than one Channel Placement Groups under the same name') elif len(found) == 1: self.channel_placement_group = camel_dict_to_snake_dict(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get MediaLive Channel Placement Group', exception=e ) @@ -321,7 +320,7 @@ def delete_channel_placement_group(self, placement_group_id: str, cluster_id: st except is_boto3_error_code('ResourceNotFoundException'): self.channel_placement_group = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete MediaLive Channel Placement Group', exception=e ) @@ -353,7 +352,7 @@ def wait_for( WaiterConfig=config ) except WaiterError as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'Timeout waiting for channel placement group {placement_group_id} to be {want.lower()}', exception=e ) diff --git a/plugins/modules/medialive_cluster.py b/plugins/modules/medialive_cluster.py index 87066d76f64..a46114440b2 100644 --- a/plugins/modules/medialive_cluster.py +++ b/plugins/modules/medialive_cluster.py @@ -214,7 +214,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveClusterManager: @@ -258,7 +258,7 @@ def do_create_cluster(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new cluster') + raise AnsibleAWSError(message=f'The {param} parameter is required when creating a new cluster') 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) @@ -268,7 +268,7 @@ def do_create_cluster(self, params): self.cluster = camel_dict_to_snake_dict(response) self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive Cluster', exception=e ) @@ -281,7 +281,7 @@ def do_update_cluster(self, params): params: Parameters for cluster update """ if not params.get('cluster_id'): - raise MedialiveAnsibleAWSError(message='The cluster_id parameter is required during cluster update.') + raise AnsibleAWSError(message='The cluster_id parameter is required during cluster update.') allowed_params = ['cluster_id', 'name', 'network_settings'] @@ -300,7 +300,7 @@ def do_update_cluster(self, params): self.cluster = camel_dict_to_snake_dict(response) self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive Cluster', exception=e ) @@ -320,12 +320,12 @@ def get_cluster_by_name(self, name: str): if cluster.get('Name') == name: found.append(cluster.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Clusters under the same name') + raise AnsibleAWSError(message='Found more than one Clusters under the same name') elif len(found) == 1: self.get_cluster_by_id(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get MediaLive Cluster', exception=e ) @@ -356,7 +356,7 @@ def delete_cluster(self, cluster_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.cluster = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive Cluster', exception=e ) @@ -387,7 +387,7 @@ def wait_for( WaiterConfig=config ) except WaiterError as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'Timeout waiting for cluster state to become {cluster_id}', exception=e ) diff --git a/plugins/modules/medialive_cluster_info.py b/plugins/modules/medialive_cluster_info.py index 25344c5202f..2a7dcf094f4 100644 --- a/plugins/modules/medialive_cluster_info.py +++ b/plugins/modules/medialive_cluster_info.py @@ -122,7 +122,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveClusterGetter: @@ -168,11 +168,11 @@ def get_cluster_by_name(self, name: str): if cluster.get('Name') == name: found.append(cluster.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Clusters under the same name') + raise AnsibleAWSError(message='Found more than one Clusters under the same name') elif len(found) == 1: self.get_cluster_by_id(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get MediaLive Cluster', exception=e ) diff --git a/plugins/modules/medialive_input.py b/plugins/modules/medialive_input.py index 8cf71529ac1..1b24a4c045c 100644 --- a/plugins/modules/medialive_input.py +++ b/plugins/modules/medialive_input.py @@ -363,7 +363,7 @@ 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.tagging import compare_aws_tags -from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveInputManager: @@ -411,16 +411,16 @@ def validate_sdi_source(self, sources: List[str], check_use=False): # to the CreateInput API you get the following BadRequest error # "The SDI sources for an SDI input must specify exactly one SDI source" if len(sources) != 1: - raise MedialiveAnsibleAWSError(message='The sdi_sources list must contain a single element') + raise AnsibleAWSError(message='The sdi_sources list must contain a single element') # Make sure the SDI source exists and is in IDLE state try: response = self.client.describe_sdi_source(SdiSourceId=sources[0]) # type: ignore if check_use and response['SdiSource']['State'] == 'IN_USE': - raise MedialiveAnsibleAWSError(message='The provided sdi_source is already in use') + raise AnsibleAWSError(message='The provided sdi_source is already in use') except is_boto3_error_code('ResourceNotFoundException'): - raise MedialiveAnsibleAWSError(message='The provided sdi_source does not exist') + raise AnsibleAWSError(message='The provided sdi_source does not exist') def get_input_by_name(self, name: str): """ @@ -438,7 +438,7 @@ def get_input_by_name(self, name: str): self.get_input_by_id(input.get('Id')) return except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Input', exception=e ) @@ -455,7 +455,7 @@ def get_input_by_id(self, input_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.input = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Input', exception=e ) @@ -484,7 +484,7 @@ def wait_for(self, WaiterConfig=config ) except WaiterError as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'Timeout waiting for Input {input_id}', exception=e ) @@ -531,7 +531,7 @@ def do_create_input(self, params): self.input = self.client.create_input(**create_params)['Input'] # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive Input', exception=e ) @@ -544,7 +544,7 @@ def do_update_input(self, params): params: Parameters for input update """ if not params.get('input_id'): - raise MedialiveAnsibleAWSError(message='The input_id parameter is required during input update.') + raise AnsibleAWSError(message='The input_id parameter is required during input update.') tags = params.get('tags') purge_tags = params.get('purge_tags') @@ -583,7 +583,7 @@ def do_update_input(self, params): self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive Input', exception=e ) @@ -602,7 +602,7 @@ def delete_input(self, input_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.input = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive Input', exception=e ) @@ -633,7 +633,7 @@ def _update_tags(self, tags: dict, purge: bool) -> bool: if to_delete: self.client.delete_tags(ResourceArn=self.input['arn'], TagKeys=to_delete) # type: ignore except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update MediaLive Input resource Tags', exception=e ) diff --git a/plugins/modules/medialive_input_info.py b/plugins/modules/medialive_input_info.py index 2fd44f5ff07..453f4c1b156 100644 --- a/plugins/modules/medialive_input_info.py +++ b/plugins/modules/medialive_input_info.py @@ -56,7 +56,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveInputGetter: @@ -108,11 +108,11 @@ def find_input_by_name(self, name: str): if input.get('Name') == name: found.append(input.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Inputs under the same name') + raise AnsibleAWSError(message='Found more than one Inputs under the same name') elif len(found) == 1: self.get_input_by_id(found[0]) except (ClientError, BotoCoreError) as e: - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Input', exception=e ) @@ -129,7 +129,7 @@ def get_input_by_id(self, input_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.input = {} except (ClientError, BotoCoreError) as e: - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Input', exception=e ) diff --git a/plugins/modules/medialive_network.py b/plugins/modules/medialive_network.py index a62dcbb1078..96bbd97978c 100644 --- a/plugins/modules/medialive_network.py +++ b/plugins/modules/medialive_network.py @@ -161,7 +161,8 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError + class MediaLiveNetworkManager: '''Manage AWS MediaLive Anywhere networks''' @@ -205,7 +206,7 @@ def do_create_network(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new Network') + raise AnsibleAWSError(message=f'The {param} parameter is required when creating a new Network') 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) @@ -214,7 +215,7 @@ def do_create_network(self, params): self.network = self.client.create_network(**create_params) # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive Network', exception=e ) @@ -228,7 +229,7 @@ def do_update_network(self, params): params: Parameters for network update """ if not params.get('network_id'): - raise MedialiveAnsibleAWSError(message='The network_id parameter is required during network update.') + raise AnsibleAWSError(message='The network_id parameter is required during network update.') allowed_params = ['ip_pools', 'name', 'routes', 'network_id'] @@ -246,7 +247,7 @@ def do_update_network(self, params): self.network = self.client.update_network(**update_params) # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive Network', exception=e ) @@ -267,12 +268,12 @@ def get_network_by_name(self, name: str): if network.get('Name') == name: found.append(network.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Networks under the same name') + raise AnsibleAWSError(message='Found more than one Networks under the same name') elif len(found) == 1: self.get_network_by_id(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Network', exception=e ) @@ -289,7 +290,7 @@ def get_network_by_id(self, network_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.network = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Network', exception=e ) @@ -308,7 +309,7 @@ def delete_network(self, network_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.network = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive Network', exception=e ) diff --git a/plugins/modules/medialive_network_info.py b/plugins/modules/medialive_network_info.py index e62ae9b6196..b82c373546c 100644 --- a/plugins/modules/medialive_network_info.py +++ b/plugins/modules/medialive_network_info.py @@ -118,7 +118,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveNetworkManager: @@ -165,11 +165,11 @@ def find_network_by_name(self, name: str): if network.get('Name') == name: found.append(network.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Networks under the same name') + raise AnsibleAWSError(message='Found more than one Networks under the same name') elif len(found) == 1: self.get_network_by_id(found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Network', exception=e ) @@ -186,7 +186,7 @@ def get_network_by_id(self, id: str): except is_boto3_error_code('ResourceNotFoundException'): self.network = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Network', exception=e ) diff --git a/plugins/modules/medialive_node.py b/plugins/modules/medialive_node.py index 1695eec9e68..f7f70eb9d18 100644 --- a/plugins/modules/medialive_node.py +++ b/plugins/modules/medialive_node.py @@ -255,7 +255,7 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveNodeManager: @@ -299,7 +299,7 @@ def do_create_node(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'The {", ".join(required_params)} parameters are required when creating a new node' ) @@ -310,7 +310,7 @@ def do_create_node(self, params): self.node = self.client.create_node(**create_params) # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive node', exception=e ) @@ -344,7 +344,7 @@ def do_update_node(self, params: dict): self.node = camel_dict_to_snake_dict(response) self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive node', exception=e ) @@ -359,7 +359,7 @@ def delete_node(self): except is_boto3_error_code('ResourceNotFoundException'): self.node = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive node', exception=e ) @@ -380,12 +380,12 @@ def get_node_by_name(self, cluster_id: str, name: str): if node.get('Name') == name: found.append(node.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Nodes under the same name') + raise AnsibleAWSError(message='Found more than one Nodes under the same name') elif len(found) == 1: self.get_node_by_id(cluster_id, found[0]) except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Node', exception=e ) diff --git a/plugins/modules/medialive_node_info.py b/plugins/modules/medialive_node_info.py index 916009c096c..8623c2bd80a 100644 --- a/plugins/modules/medialive_node_info.py +++ b/plugins/modules/medialive_node_info.py @@ -149,7 +149,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveNodeGetter: @@ -196,12 +196,12 @@ def get_node_by_name(self, cluster_id: str, name: str): if node.get('Name') == name: found.append(node.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError(message='Found more than one Nodes under the same name') + raise AnsibleAWSError(message='Found more than one Nodes under the same name') elif len(found) == 1: self.get_node_by_id(cluster_id, found[0]) except (ClientError, BotoCoreError) as e: - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive Node', exception=e ) diff --git a/plugins/modules/medialive_node_registration.py b/plugins/modules/medialive_node_registration.py index 887202e932d..ac95ec649cd 100644 --- a/plugins/modules/medialive_node_registration.py +++ b/plugins/modules/medialive_node_registration.py @@ -67,7 +67,7 @@ from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict, camel_dict_to_snake_dict from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveNodeRegistrationScriptManager: @@ -110,7 +110,7 @@ def do_create_registration_script(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message=f'The {", ".join(required_params)} parameters are required when creating a new node registration script' ) @@ -121,7 +121,7 @@ def do_create_registration_script(self, params): self.script = self.client.create_node_registration_script(**create_params) # type: ignore self.changed = True except (ClientError, BotoCoreError) as e: - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive node registration script', exception=e ) diff --git a/plugins/modules/medialive_sdi_source.py b/plugins/modules/medialive_sdi_source.py index 10d56b03311..d85dab9c103 100644 --- a/plugins/modules/medialive_sdi_source.py +++ b/plugins/modules/medialive_sdi_source.py @@ -133,7 +133,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveSdiSourceManager: @@ -182,7 +182,7 @@ def do_create_sdi_source(self, params): for param in required_params: if not params.get(param): - raise MedialiveAnsibleAWSError(message=f'The {param} parameter is required when creating a new SDI source') + 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) @@ -192,7 +192,7 @@ def do_create_sdi_source(self, params): self.sdi_source = response self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to create Medialive SDI Source', exception=e ) @@ -205,7 +205,7 @@ def do_update_sdi_source(self, params): params: Parameters for SDI source update """ if not params.get('sdi_source_id'): - raise MedialiveAnsibleAWSError(message='The sdi_source_id parameter is required during SDI source update.') + raise AnsibleAWSError(message='The sdi_source_id parameter is required during SDI source update.') allowed_params = ['sdi_source_id', 'name', 'mode', 'type'] @@ -227,7 +227,7 @@ def do_update_sdi_source(self, params): self.sdi_source = response self.changed = True except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to update Medialive SDI Source', exception=e ) @@ -247,14 +247,14 @@ def get_sdi_source_by_name(self, name: str): if sdi_source.get('Name') == name: found.append(sdi_source.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError( + 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 MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive SDI Source', exception=e ) @@ -285,7 +285,7 @@ def delete_sdi_source_by_id(self, sdi_source_id: str): except is_boto3_error_code('ResourceNotFoundException'): self.sdi_source = {} except (ClientError, BotoCoreError) as e: # type: ignore - raise MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to delete Medialive SDI source', exception=e ) diff --git a/plugins/modules/medialive_sdi_source_info.py b/plugins/modules/medialive_sdi_source_info.py index a9d9760e1e6..f02e52c7277 100644 --- a/plugins/modules/medialive_sdi_source_info.py +++ b/plugins/modules/medialive_sdi_source_info.py @@ -96,7 +96,7 @@ 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.community.aws.plugins.module_utils.medialive import MedialiveAnsibleAWSError +from ansible_collections.amazon.aws.plugins.module_utils.exceptions import AnsibleAWSError class MediaLiveSdiSourceGetter: @@ -147,14 +147,14 @@ def get_sdi_source_by_name(self, name: str): if sdi_source.get('Name') == name: found.append(sdi_source.get('Id')) if len(found) > 1: - raise MedialiveAnsibleAWSError( + 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 MedialiveAnsibleAWSError( + raise AnsibleAWSError( message='Unable to get Medialive SDI Source', exception=e )