diff --git a/aws/provider.go b/aws/provider.go index 390d86a8324..f90657d1ca2 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -683,6 +683,7 @@ func Provider() terraform.ResourceProvider { "aws_mq_broker": resourceAwsMqBroker(), "aws_mq_configuration": resourceAwsMqConfiguration(), "aws_media_convert_queue": resourceAwsMediaConvertQueue(), + "aws_medialive_input_security_group": resourceAwsMediaLiveInputSecurityGroup(), "aws_media_package_channel": resourceAwsMediaPackageChannel(), "aws_media_store_container": resourceAwsMediaStoreContainer(), "aws_media_store_container_policy": resourceAwsMediaStoreContainerPolicy(), diff --git a/aws/resource_aws_medialive_input_security_group.go b/aws/resource_aws_medialive_input_security_group.go new file mode 100644 index 00000000000..c3fcf509478 --- /dev/null +++ b/aws/resource_aws_medialive_input_security_group.go @@ -0,0 +1,248 @@ +package aws + +import ( + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/medialive" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" +) + +func resourceAwsMediaLiveInputSecurityGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsMediaLiveInputSecurityGroupCreate, + Read: resourceAwsMediaLiveInputSecurityGroupRead, + Update: resourceAwsMediaLiveInputSecurityGroupUpdate, + Delete: resourceAwsMediaLiveInputSecurityGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "whitelist_rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "tags": tagsSchema(), + }, + } +} + +func resourceAwsMediaLiveInputSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).medialiveconn + + input := &medialive.CreateInputSecurityGroupInput{} + + if v, ok := d.GetOk("whitelist_rule"); ok && len(v.([]interface{})) > 0 { + input.WhitelistRules = expandWhitelistRules( + v.([]interface{}), + ) + } + + if v := d.Get("tags").(map[string]interface{}); len(v) > 0 { + input.Tags = keyvaluetags.New(v).IgnoreAws().MedialiveTags() + } + + resp, err := conn.CreateInputSecurityGroup(input) + if err != nil { + return fmt.Errorf("Error creating MediaLive Input Security Group: %s", err) + } + + d.SetId(aws.StringValue(resp.SecurityGroup.Id)) + + return resourceAwsMediaLiveInputSecurityGroupRead(d, meta) +} + +func resourceAwsMediaLiveInputSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).medialiveconn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + input := &medialive.DescribeInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(d.Id()), + } + + resp, err := conn.DescribeInputSecurityGroup(input) + if err != nil { + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + log.Printf("[WARN] MediaLive Input Security Group %s not found, error code (404)", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error describing MediaLive Input Security Group(%s): %s", d.Id(), err) + } + + d.Set("arn", aws.StringValue(resp.Arn)) + + if err := d.Set("whitelist_rule", flattenWhitelistRules(resp.WhitelistRules)); err != nil { + return fmt.Errorf("error setting whitelist_rule: %s", err) + } + + if err := d.Set("tags", keyvaluetags.MedialiveKeyValueTags(resp.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %s", err) + } + + return nil +} + +func resourceAwsMediaLiveInputSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).medialiveconn + + if d.HasChange("whitelist_rule") { + input := &medialive.UpdateInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(d.Id()), + WhitelistRules: expandWhitelistRules( + d.Get("whitelist_rule").([]interface{}), + ), + } + + _, err := conn.UpdateInputSecurityGroup(input) + if err != nil { + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + log.Printf("[WARN] MediaLive Input Security Group %s not found, error code (404)", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error updating MediaLive Input Security Group(%s): %s", d.Id(), err) + } + } + + if d.HasChange("tags") { + o, n := d.GetChange("tags") + if err := keyvaluetags.MedialiveUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } + } + + if err := waitForMediaLiveInputSecurityGroupOperation(conn, d.Id()); err != nil { + return fmt.Errorf("Error waiting for operational MediaLive Input Security Group(%s): %s", d.Id(), err) + } + + return resourceAwsMediaLiveInputSecurityGroupRead(d, meta) +} + +func resourceAwsMediaLiveInputSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).medialiveconn + input := &medialive.DeleteInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(d.Id()), + } + + _, err := conn.DeleteInputSecurityGroup(input) + if err != nil { + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + return nil + } + return fmt.Errorf("Error deleting MediaLive Input Security Group(%s): %s", d.Id(), err) + } + + if err := waitForMediaLiveInputSecurityGroupDeletion(conn, d.Id()); err != nil { + return fmt.Errorf("Error waiting for deleting MediaLive Input Security Group(%s): %s", d.Id(), err) + } + + return nil +} + +func expandWhitelistRules(whitelistRules []interface{}) []*medialive.InputWhitelistRuleCidr { + var result []*medialive.InputWhitelistRuleCidr + if len(whitelistRules) == 0 { + return nil + } + + for _, whitelistRule := range whitelistRules { + r := whitelistRule.(map[string]interface{}) + + result = append(result, &medialive.InputWhitelistRuleCidr{ + Cidr: aws.String(r["cidr"].(string)), + }) + } + return result +} + +func flattenWhitelistRules(whitelistRules []*medialive.InputWhitelistRule) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(whitelistRules)) + for _, whitelistRule := range whitelistRules { + r := map[string]interface{}{ + "cidr": aws.StringValue(whitelistRule.Cidr), + } + result = append(result, r) + } + return result +} + +func mediaLiveInputSecurityGroupRefreshFunc(conn *medialive.MediaLive, inputSecurityGroupId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + inputSecurityGroup, err := conn.DescribeInputSecurityGroup(&medialive.DescribeInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(inputSecurityGroupId), + }) + + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + return nil, medialive.InputSecurityGroupStateDeleted, nil + } + + if err != nil { + return nil, "", fmt.Errorf("error reading MediaLive Input Security Group (%s): %s", inputSecurityGroupId, err) + } + + if inputSecurityGroup == nil { + return nil, medialive.InputSecurityGroupStateDeleted, nil + } + + return inputSecurityGroup, aws.StringValue(inputSecurityGroup.State), nil + } +} + +func waitForMediaLiveInputSecurityGroupOperation(conn *medialive.MediaLive, inputSecurityGroupId string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{medialive.InputSecurityGroupStateUpdating}, + Target: []string{ + medialive.InputSecurityGroupStateIdle, + medialive.InputSecurityGroupStateInUse, + }, + Refresh: mediaLiveInputSecurityGroupRefreshFunc(conn, inputSecurityGroupId), + Timeout: 30 * time.Minute, + } + + log.Printf("[DEBUG] Waiting for Media Live Input Security Group (%s) Operation", inputSecurityGroupId) + _, err := stateConf.WaitForState() + + return err +} + +func waitForMediaLiveInputSecurityGroupDeletion(conn *medialive.MediaLive, inputSecurityGroupId string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + medialive.InputSecurityGroupStateIdle, + medialive.InputSecurityGroupStateUpdating, + medialive.InputSecurityGroupStateInUse, + }, + Target: []string{medialive.InputSecurityGroupStateDeleted}, + Refresh: mediaLiveInputSecurityGroupRefreshFunc(conn, inputSecurityGroupId), + Timeout: 30 * time.Minute, + NotFoundChecks: 1, + } + + log.Printf("[DEBUG] Waiting for Media Live Input Security Group (%s) deletion", inputSecurityGroupId) + _, err := stateConf.WaitForState() + + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + return nil + } + + return err +} diff --git a/aws/resource_aws_medialive_input_security_group_test.go b/aws/resource_aws_medialive_input_security_group_test.go new file mode 100644 index 00000000000..ab94161bb40 --- /dev/null +++ b/aws/resource_aws_medialive_input_security_group_test.go @@ -0,0 +1,216 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/medialive" +) + +func TestAccAWSMediaLiveInputSecurityGroup_basic(t *testing.T) { + var v medialive.DescribeInputSecurityGroupOutput + resourceName := "aws_medialive_input_security_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsMediaLiveInputSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsMediaLiveInputSecurityGroupConfig("10.0.0.0/8"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.#", "1"), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.0.cidr", "10.0.0.0/8"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsMediaLiveInputSecurityGroupConfig("10.1.0.0/8"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.#", "1"), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.0.cidr", "10.1.0.0/8"), + ), + }, + { + Config: testAccAwsMediaLiveInputSecurityGroupConfigWithMultiple("10.1.0.0/8", "10.2.0.0/8"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.#", "2"), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.0.cidr", "10.1.0.0/8"), + resource.TestCheckResourceAttr( + resourceName, "whitelist_rule.1.cidr", "10.2.0.0/8"), + ), + }, + }, + }) +} + +func TestAccAWSMediaLiveInputSecurityGroup_tags(t *testing.T) { + var v medialive.DescribeInputSecurityGroupOutput + resourceName := "aws_medialive_input_security_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsMediaLiveInputSecurityGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsMediaLiveInputSecurityGroupConfigWithTag("10.0.0.0/8", "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsMediaLiveInputSecurityGroupConfigWithTag2("10.0.0.0/8", "key1", "value1update", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1update"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAwsMediaLiveInputSecurityGroupConfigWithTag("10.0.0.0/8", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccMediaLiveInputSecurityGroupExists(resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAwsMediaLiveInputSecurityGroupDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_medialive_input_security_group" { + continue + } + + conn := testAccProvider.Meta().(*AWSClient).medialiveconn + resp, err := conn.DescribeInputSecurityGroup(&medialive.DescribeInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(rs.Primary.ID), + }) + + if isAWSErr(err, medialive.ErrCodeNotFoundException, "") { + continue + } + if err != nil { + return fmt.Errorf("Error Describing Media Live Input Security Group: %s", err) + } + + if aws.StringValue(resp.Id) == rs.Primary.ID { + if aws.StringValue(resp.State) == medialive.InputSecurityGroupStateDeleted { + continue + } + return fmt.Errorf("Media Live Input Security Group %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccMediaLiveInputSecurityGroupExists(n string, v *medialive.DescribeInputSecurityGroupOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Media Live Input Security Group ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).medialiveconn + resp, err := conn.DescribeInputSecurityGroup(&medialive.DescribeInputSecurityGroupInput{ + InputSecurityGroupId: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if aws.StringValue(resp.Id) == rs.Primary.ID { + *v = *resp + return nil + } + + return fmt.Errorf("Workspaces IP Group (%s) not found", rs.Primary.ID) + } +} + +func testAccAwsMediaLiveInputSecurityGroupConfig(cidr string) string { + return fmt.Sprintf(` +resource "aws_medialive_input_security_group" "test" { + whitelist_rule { + cidr = %[1]q + } +} +`, cidr) +} + +func testAccAwsMediaLiveInputSecurityGroupConfigWithMultiple(cidr1, cidr2 string) string { + return fmt.Sprintf(` +resource "aws_medialive_input_security_group" "test" { + whitelist_rule { + cidr = %[1]q + } + + whitelist_rule { + cidr = %[2]q + } +} +`, cidr1, cidr2) +} + +func testAccAwsMediaLiveInputSecurityGroupConfigWithTag(cidr, tagKey, tagValue string) string { + return fmt.Sprintf(` +resource "aws_medialive_input_security_group" "test" { + whitelist_rule { + cidr = %[1]q + } + + tags = { + %[2]q = %[3]q + } +} +`, cidr, tagKey, tagValue) +} + +func testAccAwsMediaLiveInputSecurityGroupConfigWithTag2(cidr, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_medialive_input_security_group" "test" { + whitelist_rule { + cidr = %[1]q + } + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, cidr, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/website/allowed-subcategories.txt b/website/allowed-subcategories.txt index ded3bc6bac6..0f409b80ec3 100644 --- a/website/allowed-subcategories.txt +++ b/website/allowed-subcategories.txt @@ -70,6 +70,7 @@ MQ Macie Classic Managed Streaming for Kafka (MSK) MediaConvert +MediaLive MediaPackage MediaStore Neptune diff --git a/website/aws.erb b/website/aws.erb index 367288c3a4d..e33a4c345c5 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2239,6 +2239,19 @@ +