From efc5819d1b1fac57a7ac04dbe3609aa943464816 Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Sat, 5 Dec 2020 12:56:07 +0000 Subject: [PATCH] r/aws_ec2_managed_prefix_list_entry: new resource --- aws/provider.go | 1 + ...ource_aws_ec2_managed_prefix_list_entry.go | 289 ++++++++++ ..._aws_ec2_managed_prefix_list_entry_test.go | 498 ++++++++++++++++++ ...c2_managed_prefix_list_entry.html.markdown | 66 +++ 4 files changed, 854 insertions(+) create mode 100644 aws/resource_aws_ec2_managed_prefix_list_entry.go create mode 100644 aws/resource_aws_ec2_managed_prefix_list_entry_test.go create mode 100644 website/docs/r/ec2_managed_prefix_list_entry.html.markdown diff --git a/aws/provider.go b/aws/provider.go index ab4733b46f7e..61c70d73e105 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -600,6 +600,7 @@ func Provider() *schema.Provider { "aws_ec2_local_gateway_route": resourceAwsEc2LocalGatewayRoute(), "aws_ec2_local_gateway_route_table_vpc_association": resourceAwsEc2LocalGatewayRouteTableVpcAssociation(), "aws_ec2_managed_prefix_list": resourceAwsEc2ManagedPrefixList(), + "aws_ec2_managed_prefix_list_entry": resourceAwsEc2ManagedPrefixListEntry(), "aws_ec2_tag": resourceAwsEc2Tag(), "aws_ec2_traffic_mirror_filter": resourceAwsEc2TrafficMirrorFilter(), "aws_ec2_traffic_mirror_filter_rule": resourceAwsEc2TrafficMirrorFilterRule(), diff --git a/aws/resource_aws_ec2_managed_prefix_list_entry.go b/aws/resource_aws_ec2_managed_prefix_list_entry.go new file mode 100644 index 000000000000..82a91db7301c --- /dev/null +++ b/aws/resource_aws_ec2_managed_prefix_list_entry.go @@ -0,0 +1,289 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsEc2ManagedPrefixListEntry() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2ManagedPrefixListEntryCreate, + Read: resourceAwsEc2ManagedPrefixListEntryRead, + Update: resourceAwsEc2ManagedPrefixListEntryUpdate, + Delete: resourceAwsEc2ManagedPrefixListEntryDelete, + + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + ss := strings.Split(d.Id(), "_") + if len(ss) != 2 || ss[0] == "" || ss[1] == "" { + return nil, fmt.Errorf("invalid id %s: expected pl-123456_1.0.0.0/8", d.Id()) + } + + d.Set("prefix_list_id", ss[0]) + d.Set("cidr_block", ss[1]) + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "prefix_list_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "cidr_block": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.IsCIDR, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: "", + ValidateFunc: validation.StringLenBetween(0, 255), + }, + }, + } +} + +func resourceAwsEc2ManagedPrefixListEntryCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + prefixListId := d.Get("prefix_list_id").(string) + cidrBlock := d.Get("cidr_block").(string) + + log.Printf( + "[INFO] adding entry %s to prefix list %s...", + cidrBlock, prefixListId) + + err := modifyAwsManagedPrefixListConcurrently( + prefixListId, conn, d.Timeout(schema.TimeoutUpdate), + ec2.ModifyManagedPrefixListInput{ + PrefixListId: aws.String(prefixListId), + CurrentVersion: nil, // set by modifyAwsManagedPrefixListConcurrently + AddEntries: []*ec2.AddPrefixListEntry{ + { + Cidr: aws.String(cidrBlock), + Description: aws.String(d.Get("description").(string)), + }, + }, + }, + func(pl *ec2.ManagedPrefixList) *resource.RetryError { + currentVersion := int(aws.Int64Value(pl.Version)) + + _, ok, err := getManagedPrefixListEntryByCIDR(prefixListId, conn, currentVersion, cidrBlock) + switch { + case err != nil: + return resource.NonRetryableError(err) + case ok: + return resource.NonRetryableError(errors.New("an entry for this cidr block already exists")) + } + + return nil + }) + + if err != nil { + return fmt.Errorf("failed to add entry %s to prefix list %s: %s", cidrBlock, prefixListId, err) + } + + d.SetId(fmt.Sprintf("%s_%s", prefixListId, cidrBlock)) + + return resourceAwsEc2ManagedPrefixListEntryRead(d, meta) +} + +func resourceAwsEc2ManagedPrefixListEntryRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + prefixListId := d.Get("prefix_list_id").(string) + cidrBlock := d.Get("cidr_block").(string) + + entry, ok, err := getManagedPrefixListEntryByCIDR(prefixListId, conn, 0, cidrBlock) + switch { + case err != nil: + return err + case !ok: + log.Printf( + "[WARN] entry %s of managed prefix list %s not found; removing from state.", + cidrBlock, prefixListId) + d.SetId("") + return nil + } + + d.Set("description", entry.Description) + + return nil +} + +func resourceAwsEc2ManagedPrefixListEntryUpdate(d *schema.ResourceData, meta interface{}) error { + if !d.HasChange("description") { + return fmt.Errorf("all attributes except description should force new resource") + } + + conn := meta.(*AWSClient).ec2conn + prefixListId := d.Get("prefix_list_id").(string) + cidrBlock := d.Get("cidr_block").(string) + + err := modifyAwsManagedPrefixListConcurrently( + prefixListId, conn, d.Timeout(schema.TimeoutUpdate), + ec2.ModifyManagedPrefixListInput{ + PrefixListId: aws.String(prefixListId), + CurrentVersion: nil, // set by modifyAwsManagedPrefixListConcurrently + AddEntries: []*ec2.AddPrefixListEntry{ + { + Cidr: aws.String(cidrBlock), + Description: aws.String(d.Get("description").(string)), + }, + }, + }, + nil) + + if err != nil { + return fmt.Errorf("failed to update entry %s in prefix list %s: %s", cidrBlock, prefixListId, err) + } + + return resourceAwsEc2ManagedPrefixListEntryRead(d, meta) +} + +func resourceAwsEc2ManagedPrefixListEntryDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + prefixListId := d.Get("prefix_list_id").(string) + cidrBlock := d.Get("cidr_block").(string) + + err := modifyAwsManagedPrefixListConcurrently( + prefixListId, conn, d.Timeout(schema.TimeoutUpdate), + ec2.ModifyManagedPrefixListInput{ + PrefixListId: aws.String(prefixListId), + CurrentVersion: nil, // set by modifyAwsManagedPrefixListConcurrently + RemoveEntries: []*ec2.RemovePrefixListEntry{ + { + Cidr: aws.String(cidrBlock), + }, + }, + }, + nil) + + switch { + case isResourceNotFoundError(err): + log.Printf("[WARN] managed prefix list %s not found; removing from state", prefixListId) + return nil + case err != nil: + return fmt.Errorf("failed to remove entry %s from prefix list %s: %s", cidrBlock, prefixListId, err) + } + + return nil +} + +func getManagedPrefixListEntryByCIDR( + id string, + conn *ec2.EC2, + version int, + cidr string, +) (*ec2.PrefixListEntry, bool, error) { + input := ec2.GetManagedPrefixListEntriesInput{ + PrefixListId: aws.String(id), + } + + if version > 0 { + input.TargetVersion = aws.Int64(int64(version)) + } + + result := (*ec2.PrefixListEntry)(nil) + + err := conn.GetManagedPrefixListEntriesPages( + &input, + func(output *ec2.GetManagedPrefixListEntriesOutput, last bool) bool { + for _, entry := range output.Entries { + entryCidr := aws.StringValue(entry.Cidr) + if entryCidr == cidr { + result = entry + return false + } + } + + return true + }) + + switch { + case isAWSErr(err, "InvalidPrefixListID.NotFound", ""): + return nil, false, nil + case err != nil: + return nil, false, fmt.Errorf("failed to get entries in prefix list %s: %v", id, err) + case result == nil: + return nil, false, nil + } + + return result, true, nil +} + +func modifyAwsManagedPrefixListConcurrently( + id string, + conn *ec2.EC2, + timeout time.Duration, + input ec2.ModifyManagedPrefixListInput, + check func(pl *ec2.ManagedPrefixList) *resource.RetryError, +) error { + isModified := false + err := resource.Retry(timeout, func() *resource.RetryError { + if !isModified { + pl, ok, err := getManagedPrefixList(id, conn) + switch { + case err != nil: + return resource.NonRetryableError(err) + case !ok: + return resource.NonRetryableError(&resource.NotFoundError{}) + } + + input.CurrentVersion = pl.Version + + if check != nil { + if err := check(pl); err != nil { + return err + } + } + + switch _, err := conn.ModifyManagedPrefixList(&input); { + case isManagedPrefixListModificationConflictErr(err): + return resource.RetryableError(err) + case err != nil: + return resource.NonRetryableError(fmt.Errorf("modify failed: %s", err)) + } + + isModified = true + } + + switch settled, err := isAwsManagedPrefixListSettled(id, conn); { + case err != nil: + return resource.NonRetryableError(fmt.Errorf("resource failed to settle: %s", err)) + case !settled: + return resource.RetryableError(errors.New("resource not yet settled")) + } + + return nil + }) + + if tfresource.TimedOut(err) { + return err + } + + if err != nil { + return err + } + + return nil +} + +func isManagedPrefixListModificationConflictErr(err error) bool { + return isAWSErr(err, "IncorrectState", "in the current state (modify-in-progress)") || + isAWSErr(err, "IncorrectState", "in the current state (create-in-progress)") || + isAWSErr(err, "PrefixListVersionMismatch", "") || + isAWSErr(err, "ConcurrentMutationLimitExceeded", "") +} diff --git a/aws/resource_aws_ec2_managed_prefix_list_entry_test.go b/aws/resource_aws_ec2_managed_prefix_list_entry_test.go new file mode 100644 index 000000000000..4c241a4068e2 --- /dev/null +++ b/aws/resource_aws_ec2_managed_prefix_list_entry_test.go @@ -0,0 +1,498 @@ +package aws + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "strings" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccAwsEc2ManagedPrefixListEntry_basic(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list_entry.test" + entry := ec2.PrefixListEntry{} + + checkAttributes := func(*terraform.State) error { + if actual := aws.StringValue(entry.Cidr); actual != "1.0.0.0/8" { + return fmt.Errorf("bad cidr: %s", actual) + } + + if actual := aws.StringValue(entry.Description); actual != "Create" { + return fmt.Errorf("bad description: %s", actual) + } + + return nil + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_basic_create, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + checkAttributes, + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "Create"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_basic_update, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "Update"), + ), + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListEntryConfig_basic_create = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "Create" +} +` + +const testAccAwsEc2ManagedPrefixListEntryConfig_basic_update = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "Update" +} +` + +func testAccAwsEc2ManagedPrefixListEntryExists( + name string, + out *ec2.PrefixListEntry, +) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + switch { + case !ok: + return fmt.Errorf("resource %s not found", name) + case rs.Primary.ID == "": + return fmt.Errorf("resource %s has not set its id", name) + } + + conn := testAccProvider.Meta().(*AWSClient).ec2conn + ss := strings.Split(rs.Primary.ID, "_") + prefixListId, cidrBlock := ss[0], ss[1] + + entry, ok, err := getManagedPrefixListEntryByCIDR(prefixListId, conn, 0, cidrBlock) + switch { + case err != nil: + return err + case !ok: + return fmt.Errorf("resource %s (%s) has not been created", name, prefixListId) + } + + if out != nil { + *out = *entry + } + + return nil + } +} + +func TestAccAwsEc2ManagedPrefixListEntry_disappears(t *testing.T) { + prefixListResourceName := "aws_ec2_managed_prefix_list.test" + resourceName := "aws_ec2_managed_prefix_list_entry.test" + pl := ec2.ManagedPrefixList{} + entry := ec2.PrefixListEntry{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_disappears, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + testAccAwsEc2ManagedPrefixListExists(prefixListResourceName, &pl, nil), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ManagedPrefixListEntry(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListEntryConfig_disappears = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" +} +` + +func TestAccAwsEc2ManagedPrefixListEntry_prefixListDisappears(t *testing.T) { + prefixListResourceName := "aws_ec2_managed_prefix_list.test" + resourceName := "aws_ec2_managed_prefix_list_entry.test" + pl := ec2.ManagedPrefixList{} + entry := ec2.PrefixListEntry{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_disappears, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + testAccAwsEc2ManagedPrefixListExists(prefixListResourceName, &pl, nil), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ManagedPrefixList(), prefixListResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAwsEc2ManagedPrefixListEntry_alreadyExists(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list_entry.test" + entry := ec2.PrefixListEntry{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_alreadyExists, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + ), + ExpectError: regexp.MustCompile(`an entry for this cidr block already exists`), + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListEntryConfig_alreadyExists = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 + + entry { + cidr_block = "1.0.0.0/8" + } +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "Test" +} +` + +func TestAccAwsEc2ManagedPrefixListEntry_description(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list_entry.test" + entry := ec2.PrefixListEntry{} + + checkDescription := func(expect string) resource.TestCheckFunc { + return func(*terraform.State) error { + if actual := aws.StringValue(entry.Description); actual != expect { + return fmt.Errorf("bad description: %s", actual) + } + + return nil + } + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_description_none, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + checkDescription("Test1"), + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "Test1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_description_some, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + checkDescription("Test2"), + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", "Test2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_description_empty, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + checkDescription(""), + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_description_null, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry), + checkDescription(""), + resource.TestCheckResourceAttr(resourceName, "cidr_block", "1.0.0.0/8"), + resource.TestCheckResourceAttr(resourceName, "description", ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListEntryConfig_description_none = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "Test1" +} +` + +const testAccAwsEc2ManagedPrefixListEntryConfig_description_some = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "Test2" +} +` + +const testAccAwsEc2ManagedPrefixListEntryConfig_description_empty = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" + description = "" +} +` + +const testAccAwsEc2ManagedPrefixListEntryConfig_description_null = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "test" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "1.0.0.0/8" +} +` + +func TestAccAwsEc2ManagedPrefixListEntry_exceedLimit(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list_entry.test_1" + entry := ec2.PrefixListEntry{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_exceedLimit(2), + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListEntryExists(resourceName, &entry)), + }, + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_exceedLimit(3), + ResourceName: resourceName, + ExpectError: regexp.MustCompile(`You've reached the maximum number of entries for the prefix list.`), + }, + }, + }) +} + +func testAccAwsEc2ManagedPrefixListEntryConfig_exceedLimit(count int) string { + entries := `` + for i := 0; i < count; i++ { + entries += fmt.Sprintf(` +resource "aws_ec2_managed_prefix_list_entry" "test_%[1]d" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "%[1]d.0.0.0/8" + description = "Test_%[1]d" +} +`, + i+1) + } + + return fmt.Sprintf(` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 2 +} + +%[1]s +`, + entries) +} + +func testAccAwsEc2ManagedPrefixListSortEntries(list []*ec2.PrefixListEntry) { + sort.Slice(list, func(i, j int) bool { + return aws.StringValue(list[i].Cidr) < aws.StringValue(list[j].Cidr) + }) +} + +func TestAccAwsEc2ManagedPrefixListEntry_concurrentModification(t *testing.T) { + prefixListResourceName := "aws_ec2_managed_prefix_list.test" + pl, entries := ec2.ManagedPrefixList{}, []*ec2.PrefixListEntry(nil) + + checkAllEntriesExist := func(prefix string, count int) resource.TestCheckFunc { + return func(state *terraform.State) error { + if len(entries) != count { + return fmt.Errorf("expected %d entries", count) + } + + expectEntries := make([]*ec2.PrefixListEntry, 0, count) + for i := 0; i < count; i++ { + expectEntries = append(expectEntries, &ec2.PrefixListEntry{ + Cidr: aws.String(fmt.Sprintf("%d.0.0.0/8", i+1)), + Description: aws.String(fmt.Sprintf("%s%d", prefix, i+1))}) + } + testAccAwsEc2ManagedPrefixListSortEntries(expectEntries) + + testAccAwsEc2ManagedPrefixListSortEntries(entries) + + if !reflect.DeepEqual(expectEntries, entries) { + return fmt.Errorf("expected entries %#v, got %#v", expectEntries, entries) + } + + return nil + } + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListEntryConfig_concurrentModification("Step0_", 20), + ResourceName: prefixListResourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(prefixListResourceName, &pl, &entries), + checkAllEntriesExist("Step0_", 20)), + }, + { + // update the first 10 and drop the last 10 + Config: testAccAwsEc2ManagedPrefixListEntryConfig_concurrentModification("Step1_", 10), + ResourceName: prefixListResourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(prefixListResourceName, &pl, &entries), + checkAllEntriesExist("Step1_", 10)), + }, + }, + }) +} + +func testAccAwsEc2ManagedPrefixListEntryConfig_concurrentModification(prefix string, count int) string { + entries := `` + for i := 0; i < count; i++ { + entries += fmt.Sprintf(` +resource "aws_ec2_managed_prefix_list_entry" "test_%[1]d" { + prefix_list_id = aws_ec2_managed_prefix_list.test.id + cidr_block = "%[1]d.0.0.0/8" + description = "%[2]s%[1]d" +} +`, + i+1, + prefix) + } + + return fmt.Sprintf(` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 20 +} + +%[1]s +`, + entries) +} diff --git a/website/docs/r/ec2_managed_prefix_list_entry.html.markdown b/website/docs/r/ec2_managed_prefix_list_entry.html.markdown new file mode 100644 index 000000000000..3c2ab6b03bd9 --- /dev/null +++ b/website/docs/r/ec2_managed_prefix_list_entry.html.markdown @@ -0,0 +1,66 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_ec2_managed_prefix_list_entry" +description: |- + Provides a managed prefix list entry resource. +--- + +# Resource: aws_ec2_managed_prefix_list_entry + +Provides a managed prefix list entry resource. Represents a single `entry`, which +can be added to external Prefix Lists. + +~> **NOTE on Prefix Lists and Prefix List Entries:** Terraform currently +provides both a standalone Prefix List Entry, and a [Managed Prefix List resource](ec2_managed_prefix_list.html) +with an `entry` set defined in-line. At this time you +cannot use a Prefix List with in-line rules in conjunction with any Prefix List Entry +resources. Doing so will cause a conflict of rule settings and will unpredictably +fail or overwrite rules. + +~> **NOTE:** A Prefix List will have an upper bound on the number of rules +that it can support. + +~> **NOTE:** Resource creation will fail if the target Prefix List already has a +rule against the given CIDR block. + +## Example Usage + +Basic usage + +```hcl +resource "aws_ec2_managed_prefix_list" "example" { + name = "All VPC CIDR-s" + address_family = "IPv4" + max_entries = 5 +} + +resource "aws_ec2_managed_prefix_list_entry" "example" { + prefix_list_id = aws_ec2_managed_prefix_list.example.id + cidr_block = aws_vpc.example.cidr_block + description = "Primary" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `prefix_list_id` - (Required, Forces new resource) ID of the Prefix List to add this entry to. +* `cidr_block` - (Required, Forces new resource) The CIDR block to add an entry for. Different entries may have + overlapping CIDR blocks, but duplicating a particular block is not allowed. +* `description` - (Optional, Up to 255 characters) The description of this entry. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the prefix list entry. + +## Import + +Prefix List Entries can be imported using a concatenation of the `prefix_list_id` and `cidr_block` by an underscore (`_`). For example: + +```console +$ terraform import aws_ec2_managed_prefix_list_entry.example pl-0570a1d2d725c16be_10.30.0.0/16 +```