diff --git a/azurerm/internal/services/media/client/client.go b/azurerm/internal/services/media/client/client.go index 7edd3885277f..81effb43d758 100644 --- a/azurerm/internal/services/media/client/client.go +++ b/azurerm/internal/services/media/client/client.go @@ -15,6 +15,7 @@ type Client struct { ContentKeyPoliciesClient *media.ContentKeyPoliciesClient StreamingPoliciesClient *media.StreamingPoliciesClient LiveEventsClient *media.LiveEventsClient + LiveOutputsClient *media.LiveOutputsClient } func NewClient(o *common.ClientOptions) *Client { @@ -45,6 +46,9 @@ func NewClient(o *common.ClientOptions) *Client { LiveEventsClient := media.NewLiveEventsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) o.ConfigureClient(&LiveEventsClient.Client, o.ResourceManagerAuthorizer) + LiveOutputsClient := media.NewLiveOutputsClientWithBaseURI(o.ResourceManagerEndpoint, o.SubscriptionId) + o.ConfigureClient(&LiveOutputsClient.Client, o.ResourceManagerAuthorizer) + return &Client{ ServicesClient: &ServicesClient, AssetsClient: &AssetsClient, @@ -55,5 +59,6 @@ func NewClient(o *common.ClientOptions) *Client { ContentKeyPoliciesClient: &ContentKeyPoliciesClient, StreamingPoliciesClient: &StreamingPoliciesClient, LiveEventsClient: &LiveEventsClient, + LiveOutputsClient: &LiveOutputsClient, } } diff --git a/azurerm/internal/services/media/media_live_output_resource.go b/azurerm/internal/services/media/media_live_output_resource.go new file mode 100644 index 000000000000..7dfb898d2e52 --- /dev/null +++ b/azurerm/internal/services/media/media_live_output_resource.go @@ -0,0 +1,239 @@ +package media + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/Azure/azure-sdk-for-go/services/mediaservices/mgmt/2020-05-01/media" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/media/parse" + azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceMediaLiveOutput() *schema.Resource { + return &schema.Resource{ + Create: resourceMediaLiveOutputCreate, + Read: resourceMediaLiveOutputRead, + Delete: resourceMediaLiveOutputDelete, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.LiveOutputID(id) + return err + }), + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "live_event_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "archive_window_duration": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "asset_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringMatch( + regexp.MustCompile("^[-a-zA-Z0-9]{1,128}$"), + "Asset name must be 1 - 128 characters long, contain only letters, hyphen and numbers.", + ), + }, + + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "hls_fragments_per_ts_segment": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + + "manifest_name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "output_snap_time_in_seconds": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + }, + } +} + +func resourceMediaLiveOutputCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Media.LiveOutputsClient + subscriptionID := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + eventID, err := parse.LiveEventID(d.Get("live_event_id").(string)) + if err != nil { + return err + } + id := parse.NewLiveOutputID(subscriptionID, eventID.ResourceGroup, eventID.MediaserviceName, eventID.Name, d.Get("name").(string)) + if d.IsNewResource() { + existing, err := client.Get(ctx, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_media_live_event_output", id.ID()) + } + } + + parameters := media.LiveOutput{ + LiveOutputProperties: &media.LiveOutputProperties{}, + } + + if archiveWindowLength, ok := d.GetOk("archive_window_duration"); ok { + parameters.LiveOutputProperties.ArchiveWindowLength = utils.String(archiveWindowLength.(string)) + } + + if assetName, ok := d.GetOk("asset_name"); ok { + parameters.LiveOutputProperties.AssetName = utils.String(assetName.(string)) + } + + if description, ok := d.GetOk("description"); ok { + parameters.LiveOutputProperties.Description = utils.String(description.(string)) + } + + if hlsFragmentsPerTsSegment, ok := d.GetOk("hls_fragments_per_ts_segment"); ok { + parameters.LiveOutputProperties.Hls = &media.Hls{ + FragmentsPerTsSegment: utils.Int32(int32(hlsFragmentsPerTsSegment.(int))), + } + } + + if manifestName, ok := d.GetOk("manifest_name"); ok { + parameters.LiveOutputProperties.ManifestName = utils.String(manifestName.(string)) + } + + if outputSnapTime, ok := d.GetOk("output_snap_time_in_seconds"); ok { + parameters.LiveOutputProperties.OutputSnapTime = utils.Int64(int64(outputSnapTime.(int))) + } + + future, err := client.Create(ctx, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name, parameters) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for creation %s: %+v", id, err) + } + + d.SetId(id.ID()) + + return resourceMediaLiveOutputRead(d, meta) +} + +func resourceMediaLiveOutputRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Media.LiveOutputsClient + subscriptionID := meta.(*clients.Client).Account.SubscriptionId + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.LiveOutputID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[INFO] %s was not found - removing from state", id) + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + d.Set("name", id.Name) + + eventID := parse.NewLiveEventID(subscriptionID, id.ResourceGroup, id.MediaserviceName, id.LiveeventName) + d.Set("live_event_id", eventID.ID()) + + if props := resp.LiveOutputProperties; props != nil { + d.Set("archive_window_duration", props.ArchiveWindowLength) + d.Set("asset_name", props.AssetName) + d.Set("description", props.Description) + + var hlsFragmentsPerTsSegment int32 + if props.Hls != nil && props.Hls.FragmentsPerTsSegment != nil { + hlsFragmentsPerTsSegment = *props.Hls.FragmentsPerTsSegment + } + d.Set("hls_fragments_per_ts_segment", hlsFragmentsPerTsSegment) + d.Set("manifest_name", props.ManifestName) + + var outputSnapTime int64 + if props.OutputSnapTime != nil { + outputSnapTime = *props.OutputSnapTime + } + d.Set("output_snap_time_in_seconds", outputSnapTime) + } + + return nil +} + +func resourceMediaLiveOutputDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Media.LiveOutputsClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.LiveOutputID(d.Id()) + if err != nil { + return err + } + + future, err := client.Delete(ctx, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name) + if err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for %s to delete: %+v", id, err) + } + + return nil +} diff --git a/azurerm/internal/services/media/media_live_output_resource_test.go b/azurerm/internal/services/media/media_live_output_resource_test.go new file mode 100644 index 000000000000..8561627f1438 --- /dev/null +++ b/azurerm/internal/services/media/media_live_output_resource_test.go @@ -0,0 +1,209 @@ +package media_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/media/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type LiveOutputResource struct{} + +func TestAccLiveOutput_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_media_live_event_output", "test") + r := LiveOutputResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue("Output-1"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLiveOutput_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_media_live_event_output", "test") + r := LiveOutputResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue("Output-1"), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccLiveOutput_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_media_live_event_output", "test") + r := LiveOutputResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.complete(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("manifest_name").HasValue("testmanifest"), + check.That(data.ResourceName).Key("hls_fragments_per_ts_segment").HasValue("5"), + ), + }, + data.ImportStep(), + }) +} + +func TestAccLiveOutput_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_media_live_event_output", "test") + r := LiveOutputResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue("Output-1"), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("manifest_name").HasValue("testmanifest"), + check.That(data.ResourceName).Key("hls_fragments_per_ts_segment").HasValue("5"), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(data.ResourceName).Key("name").HasValue("Output-1"), + ), + }, + data.ImportStep(), + }) +} + +func (LiveOutputResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + id, err := parse.LiveOutputID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Media.LiveOutputsClient.Get(ctx, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name) + if err != nil { + return nil, fmt.Errorf("retrieving Live Event Output %s (Media Services Account %s) (resource group: %s): %v", id.Name, id.MediaserviceName, id.ResourceGroup, err) + } + + return utils.Bool(resp.LiveOutputProperties != nil), nil +} + +func (r LiveOutputResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_media_live_event_output" "test" { + name = "Output-1" + live_event_id = azurerm_media_live_event.test.id + archive_window_duration = "PT5M" + asset_name = azurerm_media_asset.test.name +} + +`, r.template(data)) +} + +func (r LiveOutputResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_media_live_event_output" "import" { + name = azurerm_media_live_event_output.test.name + live_event_id = azurerm_media_live_event.test.id + archive_window_duration = "PT5M" + asset_name = azurerm_media_asset.test.name +} + +`, r.basic(data)) +} + +func (r LiveOutputResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_media_live_event_output" "test" { + name = "Output-2" + live_event_id = azurerm_media_live_event.test.id + archive_window_duration = "PT5M" + asset_name = azurerm_media_asset.test.name + description = "Test live output 1" + manifest_name = "testmanifest" + output_snap_time_in_seconds = 0 + hls_fragments_per_ts_segment = 5 +} + +`, r.template(data)) +} + +func (LiveOutputResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-media-%d" + location = "%s" +} + +resource "azurerm_storage_account" "test" { + name = "acctestsa1%s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + account_tier = "Standard" + account_replication_type = "GRS" +} + +resource "azurerm_media_services_account" "test" { + name = "acctestmsa%s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + storage_account { + id = azurerm_storage_account.test.id + is_primary = true + } +} + +resource "azurerm_media_asset" "test" { + name = "inputAsset" + resource_group_name = azurerm_resource_group.test.name + media_services_account_name = azurerm_media_services_account.test.name +} + +resource "azurerm_media_live_event" "test" { + name = "event" + resource_group_name = azurerm_resource_group.test.name + media_services_account_name = azurerm_media_services_account.test.name + location = azurerm_resource_group.test.location + + input { + streaming_protocol = "RTMP" + key_frame_interval_duration = "PT6S" + ip_access_control_allow { + name = "AllowAll" + address = "0.0.0.0" + subnet_prefix_length = 0 + } + } +} + +`, data.RandomInteger, data.Locations.Primary, data.RandomString, data.RandomString) +} diff --git a/azurerm/internal/services/media/parse/live_output.go b/azurerm/internal/services/media/parse/live_output.go new file mode 100644 index 000000000000..bf105a2f4fd3 --- /dev/null +++ b/azurerm/internal/services/media/parse/live_output.go @@ -0,0 +1,81 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" +) + +type LiveOutputId struct { + SubscriptionId string + ResourceGroup string + MediaserviceName string + LiveeventName string + Name string +} + +func NewLiveOutputID(subscriptionId, resourceGroup, mediaserviceName, liveeventName, name string) LiveOutputId { + return LiveOutputId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + MediaserviceName: mediaserviceName, + LiveeventName: liveeventName, + Name: name, + } +} + +func (id LiveOutputId) String() string { + segments := []string{ + fmt.Sprintf("Name %q", id.Name), + fmt.Sprintf("Liveevent Name %q", id.LiveeventName), + fmt.Sprintf("Mediaservice Name %q", id.MediaserviceName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Live Output", segmentsStr) +} + +func (id LiveOutputId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Media/mediaservices/%s/liveevents/%s/liveoutputs/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.MediaserviceName, id.LiveeventName, id.Name) +} + +// LiveOutputID parses a LiveOutput ID into an LiveOutputId struct +func LiveOutputID(input string) (*LiveOutputId, error) { + id, err := azure.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := LiveOutputId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.MediaserviceName, err = id.PopSegment("mediaservices"); err != nil { + return nil, err + } + if resourceId.LiveeventName, err = id.PopSegment("liveevents"); err != nil { + return nil, err + } + if resourceId.Name, err = id.PopSegment("liveoutputs"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/azurerm/internal/services/media/parse/live_output_test.go b/azurerm/internal/services/media/parse/live_output_test.go new file mode 100644 index 000000000000..a65f9b05857c --- /dev/null +++ b/azurerm/internal/services/media/parse/live_output_test.go @@ -0,0 +1,144 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/resourceid" +) + +var _ resourceid.Formatter = LiveOutputId{} + +func TestLiveOutputIDFormatter(t *testing.T) { + actual := NewLiveOutputID("12345678-1234-9876-4563-123456789012", "resGroup1", "account1", "event1", "output1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/output1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestLiveOutputID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *LiveOutputId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing MediaserviceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/", + Error: true, + }, + + { + // missing value for MediaserviceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/", + Error: true, + }, + + { + // missing LiveeventName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/", + Error: true, + }, + + { + // missing value for LiveeventName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/", + Error: true, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/", + Error: true, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/output1", + Expected: &LiveOutputId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + MediaserviceName: "account1", + LiveeventName: "event1", + Name: "output1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.MEDIA/MEDIASERVICES/ACCOUNT1/LIVEEVENTS/EVENT1/LIVEOUTPUTS/OUTPUT1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := LiveOutputID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.MediaserviceName != v.Expected.MediaserviceName { + t.Fatalf("Expected %q but got %q for MediaserviceName", v.Expected.MediaserviceName, actual.MediaserviceName) + } + if actual.LiveeventName != v.Expected.LiveeventName { + t.Fatalf("Expected %q but got %q for LiveeventName", v.Expected.LiveeventName, actual.LiveeventName) + } + if actual.Name != v.Expected.Name { + t.Fatalf("Expected %q but got %q for Name", v.Expected.Name, actual.Name) + } + } +} diff --git a/azurerm/internal/services/media/registration.go b/azurerm/internal/services/media/registration.go index d62144094c87..f70757f7426d 100644 --- a/azurerm/internal/services/media/registration.go +++ b/azurerm/internal/services/media/registration.go @@ -35,5 +35,6 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_media_content_key_policy": resourceMediaContentKeyPolicy(), "azurerm_media_streaming_policy": resourceMediaStreamingPolicy(), "azurerm_media_live_event": resourceMediaLiveEvent(), + "azurerm_media_live_event_output": resourceMediaLiveOutput(), } } diff --git a/azurerm/internal/services/media/resourceids.go b/azurerm/internal/services/media/resourceids.go index c76758dca0d5..1c601e1d4add 100644 --- a/azurerm/internal/services/media/resourceids.go +++ b/azurerm/internal/services/media/resourceids.go @@ -9,3 +9,4 @@ package media //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=ContentKeyPolicy -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/contentkeypolicies/policy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=StreamingPolicy -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/streamingpolicies/policy1 //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LiveEvent -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=LiveOutput -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/output1 diff --git a/azurerm/internal/services/media/validate/live_output_id.go b/azurerm/internal/services/media/validate/live_output_id.go new file mode 100644 index 000000000000..4ccea1c48845 --- /dev/null +++ b/azurerm/internal/services/media/validate/live_output_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/media/parse" +) + +func LiveOutputID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.LiveOutputID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/azurerm/internal/services/media/validate/live_output_id_test.go b/azurerm/internal/services/media/validate/live_output_id_test.go new file mode 100644 index 000000000000..2b1c1ad0e155 --- /dev/null +++ b/azurerm/internal/services/media/validate/live_output_id_test.go @@ -0,0 +1,100 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestLiveOutputID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing MediaserviceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/", + Valid: false, + }, + + { + // missing value for MediaserviceName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/", + Valid: false, + }, + + { + // missing LiveeventName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/", + Valid: false, + }, + + { + // missing value for LiveeventName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/", + Valid: false, + }, + + { + // missing Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/", + Valid: false, + }, + + { + // missing value for Name + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/output1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.MEDIA/MEDIASERVICES/ACCOUNT1/LIVEEVENTS/EVENT1/LIVEOUTPUTS/OUTPUT1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := LiveOutputID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/media_live_output.html.markdown b/website/docs/r/media_live_output.html.markdown new file mode 100644 index 000000000000..b064b72bb523 --- /dev/null +++ b/website/docs/r/media_live_output.html.markdown @@ -0,0 +1,119 @@ +--- +subcategory: "Media" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_media_live_event_output" +description: |- + Manages an Azure Media Live Event Output. +--- + +# azurerm_media_live_event_output + +Manages a Azure Media Live Event Output. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "media-resources" + location = "West Europe" +} + +resource "azurerm_storage_account" "example" { + name = "examplestoracc" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + account_tier = "Standard" + account_replication_type = "GRS" +} + +resource "azurerm_media_services_account" "example" { + name = "examplemediaacc" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + storage_account { + id = azurerm_storage_account.example.id + is_primary = true + } +} + +resource "azurerm_media_asset" "example" { + name = "inputAsset" + resource_group_name = azurerm_resource_group.example.name + media_services_account_name = azurerm_media_services_account.example.name +} + +resource "azurerm_media_live_event" "example" { + name = "exampleevent" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + media_services_account_name = azurerm_media_services_account.example.name + description = "My Event Description" + + input { + streaming_protocol = "RTMP" + key_frame_interval_duration = "PT6S" + ip_access_control_allow { + name = "AllowAll" + address = "0.0.0.0" + subnet_prefix_length = 0 + } + } +} + +resource "azurerm_media_live_event_output" "example" { + name = "exampleoutput" + live_event_id = azurerm_media_live_event.example.id + archive_window_length = "PT5M" + asset_name = azurerm_media_asset.example.name + description = "Test live output 1" + manifest_name = "testmanifest" + output_snap_time_in_seconds = 0 + hls_fragments_per_ts_segment = 5 +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `archive_window_duration` - (Required) `ISO 8601` time between 1 minute to 25 hours to indicate the maximum content length that can be archived in the asset for this live output. This also sets the maximum content length for the rewind window. For example, use `PT1H30M` to indicate 1 hour and 30 minutes of archive window. Changing this forces a new Live Output to be created. + +* `asset_name` - (Required) The asset that the live output will write to. Changing this forces a new Live Output to be created. + +* `live_event_id` - (Required) The id of the live event. Changing this forces a new Live Output to be created. + +* `name` - (Required) The name which should be used for this Live Event Output. Changing this forces a new Live Output to be created. + +--- + +* `description` - (Optional) The description of the live output. Changing this forces a new Live Output to be created. + +* `hls_fragments_per_ts_segment` - (Optional) The number of fragments in an HTTP Live Streaming (HLS) TS segment in the output of the live event. This value does not affect the packing ratio for HLS CMAF output. Changing this forces a new Live Output to be created. + +* `manifest_name` - (Optional) The manifest file name. If not provided, the service will generate one automatically. Changing this forces a new Live Output to be created. + +* `output_snap_timestamp_in_seconds` - (Optional) The initial timestamp that the live output will start at, any content before this value will not be archived. Changing this forces a new Live Output to be created. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Live Output. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Live Output. +* `read` - (Defaults to 5 minutes) Used when retrieving the Live Output. +* `update` - (Defaults to 30 minutes) Used when updating the Live Output. +* `delete` - (Defaults to 30 minutes) Used when deleting the Live Output. + +## Import + +Live Outputs can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_media_live_output.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Media/mediaservices/account1/liveevents/event1/liveoutputs/output1 +```