diff --git a/internal/service/mediapackage/exports_test.go b/internal/service/mediapackage/exports_test.go index 3aa29c44ccc2..7bf807ca251b 100644 --- a/internal/service/mediapackage/exports_test.go +++ b/internal/service/mediapackage/exports_test.go @@ -5,5 +5,6 @@ package mediapackage // Exports for use in tests only. var ( - FindChannelByID = findChannelByID + FindChannelByID = findChannelByID + FindOriginEndpointByID = findOriginEndpoint ) diff --git a/internal/service/mediapackage/origin_endpoint.go b/internal/service/mediapackage/origin_endpoint.go new file mode 100644 index 000000000000..3df8d681d3ac --- /dev/null +++ b/internal/service/mediapackage/origin_endpoint.go @@ -0,0 +1,1829 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mediapackage + +// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** +// +// TIP: ==== INTRODUCTION ==== +// Thank you for trying the skaff tool! +// +// You have opted to include these helpful comments. They all include "TIP:" +// to help you find and remove them when you're done with them. +// +// While some aspects of this file are customized to your input, the +// scaffold tool does *not* look at the AWS API and ensure it has correct +// function, structure, and variable names. It makes guesses based on +// commonalities. You will need to make significant adjustments. +// +// In other words, as generated, this is a rough outline of the work you will +// need to do. If something doesn't make sense for your situation, get rid of +// it. + +import ( + // TIP: ==== IMPORTS ==== + // This is a common set of imports but not customized to your code since + // your code hasn't been written yet. Make sure you, your IDE, or + // goimports -w fixes these imports. + // + // The provider linter wants your imports to be in two groups: first, + // standard library (i.e., "fmt" or "strings"), second, everything else. + // + // Also, AWS Go SDK v2 may handle nested structures differently than v1, + // using the services/mediapackage/types package. If so, you'll + // need to import types and reference the nested types, e.g., as + // types.. + "context" + "errors" + "log" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/mediapackage" + "github.com/aws/aws-sdk-go-v2/service/mediapackage/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/mediapackage/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// TIP: ==== FILE STRUCTURE ==== +// All resources should follow this basic outline. Improve this resource's +// maintainability by sticking to it. +// +// 1. Package declaration +// 2. Imports +// 3. Main resource function with schema +// 4. Create, read, update, delete functions (in that order) +// 5. Other functions (flatteners, expanders, waiters, finders, etc.) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @SDKResource("aws_mediapackage_origin_endpoint", name="Origin Endpoint") +func ResourceOriginEndpoint() *schema.Resource { + return &schema.Resource{ + // TIP: ==== ASSIGN CRUD FUNCTIONS ==== + // These 4 functions handle CRUD responsibilities below. + CreateWithoutTimeout: resourceOriginEndpointCreate, + ReadWithoutTimeout: resourceOriginEndpointRead, + UpdateWithoutTimeout: resourceOriginEndpointUpdate, + DeleteWithoutTimeout: resourceOriginEndpointDelete, + + // TIP: ==== TERRAFORM IMPORTING ==== + // If Read can get all the information it needs from the Identifier + // (i.e., d.Id()), you can use the Passthrough importer. Otherwise, + // you'll need a custom import function. + // + // See more: + // https://hashicorp.github.io/terraform-provider-aws/add-import-support/ + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#implicit-state-passthrough + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#virtual-attributes + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + // TIP: ==== CONFIGURABLE TIMEOUTS ==== + // Users can configure timeout lengths but you need to use the times they + // provide. Access the timeout they configure (or the defaults) using, + // e.g., d.Timeout(schema.TimeoutCreate) (see below). The times here are + // the defaults if they don't configure timeouts. + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + // TIP: ==== SCHEMA ==== + // In the schema, add each of the attributes in snake case (e.g., + // delete_automated_backups). + // + // Formatting rules: + // * Alphabetize attributes to make them easier to find. + // * Do not add a blank line between attributes. + // + // Attribute basics: + // * If a user can provide a value ("configure a value") for an + // attribute (e.g., instances = 5), we call the attribute an + // "argument." + // * You change the way users interact with attributes using: + // - Required + // - Optional + // - Computed + // * There are only four valid combinations: + // + // 1. Required only - the user must provide a value + // Required: true, + // + // 2. Optional only - the user can configure or omit a value; do not + // use Default or DefaultFunc + // Optional: true, + // + // 3. Computed only - the provider can provide a value but the user + // cannot, i.e., read-only + // Computed: true, + // + // 4. Optional AND Computed - the provider or user can provide a value; + // use this combination if you are using Default or DefaultFunc + // Optional: true, + // Computed: true, + // + // You will typically find arguments in the input struct + // (e.g., CreateDBInstanceInput) for the create operation. Sometimes + // they are only in the input struct (e.g., ModifyDBInstanceInput) for + // the modify operation. + // + // For more about schema options, visit + // https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema#Schema + + Schema: map[string]*schema.Schema{ + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + "channel_id": { + Type: schema.TypeString, + Required: true, + }, + names.AttrID: { + Type: schema.TypeString, + Required: true, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrCreatedAt: { + Type: schema.TypeString, + Computed: true, + }, + "authorization": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cdn_identifier_secret": { + Type: schema.TypeString, + Required: true, + }, + "secrets_role_arn": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "cmaf_package": { + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "encryption": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "speke_key_provider": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "system_ids": { + Type: schema.TypeList, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_contract_configuration": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "preset_speke20_audio": { + Type: schema.TypeString, + Required: true, + }, + "preset_speke20_video": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "constant_initialization_vector": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_method": { + Type: schema.TypeString, + Optional: true, + }, + "key_rotation_interval_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "hls_manifests": { + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + names.AttrID: { + Type: schema.TypeString, + Required: true, + }, + "ad_markers": { + Type: schema.TypeString, + Optional: true, + }, + "ad_triggers": { + Type: schema.TypeList, + Optional: true, + }, + "ads_on_delivery_restrictions": { + Type: schema.TypeString, + Optional: true, + }, + "include_iframe_only_stream": { + Type: schema.TypeBool, + Optional: true, + }, + "manifest_name": { + Type: schema.TypeString, + Optional: true, + }, + "playlist_type": { + Type: schema.TypeString, + Optional: true, + }, + "playlist_window_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "program_date_time_interval_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "segment_duration_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "segment_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "stream_selection": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "min_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_order": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "dash_package": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ad_triggers": { + Type: schema.TypeList, + Optional: true, + }, + "ads_on_delivery_restrictions": { + Type: schema.TypeString, + Optional: true, + }, + "encryption": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "speke_key_provider": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "system_ids": { + Type: schema.TypeList, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_contract_configuration": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "preset_speke20_audio": { + Type: schema.TypeString, + Required: true, + }, + "preset_speke20_video": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "key_rotation_interval_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + "included_iframe_only_stream": { + Type: schema.TypeBool, + Optional: true, + }, + "manifest_layout": { + Type: schema.TypeString, + Optional: true, + }, + "manifest_window_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "min_buffer_time_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "min_update_period_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "period_triggers": { + Type: schema.TypeList, + Optional: true, + }, + "profile": { + Type: schema.TypeString, + Optional: true, + }, + "segment_duration_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "segment_template_format": { + Type: schema.TypeString, + Optional: true, + }, + "stream_selection": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "min_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_order": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "suggested_presentation_delay_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "utc_timing": { + Type: schema.TypeString, + Optional: true, + }, + "utc_timing_uri": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + names.AttrDescription: { + Type: schema.TypeString, + Optional: true, + }, + "hls_package": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ad_markers": { + Type: schema.TypeString, + Optional: true, + }, + "ad_triggers": { + Type: schema.TypeList, + Optional: true, + }, + "ads_on_delivery_restrictions": { + Type: schema.TypeString, + Optional: true, + }, + "encryption": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "speke_key_provider": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "system_ids": { + Type: schema.TypeList, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_contract_configuration": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "preset_speke20_audio": { + Type: schema.TypeString, + Required: true, + }, + "preset_speke20_video": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "constant_initialization_vector": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_method": { + Type: schema.TypeString, + Optional: true, + }, + "key_rotation_interval_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "repeat_ext_x_key": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "include_dvb_subtitles": { + Type: schema.TypeBool, + Optional: true, + }, + "include_iframe_only_stream": { + Type: schema.TypeBool, + Optional: true, + }, + "playlist_type": { + Type: schema.TypeString, + Optional: true, + }, + "playlist_window_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "program_date_time_interval_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "segment_duration_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_selection": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "min_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_order": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "use_audio_rendition_group": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "include_iframe_only_stream": { + Type: schema.TypeBool, + Optional: true, + }, + "manifest_name": { + Type: schema.TypeString, + Optional: true, + }, + "mss_package": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "encryption": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "speke_key_provider": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_id": { + Type: schema.TypeString, + Required: true, + }, + "role_arn": { + Type: schema.TypeString, + Required: true, + }, + "system_ids": { + Type: schema.TypeList, + Required: true, + }, + "url": { + Type: schema.TypeString, + Required: true, + }, + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + }, + "encryption_contract_configuration": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "preset_speke20_audio": { + Type: schema.TypeString, + Required: true, + }, + "preset_speke20_video": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "manifest_window_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "segment_duration_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_selection": { + Type: schema.TypeSet, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "max_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "min_video_bits_per_second": { + Type: schema.TypeInt, + Optional: true, + }, + "stream_order": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + "origination": { + Type: schema.TypeString, + Optional: true, + }, + "start_over_window_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "time_delay_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + "whitelist": { + Type: schema.TypeList, + Optional: true, + }, + }, + } +} + +const ( + ResNameOriginEndpoint = "Origin Endpoint" +) + +func resourceOriginEndpointCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + // TIP: ==== RESOURCE CREATE ==== + // Generally, the Create function should do the following things. Make + // sure there is a good reason if you don't do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Populate a create input structure + // 3. Call the AWS create/put function + // 4. Using the output from the create function, set the minimum arguments + // and attributes for the Read function to work. At a minimum, set the + // resource ID. E.g., d.SetId() + // 5. Use a waiter to wait for create to complete + // 6. Call the Read function in the Create return + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).MediaPackageClient(ctx) + + // TIP: -- 2. Populate a create input structure + in := &mediapackage.CreateOriginEndpointInput{ + ChannelId: aws.String(d.Get("channel_id").(string)), + Id: aws.String(d.Get("id").(string)), + Tags: getTagsIn(ctx), + } + if v, ok := d.GetOk("authorization"); ok { + in.Authorization = expandAuthorization(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("cmaf_package"); ok && len(v.(*schema.Set).List()) > 0 { + in.CmafPackage = expandCmafPackage(v.(*schema.Set).List()[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("dash_package"); ok && len(v.(*schema.Set).List()) > 0 { + in.DashPackage = expandDashPackage(v.(*schema.Set).List()[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("description"); ok { + in.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("hls_package"); ok { + in.HlsPackage = expandHlsPackage(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("mss_package"); ok { + in.MssPackage = expandMssPackage(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("manifest_name"); ok { + in.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("origination"); ok { + in.Origination = types.Origination(v.(string)) + } + + if v, ok := d.GetOk("start_over_window_seconds"); ok { + in.StartoverWindowSeconds = aws.Int32(int32(v.(int))) + } + + if v, ok := d.GetOk("time_delay_seconds"); ok { + in.TimeDelaySeconds = aws.Int32(int32(v.(int))) + } + + if v, ok := d.GetOk("whitelist"); ok { + in.Whitelist = v.([]string) + } + + // TIP: -- 3. Call the AWS create function + out, err := conn.CreateOriginEndpoint(ctx, in) + if err != nil { + // TIP: Since d.SetId() has not been called yet, you cannot use d.Id() + // in error messages at this point. + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionCreating, ResNameOriginEndpoint, d.Get("name").(string), err) + } + + if out == nil || out.Origination == "" { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionCreating, ResNameOriginEndpoint, d.Get("name").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(out.Id)) + + // TIP: -- 6. Call the Read function in the Create return + return append(diags, resourceOriginEndpointRead(ctx, d, meta)...) +} + +func resourceOriginEndpointRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + // TIP: ==== RESOURCE READ ==== + // Generally, the Read function should do the following things. Make + // sure there is a good reason if you don't do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Get the resource from AWS + // 3. Set ID to empty where resource is not new and not found + // 4. Set the arguments and attributes + // 5. Set the tags + // 6. Return diags + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).MediaPackageClient(ctx) + + // TIP: -- 2. Get the resource from AWS using an API Get, List, or Describe- + // type function, or, better yet, using a finder. + out, err := findOriginEndpoint(ctx, conn, d.Get("id").(string), d.Get("channel_id").(string)) + + // TIP: -- 3. Set ID to empty where resource is not new and not found + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] MediaPackage OriginEndpoint (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionReading, ResNameOriginEndpoint, d.Id(), err) + } + + // TIP: -- 4. Set the arguments and attributes + // + // For simple data types (i.e., schema.TypeString, schema.TypeBool, + // schema.TypeInt, and schema.TypeFloat), a simple Set call (e.g., + // d.Set("arn", out.Arn) is sufficient. No error or nil checking is + // necessary. + // + // However, there are some situations where more handling is needed. + // a. Complex data types (e.g., schema.TypeList, schema.TypeSet) + // b. Where errorneous diffs occur. For example, a schema.TypeString may be + // a JSON. AWS may return the JSON in a slightly different order but it + // is equivalent to what is already set. In that case, you may check if + // it is equivalent before setting the different JSON. + d.Set("arn", out.Arn) + + // TIP: Setting a complex type. + // For more information, see: + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#data-handling-and-conversion + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#flatten-functions-for-blocks + // https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/#root-typeset-of-resource-and-aws-list-of-structure + + if err := d.Set("authorization", flattenAuthorization(out.Authorization)); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("cmaf_package", flattenCmafPackage(out.CmafPackage)); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("dash_package", flattenDashPackage(out.DashPackage)); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("description", out.Description); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("hls_package", flattenHlsPackage(out.HlsPackage)); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("mss_package", flattenMssPackage(out.MssPackage)); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("origination", out.Origination); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("start_over_window_seconds", out.StartoverWindowSeconds); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("time_delay_seconds", out.TimeDelaySeconds); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + if err := d.Set("whitelist", out.Whitelist); err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionSetting, ResNameOriginEndpoint, d.Id(), err) + } + + return diags +} + +func resourceOriginEndpointUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + // TIP: ==== RESOURCE UPDATE ==== + // Not all resources have Update functions. There are a few reasons: + // a. The AWS API does not support changing a resource + // b. All arguments have ForceNew: true, set + // c. The AWS API uses a create call to modify an existing resource + // + // In the cases of a. and b., the main resource function will not have a + // UpdateWithoutTimeout defined. In the case of c., Update and Create are + // the same. + // + // The rest of the time, there should be an Update function and it should + // do the following things. Make sure there is a good reason if you don't + // do one of these. + // + // 1. Get a client connection to the relevant service + // 2. Populate a modify input structure and check for changes + // 3. Call the AWS modify/update function + // 4. Use a waiter to wait for update to complete + // 5. Call the Read function in the Update return + + // TIP: -- 1. Get a client connection to the relevant service + conn := meta.(*conns.AWSClient).MediaPackageClient(ctx) + + // TIP: -- 2. Populate a modify input structure and check for changes + // + // When creating the input structure, only include mandatory fields. Other + // fields are set as needed. You can use a flag, such as update below, to + // determine if a certain portion of arguments have been changed and + // whether to call the AWS update function. + update := false + + in := &mediapackage.UpdateOriginEndpointInput{ + Id: aws.String(d.Id()), + } + + if d.HasChanges("an_argument") { + in.Authorization = expandAuthorization(d.Get("an_argument").(map[string]interface{})) + update = true + } + + if d.HasChanges("cmaf_package") { + in.CmafPackage = expandCmafPackage(d.Get("cmaf_package").(map[string]interface{})) + update = true + } + + if d.HasChanges("description") { + in.Description = aws.String(d.Get("description").(string)) + update = true + } + + if d.HasChanges("hls_package") { + in.HlsPackage = expandHlsPackage(d.Get("hls_package").(map[string]interface{})) + update = true + } + + if d.HasChanges("mss_package") { + in.MssPackage = expandMssPackage(d.Get("mss_package").(map[string]interface{})) + update = true + } + + if !update { + return diags + } + + log.Printf("[DEBUG] Updating MediaPackage OriginEndpoint (%s): %#v", d.Id(), in) + _, err := conn.UpdateOriginEndpoint(ctx, in) + if err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionUpdating, ResNameOriginEndpoint, d.Id(), err) + } + + return append(diags, resourceOriginEndpointRead(ctx, d, meta)...) +} + +func resourceOriginEndpointDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + + conn := meta.(*conns.AWSClient).MediaPackageClient(ctx) + + log.Printf("[INFO] Deleting MediaPackage OriginEndpoint %s", d.Id()) + + _, err := conn.DeleteOriginEndpoint(ctx, &mediapackage.DeleteOriginEndpointInput{ + Id: aws.String(d.Id()), + }) + + if errs.IsA[*awstypes.NotFoundException](err) { + return diags + } + if err != nil { + return create.AppendDiagError(diags, names.MediaPackage, create.ErrActionDeleting, ResNameOriginEndpoint, d.Id(), err) + } + + return diags +} + +// TIP: ==== FINDERS ==== +// The find function is not strictly necessary. You could do the API +// request from the status function. However, we have found that find often +// comes in handy in other places besides the status function. As a result, it +// is good practice to define it separately. +func findOriginEndpoint(ctx context.Context, conn *mediapackage.Client, id, channelID string) (*types.OriginEndpoint, error) { + in := &mediapackage.ListOriginEndpointsInput{ + ChannelId: aws.String(channelID), + } + + out, err := conn.ListOriginEndpoints(ctx, in) + if err != nil { + return nil, err + } + + if len(out.OriginEndpoints) == 0 { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + var ep *types.OriginEndpoint + + for _, e := range out.OriginEndpoints { + if aws.ToString(e.Id) == id { + ep = &e + } + } + + return ep, nil +} + +func expandAuthorization(tfMap map[string]interface{}) *types.Authorization { + if tfMap == nil { + return nil + } + + a := &types.Authorization{} + + if v, ok := tfMap["cdn_identifier_secret"].(string); ok && v != "" { + a.CdnIdentifierSecret = aws.String(v) + } + + if v, ok := tfMap["secrets_role_arn"].(string); ok && v != "" { + a.SecretsRoleArn = aws.String(v) + } + + return a +} + +func flattenAuthorization(apiObject *types.Authorization) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.CdnIdentifierSecret; v != nil { + m["cdn_identifier_secret"] = aws.ToString(v) + } + + if v := apiObject.SecretsRoleArn; v != nil { + m["secrets_role_arn"] = aws.ToString(v) + } + + return m +} + +func expandHlsPackage(tfMap map[string]interface{}) *types.HlsPackage { + if tfMap == nil { + return nil + } + + h := &types.HlsPackage{} + + if v, ok := tfMap["ad_markers"].(string); ok && v != "" { + h.AdMarkers = types.AdMarkers(v) + } + + if v, ok := tfMap["ad_triggers"].([]interface{}); ok && len(v) > 0 { + h.AdTriggers = expandAdTriggers(v) + } + + if v, ok := tfMap["ads_on_delivery_restrictions"].(string); ok && v != "" { + h.AdsOnDeliveryRestrictions = types.AdsOnDeliveryRestrictions(v) + } + + if v, ok := tfMap["encryption"].(map[string]interface{}); ok && len(v) > 0 { + h.Encryption = expandHlsEncryption(v) + } + + if v, ok := tfMap["include_dvb_subtitles"].(bool); ok { + h.IncludeDvbSubtitles = aws.Bool(v) + } + + if v, ok := tfMap["include_iframe_only_stream"].(bool); ok { + h.IncludeIframeOnlyStream = aws.Bool(v) + } + + if v, ok := tfMap["playlist_type"].(string); ok && v != "" { + h.PlaylistType = types.PlaylistType(v) + } + + if v, ok := tfMap["playlist_window_seconds"].(int); ok { + h.PlaylistWindowSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["program_date_time_interval_seconds"].(int); ok { + h.ProgramDateTimeIntervalSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["segment_duration_seconds"].(int); ok { + h.SegmentDurationSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["stream_selection"].(map[string]interface{}); ok && len(v) > 0 { + h.StreamSelection = expandStreamSelection(v) + } + + if v, ok := tfMap["use_audio_rendition_group"].(bool); ok { + h.UseAudioRenditionGroup = aws.Bool(v) + } + + return h +} + +func flattenHlsPackage(apiObject *types.HlsPackage) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.AdMarkers; v != "" { + m["ad_markers"] = string(v) + } + + if v := apiObject.AdTriggers; v != nil { + m["ad_triggers"] = flattenAdTriggers(v) + } + + if v := apiObject.AdsOnDeliveryRestrictions; v != "" { + m["ads_on_delivery_restrictions"] = string(v) + } + + if v := apiObject.Encryption; v != nil { + m["encryption"] = flattenHlsEncryption(v) + } + + if v := apiObject.IncludeDvbSubtitles; v != nil { + m["include_dvb_subtitles"] = aws.ToBool(v) + } + + if v := apiObject.IncludeIframeOnlyStream; v != nil { + m["include_iframe_only_stream"] = aws.ToBool(v) + } + + if v := apiObject.PlaylistType; v != "" { + m["playlist_type"] = string(v) + } + + if v := apiObject.PlaylistWindowSeconds; v != nil { + m["playlist_window_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.ProgramDateTimeIntervalSeconds; v != nil { + m["program_date_time_interval_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.SegmentDurationSeconds; v != nil { + m["segment_duration_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.StreamSelection; v != nil { + m["stream_selection"] = flattenStreamSelection(v) + } + + if v := apiObject.UseAudioRenditionGroup; v != nil { + m["use_audio_rendition_group"] = aws.ToBool(v) + } + + return m +} + +func expandAdTriggers(tfList []interface{}) []types.AdTriggersElement { + if len(tfList) == 0 { + return nil + } + + var as []types.AdTriggersElement + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + var a types.AdTriggersElement + + if v, ok := m["ad_trigger"].(string); ok && v != "" { + a = types.AdTriggersElement(v) + } + + as = append(as, a) + } + + return as +} + +func flattenAdTriggers(apiList []types.AdTriggersElement) []interface{} { + if len(apiList) == 0 { + return nil + } + + var l []interface{} + + for _, v := range apiList { + l = append(l, map[string]interface{}{ + "ad_trigger": v, + }) + } + + return l +} + +func expandHlsEncryption(tfMap map[string]interface{}) *types.HlsEncryption { + if tfMap == nil { + return nil + } + + h := &types.HlsEncryption{} + + if v, ok := tfMap["speke_key_provider"].(map[string]interface{}); ok && v != nil { + h.SpekeKeyProvider = expandSpekeKeyProvider(v) + } + + if v, ok := tfMap["constant_initialization_vector"].(string); ok && v != "" { + h.ConstantInitializationVector = aws.String(v) + } + + if v, ok := tfMap["encryption_method"].(string); ok && v != "" { + h.EncryptionMethod = types.EncryptionMethod(v) + } + + if v, ok := tfMap["key_rotation_interval_seconds"].(int); ok { + h.KeyRotationIntervalSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["repeat_ext_x_key"].(bool); ok { + h.RepeatExtXKey = aws.Bool(v) + } + + return h +} + +func flattenHlsEncryption(apiObject *types.HlsEncryption) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SpekeKeyProvider; v != nil { + m["speke_key_provider"] = flattenSpekeKeyProvider(v) + } + + if v := apiObject.ConstantInitializationVector; v != nil { + m["constant_initialization_vector"] = aws.ToString(v) + } + + if v := apiObject.EncryptionMethod; v != "" { + m["encryption_method"] = string(v) + } + + if v := apiObject.KeyRotationIntervalSeconds; v != nil { + m["key_rotation_interval_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.RepeatExtXKey; v != nil { + m["repeat_ext_x_key"] = aws.ToBool(v) + } + + return m +} + +func expandSpekeKeyProvider(tfMap map[string]interface{}) *types.SpekeKeyProvider { + s := &types.SpekeKeyProvider{ + ResourceId: aws.String(tfMap["resource_id"].(string)), + RoleArn: aws.String(tfMap["role_arn"].(string)), + Url: aws.String(tfMap["url"].(string)), + SystemIds: tfMap["system_ids"].([]string), + } + + if v, ok := tfMap["certificate_arn"].(string); ok && v != "" { + s.CertificateArn = aws.String(v) + } + + if v, ok := tfMap["encryption_contract_configuration"]; ok && len(v.(map[string]interface{})) > 0 { + c := &types.EncryptionContractConfiguration{ + PresetSpeke20Audio: types.PresetSpeke20Audio(v.(map[string]interface{})["preset_speke20_audio"].(string)), + PresetSpeke20Video: types.PresetSpeke20Video(v.(map[string]interface{})["preset_speke20_video"].(string)), + } + s.EncryptionContractConfiguration = c + } + + return s +} + +func flattenSpekeKeyProvider(apiObject *types.SpekeKeyProvider) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{ + "resource_id": aws.ToString(apiObject.ResourceId), + "role_arn": aws.ToString(apiObject.RoleArn), + "url": aws.ToString(apiObject.Url), + "system_ids": apiObject.SystemIds, + } + + if v := apiObject.CertificateArn; v != nil { + m["certificate_arn"] = aws.ToString(v) + } + + if v := apiObject.EncryptionContractConfiguration; v != nil { + m["encryption_contract_configuration"] = map[string]interface{}{ + "preset_speke20_audio": string(v.PresetSpeke20Audio), + "preset_speke20_video": string(v.PresetSpeke20Video), + } + } + + return m +} + +func expandStreamSelection(tfMap map[string]interface{}) *types.StreamSelection { + s := &types.StreamSelection{} + + if v, ok := tfMap["max_video_bits_per_second"].(int); ok { + s.MaxVideoBitsPerSecond = aws.Int32(int32(v)) + } + + if v, ok := tfMap["min_video_bits_per_second"].(int); ok { + s.MinVideoBitsPerSecond = aws.Int32(int32(v)) + } + + if v, ok := tfMap["stream_order"].(string); ok { + s.StreamOrder = types.StreamOrder(v) + } + + return s +} + +func flattenStreamSelection(apiObject *types.StreamSelection) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.MaxVideoBitsPerSecond; v != nil { + m["max_video_bits_per_second"] = aws.ToInt32(v) + } + + if v := apiObject.MinVideoBitsPerSecond; v != nil { + m["min_video_bits_per_second"] = aws.ToInt32(v) + } + + if v := apiObject.StreamOrder; v != "" { + m["stream_order"] = string(v) + } + + return m +} + +func expandMssPackage(tfMap map[string]interface{}) *types.MssPackage { + m := &types.MssPackage{} + + if v, ok := tfMap["encryption"]; ok && len(v.(map[string]interface{})) > 0 { + m.Encryption = expandMssEncryption(v.(map[string]interface{})) + } + + if v, ok := tfMap["manifest_window_seconds"].(int); ok { + m.ManifestWindowSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["segment_duration_seconds"].(int); ok { + m.SegmentDurationSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["stream_selection"]; ok && len(v.(map[string]interface{})) > 0 { + m.StreamSelection = expandStreamSelection(v.(map[string]interface{})) + } + + return m +} + +func flattenMssPackage(apiObject *types.MssPackage) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.Encryption; v != nil { + m["encryption"] = flattenMssEncryption(v) + } + + if v := apiObject.ManifestWindowSeconds; v != nil { + m["manifest_window_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.SegmentDurationSeconds; v != nil { + m["segment_duration_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.StreamSelection; v != nil { + m["stream_selection"] = flattenStreamSelection(v) + } + + return m +} + +func expandMssEncryption(mssEncryptionSettings map[string]interface{}) *types.MssEncryption { + m := &types.MssEncryption{} + + if v, ok := mssEncryptionSettings["speke_key_provider"]; ok && len(v.(map[string]interface{})) > 0 { + m.SpekeKeyProvider = expandSpekeKeyProvider(v.(map[string]interface{})) + } + + return m +} + +func flattenMssEncryption(apiObject *types.MssEncryption) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SpekeKeyProvider; v != nil { + m["speke_key_provider"] = flattenSpekeKeyProvider(v) + } + + return m +} + +func expandCmafPackage(cmafPackageSettings map[string]interface{}) *types.CmafPackageCreateOrUpdateParameters { + c := &types.CmafPackageCreateOrUpdateParameters{} + + if v, ok := cmafPackageSettings["encryption"]; ok && len(v.(map[string]interface{})) > 0 { + c.Encryption = expandCmafEncryption(v.(map[string]interface{})) + } + + if v, ok := cmafPackageSettings["hls_manifests"]; ok { + c.HlsManifests = expandHlsManifests(v.([]interface{})) + } + + if v, ok := cmafPackageSettings["segment_duration_seconds"].(int); ok && v > 0 { + c.SegmentDurationSeconds = aws.Int32(int32(v)) + } + + if v, ok := cmafPackageSettings["segment_prefix"].(string); ok && v != "" { + c.SegmentPrefix = aws.String(v) + } + + if v, ok := cmafPackageSettings["stream_selection"]; ok && len(v.(map[string]interface{})) > 0 { + c.StreamSelection = expandStreamSelection(v.(map[string]interface{})) + } + + return c +} + +func flattenCmafPackage(apiObject *types.CmafPackage) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.Encryption; v != nil { + m["encryption"] = flattenCmafEncryption(v) + } + + if v := apiObject.HlsManifests; v != nil { + m["hls_manifests"] = flattenHlsManifests(v) + } + + if v := apiObject.SegmentDurationSeconds; v != nil { + m["segment_duration_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.SegmentPrefix; v != nil { + m["segment_prefix"] = aws.ToString(v) + } + + if v := apiObject.StreamSelection; v != nil { + m["stream_selection"] = flattenStreamSelection(v) + } + + return m +} + +func expandDashPackage(tfMap map[string]interface{}) *types.DashPackage { + if tfMap == nil { + return nil + } + + d := &types.DashPackage{} + + if v, ok := tfMap["ad_triggers"].([]interface{}); ok && len(v) > 0 { + d.AdTriggers = expandAdTriggers(v) + } + + if v, ok := tfMap["ads_on_delivery_restrictions"].(string); ok && v != "" { + d.AdsOnDeliveryRestrictions = types.AdsOnDeliveryRestrictions(v) + } + + if v, ok := tfMap["encryption"].(map[string]interface{}); ok && len(v) > 0 { + d.Encryption = expandDashEncryption(v) + } + + if v, ok := tfMap["manifest_window_seconds"].(int); ok && v > 0 { + d.ManifestWindowSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["min_buffer_time_seconds"].(int); ok && v > 0 { + d.MinBufferTimeSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["min_update_period_seconds"].(int); ok && v > 0 { + d.MinUpdatePeriodSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["period_triggers"].([]interface{}); ok && len(v) > 0 { + d.PeriodTriggers = expandPeriodTriggers(v) + } + + if v, ok := tfMap["profile"].(string); ok && v != "" { + d.Profile = types.Profile(v) + } + + if v, ok := tfMap["segment_duration_seconds"].(int); ok && v > 0 { + d.SegmentDurationSeconds = aws.Int32(int32(v)) + } + + if v, ok := tfMap["stream_selection"].(map[string]interface{}); ok && len(v) > 0 { + d.StreamSelection = expandStreamSelection(v) + } + + return d +} + +func flattenDashPackage(apiObject *types.DashPackage) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.AdTriggers; v != nil { + m["ad_triggers"] = flattenAdTriggers(v) + } + + if v := apiObject.AdsOnDeliveryRestrictions; v != "" { + m["ads_on_delivery_restrictions"] = string(v) + } + + if v := apiObject.Encryption; v != nil { + m["encryption"] = flattenDashEncryption(v) + } + + if v := apiObject.ManifestWindowSeconds; v != nil { + m["manifest_window_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.MinBufferTimeSeconds; v != nil { + m["min_buffer_time_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.MinUpdatePeriodSeconds; v != nil { + m["min_update_period_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.PeriodTriggers; v != nil { + m["period_triggers"] = flattenPeriodTriggers(v) + } + + if v := apiObject.Profile; v != "" { + m["profile"] = string(v) + } + + if v := apiObject.SegmentDurationSeconds; v != nil { + m["segment_duration_seconds"] = aws.ToInt32(v) + } + + if v := apiObject.StreamSelection; v != nil { + m["stream_selection"] = flattenStreamSelection(v) + } + + return m +} + +func expandDashEncryption(tfMap map[string]interface{}) *types.DashEncryption { + if tfMap == nil { + return nil + } + + d := &types.DashEncryption{} + + if v, ok := tfMap["speke_key_provider"].(map[string]interface{}); ok && len(v) > 0 { + d.SpekeKeyProvider = expandSpekeKeyProvider(v) + } + + if v, ok := tfMap["key_rotation_interval_seconds"].(int); ok && v > 0 { + d.KeyRotationIntervalSeconds = aws.Int32(int32(v)) + } + + return d +} + +func flattenDashEncryption(apiObject *types.DashEncryption) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SpekeKeyProvider; v != nil { + m["speke_key_provider"] = flattenSpekeKeyProvider(v) + } + + if v := apiObject.KeyRotationIntervalSeconds; v != nil { + m["key_rotation_interval_seconds"] = aws.ToInt32(v) + } + + return m +} + +func expandPeriodTriggers(tfList []interface{}) []types.PeriodTriggersElement { + if len(tfList) == 0 { + return nil + } + + var l []types.PeriodTriggersElement + + for _, r := range tfList { + m, ok := r.(map[string]interface{}) + + if !ok { + continue + } + + var p types.PeriodTriggersElement + + if v, ok := m["period_trigger"].(string); ok && v != "" { + p = types.PeriodTriggersElement(v) + } + + l = append(l, p) + } + + return l +} + +func flattenPeriodTriggers(apiList []types.PeriodTriggersElement) []interface{} { + if len(apiList) == 0 { + return nil + } + + var l []interface{} + + for _, v := range apiList { + l = append(l, map[string]interface{}{ + "period_trigger": v, + }) + } + + return l +} + +func expandCmafEncryption(tfMap map[string]interface{}) *types.CmafEncryption { + if tfMap == nil { + return nil + } + + e := &types.CmafEncryption{} + + if v, ok := tfMap["speke_key_provider"].(map[string]interface{}); ok && len(v) > 0 { + e.SpekeKeyProvider = expandSpekeKeyProvider(v) + } + + if v, ok := tfMap["constant_initialization_vector"].(string); ok && v != "" { + e.ConstantInitializationVector = aws.String(v) + } + + if v, ok := tfMap["encryption_method"].(string); ok && v != "" { + e.EncryptionMethod = types.CmafEncryptionMethod(v) + } + + if v, ok := tfMap["key_rotation_interval_seconds"].(int); ok && v > 0 { + e.KeyRotationIntervalSeconds = aws.Int32(int32(v)) + } + + return e +} + +func flattenCmafEncryption(apiObject *types.CmafEncryption) map[string]interface{} { + if apiObject == nil { + return nil + } + + m := map[string]interface{}{} + + if v := apiObject.SpekeKeyProvider; v != nil { + m["speke_key_provider"] = flattenSpekeKeyProvider(v) + } + + if v := apiObject.ConstantInitializationVector; v != nil { + m["constant_initialization_vector"] = aws.ToString(v) + } + + if v := apiObject.EncryptionMethod; v != "" { + m["encryption_method"] = string(v) + } + + if v := apiObject.KeyRotationIntervalSeconds; v != nil { + m["key_rotation_interval_seconds"] = aws.ToInt32(v) + } + + return m +} + +func expandHlsManifests(tfList []interface{}) []types.HlsManifestCreateOrUpdateParameters { + if len(tfList) == 0 { + return nil + } + + var hs []types.HlsManifestCreateOrUpdateParameters + + for _, tfManifest := range tfList { + manifest := tfManifest.(map[string]interface{}) + m := types.HlsManifestCreateOrUpdateParameters{ + Id: aws.String(manifest["id"].(string)), + } + + if v, ok := manifest["ad_markers"].(string); ok && v != "" { + m.AdMarkers = types.AdMarkers(v) + } + + if v, ok := manifest["ad_triggers"].([]interface{}); ok && len(v) > 0 { + m.AdTriggers = expandAdTriggers(v) + } + + if v, ok := manifest["ads_on_delivery_restrictions"].(string); ok && v != "" { + m.AdsOnDeliveryRestrictions = types.AdsOnDeliveryRestrictions(v) + } + + if v, ok := manifest["include_iframe_only_stream"].(bool); ok { + m.IncludeIframeOnlyStream = aws.Bool(v) + } + + if v, ok := manifest["manifest_name"].(string); ok && v != "" { + m.ManifestName = aws.String(v) + } + + if v, ok := manifest["playlist_type"].(string); ok && v != "" { + m.PlaylistType = types.PlaylistType(v) + } + + if v, ok := manifest["playlist_window_seconds"].(int); ok && v > 0 { + m.PlaylistWindowSeconds = aws.Int32(int32(v)) + } + + if v, ok := manifest["program_date_time_interval_seconds"].(int); ok && v > 0 { + m.ProgramDateTimeIntervalSeconds = aws.Int32(int32(v)) + } + + hs = append(hs, m) + } + + return hs +} + +func flattenHlsManifests(apiList []types.HlsManifest) []interface{} { + if len(apiList) == 0 { + return nil + } + + var hs []interface{} + + for _, manifest := range apiList { + m := map[string]interface{}{ + "id": manifest.Id, + } + + if v := manifest.AdMarkers; v != "" { + m["ad_markers"] = string(v) + } + + if v := manifest.AdTriggers; v != nil { + m["ad_triggers"] = flattenAdTriggers(v) + } + + if v := manifest.AdsOnDeliveryRestrictions; v != "" { + m["ads_on_delivery_restrictions"] = string(v) + } + + if v := manifest.IncludeIframeOnlyStream; v != nil { + m["include_iframe_only_stream"] = aws.ToBool(v) + } + + if v := manifest.ManifestName; v != nil { + m["manifest_name"] = aws.ToString(v) + } + + if v := manifest.PlaylistType; v != "" { + m["playlist_type"] = string(v) + } + + if v := manifest.PlaylistWindowSeconds; v != nil { + m["playlist_window_seconds"] = aws.ToInt32(v) + } + + if v := manifest.ProgramDateTimeIntervalSeconds; v != nil { + m["program_date_time_interval_seconds"] = aws.ToInt32(v) + } + + hs = append(hs, m) + } + + return hs +} diff --git a/internal/service/mediapackage/origin_endpoint_test.go b/internal/service/mediapackage/origin_endpoint_test.go new file mode 100644 index 000000000000..fd04b18857e6 --- /dev/null +++ b/internal/service/mediapackage/origin_endpoint_test.go @@ -0,0 +1,321 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mediapackage_test + +// **PLEASE DELETE THIS AND ALL TIP COMMENTS BEFORE SUBMITTING A PR FOR REVIEW!** +// +// TIP: ==== INTRODUCTION ==== +// Thank you for trying the skaff tool! +// +// You have opted to include these helpful comments. They all include "TIP:" +// to help you find and remove them when you're done with them. +// +// While some aspects of this file are customized to your input, the +// scaffold tool does *not* look at the AWS API and ensure it has correct +// function, structure, and variable names. It makes guesses based on +// commonalities. You will need to make significant adjustments. +// +// In other words, as generated, this is a rough outline of the work you will +// need to do. If something doesn't make sense for your situation, get rid of +// it. + +import ( + // TIP: ==== IMPORTS ==== + // This is a common set of imports but not customized to your code since + // your code hasn't been written yet. Make sure you, your IDE, or + // goimports -w fixes these imports. + // + // The provider linter wants your imports to be in two groups: first, + // standard library (i.e., "fmt" or "strings"), second, everything else. + // + // Also, AWS Go SDK v2 may handle nested structures differently than v1, + // using the services/mediapackage/types package. If so, you'll + // need to import types and reference the nested types, e.g., as + // types.. + "context" + "errors" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/mediapackage" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" + + // TIP: You will often need to import the package that this test file lives + // in. Since it is in the "test" context, it must import the package to use + // any normal context constants, variables, or functions. + tfmediapackage "github.com/hashicorp/terraform-provider-aws/internal/service/mediapackage" +) + +// TIP: File Structure. The basic outline for all test files should be as +// follows. Improve this resource's maintainability by following this +// outline. +// +// 1. Package declaration (add "_test" since this is a test file) +// 2. Imports +// 3. Unit tests +// 4. Basic test +// 5. Disappears test +// 6. All the other tests +// 7. Helper functions (exists, destroy, check, etc.) +// 8. Functions that return Terraform configurations + +// TIP: ==== UNIT TESTS ==== +// This is an example of a unit test. Its name is not prefixed with +// "TestAcc" like an acceptance test. +// +// Unlike acceptance tests, unit tests do not access AWS and are focused on a +// function (or method). Because of this, they are quick and cheap to run. +// +// In designing a resource's implementation, isolate complex bits from AWS bits +// so that they can be tested through a unit test. We encourage more unit tests +// in the provider. +// +// Cut and dry functions using well-used patterns, like typical flatteners and +// expanders, don't need unit testing. However, if they are complex or +// intricate, they should be unit tested. +func TestOriginEndpointExampleUnitTest(t *testing.T) { + t.Parallel() + + testCases := []struct { + TestName string + Input string + Expected string + Error bool + }{ + { + TestName: "empty", + Input: "", + Expected: "", + Error: true, + }, + { + TestName: "descriptive name", + Input: "some input", + Expected: "some output", + Error: false, + }, + { + TestName: "another descriptive name", + Input: "more input", + Expected: "more output", + Error: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + t.Parallel() + got, err := tfmediapackage.FunctionFromResource(testCase.Input) + + if err != nil && !testCase.Error { + t.Errorf("got error (%s), expected no error", err) + } + + if err == nil && testCase.Error { + t.Errorf("got (%s) and no error, expected error", got) + } + + if got != testCase.Expected { + t.Errorf("got %s, expected %s", got, testCase.Expected) + } + }) + } +} + +// TIP: ==== ACCEPTANCE TESTS ==== +// This is an example of a basic acceptance test. This should test as much of +// standard functionality of the resource as possible, and test importing, if +// applicable. We prefix its name with "TestAcc", the service, and the +// resource name. +// +// Acceptance test access AWS and cost money to run. +func TestAccMediaPackageOriginEndpoint_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var originendpoint mediapackage.DescribeOriginEndpointResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_mediapackage_origin_endpoint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.MediaPackageOriginEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.MediaPackageServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOriginEndpointDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOriginEndpointConfig_basic(rName, "1.0.0"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckOriginEndpointExists(ctx, resourceName, &originEndpoint), + resource.TestCheckResourceAttr(resourceName, "origin_endpoint_name", rName), + resource.TestCheckResourceAttr(resourceName, "engine_type", "ActiveMediaPackage"), + resource.TestCheckResourceAttr(resourceName, "engine_version", "1.0.0"), + resource.TestCheckResourceAttr(resourceName, "host_instance_type", "mediapackage.t2.micro"), + resource.TestCheckResourceAttr(resourceName, "authentication_strategy", "simple"), + resource.TestCheckResourceAttr(resourceName, "storage_type", "efs"), + resource.TestCheckResourceAttr(resourceName, "logs.0.general", "true"), + resource.TestCheckResourceAttr(resourceName, "user.0.username", "Test"), + resource.TestCheckResourceAttr(resourceName, "user.0.password", "TestTest1234"), + acctest.MatchResourceAttrRegionalARN(ctx, resourceName, names.AttrARN, "mediapackage", regexache.MustCompile(`originendpoint:.+$`)), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"apply_immediately", "user"}, + }, + }, + }) +} + +func TestAccMediaPackageOriginEndpoint_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var originendpoint mediapackage.DescribeOriginEndpointResponse + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_mediapackage_origin_endpoint.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.MediaPackageEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.MediaPackageServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOriginEndpointDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOriginEndpointConfig_basic(rName, testAccOriginEndpointVersionNewer), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckOriginEndpointExists(ctx, resourceName, &originendpoint), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfmediapackage.ResourceOriginEndpoint(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckOriginEndpointDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).MediaPackageClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_mediapackage_origin_endpoint" { + continue + } + + // TIP: ==== FINDERS ==== + // The find function should be exported. Since it won't be used outside of the package, it can be exported + // in the `exports_test.go` file. + _, err := tfmediapackage.FindOriginEndpointByID(ctx, conn, rs.Primary.ID) + if tfresource.NotFound(err) { + return nil + } + if err != nil { + return create.Error(names.MediaPackage, create.ErrActionCheckingDestroyed, tfmediapackage.ResNameOriginEndpoint, rs.Primary.ID, err) + } + + return create.Error(names.MediaPackage, create.ErrActionCheckingDestroyed, tfmediapackage.ResNameOriginEndpoint, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckOriginEndpointExists(ctx context.Context, name string, originendpoint *mediapackage.DescribeOriginEndpointResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.MediaPackage, create.ErrActionCheckingExistence, tfmediapackage.ResNameOriginEndpoint, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.MediaPackage, create.ErrActionCheckingExistence, tfmediapackage.ResNameOriginEndpoint, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).MediaPackageClient(ctx) + + resp, err := tfmediapackage.FindOriginEndpointByID(ctx, conn, rs.Primary.ID) + if err != nil { + return create.Error(names.MediaPackage, create.ErrActionCheckingExistence, tfmediapackage.ResNameOriginEndpoint, rs.Primary.ID, err) + } + + *originendpoint = *resp + + return nil + } +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).MediaPackageClient(ctx) + + input := &mediapackage.ListOriginEndpointsInput{} + + _, err := conn.ListOriginEndpoints(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccCheckOriginEndpointNotRecreated(before, after *mediapackage.DescribeOriginEndpointResponse) resource.TestCheckFunc { + return func(s *terraform.State) error { + if before, after := aws.ToString(before.OriginEndpointId), aws.ToString(after.OriginEndpointId); before != after { + return create.Error(names.MediaPackage, create.ErrActionCheckingNotRecreated, tfmediapackage.ResNameOriginEndpoint, aws.ToString(before.OriginEndpointId), errors.New("recreated")) + } + + return nil + } +} + +func testAccOriginEndpointConfig_basic(rName, version string) string { + return fmt.Sprintf(` +resource "aws_security_group" "test" { + name = %[1]q +} + +resource "aws_mediapackage_origin_endpoint" "test" { + origin_endpoint_name = %[1]q + engine_type = "ActiveMediaPackage" + engine_version = %[2]q + host_instance_type = "mediapackage.t2.micro" + security_groups = [aws_security_group.test.id] + authentication_strategy = "simple" + storage_type = "efs" + + logs { + general = true + } + + user { + username = "Test" + password = "TestTest1234" + } +} +`, rName, version) +} diff --git a/internal/service/mediapackage/service_package_gen.go b/internal/service/mediapackage/service_package_gen.go index d0346ad78983..810bff641944 100644 --- a/internal/service/mediapackage/service_package_gen.go +++ b/internal/service/mediapackage/service_package_gen.go @@ -36,6 +36,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: names.AttrARN, }, }, + { + Factory: ResourceOriginEndpoint, + TypeName: "aws_mediapackage_origin_endpoint", + Name: "Origin Endpoint", + }, } } diff --git a/names/names.go b/names/names.go index 6c63c0ff57a9..ed3e9b940ecb 100644 --- a/names/names.go +++ b/names/names.go @@ -108,6 +108,7 @@ const ( Macie2EndpointID = "macie2" MediaConvertEndpointID = "mediaconvert" MediaLiveEndpointID = "medialive" + MediaPackageOriginEndpointID = "mediapackage-origin-endpoint" ObservabilityAccessManagerEndpointID = "oam" OpenSearchIngestionEndpointID = "osis" OpenSearchServerlessEndpointID = "aoss" diff --git a/website/docs/r/mediapackage_origin_endpoint.html.markdown b/website/docs/r/mediapackage_origin_endpoint.html.markdown new file mode 100644 index 000000000000..2b9bf98aa629 --- /dev/null +++ b/website/docs/r/mediapackage_origin_endpoint.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "Elemental MediaPackage" +layout: "aws" +page_title: "AWS: aws_mediapackage_origin_endpoint" +description: |- + Terraform resource for managing an AWS Elemental MediaPackage Origin Endpoint. +--- +` +# Resource: aws_mediapackage_origin_endpoint + +Terraform resource for managing an AWS Elemental MediaPackage Origin Endpoint. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_mediapackage_origin_endpoint" "example" { +} +``` + +## Argument Reference + +The following arguments are required: + +* `example_arg` - (Required) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +The following arguments are optional: + +* `optional_arg` - (Optional) Concise argument description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the Origin Endpoint. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `example_attribute` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `60m`) +* `update` - (Default `180m`) +* `delete` - (Default `90m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Elemental MediaPackage Origin Endpoint using the `example_id_arg`. For example: + +```terraform +import { + to = aws_mediapackage_origin_endpoint.example + id = "origin_endpoint-id-12345678" +} +``` + +Using `terraform import`, import Elemental MediaPackage Origin Endpoint using the `example_id_arg`. For example: + +```console +% terraform import aws_mediapackage_origin_endpoint.example origin_endpoint-id-12345678 +```