From 318b57e4556560bffe9776e16bdc64f038c792a0 Mon Sep 17 00:00:00 2001 From: Minuk Song Date: Thu, 23 Sep 2021 12:27:54 +0900 Subject: [PATCH 01/10] Add RepositoryFilter to ECR Replication --- ...ource_aws_ecr_replication_configuration.go | 275 ++++++++++++++++++ ..._aws_ecr_replication_configuration_test.go | 187 ++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 aws/resource_aws_ecr_replication_configuration.go create mode 100644 aws/resource_aws_ecr_replication_configuration_test.go diff --git a/aws/resource_aws_ecr_replication_configuration.go b/aws/resource_aws_ecr_replication_configuration.go new file mode 100644 index 00000000000..5582355b7d9 --- /dev/null +++ b/aws/resource_aws_ecr_replication_configuration.go @@ -0,0 +1,275 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceAwsEcrReplicationConfiguration() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEcrReplicationConfigurationPut, + Read: resourceAwsEcrReplicationConfigurationRead, + Update: resourceAwsEcrReplicationConfigurationPut, + Delete: resourceAwsEcrReplicationConfigurationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "registry_id": { + Type: schema.TypeString, + Computed: true, + }, + "replication_configuration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rule": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "destination": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Required: true, + }, + "registry_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateAwsAccountId, + }, + }, + }, + }, + "repository_filter": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 100, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Required: true, + }, + "filter_type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceAwsEcrReplicationConfigurationPut(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ecrconn + + input := ecr.PutReplicationConfigurationInput{ + ReplicationConfiguration: expandEcrReplicationConfigurationReplicationConfiguration(d.Get("replication_configuration").([]interface{})), + } + + _, err := conn.PutReplicationConfiguration(&input) + if err != nil { + return fmt.Errorf("error creating ECR Replication Configuration: %w", err) + } + + d.SetId(meta.(*AWSClient).accountid) + + return resourceAwsEcrReplicationConfigurationRead(d, meta) +} + +func resourceAwsEcrReplicationConfigurationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ecrconn + + log.Printf("[DEBUG] Reading ECR Replication Configuration %s", d.Id()) + out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) + if err != nil { + return fmt.Errorf("error reading ECR Replication Configuration: %w", err) + } + + d.Set("registry_id", out.RegistryId) + + if err := d.Set("replication_configuration", flattenEcrReplicationConfigurationReplicationConfiguration(out.ReplicationConfiguration)); err != nil { + return fmt.Errorf("error setting replication_configuration for ECR Replication Configuration: %w", err) + } + + return nil +} + +func resourceAwsEcrReplicationConfigurationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ecrconn + + input := ecr.PutReplicationConfigurationInput{ + ReplicationConfiguration: &ecr.ReplicationConfiguration{ + Rules: []*ecr.ReplicationRule{}, + }, + } + + _, err := conn.PutReplicationConfiguration(&input) + if err != nil { + return fmt.Errorf("error deleting ECR Replication Configuration: %w", err) + } + + return nil +} + +func expandEcrReplicationConfigurationReplicationConfiguration(data []interface{}) *ecr.ReplicationConfiguration { + if len(data) == 0 || data[0] == nil { + return nil + } + + ec := data[0].(map[string]interface{}) + config := &ecr.ReplicationConfiguration{ + Rules: expandEcrReplicationConfigurationReplicationConfigurationRules(ec["rule"].([]interface{})), + } + return config +} + +func flattenEcrReplicationConfigurationReplicationConfiguration(ec *ecr.ReplicationConfiguration) []map[string]interface{} { + if ec == nil { + return nil + } + + config := map[string]interface{}{ + "rule": flattenEcrReplicationConfigurationReplicationConfigurationRules(ec.Rules), + } + + return []map[string]interface{}{ + config, + } +} + +func expandEcrReplicationConfigurationReplicationConfigurationRules(data []interface{}) []*ecr.ReplicationRule { + if len(data) == 0 || data[0] == nil { + return nil + } + + var rules []*ecr.ReplicationRule + + for _, rule := range data { + ec := rule.(map[string]interface{}) + config := &ecr.ReplicationRule{ + Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), + RepositoryFilters: expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec["repository_filter"].([]interface{})), + } + + rules = append(rules, config) + + } + return rules +} + +func flattenEcrReplicationConfigurationReplicationConfigurationRules(ec []*ecr.ReplicationRule) []interface{} { + if len(ec) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range ec { + tfMap := map[string]interface{}{ + "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "repository_filter": flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(apiObject.RepositoryFilters), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} + +func expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(data []interface{}) []*ecr.ReplicationDestination { + if len(data) == 0 || data[0] == nil { + return nil + } + + var dests []*ecr.ReplicationDestination + + for _, dest := range data { + ec := dest.(map[string]interface{}) + config := &ecr.ReplicationDestination{ + Region: aws.String(ec["region"].(string)), + RegistryId: aws.String(ec["registry_id"].(string)), + } + + dests = append(dests, config) + } + return dests +} + +func flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec []*ecr.ReplicationDestination) []interface{} { + if len(ec) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range ec { + tfMap := map[string]interface{}{ + "region": aws.StringValue(apiObject.Region), + "registry_id": aws.StringValue(apiObject.RegistryId), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} + +func expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(data []interface{}) []*ecr.RepositoryFilter { + if len(data) == 0 || data[0] == nil { + return nil + } + + var filters []*ecr.RepositoryFilter + + for _, filter := range data { + ec := filter.(map[string]interface{}) + config := &ecr.RepositoryFilter{ + Filter: aws.String(ec["filter"].(string)), + FilterType: aws.String(ec["filter_type"].(string)), + } + + filters = append(filters, config) + } + return filters +} + +func flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec []*ecr.RepositoryFilter) []interface{} { + if len(ec) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range ec { + tfMap := map[string]interface{}{ + "filter": aws.StringValue(apiObject.Filter), + "filter_type": aws.StringValue(apiObject.FilterType), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} diff --git a/aws/resource_aws_ecr_replication_configuration_test.go b/aws/resource_aws_ecr_replication_configuration_test.go new file mode 100644 index 00000000000..65f99a2ec64 --- /dev/null +++ b/aws/resource_aws_ecr_replication_configuration_test.go @@ -0,0 +1,187 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAWSEcrReplicationConfiguration_basic(t *testing.T) { + resourceName := "aws_ecr_replication_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, ecr.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSEcrReplicationConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcrReplicationConfiguration(testAccGetAlternateRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcrReplicationConfigurationExists(resourceName), + testAccCheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), + testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSEcrReplicationMultipleRegionConfiguration(testAccGetAlternateRegion(), testAccGetThirdRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcrReplicationConfigurationExists(resourceName), + testAccCheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "2"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), + testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.1.region", testAccGetThirdRegion()), + testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.1.registry_id"), + ), + }, + { + Config: testAccAWSEcrReplicationConfiguration(testAccGetAlternateRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcrReplicationConfigurationExists(resourceName), + testAccCheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), + testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), + ), + }, + { + Config: testAccAWSEcrReplicationConfigurationRepositoryFilter(testAccGetAlternateRegion(), "custom-filter1", "custom-filter2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSEcrReplicationConfigurationExists(resourceName), + testAccCheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "custom-filter1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.1.filter", "custom-filter2"), + testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), + ), + }, + }, + }) +} + +func testAccCheckAWSEcrReplicationConfigurationExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + conn := testAccProvider.Meta().(*AWSClient).ecrconn + out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) + if err != nil { + return fmt.Errorf("ECR replication rules not found: %w", err) + } + + if len(out.ReplicationConfiguration.Rules) == 0 { + return fmt.Errorf("ECR replication rules not found") + } + + return nil + } +} + +func testAccCheckAWSEcrReplicationConfigurationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ecrconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ecr_replication_configuration" { + continue + } + + out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) + if err != nil { + return err + } + + if len(out.ReplicationConfiguration.Rules) != 0 { + return fmt.Errorf("ECR replication rules found") + } + } + + return nil +} + +func testAccAWSEcrReplicationConfiguration(region string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + } + } +} +`, region) +} + +func testAccAWSEcrReplicationMultipleRegionConfiguration(region1, region2 string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + + + destination { + region = %[2]q + registry_id = data.aws_caller_identity.current.account_id + } + } + } +} +`, region1, region2) +} + +func testAccAWSEcrReplicationConfigurationRepositoryFilter(region string, filter1Name string, filter2Name string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + repository_filter { + filter = "%[2]q" + filter_type = "PREFIX_MATCH" + } + repository_filter { + filter = "%[3]q" + filter_type = "PREFIX_MATCH" + } + } + } +} +`, region, filter1Name, filter2Name) +} From 93c7f47881f51c97d3de9e8e4940fd5baa7f47bf Mon Sep 17 00:00:00 2001 From: Minuk Song Date: Thu, 23 Sep 2021 12:35:20 +0900 Subject: [PATCH 02/10] Update docs --- ...cr_replication_configuration.html.markdown | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/website/docs/r/ecr_replication_configuration.html.markdown b/website/docs/r/ecr_replication_configuration.html.markdown index ac0db4c05f0..f0824ae0243 100644 --- a/website/docs/r/ecr_replication_configuration.html.markdown +++ b/website/docs/r/ecr_replication_configuration.html.markdown @@ -53,6 +53,30 @@ resource "aws_ecr_replication_configuration" "example" { } ``` +## Repository Filter Usage + +```terraform +data "aws_caller_identity" "current" {} + +data "aws_regions" "example" {} + +resource "aws_ecr_replication_configuration" "example" { + replication_configuration { + rule { + destination { + region = data.aws_regions.example.names[0] + registry_id = data.aws_caller_identity.current.account_id + } + + repository_filter { + filter = "prod-microservice" + filter_type = "PREFIX_MATCH" + } + } + } +} +``` + ## Argument Reference The following arguments are supported: @@ -66,12 +90,18 @@ The following arguments are supported: ### Rule * `destination` - (Required) the details of a replication destination. See [Destination](#destination). +* `repository_filter` - (Optional) filters for a replication rule. See [Repository Filter](#repository-filter). ### Destination * `region` - (Required) A Region to replicate to. * `registry_id` - (Required) The account ID of the destination registry to replicate to. +### Repository Filter +* `filter` - (Required) The repository filter details. +* `filter_type` - (Required) The repository filter type. The only supported value is PREFIX_MATCH, which is a repository name prefix specified with the filter parameter. + + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 058fc9f1675b6ad898c4d623896d1054aaac5421 Mon Sep 17 00:00:00 2001 From: Minuk Song Date: Thu, 23 Sep 2021 12:38:40 +0900 Subject: [PATCH 03/10] reformat code --- aws/resource_aws_ecr_replication_configuration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws/resource_aws_ecr_replication_configuration.go b/aws/resource_aws_ecr_replication_configuration.go index 5582355b7d9..00d4574f17f 100644 --- a/aws/resource_aws_ecr_replication_configuration.go +++ b/aws/resource_aws_ecr_replication_configuration.go @@ -188,7 +188,7 @@ func flattenEcrReplicationConfigurationReplicationConfigurationRules(ec []*ecr.R for _, apiObject := range ec { tfMap := map[string]interface{}{ - "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), "repository_filter": flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(apiObject.RepositoryFilters), } From 4d46148fa09a4150a9761ef85c74340ab3c6eb9f Mon Sep 17 00:00:00 2001 From: Minuk Song Date: Thu, 23 Sep 2021 16:46:25 +0900 Subject: [PATCH 04/10] fix test code --- aws/resource_aws_ecr_replication_configuration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aws/resource_aws_ecr_replication_configuration_test.go b/aws/resource_aws_ecr_replication_configuration_test.go index 65f99a2ec64..f7872c97785 100644 --- a/aws/resource_aws_ecr_replication_configuration_test.go +++ b/aws/resource_aws_ecr_replication_configuration_test.go @@ -173,11 +173,11 @@ resource "aws_ecr_replication_configuration" "test" { registry_id = data.aws_caller_identity.current.account_id } repository_filter { - filter = "%[2]q" + filter = %[2]q filter_type = "PREFIX_MATCH" } repository_filter { - filter = "%[3]q" + filter = %[3]q filter_type = "PREFIX_MATCH" } } From 99de12f83e63ee17a6ed19b1c9c676dba67e6f54 Mon Sep 17 00:00:00 2001 From: Minuk Song Date: Wed, 20 Oct 2021 15:12:43 +0900 Subject: [PATCH 05/10] Rebase refactored code --- ...ource_aws_ecr_replication_configuration.go | 275 ------------------ ..._aws_ecr_replication_configuration_test.go | 187 ------------ .../service/ecr/replication_configuration.go | 62 +++- .../ecr/replication_configuration_test.go | 29 +- 4 files changed, 88 insertions(+), 465 deletions(-) delete mode 100644 aws/resource_aws_ecr_replication_configuration.go delete mode 100644 aws/resource_aws_ecr_replication_configuration_test.go diff --git a/aws/resource_aws_ecr_replication_configuration.go b/aws/resource_aws_ecr_replication_configuration.go deleted file mode 100644 index 00d4574f17f..00000000000 --- a/aws/resource_aws_ecr_replication_configuration.go +++ /dev/null @@ -1,275 +0,0 @@ -package aws - -import ( - "fmt" - "log" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func resourceAwsEcrReplicationConfiguration() *schema.Resource { - return &schema.Resource{ - Create: resourceAwsEcrReplicationConfigurationPut, - Read: resourceAwsEcrReplicationConfigurationRead, - Update: resourceAwsEcrReplicationConfigurationPut, - Delete: resourceAwsEcrReplicationConfigurationDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "registry_id": { - Type: schema.TypeString, - Computed: true, - }, - "replication_configuration": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "rule": { - Type: schema.TypeList, - Required: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "destination": { - Type: schema.TypeList, - Required: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "region": { - Type: schema.TypeString, - Required: true, - }, - "registry_id": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateAwsAccountId, - }, - }, - }, - }, - "repository_filter": { - Type: schema.TypeList, - Optional: true, - MinItems: 1, - MaxItems: 100, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "filter": { - Type: schema.TypeString, - Required: true, - }, - "filter_type": { - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} - -func resourceAwsEcrReplicationConfigurationPut(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ecrconn - - input := ecr.PutReplicationConfigurationInput{ - ReplicationConfiguration: expandEcrReplicationConfigurationReplicationConfiguration(d.Get("replication_configuration").([]interface{})), - } - - _, err := conn.PutReplicationConfiguration(&input) - if err != nil { - return fmt.Errorf("error creating ECR Replication Configuration: %w", err) - } - - d.SetId(meta.(*AWSClient).accountid) - - return resourceAwsEcrReplicationConfigurationRead(d, meta) -} - -func resourceAwsEcrReplicationConfigurationRead(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ecrconn - - log.Printf("[DEBUG] Reading ECR Replication Configuration %s", d.Id()) - out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) - if err != nil { - return fmt.Errorf("error reading ECR Replication Configuration: %w", err) - } - - d.Set("registry_id", out.RegistryId) - - if err := d.Set("replication_configuration", flattenEcrReplicationConfigurationReplicationConfiguration(out.ReplicationConfiguration)); err != nil { - return fmt.Errorf("error setting replication_configuration for ECR Replication Configuration: %w", err) - } - - return nil -} - -func resourceAwsEcrReplicationConfigurationDelete(d *schema.ResourceData, meta interface{}) error { - conn := meta.(*AWSClient).ecrconn - - input := ecr.PutReplicationConfigurationInput{ - ReplicationConfiguration: &ecr.ReplicationConfiguration{ - Rules: []*ecr.ReplicationRule{}, - }, - } - - _, err := conn.PutReplicationConfiguration(&input) - if err != nil { - return fmt.Errorf("error deleting ECR Replication Configuration: %w", err) - } - - return nil -} - -func expandEcrReplicationConfigurationReplicationConfiguration(data []interface{}) *ecr.ReplicationConfiguration { - if len(data) == 0 || data[0] == nil { - return nil - } - - ec := data[0].(map[string]interface{}) - config := &ecr.ReplicationConfiguration{ - Rules: expandEcrReplicationConfigurationReplicationConfigurationRules(ec["rule"].([]interface{})), - } - return config -} - -func flattenEcrReplicationConfigurationReplicationConfiguration(ec *ecr.ReplicationConfiguration) []map[string]interface{} { - if ec == nil { - return nil - } - - config := map[string]interface{}{ - "rule": flattenEcrReplicationConfigurationReplicationConfigurationRules(ec.Rules), - } - - return []map[string]interface{}{ - config, - } -} - -func expandEcrReplicationConfigurationReplicationConfigurationRules(data []interface{}) []*ecr.ReplicationRule { - if len(data) == 0 || data[0] == nil { - return nil - } - - var rules []*ecr.ReplicationRule - - for _, rule := range data { - ec := rule.(map[string]interface{}) - config := &ecr.ReplicationRule{ - Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), - RepositoryFilters: expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec["repository_filter"].([]interface{})), - } - - rules = append(rules, config) - - } - return rules -} - -func flattenEcrReplicationConfigurationReplicationConfigurationRules(ec []*ecr.ReplicationRule) []interface{} { - if len(ec) == 0 { - return nil - } - - var tfList []interface{} - - for _, apiObject := range ec { - tfMap := map[string]interface{}{ - "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), - "repository_filter": flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(apiObject.RepositoryFilters), - } - - tfList = append(tfList, tfMap) - } - - return tfList -} - -func expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(data []interface{}) []*ecr.ReplicationDestination { - if len(data) == 0 || data[0] == nil { - return nil - } - - var dests []*ecr.ReplicationDestination - - for _, dest := range data { - ec := dest.(map[string]interface{}) - config := &ecr.ReplicationDestination{ - Region: aws.String(ec["region"].(string)), - RegistryId: aws.String(ec["registry_id"].(string)), - } - - dests = append(dests, config) - } - return dests -} - -func flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec []*ecr.ReplicationDestination) []interface{} { - if len(ec) == 0 { - return nil - } - - var tfList []interface{} - - for _, apiObject := range ec { - tfMap := map[string]interface{}{ - "region": aws.StringValue(apiObject.Region), - "registry_id": aws.StringValue(apiObject.RegistryId), - } - - tfList = append(tfList, tfMap) - } - - return tfList -} - -func expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(data []interface{}) []*ecr.RepositoryFilter { - if len(data) == 0 || data[0] == nil { - return nil - } - - var filters []*ecr.RepositoryFilter - - for _, filter := range data { - ec := filter.(map[string]interface{}) - config := &ecr.RepositoryFilter{ - Filter: aws.String(ec["filter"].(string)), - FilterType: aws.String(ec["filter_type"].(string)), - } - - filters = append(filters, config) - } - return filters -} - -func flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec []*ecr.RepositoryFilter) []interface{} { - if len(ec) == 0 { - return nil - } - - var tfList []interface{} - - for _, apiObject := range ec { - tfMap := map[string]interface{}{ - "filter": aws.StringValue(apiObject.Filter), - "filter_type": aws.StringValue(apiObject.FilterType), - } - - tfList = append(tfList, tfMap) - } - - return tfList -} diff --git a/aws/resource_aws_ecr_replication_configuration_test.go b/aws/resource_aws_ecr_replication_configuration_test.go deleted file mode 100644 index f7872c97785..00000000000 --- a/aws/resource_aws_ecr_replication_configuration_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func TestAccAWSEcrReplicationConfiguration_basic(t *testing.T) { - resourceName := "aws_ecr_replication_configuration.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ErrorCheck: testAccErrorCheck(t, ecr.EndpointsID), - Providers: testAccProviders, - CheckDestroy: testAccCheckAWSEcrReplicationConfigurationDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAWSEcrReplicationConfiguration(testAccGetAlternateRegion()), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEcrReplicationConfigurationExists(resourceName), - testAccCheckResourceAttrAccountID(resourceName, "registry_id"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), - testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAWSEcrReplicationMultipleRegionConfiguration(testAccGetAlternateRegion(), testAccGetThirdRegion()), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEcrReplicationConfigurationExists(resourceName), - testAccCheckResourceAttrAccountID(resourceName, "registry_id"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "2"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), - testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.1.region", testAccGetThirdRegion()), - testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.1.registry_id"), - ), - }, - { - Config: testAccAWSEcrReplicationConfiguration(testAccGetAlternateRegion()), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEcrReplicationConfigurationExists(resourceName), - testAccCheckResourceAttrAccountID(resourceName, "registry_id"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), - testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), - ), - }, - { - Config: testAccAWSEcrReplicationConfigurationRepositoryFilter(testAccGetAlternateRegion(), "custom-filter1", "custom-filter2"), - Check: resource.ComposeTestCheckFunc( - testAccCheckAWSEcrReplicationConfigurationExists(resourceName), - testAccCheckResourceAttrAccountID(resourceName, "registry_id"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", testAccGetAlternateRegion()), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "custom-filter1"), - resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.1.filter", "custom-filter2"), - testAccCheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), - ), - }, - }, - }) -} - -func testAccCheckAWSEcrReplicationConfigurationExists(name string) resource.TestCheckFunc { - return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("Not found: %s", name) - } - - conn := testAccProvider.Meta().(*AWSClient).ecrconn - out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) - if err != nil { - return fmt.Errorf("ECR replication rules not found: %w", err) - } - - if len(out.ReplicationConfiguration.Rules) == 0 { - return fmt.Errorf("ECR replication rules not found") - } - - return nil - } -} - -func testAccCheckAWSEcrReplicationConfigurationDestroy(s *terraform.State) error { - conn := testAccProvider.Meta().(*AWSClient).ecrconn - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_ecr_replication_configuration" { - continue - } - - out, err := conn.DescribeRegistry(&ecr.DescribeRegistryInput{}) - if err != nil { - return err - } - - if len(out.ReplicationConfiguration.Rules) != 0 { - return fmt.Errorf("ECR replication rules found") - } - } - - return nil -} - -func testAccAWSEcrReplicationConfiguration(region string) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -resource "aws_ecr_replication_configuration" "test" { - replication_configuration { - rule { - destination { - region = %[1]q - registry_id = data.aws_caller_identity.current.account_id - } - } - } -} -`, region) -} - -func testAccAWSEcrReplicationMultipleRegionConfiguration(region1, region2 string) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -resource "aws_ecr_replication_configuration" "test" { - replication_configuration { - rule { - destination { - region = %[1]q - registry_id = data.aws_caller_identity.current.account_id - } - - - destination { - region = %[2]q - registry_id = data.aws_caller_identity.current.account_id - } - } - } -} -`, region1, region2) -} - -func testAccAWSEcrReplicationConfigurationRepositoryFilter(region string, filter1Name string, filter2Name string) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -resource "aws_ecr_replication_configuration" "test" { - replication_configuration { - rule { - destination { - region = %[1]q - registry_id = data.aws_caller_identity.current.account_id - } - repository_filter { - filter = %[2]q - filter_type = "PREFIX_MATCH" - } - repository_filter { - filter = %[3]q - filter_type = "PREFIX_MATCH" - } - } - } -} -`, region, filter1Name, filter2Name) -} diff --git a/internal/service/ecr/replication_configuration.go b/internal/service/ecr/replication_configuration.go index 02faae98982..73d58b061b4 100644 --- a/internal/service/ecr/replication_configuration.go +++ b/internal/service/ecr/replication_configuration.go @@ -55,6 +55,24 @@ func ResourceReplicationConfiguration() *schema.Resource { }, }, }, + "repository_filter": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 100, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Required: true, + }, + "filter_type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, }, }, @@ -153,7 +171,8 @@ func expandEcrReplicationConfigurationReplicationConfigurationRules(data []inter for _, rule := range data { ec := rule.(map[string]interface{}) config := &ecr.ReplicationRule{ - Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), + Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), + RepositoryFilters: expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec["repository_filter"].([]interface{})), } rules = append(rules, config) @@ -171,7 +190,8 @@ func flattenEcrReplicationConfigurationReplicationConfigurationRules(ec []*ecr.R for _, apiObject := range ec { tfMap := map[string]interface{}{ - "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "repository_filter": flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(apiObject.RepositoryFilters), } tfList = append(tfList, tfMap) @@ -217,3 +237,41 @@ func flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations return tfList } + +func expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(data []interface{}) []*ecr.RepositoryFilter { + if len(data) == 0 || data[0] == nil { + return nil + } + + var filters []*ecr.RepositoryFilter + + for _, filter := range data { + ec := filter.(map[string]interface{}) + config := &ecr.RepositoryFilter{ + Filter: aws.String(ec["filter"].(string)), + FilterType: aws.String(ec["filter_type"].(string)), + } + + filters = append(filters, config) + } + return filters +} + +func flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec []*ecr.RepositoryFilter) []interface{} { + if len(ec) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range ec { + tfMap := map[string]interface{}{ + "filter": aws.StringValue(apiObject.Filter), + "filter_type": aws.StringValue(apiObject.FilterType), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} diff --git a/internal/service/ecr/replication_configuration_test.go b/internal/service/ecr/replication_configuration_test.go index 1ee9eb193fa..e692e3f7850 100644 --- a/internal/service/ecr/replication_configuration_test.go +++ b/internal/service/ecr/replication_configuration_test.go @@ -52,7 +52,7 @@ func TestAccECRReplicationConfiguration_basic(t *testing.T) { ), }, { - Config: testAccReplicationConfiguration(acctest.AlternateRegion()), + Config: testAccReplicationConfigurationRepositoryFilter(acctest.AlternateRegion(), "custom-filter1", "custom-filter2"), Check: resource.ComposeTestCheckFunc( testAccCheckReplicationConfigurationExists(resourceName), acctest.CheckResourceAttrAccountID(resourceName, "registry_id"), @@ -60,6 +60,8 @@ func TestAccECRReplicationConfiguration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.#", "1"), resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.destination.0.region", acctest.AlternateRegion()), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "custom-filter1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.1.filter", "custom-filter2"), acctest.CheckResourceAttrAccountID(resourceName, "replication_configuration.0.rule.0.destination.0.registry_id"), ), }, @@ -148,3 +150,28 @@ resource "aws_ecr_replication_configuration" "test" { } `, region1, region2) } + +func testAccReplicationConfigurationRepositoryFilter(region string, filter1Name string, filter2Name string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + repository_filter { + filter = %[2]q + filter_type = "PREFIX_MATCH" + } + repository_filter { + filter = %[3]q + filter_type = "PREFIX_MATCH" + } + } + } +} +`, region, filter1Name, filter2Name) +} From 3c714971300f9ca50956206dfa8bebb6bb537d08 Mon Sep 17 00:00:00 2001 From: Stijn De Haes Date: Mon, 11 Oct 2021 07:48:48 +0200 Subject: [PATCH 06/10] Added support for repository filter to ecr replications Signed-off-by: Stijn De Haes --- .changelog/21231.txt | 4 + .../service/ecr/replication_configuration.go | 60 ++++++++++- .../ecr/replication_configuration_test.go | 102 ++++++++++++++++++ ...cr_replication_configuration.html.markdown | 31 ++++++ 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 .changelog/21231.txt diff --git a/.changelog/21231.txt b/.changelog/21231.txt new file mode 100644 index 00000000000..fe9f7f4fb19 --- /dev/null +++ b/.changelog/21231.txt @@ -0,0 +1,4 @@ + +```release-note:enhancement +resource/aws_ecr_replication_configuration: Added support for repository filters +``` \ No newline at end of file diff --git a/internal/service/ecr/replication_configuration.go b/internal/service/ecr/replication_configuration.go index 02faae98982..02704823708 100644 --- a/internal/service/ecr/replication_configuration.go +++ b/internal/service/ecr/replication_configuration.go @@ -55,6 +55,22 @@ func ResourceReplicationConfiguration() *schema.Resource { }, }, }, + "repository_filter": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "filter": { + Type: schema.TypeString, + Required: true, + }, + "filter_type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, }, }, }, @@ -153,7 +169,8 @@ func expandEcrReplicationConfigurationReplicationConfigurationRules(data []inter for _, rule := range data { ec := rule.(map[string]interface{}) config := &ecr.ReplicationRule{ - Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), + Destinations: expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec["destination"].([]interface{})), + RepositoryFilters: expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec["repository_filter"].([]interface{})), } rules = append(rules, config) @@ -171,7 +188,8 @@ func flattenEcrReplicationConfigurationReplicationConfigurationRules(ec []*ecr.R for _, apiObject := range ec { tfMap := map[string]interface{}{ - "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "destination": flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(apiObject.Destinations), + "repository_filter": flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(apiObject.RepositoryFilters), } tfList = append(tfList, tfMap) @@ -199,6 +217,25 @@ func expandEcrReplicationConfigurationReplicationConfigurationRulesDestinations( return dests } +func expandEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(data []interface{}) []*ecr.RepositoryFilter { + if len(data) == 0 || data[0] == nil { + return nil + } + + var dests []*ecr.RepositoryFilter + + for _, dest := range data { + ec := dest.(map[string]interface{}) + config := &ecr.RepositoryFilter{ + Filter: aws.String(ec["filter"].(string)), + FilterType: aws.String(ec["filter_type"].(string)), + } + + dests = append(dests, config) + } + return dests +} + func flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations(ec []*ecr.ReplicationDestination) []interface{} { if len(ec) == 0 { return nil @@ -217,3 +254,22 @@ func flattenEcrReplicationConfigurationReplicationConfigurationRulesDestinations return tfList } + +func flattenEcrReplicationConfigurationReplicationConfigurationRulesRepositoryFilters(ec []*ecr.RepositoryFilter) []interface{} { + if len(ec) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range ec { + tfMap := map[string]interface{}{ + "filter": aws.StringValue(apiObject.Filter), + "filter_type": aws.StringValue(apiObject.FilterType), + } + + tfList = append(tfList, tfMap) + } + + return tfList +} diff --git a/internal/service/ecr/replication_configuration_test.go b/internal/service/ecr/replication_configuration_test.go index 1ee9eb193fa..7b915cd74cc 100644 --- a/internal/service/ecr/replication_configuration_test.go +++ b/internal/service/ecr/replication_configuration_test.go @@ -67,6 +67,62 @@ func TestAccECRReplicationConfiguration_basic(t *testing.T) { }) } +func TestAccECRReplicationConfiguration_repositoryFilter(t *testing.T) { + resourceName := "aws_ecr_replication_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, ecr.EndpointsID), + Providers: acctest.Providers, + CheckDestroy: testAccCheckReplicationConfigurationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSEcrReplicationConfigurationRepositoryFilter(acctest.AlternateRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckReplicationConfigurationExists(resourceName), + acctest.CheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "a-prefix"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter_type", "PREFIX_MATCH"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAWSEcrReplicationConfigurationRepositoryFilterMultiple(acctest.AlternateRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckReplicationConfigurationExists(resourceName), + acctest.CheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.#", "2"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "a-prefix"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter_type", "PREFIX_MATCH"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.1.filter", "a-second-prefix"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.1.filter_type", "PREFIX_MATCH"), + ), + }, + { + Config: testAccAWSEcrReplicationConfigurationRepositoryFilter(acctest.AlternateRegion()), + Check: resource.ComposeTestCheckFunc( + testAccCheckReplicationConfigurationExists(resourceName), + acctest.CheckResourceAttrAccountID(resourceName, "registry_id"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.#", "1"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter", "a-prefix"), + resource.TestCheckResourceAttr(resourceName, "replication_configuration.0.rule.0.repository_filter.0.filter_type", "PREFIX_MATCH"), + ), + }, + }, + }) +} + func testAccCheckReplicationConfigurationExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { _, ok := s.RootModule().Resources[name] @@ -148,3 +204,49 @@ resource "aws_ecr_replication_configuration" "test" { } `, region1, region2) } + +func testAccAWSEcrReplicationConfigurationRepositoryFilter(region string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + repository_filter { + filter = "a-prefix" + filter_type = "PREFIX_MATCH" + } + } + } +} +`, region) +} + +func testAccAWSEcrReplicationConfigurationRepositoryFilterMultiple(region string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecr_replication_configuration" "test" { + replication_configuration { + rule { + destination { + region = %[1]q + registry_id = data.aws_caller_identity.current.account_id + } + repository_filter { + filter = "a-prefix" + filter_type = "PREFIX_MATCH" + } + repository_filter { + filter = "a-second-prefix" + filter_type = "PREFIX_MATCH" + } + } + } +} +`, region) +} diff --git a/website/docs/r/ecr_replication_configuration.html.markdown b/website/docs/r/ecr_replication_configuration.html.markdown index ac0db4c05f0..8d2c645ff27 100644 --- a/website/docs/r/ecr_replication_configuration.html.markdown +++ b/website/docs/r/ecr_replication_configuration.html.markdown @@ -53,6 +53,31 @@ resource "aws_ecr_replication_configuration" "example" { } ``` +## Repository Filter Usage + +```terraform +data "aws_caller_identity" "current" {} + +data "aws_regions" "example" {} + +resource "aws_ecr_replication_configuration" "example" { + replication_configuration { + rule { + destination { + region = data.aws_regions.example.names[0] + registry_id = data.aws_caller_identity.current.account_id + } + repository_filter { + filter = "a-prefix" + filter_type = "PREFIX_MATCH" + } + } + } +} +``` + + + ## Argument Reference The following arguments are supported: @@ -66,12 +91,18 @@ The following arguments are supported: ### Rule * `destination` - (Required) the details of a replication destination. See [Destination](#destination). +* `repository_filter` - (Optional) the details of a replication repository filter. See [Repository Filter](#repository-filter). ### Destination * `region` - (Required) A Region to replicate to. * `registry_id` - (Required) The account ID of the destination registry to replicate to. +### Repository Filter + +* `filter` - (Required) The repository name prefixe to configure the replication for. +* `registry_id` - (Required) The repository filter type, only support value is `PREFIX_MATCH`. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 09e0df0d6c2434b99f3f6b3e991fba3c5262fd1b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Dec 2021 09:04:04 -0500 Subject: [PATCH 07/10] Documentation tweaks. --- .changelog/21231.txt | 2 +- website/docs/r/ecr_replication_configuration.html.markdown | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.changelog/21231.txt b/.changelog/21231.txt index fe9f7f4fb19..80d6553af73 100644 --- a/.changelog/21231.txt +++ b/.changelog/21231.txt @@ -1,4 +1,4 @@ ```release-note:enhancement -resource/aws_ecr_replication_configuration: Added support for repository filters +resource/aws_ecr_replication_configuration: Add `repository_filter` to `replication_configuration` block ``` \ No newline at end of file diff --git a/website/docs/r/ecr_replication_configuration.html.markdown b/website/docs/r/ecr_replication_configuration.html.markdown index 3fcdea03dba..94b739021e8 100644 --- a/website/docs/r/ecr_replication_configuration.html.markdown +++ b/website/docs/r/ecr_replication_configuration.html.markdown @@ -101,7 +101,6 @@ The following arguments are supported: * `filter` - (Required) The repository filter details. * `filter_type` - (Required) The repository filter type. The only supported value is `PREFIX_MATCH`, which is a repository name prefix specified with the filter parameter. - ## Attributes Reference In addition to all arguments above, the following attributes are exported: From 18aed860d62f608c5efaecdc381cb74df6f49413 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Dec 2021 09:11:11 -0500 Subject: [PATCH 08/10] Fix markdown-lint error: 'MD032/blanks-around-lists Lists should be surrounded by blank lines'. --- website/docs/r/ecr_replication_configuration.html.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/r/ecr_replication_configuration.html.markdown b/website/docs/r/ecr_replication_configuration.html.markdown index 94b739021e8..7603b1011ea 100644 --- a/website/docs/r/ecr_replication_configuration.html.markdown +++ b/website/docs/r/ecr_replication_configuration.html.markdown @@ -98,6 +98,7 @@ The following arguments are supported: * `registry_id` - (Required) The account ID of the destination registry to replicate to. ### Repository Filter + * `filter` - (Required) The repository filter details. * `filter_type` - (Required) The repository filter type. The only supported value is `PREFIX_MATCH`, which is a repository name prefix specified with the filter parameter. From c157590aa0473e697812b82dfb43a6aa5f9249ca Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Dec 2021 09:31:09 -0500 Subject: [PATCH 09/10] r/aws_ecr_replication_configuration: Validate region. --- internal/service/ecr/replication_configuration.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/service/ecr/replication_configuration.go b/internal/service/ecr/replication_configuration.go index 8b08c37cdaa..dcb0ce7516a 100644 --- a/internal/service/ecr/replication_configuration.go +++ b/internal/service/ecr/replication_configuration.go @@ -45,8 +45,9 @@ func ResourceReplicationConfiguration() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "region": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidRegionName, }, "registry_id": { Type: schema.TypeString, From 9b11f74879d454395bfe199362528a15cf3553c6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 20 Dec 2021 09:34:21 -0500 Subject: [PATCH 10/10] r/aws_ecr_replication_configuration: Serialize tests. --- .../ecr/replication_configuration_test.go | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/internal/service/ecr/replication_configuration_test.go b/internal/service/ecr/replication_configuration_test.go index 17d845f1bb0..114cbfe5641 100644 --- a/internal/service/ecr/replication_configuration_test.go +++ b/internal/service/ecr/replication_configuration_test.go @@ -11,10 +11,25 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" ) -func TestAccECRReplicationConfiguration_basic(t *testing.T) { +func TestAccECRReplicationConfiguration_serial(t *testing.T) { + testFuncs := map[string]func(t *testing.T){ + "basic": testAccReplicationConfiguration_basic, + "repositoryFilter": testAccReplicationConfiguration_repositoryFilter, + } + + for name, testFunc := range testFuncs { + testFunc := testFunc + + t.Run(name, func(t *testing.T) { + testFunc(t) + }) + } +} + +func testAccReplicationConfiguration_basic(t *testing.T) { resourceName := "aws_ecr_replication_configuration.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ecr.EndpointsID), Providers: acctest.Providers, @@ -70,10 +85,10 @@ func TestAccECRReplicationConfiguration_basic(t *testing.T) { }) } -func TestAccECRReplicationConfiguration_repositoryFilter(t *testing.T) { +func testAccReplicationConfiguration_repositoryFilter(t *testing.T) { resourceName := "aws_ecr_replication_configuration.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, ecr.EndpointsID), Providers: acctest.Providers,