From 56e912ffa16bee073758cf02939e5a34792519c5 Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Wed, 1 Jul 2020 15:19:02 +0100 Subject: [PATCH 1/4] r/aws_ec2_managed_prefix_list: new resource --- aws/provider.go | 1 + aws/resource_aws_ec2_managed_prefix_list.go | 475 +++++++++++ ...source_aws_ec2_managed_prefix_list_test.go | 750 ++++++++++++++++++ .../r/ec2_managed_prefix_list.html.markdown | 85 ++ website/docs/r/security_group.html.markdown | 11 +- .../docs/r/security_group_rule.html.markdown | 11 +- 6 files changed, 1325 insertions(+), 8 deletions(-) create mode 100644 aws/resource_aws_ec2_managed_prefix_list.go create mode 100644 aws/resource_aws_ec2_managed_prefix_list_test.go create mode 100644 website/docs/r/ec2_managed_prefix_list.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 79d050c6a0a..ab4733b46f7 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -599,6 +599,7 @@ func Provider() *schema.Provider { "aws_ec2_fleet": resourceAwsEc2Fleet(), "aws_ec2_local_gateway_route": resourceAwsEc2LocalGatewayRoute(), "aws_ec2_local_gateway_route_table_vpc_association": resourceAwsEc2LocalGatewayRouteTableVpcAssociation(), + "aws_ec2_managed_prefix_list": resourceAwsEc2ManagedPrefixList(), "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.go b/aws/resource_aws_ec2_managed_prefix_list.go new file mode 100644 index 00000000000..aa88a32d50a --- /dev/null +++ b/aws/resource_aws_ec2_managed_prefix_list.go @@ -0,0 +1,475 @@ +package aws + +import ( + "errors" + "fmt" + "log" + "sort" + "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/keyvaluetags" +) + +var ( + awsPrefixListEntrySetHashFunc = schema.HashResource(prefixListEntrySchema()) +) + +func resourceAwsEc2ManagedPrefixList() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsEc2ManagedPrefixListCreate, + Read: resourceAwsEc2ManagedPrefixListRead, + Update: resourceAwsEc2ManagedPrefixListUpdate, + Delete: resourceAwsEc2ManagedPrefixListDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "address_family": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice( + []string{"IPv4", "IPv6"}, + false), + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "entry": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + ConfigMode: schema.SchemaConfigModeAttr, + Elem: prefixListEntrySchema(), + Set: awsPrefixListEntrySetHashFunc, + }, + "max_entries": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(1), + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "owner_id": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), + }, + } +} + +func prefixListEntrySchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr_block": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsCIDR, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + }, + } +} + +func resourceAwsEc2ManagedPrefixListCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + + input := ec2.CreateManagedPrefixListInput{} + + input.AddressFamily = aws.String(d.Get("address_family").(string)) + + if v, ok := d.GetOk("entry"); ok { + input.Entries = expandAddPrefixListEntries(v) + } + + input.MaxEntries = aws.Int64(int64(d.Get("max_entries").(int))) + input.PrefixListName = aws.String(d.Get("name").(string)) + + if v, ok := d.GetOk("tags"); ok { + input.TagSpecifications = ec2TagSpecificationsFromMap( + v.(map[string]interface{}), + "prefix-list") // no ec2.ResourceTypePrefixList as of 01/07/20 + } + + output, err := conn.CreateManagedPrefixList(&input) + if err != nil { + return fmt.Errorf("failed to create managed prefix list: %v", err) + } + + id := aws.StringValue(output.PrefixList.PrefixListId) + + log.Printf("[INFO] Created Managed Prefix List %s (%s)", d.Get("name").(string), id) + + if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutCreate)); err != nil { + return fmt.Errorf("prefix list %s did not settle after create: %s", id, err) + } + + d.SetId(id) + + return resourceAwsEc2ManagedPrefixListRead(d, meta) +} + +func resourceAwsEc2ManagedPrefixListRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + id := d.Id() + + pl, ok, err := getManagedPrefixList(id, conn) + switch { + case err != nil: + return err + case !ok: + log.Printf("[WARN] Managed Prefix List %s not found; removing from state.", id) + d.SetId("") + return nil + } + + d.Set("address_family", pl.AddressFamily) + d.Set("arn", pl.PrefixListArn) + + entries, err := getPrefixListEntries(id, conn, 0) + if err != nil { + return err + } + + if err := d.Set("entry", flattenPrefixListEntries(entries)); err != nil { + return fmt.Errorf("error setting attribute entry of managed prefix list %s: %s", id, err) + } + + d.Set("max_entries", pl.MaxEntries) + d.Set("name", pl.PrefixListName) + d.Set("owner_id", pl.OwnerId) + + if err := d.Set("tags", keyvaluetags.Ec2KeyValueTags(pl.Tags).IgnoreAws().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error settings attribute tags of managed prefix list %s: %s", id, err) + } + + return nil +} + +func resourceAwsEc2ManagedPrefixListUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + id := d.Id() + modifyPrefixList := false + + input := ec2.ModifyManagedPrefixListInput{} + + input.PrefixListId = aws.String(id) + + if d.HasChange("name") { + input.PrefixListName = aws.String(d.Get("name").(string)) + modifyPrefixList = true + } + + if d.HasChange("entry") { + pl, ok, err := getManagedPrefixList(id, conn) + switch { + case err != nil: + return err + case !ok: + return &resource.NotFoundError{} + } + + currentVersion := aws.Int64Value(pl.Version) + + oldEntries, err := getPrefixListEntries(id, conn, currentVersion) + if err != nil { + return err + } + + newEntries := expandAddPrefixListEntries(d.Get("entry")) + adds, removes := computePrefixListEntriesModification(oldEntries, newEntries) + + if len(adds) > 0 || len(removes) > 0 { + if len(adds) > 0 { + // the Modify API doesn't like empty lists + input.AddEntries = adds + } + + if len(removes) > 0 { + // the Modify API doesn't like empty lists + input.RemoveEntries = removes + } + + input.CurrentVersion = aws.Int64(currentVersion) + modifyPrefixList = true + } + } + + if modifyPrefixList { + log.Printf("[INFO] modifying managed prefix list %s...", id) + + switch _, err := conn.ModifyManagedPrefixList(&input); { + case isAWSErr(err, "PrefixListVersionMismatch", "prefix list has the incorrect version number"): + return fmt.Errorf("failed to modify managed prefix list %s: conflicting change", id) + case err != nil: + return fmt.Errorf("failed to modify managed prefix list %s: %s", id, err) + } + + if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutUpdate)); err != nil { + return fmt.Errorf("prefix list did not settle after update: %s", err) + } + } + + if d.HasChange("tags") { + before, after := d.GetChange("tags") + if err := keyvaluetags.Ec2UpdateTags(conn, id, before, after); err != nil { + return fmt.Errorf("failed to update tags of managed prefix list %s: %s", id, err) + } + } + + return resourceAwsEc2ManagedPrefixListRead(d, meta) +} + +func resourceAwsEc2ManagedPrefixListDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).ec2conn + id := d.Id() + + input := ec2.DeleteManagedPrefixListInput{ + PrefixListId: aws.String(id), + } + + err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { + _, err := conn.DeleteManagedPrefixList(&input) + switch { + case isManagedPrefixListModificationConflictErr(err): + return resource.RetryableError(err) + case isAWSErr(err, "InvalidPrefixListID.NotFound", ""): + log.Printf("[WARN] managed prefix list %s has already been deleted", id) + return nil + case err != nil: + return resource.NonRetryableError(err) + } + + return nil + }) + + if isResourceTimeoutError(err) { + _, err = conn.DeleteManagedPrefixList(&input) + } + + if err != nil { + return fmt.Errorf("failed to delete managed prefix list %s: %s", id, err) + } + + if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutDelete)); err != nil { + return fmt.Errorf("prefix list %s did not settle after delete: %s", id, err) + } + + return nil +} + +func expandAddPrefixListEntries(input interface{}) []*ec2.AddPrefixListEntry { + if input == nil { + return nil + } + + list := input.(*schema.Set).List() + result := make([]*ec2.AddPrefixListEntry, 0, len(list)) + + for _, entry := range list { + m := entry.(map[string]interface{}) + + output := ec2.AddPrefixListEntry{} + + output.Cidr = aws.String(m["cidr_block"].(string)) + + if v, ok := m["description"]; ok { + output.Description = aws.String(v.(string)) + } + + result = append(result, &output) + } + + return result +} + +func flattenPrefixListEntries(entries []*ec2.PrefixListEntry) *schema.Set { + list := make([]interface{}, 0, len(entries)) + + for _, entry := range entries { + m := make(map[string]interface{}, 2) + m["cidr_block"] = aws.StringValue(entry.Cidr) + + if entry.Description != nil { + m["description"] = aws.StringValue(entry.Description) + } + + list = append(list, m) + } + + return schema.NewSet(awsPrefixListEntrySetHashFunc, list) +} + +func getManagedPrefixList( + id string, + conn *ec2.EC2, +) (*ec2.ManagedPrefixList, bool, error) { + input := ec2.DescribeManagedPrefixListsInput{ + PrefixListIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeManagedPrefixLists(&input) + switch { + case isAWSErr(err, "InvalidPrefixListID.NotFound", ""): + return nil, false, nil + case err != nil: + return nil, false, fmt.Errorf("describe managed prefix list %s: %v", id, err) + case len(output.PrefixLists) != 1: + return nil, false, nil + } + + return output.PrefixLists[0], true, nil +} + +func getPrefixListEntries( + id string, + conn *ec2.EC2, + version int64, +) ([]*ec2.PrefixListEntry, error) { + input := ec2.GetManagedPrefixListEntriesInput{ + PrefixListId: aws.String(id), + } + + if version > 0 { + input.TargetVersion = aws.Int64(version) + } + + result := []*ec2.PrefixListEntry(nil) + switch err := conn.GetManagedPrefixListEntriesPages( + &input, + func(output *ec2.GetManagedPrefixListEntriesOutput, last bool) bool { + result = append(result, output.Entries...) + return true + }); { + case err != nil: + return nil, fmt.Errorf("failed to get entries in prefix list %s: %v", id, err) + } + + return result, nil +} + +func computePrefixListEntriesModification( + oldEntries []*ec2.PrefixListEntry, + newEntries []*ec2.AddPrefixListEntry, +) ([]*ec2.AddPrefixListEntry, []*ec2.RemovePrefixListEntry) { + adds := map[string]string{} // CIDR => Description + + removes := map[string]struct{}{} // set of CIDR + for _, oldEntry := range oldEntries { + oldCIDR := aws.StringValue(oldEntry.Cidr) + removes[oldCIDR] = struct{}{} + } + + for _, newEntry := range newEntries { + newCIDR := aws.StringValue(newEntry.Cidr) + newDescription := aws.StringValue(newEntry.Description) + + for _, oldEntry := range oldEntries { + oldCIDR := aws.StringValue(oldEntry.Cidr) + oldDescription := aws.StringValue(oldEntry.Description) + + if oldCIDR == newCIDR { + delete(removes, oldCIDR) + + if oldDescription != newDescription { + adds[oldCIDR] = newDescription + } + + goto nextNewEntry + } + } + + // reach this point when no matching oldEntry found + adds[newCIDR] = newDescription + + nextNewEntry: + } + + addList := make([]*ec2.AddPrefixListEntry, 0, len(adds)) + for cidr, description := range adds { + addList = append(addList, &ec2.AddPrefixListEntry{ + Cidr: aws.String(cidr), + Description: aws.String(description), + }) + } + sort.Slice(addList, func(i, j int) bool { + return aws.StringValue(addList[i].Cidr) < aws.StringValue(addList[j].Cidr) + }) + + removeList := make([]*ec2.RemovePrefixListEntry, 0, len(removes)) + for cidr := range removes { + removeList = append(removeList, &ec2.RemovePrefixListEntry{ + Cidr: aws.String(cidr), + }) + } + sort.Slice(removeList, func(i, j int) bool { + return aws.StringValue(removeList[i].Cidr) < aws.StringValue(removeList[j].Cidr) + }) + + return addList, removeList +} + +func waitUntilAwsManagedPrefixListSettled( + id string, + conn *ec2.EC2, + timeout time.Duration, +) error { + log.Printf("[INFO] Waiting for managed prefix list %s to settle...", id) + + err := resource.Retry(timeout, func() *resource.RetryError { + settled, err := isAwsManagedPrefixListSettled(id, conn) + switch { + case err != nil: + return resource.NonRetryableError(err) + case !settled: + return resource.RetryableError(errors.New("resource not yet settled")) + } + + return nil + }) + + if isResourceTimeoutError(err) { + return fmt.Errorf("timed out: %s", err) + } + + return nil +} + +func isAwsManagedPrefixListSettled(id string, conn *ec2.EC2) (bool, error) { + pl, ok, err := getManagedPrefixList(id, conn) + switch { + case err != nil: + return false, err + case !ok: + return true, nil + } + + switch state := aws.StringValue(pl.State); state { + case ec2.PrefixListStateCreateComplete, ec2.PrefixListStateModifyComplete, ec2.PrefixListStateDeleteComplete: + return true, nil + case ec2.PrefixListStateCreateInProgress, ec2.PrefixListStateModifyInProgress, ec2.PrefixListStateDeleteInProgress: + return false, nil + case ec2.PrefixListStateCreateFailed, ec2.PrefixListStateModifyFailed, ec2.PrefixListStateDeleteFailed: + return false, fmt.Errorf("terminal state %s indicates failure", state) + default: + return false, fmt.Errorf("unexpected state %s", state) + } +} diff --git a/aws/resource_aws_ec2_managed_prefix_list_test.go b/aws/resource_aws_ec2_managed_prefix_list_test.go new file mode 100644 index 00000000000..ec2667036d3 --- /dev/null +++ b/aws/resource_aws_ec2_managed_prefix_list_test.go @@ -0,0 +1,750 @@ +package aws + +import ( + "fmt" + "reflect" + "regexp" + "sort" + "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 TestAccAwsEc2ManagedPrefixList_computePrefixListEntriesModification(t *testing.T) { + type testEntry struct { + CIDR string + Description string + } + + tests := []struct { + name string + oldEntries []testEntry + newEntries []testEntry + expectedAdds []testEntry + expectedRemoves []testEntry + }{ + { + name: "add two", + oldEntries: []testEntry{}, + newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + expectedAdds: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + expectedRemoves: []testEntry{}, + }, + { + name: "remove one", + oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + newEntries: []testEntry{{"1.2.3.4/32", "test1"}}, + expectedAdds: []testEntry{}, + expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, + }, + { + name: "modify description of one", + oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2-1"}}, + expectedAdds: []testEntry{{"2.3.4.5/32", "test2-1"}}, + expectedRemoves: []testEntry{}, + }, + { + name: "add third", + oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}, {"3.4.5.6/32", "test3"}}, + expectedAdds: []testEntry{{"3.4.5.6/32", "test3"}}, + expectedRemoves: []testEntry{}, + }, + { + name: "add and remove one", + oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"3.4.5.6/32", "test3"}}, + expectedAdds: []testEntry{{"3.4.5.6/32", "test3"}}, + expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, + }, + { + name: "add and remove one with description change", + oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, + newEntries: []testEntry{{"1.2.3.4/32", "test1-1"}, {"3.4.5.6/32", "test3"}}, + expectedAdds: []testEntry{{"1.2.3.4/32", "test1-1"}, {"3.4.5.6/32", "test3"}}, + expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, + }, + { + name: "basic test update", + oldEntries: []testEntry{{"1.0.0.0/8", "Test1"}}, + newEntries: []testEntry{{"1.0.0.0/8", "Test1-1"}, {"2.2.0.0/16", "Test2"}}, + expectedAdds: []testEntry{{"1.0.0.0/8", "Test1-1"}, {"2.2.0.0/16", "Test2"}}, + expectedRemoves: []testEntry{}, + }, + } + + for _, test := range tests { + oldEntryList := []*ec2.PrefixListEntry(nil) + for _, entry := range test.oldEntries { + oldEntryList = append(oldEntryList, &ec2.PrefixListEntry{ + Cidr: aws.String(entry.CIDR), + Description: aws.String(entry.Description), + }) + } + + newEntryList := []*ec2.AddPrefixListEntry(nil) + for _, entry := range test.newEntries { + newEntryList = append(newEntryList, &ec2.AddPrefixListEntry{ + Cidr: aws.String(entry.CIDR), + Description: aws.String(entry.Description), + }) + } + + addList, removeList := computePrefixListEntriesModification(oldEntryList, newEntryList) + + if len(addList) != len(test.expectedAdds) { + t.Errorf("expected %d adds, got %d", len(test.expectedAdds), len(addList)) + } + + for i, added := range addList { + expected := test.expectedAdds[i] + + actualCidr := aws.StringValue(added.Cidr) + expectedCidr := expected.CIDR + if actualCidr != expectedCidr { + t.Errorf("add[%d]: expected cidr %s, got %s", i, expectedCidr, actualCidr) + } + + actualDesc := aws.StringValue(added.Description) + expectedDesc := expected.Description + if actualDesc != expectedDesc { + t.Errorf("add[%d]: expected description '%s', got '%s'", i, expectedDesc, actualDesc) + } + } + + if len(removeList) != len(test.expectedRemoves) { + t.Errorf("expected %d removes, got %d", len(test.expectedRemoves), len(removeList)) + } + + for i, removed := range removeList { + expected := test.expectedRemoves[i] + + actualCidr := aws.StringValue(removed.Cidr) + expectedCidr := expected.CIDR + if actualCidr != expectedCidr { + t.Errorf("add[%d]: expected cidr %s, got %s", i, expectedCidr, actualCidr) + } + } + } +} + +func testAccCheckAwsEc2ManagedPrefixListDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ec2conn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ec2_managed_prefix_list" { + continue + } + + id := rs.Primary.ID + + switch _, ok, err := getManagedPrefixList(id, conn); { + case err != nil: + return err + case ok: + return fmt.Errorf("managed prefix list %s still exists", id) + } + } + + return nil +} + +func testAccCheckAwsEc2ManagedPrefixListVersion( + prefixList *ec2.ManagedPrefixList, + version int64, +) resource.TestCheckFunc { + return func(state *terraform.State) error { + if actual := aws.Int64Value(prefixList.Version); actual != version { + return fmt.Errorf("expected prefix list version %d, got %d", version, actual) + } + + return nil + } +} + +func TestAccAwsEc2ManagedPrefixList_basic(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + pl, entries := ec2.ManagedPrefixList{}, []*ec2.PrefixListEntry(nil) + + checkAttributes := func(*terraform.State) error { + if actual := aws.StringValue(pl.AddressFamily); actual != "IPv4" { + return fmt.Errorf("bad address family: %s", actual) + } + + if actual := aws.Int64Value(pl.MaxEntries); actual != 5 { + return fmt.Errorf("bad max entries: %d", actual) + } + + if actual := aws.StringValue(pl.OwnerId); actual != testAccGetAccountID() { + return fmt.Errorf("bad owner id: %s", actual) + } + + if actual := aws.StringValue(pl.PrefixListName); actual != "tf-test-basic-create" { + return fmt.Errorf("bad name: %s", actual) + } + + sort.Slice(pl.Tags, func(i, j int) bool { + return aws.StringValue(pl.Tags[i].Key) < aws.StringValue(pl.Tags[j].Key) + }) + + expectTags := []*ec2.Tag{ + {Key: aws.String("Key1"), Value: aws.String("Value1")}, + {Key: aws.String("Key2"), Value: aws.String("Value2")}, + } + + if !reflect.DeepEqual(expectTags, pl.Tags) { + return fmt.Errorf("expected tags %#v, got %#v", expectTags, pl.Tags) + } + + sort.Slice(entries, func(i, j int) bool { + return aws.StringValue(entries[i].Cidr) < aws.StringValue(entries[j].Cidr) + }) + + expectEntries := []*ec2.PrefixListEntry{ + {Cidr: aws.String("1.0.0.0/8"), Description: aws.String("Test1")}, + {Cidr: aws.String("2.0.0.0/8"), Description: aws.String("Test2")}, + } + + 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: testAccAwsEc2ManagedPrefixListConfig_basic_create, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, &entries), + checkAttributes, + resource.TestCheckResourceAttr(resourceName, "name", "tf-test-basic-create"), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`prefix-list/pl-[[:xdigit:]]+`)), + resource.TestCheckResourceAttr(resourceName, "address_family", "IPv4"), + resource.TestCheckResourceAttr(resourceName, "max_entries", "5"), + resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "entry.*", map[string]string{ + "cidr_block": "1.0.0.0/8", + "description": "Test1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "entry.*", map[string]string{ + "cidr_block": "2.0.0.0/8", + "description": "Test2", + }), + testAccCheckResourceAttrAccountID(resourceName, "owner_id"), + testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_basic_update, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, &entries), + resource.TestCheckResourceAttr(resourceName, "name", "tf-test-basic-update"), + resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "entry.*", map[string]string{ + "cidr_block": "1.0.0.0/8", + "description": "Test1", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "entry.*", map[string]string{ + "cidr_block": "3.0.0.0/8", + "description": "Test3", + }), + testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + ), + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListConfig_basic_create = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-basic-create" + address_family = "IPv4" + max_entries = 5 + + entry { + cidr_block = "1.0.0.0/8" + description = "Test1" + } + + entry { + cidr_block = "2.0.0.0/8" + description = "Test2" + } + + tags = { + Key1 = "Value1" + Key2 = "Value2" + } +} +` + +const testAccAwsEc2ManagedPrefixListConfig_basic_update = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-basic-update" + address_family = "IPv4" + max_entries = 5 + + entry { + cidr_block = "1.0.0.0/8" + description = "Test1" + } + + entry { + cidr_block = "3.0.0.0/8" + description = "Test3" + } + + tags = { + Key1 = "Value1" + Key3 = "Value3" + } +} +` + +func testAccAwsEc2ManagedPrefixListExists( + name string, + out *ec2.ManagedPrefixList, + entries *[]*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 + id := rs.Primary.ID + + pl, ok, err := getManagedPrefixList(id, conn) + switch { + case err != nil: + return err + case !ok: + return fmt.Errorf("resource %s (%s) has not been created", name, id) + } + + if out != nil { + *out = *pl + } + + if entries != nil { + entries1, err := getPrefixListEntries(id, conn, *pl.Version) + if err != nil { + return err + } + + *entries = entries1 + } + + return nil + } +} + +func TestAccAwsEc2ManagedPrefixList_disappears(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + pl := ec2.ManagedPrefixList{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListConfig_disappears, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + testAccCheckResourceDisappears(testAccProvider, resourceAwsEc2ManagedPrefixList(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListConfig_disappears = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-disappears" + address_family = "IPv4" + max_entries = 2 + + entry { + cidr_block = "1.0.0.0/8" + } +} +` + +func TestAccAwsEc2ManagedPrefixList_name(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + pl := ec2.ManagedPrefixList{} + + checkName := func(name string) resource.TestCheckFunc { + return func(*terraform.State) error { + if actual := aws.StringValue(pl.PrefixListName); actual != name { + return fmt.Errorf("expected name %s, got %s", name, actual) + } + + return nil + } + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListConfig_name_create, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + resource.TestCheckResourceAttr(resourceName, "name", "tf-test-name-create"), + checkName("tf-test-name-create"), + testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 1), + ), + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_name_update, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + resource.TestCheckResourceAttr(resourceName, "name", "tf-test-name-update"), + checkName("tf-test-name-update"), + testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListConfig_name_create = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-name-create" + address_family = "IPv4" + max_entries = 5 +} +` + +const testAccAwsEc2ManagedPrefixListConfig_name_update = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-name-update" + address_family = "IPv4" + max_entries = 5 +} +` + +func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + pl := ec2.ManagedPrefixList{} + + checkTags := func(m map[string]string) resource.TestCheckFunc { + return func(*terraform.State) error { + sort.Slice(pl.Tags, func(i, j int) bool { + return aws.StringValue(pl.Tags[i].Key) < aws.StringValue(pl.Tags[j].Key) + }) + + expectTags := []*ec2.Tag(nil) + + if m != nil { + for k, v := range m { + expectTags = append(expectTags, &ec2.Tag{ + Key: aws.String(k), + Value: aws.String(v), + }) + } + + sort.Slice(expectTags, func(i, j int) bool { + return aws.StringValue(expectTags[i].Key) < aws.StringValue(expectTags[j].Key) + }) + } + + if !reflect.DeepEqual(expectTags, pl.Tags) { + return fmt.Errorf("expected tags %#v, got %#v", expectTags, pl.Tags) + } + + return nil + } + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListConfig_tags_none, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + checkTags(nil), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_tags_addSome, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + checkTags(map[string]string{"Key1": "Value1", "Key2": "Value2", "Key3": "Value3"}), + resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), + resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + checkTags(map[string]string{"Key2": "Value2-1", "Key3": "Value3"}), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2-1"), + resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_tags_empty, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + checkTags(nil), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_tags_none, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), + checkTags(nil), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListConfig_tags_none = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 +} +` + +const testAccAwsEc2ManagedPrefixListConfig_tags_addSome = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 + + tags = { + Key1 = "Value1" + Key2 = "Value2" + Key3 = "Value3" + } +} +` + +const testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 + + tags = { + Key2 = "Value2-1" + Key3 = "Value3" + } +} +` + +const testAccAwsEc2ManagedPrefixListConfig_tags_empty = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + address_family = "IPv4" + max_entries = 5 + tags = {} +} +` + +func TestAccAwsEc2ManagedPrefixList_entryConfigMode(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + prefixList := ec2.ManagedPrefixList{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_blocks, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), + resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_noBlocks, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), + resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), + ), + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_zeroed, + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), + resource.TestCheckResourceAttr(resourceName, "entry.#", "0"), + ), + }, + }, + }) +} + +const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_blocks = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + max_entries = 5 + address_family = "IPv4" + + entry { + cidr_block = "1.0.0.0/8" + description = "Entry1" + } + + entry { + cidr_block = "2.0.0.0/8" + description = "Entry2" + } +} +` + +const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_noBlocks = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + max_entries = 5 + address_family = "IPv4" +} +` + +const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_zeroed = ` +resource "aws_ec2_managed_prefix_list" "test" { + name = "tf-test-acc" + max_entries = 5 + address_family = "IPv4" + entry = [] +} +` + +func TestAccAwsEc2ManagedPrefixList_exceedLimit(t *testing.T) { + resourceName := "aws_ec2_managed_prefix_list.test" + prefixList := ec2.ManagedPrefixList{} + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(2), + ResourceName: resourceName, + Check: resource.ComposeAggregateTestCheckFunc( + testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), + resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), + ), + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(3), + ResourceName: resourceName, + ExpectError: regexp.MustCompile(`You've reached the maximum number of entries for the prefix list.`), + }, + }, + }) +} + +func testAccAwsEc2ManagedPrefixListConfig_exceedLimit(count int) string { + entries := `` + for i := 0; i < count; i++ { + entries += fmt.Sprintf(` + entry { + 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) +} diff --git a/website/docs/r/ec2_managed_prefix_list.html.markdown b/website/docs/r/ec2_managed_prefix_list.html.markdown new file mode 100644 index 00000000000..d372d874d75 --- /dev/null +++ b/website/docs/r/ec2_managed_prefix_list.html.markdown @@ -0,0 +1,85 @@ +--- +subcategory: "VPC" +layout: "aws" +page_title: "AWS: aws_ec2_managed_prefix_list" +description: |- + Provides a managed prefix list resource. +--- + +# Resource: aws_ec2_managed_prefix_list + +Provides a managed prefix list resource. + +~> **NOTE on Prefix Lists and Prefix List Entries:** Terraform currently +provides both a standalone [Managed Prefix List Entry resource](ec2_managed_prefix_list_entry.html), +and a Prefix List resource 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 on `max_entries`:** When you reference a Prefix List in a resource, +the maximum number of entries for the prefix lists counts as the same number of rules +or entries for the resource. For example, if you create a prefix list with a maximum +of 20 entries and you reference that prefix list in a security group rule, this counts +as 20 rules for the security group. + +## Example Usage + +Basic usage + +```hcl +resource "aws_ec2_managed_prefix_list" "example" { + name = "All VPC CIDR-s" + address_family = "IPv4" + max_entries = 5 + + entry { + cidr_block = aws_vpc.example.cidr_block + description = "Primary" + } + + entry { + cidr_block = aws_vpc_ipv4_cidr_block_association.example.cidr_block + description = "Secondary" + } + + tags = { + Env = "live" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of this resource. The name must not start with `com.amazonaws`. +* `address_family` - (Required, Forces new resource) The address family (`IPv4` or `IPv6`) of + this prefix list. +* `entry` - (Optional) Can be specified multiple times for each prefix list entry. + Each entry block supports fields documented below. Different entries may have + overlapping CIDR blocks, but a particular CIDR should not be duplicated. +* `max_entries` - (Required, Forces new resource) The maximum number of entries that + this prefix list can contain. +* `tags` - (Optional) A map of tags to assign to this resource. + +The `entry` block supports: + +* `cidr_block` - (Required) The CIDR block of this entry. +* `description` - (Optional) Description of this entry. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The ID of the prefix list. +* `arn` - The ARN of the prefix list. +* `owner_id` - The ID of the AWS account that owns this prefix list. + +## Import + +Prefix Lists can be imported using the `id`, e.g. + +``` +$ terraform import aws_ec2_managed_prefix_list.default pl-0570a1d2d725c16be +``` diff --git a/website/docs/r/security_group.html.markdown b/website/docs/r/security_group.html.markdown index 584297739a7..da0f0c21efd 100644 --- a/website/docs/r/security_group.html.markdown +++ b/website/docs/r/security_group.html.markdown @@ -84,7 +84,7 @@ The `ingress` block supports: * `cidr_blocks` - (Optional) List of CIDR blocks. * `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. -* `prefix_list_ids` - (Optional) List of prefix list IDs. +* `prefix_list_ids` - (Optional) List of Prefix List IDs. * `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp" or "icmpv6") * `protocol` - (Required) The protocol. If you select a protocol of "-1" (semantically equivalent to `"all"`, which is not a valid value here), you must specify a "from_port" and "to_port" equal to 0. If not icmp, icmpv6, tcp, udp, or "-1" use the [protocol number](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) @@ -99,7 +99,7 @@ The `egress` block supports: * `cidr_blocks` - (Optional) List of CIDR blocks. * `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. -* `prefix_list_ids` - (Optional) List of prefix list IDs (for allowing access to VPC endpoints) +* `prefix_list_ids` - (Optional) List of Prefix List IDs. * `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp") * `protocol` - (Required) The protocol. If you select a protocol of "-1" (semantically equivalent to `"all"`, which is not a valid value here), you must specify a "from_port" and "to_port" equal to 0. If not icmp, tcp, udp, or "-1" use the [protocol number](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) @@ -132,8 +132,9 @@ resource "aws_security_group" "example" { ## Usage with prefix list IDs -Prefix list IDs are managed by AWS internally. Prefix list IDs -are associated with a prefix list name, or service name, that is linked to a specific region. +Prefix Lists are either managed by AWS internally, or created by the customer using a +[Prefix List resource](ec2_managed_prefix_list.html). Prefix Lists provided by +AWS are associated with a prefix list name, or service name, that is linked to a specific region. Prefix list IDs are exported on VPC Endpoints, so you can use this format: ```hcl @@ -153,6 +154,8 @@ resource "aws_vpc_endpoint" "my_endpoint" { } ``` +You can also find a specific Prefix List using the `aws_prefix_list` data source. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: diff --git a/website/docs/r/security_group_rule.html.markdown b/website/docs/r/security_group_rule.html.markdown index dc28d84e627..9b1c0b24915 100644 --- a/website/docs/r/security_group_rule.html.markdown +++ b/website/docs/r/security_group_rule.html.markdown @@ -45,8 +45,7 @@ The following arguments are supported: or `egress` (outbound). * `cidr_blocks` - (Optional) List of CIDR blocks. Cannot be specified with `source_security_group_id`. * `ipv6_cidr_blocks` - (Optional) List of IPv6 CIDR blocks. -* `prefix_list_ids` - (Optional) List of prefix list IDs (for allowing access to VPC endpoints). -Only valid with `egress`. +* `prefix_list_ids` - (Optional) List of Prefix List IDs. * `from_port` - (Required) The start port (or ICMP type number if protocol is "icmp" or "icmpv6"). * `protocol` - (Required) The protocol. If not icmp, icmpv6, tcp, udp, or all use the [protocol number](https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml) * `security_group_id` - (Required) The security group to apply this rule to. @@ -59,8 +58,10 @@ Only valid with `egress`. ## Usage with prefix list IDs -Prefix list IDs are managed by AWS internally. Prefix list IDs -are associated with a prefix list name, or service name, that is linked to a specific region. +Prefix Lists are either managed by AWS internally, or created by the customer using a +[Managed Prefix List resource](ec2_managed_prefix_list.html). Prefix Lists provided by +AWS are associated with a prefix list name, or service name, that is linked to a specific region. + Prefix list IDs are exported on VPC Endpoints, so you can use this format: ```hcl @@ -79,6 +80,8 @@ resource "aws_vpc_endpoint" "my_endpoint" { } ``` +You can also find a specific Prefix List using the `aws_prefix_list` data source. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: From efc5819d1b1fac57a7ac04dbe3609aa943464816 Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Sat, 5 Dec 2020 12:56:07 +0000 Subject: [PATCH 2/4] 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 ab4733b46f7..61c70d73e10 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 00000000000..82a91db7301 --- /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 00000000000..4c241a4068e --- /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 00000000000..3c2ab6b03bd --- /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 +``` From 0dcab47720b2a089978d6fbf95ed92926b47f739 Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Wed, 16 Dec 2020 15:13:41 +0000 Subject: [PATCH 3/4] r/aws_ec2_managed_prefix_list_entry: remove 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 deletions(-) delete mode 100644 aws/resource_aws_ec2_managed_prefix_list_entry.go delete mode 100644 aws/resource_aws_ec2_managed_prefix_list_entry_test.go delete mode 100644 website/docs/r/ec2_managed_prefix_list_entry.html.markdown diff --git a/aws/provider.go b/aws/provider.go index 61c70d73e10..ab4733b46f7 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -600,7 +600,6 @@ 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 deleted file mode 100644 index 82a91db7301..00000000000 --- a/aws/resource_aws_ec2_managed_prefix_list_entry.go +++ /dev/null @@ -1,289 +0,0 @@ -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 deleted file mode 100644 index 4c241a4068e..00000000000 --- a/aws/resource_aws_ec2_managed_prefix_list_entry_test.go +++ /dev/null @@ -1,498 +0,0 @@ -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 deleted file mode 100644 index 3c2ab6b03bd..00000000000 --- a/website/docs/r/ec2_managed_prefix_list_entry.html.markdown +++ /dev/null @@ -1,66 +0,0 @@ ---- -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 -``` From a78644ee6c0602059b04f43a48569519f0a7d9bc Mon Sep 17 00:00:00 2001 From: Roberth Kulbin Date: Wed, 16 Dec 2020 16:33:07 +0000 Subject: [PATCH 4/4] r/aws_ec2_managed_prefix_list: code review updates --- aws/internal/service/ec2/finder/finder.go | 17 + aws/internal/service/ec2/waiter/status.go | 19 + aws/internal/service/ec2/waiter/waiter.go | 60 +++ aws/resource_aws_ec2_managed_prefix_list.go | 293 ++++-------- ...source_aws_ec2_managed_prefix_list_test.go | 418 ++++-------------- .../r/ec2_managed_prefix_list.html.markdown | 8 +- 6 files changed, 262 insertions(+), 553 deletions(-) diff --git a/aws/internal/service/ec2/finder/finder.go b/aws/internal/service/ec2/finder/finder.go index 6752919b3ec..1cac48c0163 100644 --- a/aws/internal/service/ec2/finder/finder.go +++ b/aws/internal/service/ec2/finder/finder.go @@ -130,3 +130,20 @@ func VpnGatewayByID(conn *ec2.EC2, id string) (*ec2.VpnGateway, error) { return output.VpnGateways[0], nil } + +func ManagedPrefixListByID(conn *ec2.EC2, id string) (*ec2.ManagedPrefixList, error) { + input := &ec2.DescribeManagedPrefixListsInput{ + PrefixListIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeManagedPrefixLists(input) + if err != nil { + return nil, err + } + + if output == nil || len(output.PrefixLists) == 0 { + return nil, nil + } + + return output.PrefixLists[0], nil +} diff --git a/aws/internal/service/ec2/waiter/status.go b/aws/internal/service/ec2/waiter/status.go index 3bdd28b219f..dc5d400785c 100644 --- a/aws/internal/service/ec2/waiter/status.go +++ b/aws/internal/service/ec2/waiter/status.go @@ -267,3 +267,22 @@ func VpnGatewayVpcAttachmentState(conn *ec2.EC2, vpnGatewayID, vpcID string) res return vpcAttachment, aws.StringValue(vpcAttachment.State), nil } } + +const ( + managedPrefixListStateNotFound = "NotFound" + managedPrefixListStateUnknown = "Unknown" +) + +func ManagedPrefixListState(conn *ec2.EC2, prefixListId string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + managedPrefixList, err := finder.ManagedPrefixListByID(conn, prefixListId) + if err != nil { + return nil, managedPrefixListStateUnknown, err + } + if managedPrefixList == nil { + return nil, managedPrefixListStateNotFound, nil + } + + return managedPrefixList, aws.StringValue(managedPrefixList.State), nil + } +} diff --git a/aws/internal/service/ec2/waiter/waiter.go b/aws/internal/service/ec2/waiter/waiter.go index cb597291ee9..886e9133b34 100644 --- a/aws/internal/service/ec2/waiter/waiter.go +++ b/aws/internal/service/ec2/waiter/waiter.go @@ -4,6 +4,7 @@ import ( "time" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -244,3 +245,62 @@ func VpnGatewayVpcAttachmentDetached(conn *ec2.EC2, vpnGatewayID, vpcID string) return nil, err } + +const ( + ManagedPrefixListTimeout = 15 * time.Minute +) + +func ManagedPrefixListCreated(conn *ec2.EC2, prefixListId string) (*ec2.ManagedPrefixList, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.PrefixListStateCreateInProgress}, + Target: []string{ec2.PrefixListStateCreateComplete}, + Timeout: ManagedPrefixListTimeout, + Refresh: ManagedPrefixListState(conn, prefixListId), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ManagedPrefixList); ok { + return output, err + } + + return nil, err +} + +func ManagedPrefixListModified(conn *ec2.EC2, prefixListId string) (*ec2.ManagedPrefixList, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.PrefixListStateModifyInProgress}, + Target: []string{ec2.PrefixListStateModifyComplete}, + Timeout: ManagedPrefixListTimeout, + Refresh: ManagedPrefixListState(conn, prefixListId), + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*ec2.ManagedPrefixList); ok { + return output, err + } + + return nil, err +} + +func ManagedPrefixListDeleted(conn *ec2.EC2, prefixListId string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ec2.PrefixListStateDeleteInProgress}, + Target: []string{ec2.PrefixListStateDeleteComplete}, + Timeout: ManagedPrefixListTimeout, + Refresh: ManagedPrefixListState(conn, prefixListId), + } + + _, err := stateConf.WaitForState() + + if tfawserr.ErrCodeEquals(err, "InvalidPrefixListID.NotFound") { + return nil + } + + if err != nil { + return err + } + + return nil +} diff --git a/aws/resource_aws_ec2_managed_prefix_list.go b/aws/resource_aws_ec2_managed_prefix_list.go index aa88a32d50a..fddf4835120 100644 --- a/aws/resource_aws_ec2_managed_prefix_list.go +++ b/aws/resource_aws_ec2_managed_prefix_list.go @@ -1,22 +1,16 @@ package aws import ( - "errors" "fmt" "log" - "sort" - "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/aws-sdk-go-base/tfawserr" "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/keyvaluetags" -) - -var ( - awsPrefixListEntrySetHashFunc = schema.HashResource(prefixListEntrySchema()) + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ec2/waiter" ) func resourceAwsEc2ManagedPrefixList() *schema.Resource { @@ -44,12 +38,24 @@ func resourceAwsEc2ManagedPrefixList() *schema.Resource { Computed: true, }, "entry": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ConfigMode: schema.SchemaConfigModeAttr, - Elem: prefixListEntrySchema(), - Set: awsPrefixListEntrySetHashFunc, + Type: schema.TypeSet, + Optional: true, + // Computed: true, + // ConfigMode: schema.SchemaConfigModeAttr, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cidr_block": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.IsCIDR, + }, + "description": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + }, + }, }, "max_entries": { Type: schema.TypeInt, @@ -67,22 +73,9 @@ func resourceAwsEc2ManagedPrefixList() *schema.Resource { Computed: true, }, "tags": tagsSchema(), - }, - } -} - -func prefixListEntrySchema() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "cidr_block": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.IsCIDR, - }, - "description": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringLenBetween(0, 255), + "version": { + Type: schema.TypeInt, + Computed: true, }, }, } @@ -110,19 +103,17 @@ func resourceAwsEc2ManagedPrefixListCreate(d *schema.ResourceData, meta interfac output, err := conn.CreateManagedPrefixList(&input) if err != nil { - return fmt.Errorf("failed to create managed prefix list: %v", err) + return fmt.Errorf("failed to create managed prefix list: %w", err) } - id := aws.StringValue(output.PrefixList.PrefixListId) + d.SetId(aws.StringValue(output.PrefixList.PrefixListId)) - log.Printf("[INFO] Created Managed Prefix List %s (%s)", d.Get("name").(string), id) + log.Printf("[INFO] Created Managed Prefix List %s (%s)", d.Get("name").(string), d.Id()) - if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutCreate)); err != nil { - return fmt.Errorf("prefix list %s did not settle after create: %s", id, err) + if _, err := waiter.ManagedPrefixListCreated(conn, d.Id()); err != nil { + return fmt.Errorf("managed prefix list %s failed to create: %w", d.Id(), err) } - d.SetId(id) - return resourceAwsEc2ManagedPrefixListRead(d, meta) } @@ -132,10 +123,11 @@ func resourceAwsEc2ManagedPrefixListRead(d *schema.ResourceData, meta interface{ id := d.Id() pl, ok, err := getManagedPrefixList(id, conn) - switch { - case err != nil: - return err - case !ok: + if err != nil { + return fmt.Errorf("failed to get managed prefix list %s: %w", id, err) + } + + if !ok { log.Printf("[WARN] Managed Prefix List %s not found; removing from state.", id) d.SetId("") return nil @@ -146,7 +138,7 @@ func resourceAwsEc2ManagedPrefixListRead(d *schema.ResourceData, meta interface{ entries, err := getPrefixListEntries(id, conn, 0) if err != nil { - return err + return fmt.Errorf("error listing entries of EC2 Managed Prefix List (%s): %w", d.Id(), err) } if err := d.Set("entry", flattenPrefixListEntries(entries)); err != nil { @@ -161,70 +153,56 @@ func resourceAwsEc2ManagedPrefixListRead(d *schema.ResourceData, meta interface{ return fmt.Errorf("error settings attribute tags of managed prefix list %s: %s", id, err) } + d.Set("version", pl.Version) + return nil } func resourceAwsEc2ManagedPrefixListUpdate(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).ec2conn id := d.Id() - modifyPrefixList := false input := ec2.ModifyManagedPrefixListInput{} input.PrefixListId = aws.String(id) - if d.HasChange("name") { + if d.HasChangeExcept("tags") { input.PrefixListName = aws.String(d.Get("name").(string)) - modifyPrefixList = true - } - - if d.HasChange("entry") { - pl, ok, err := getManagedPrefixList(id, conn) - switch { - case err != nil: - return err - case !ok: - return &resource.NotFoundError{} - } + currentVersion := int64(d.Get("version").(int)) + wait := false - currentVersion := aws.Int64Value(pl.Version) + oldAttr, newAttr := d.GetChange("entry") + os := oldAttr.(*schema.Set) + ns := newAttr.(*schema.Set) - oldEntries, err := getPrefixListEntries(id, conn, currentVersion) - if err != nil { - return err + if addEntries := ns.Difference(os); addEntries.Len() > 0 { + input.AddEntries = expandAddPrefixListEntries(addEntries) + input.CurrentVersion = aws.Int64(currentVersion) + wait = true } - newEntries := expandAddPrefixListEntries(d.Get("entry")) - adds, removes := computePrefixListEntriesModification(oldEntries, newEntries) - - if len(adds) > 0 || len(removes) > 0 { - if len(adds) > 0 { - // the Modify API doesn't like empty lists - input.AddEntries = adds - } - - if len(removes) > 0 { - // the Modify API doesn't like empty lists - input.RemoveEntries = removes - } - + if removeEntries := os.Difference(ns); removeEntries.Len() > 0 { + input.RemoveEntries = expandRemovePrefixListEntries(removeEntries) input.CurrentVersion = aws.Int64(currentVersion) - modifyPrefixList = true + wait = true } - } - if modifyPrefixList { log.Printf("[INFO] modifying managed prefix list %s...", id) - switch _, err := conn.ModifyManagedPrefixList(&input); { - case isAWSErr(err, "PrefixListVersionMismatch", "prefix list has the incorrect version number"): + _, err := conn.ModifyManagedPrefixList(&input) + + if isAWSErr(err, "PrefixListVersionMismatch", "prefix list has the incorrect version number") { return fmt.Errorf("failed to modify managed prefix list %s: conflicting change", id) - case err != nil: + } + + if err != nil { return fmt.Errorf("failed to modify managed prefix list %s: %s", id, err) } - if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutUpdate)); err != nil { - return fmt.Errorf("prefix list did not settle after update: %s", err) + if wait { + if _, err := waiter.ManagedPrefixListModified(conn, d.Id()); err != nil { + return fmt.Errorf("failed to modify managed prefix list %s: %w", d.Id(), err) + } } } @@ -246,31 +224,18 @@ func resourceAwsEc2ManagedPrefixListDelete(d *schema.ResourceData, meta interfac PrefixListId: aws.String(id), } - err := resource.Retry(d.Timeout(schema.TimeoutDelete), func() *resource.RetryError { - _, err := conn.DeleteManagedPrefixList(&input) - switch { - case isManagedPrefixListModificationConflictErr(err): - return resource.RetryableError(err) - case isAWSErr(err, "InvalidPrefixListID.NotFound", ""): - log.Printf("[WARN] managed prefix list %s has already been deleted", id) - return nil - case err != nil: - return resource.NonRetryableError(err) - } + _, err := conn.DeleteManagedPrefixList(&input) + if tfawserr.ErrCodeEquals(err, "InvalidPrefixListID.NotFound") { return nil - }) - - if isResourceTimeoutError(err) { - _, err = conn.DeleteManagedPrefixList(&input) } if err != nil { - return fmt.Errorf("failed to delete managed prefix list %s: %s", id, err) + return fmt.Errorf("error deleting EC2 Managed Prefix List (%s): %w", d.Id(), err) } - if err := waitUntilAwsManagedPrefixListSettled(id, conn, d.Timeout(schema.TimeoutDelete)); err != nil { - return fmt.Errorf("prefix list %s did not settle after delete: %s", id, err) + if err := waiter.ManagedPrefixListDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("failed to delete managed prefix list %s: %w", d.Id(), err) } return nil @@ -301,7 +266,25 @@ func expandAddPrefixListEntries(input interface{}) []*ec2.AddPrefixListEntry { return result } -func flattenPrefixListEntries(entries []*ec2.PrefixListEntry) *schema.Set { +func expandRemovePrefixListEntries(input interface{}) []*ec2.RemovePrefixListEntry { + if input == nil { + return nil + } + + list := input.(*schema.Set).List() + result := make([]*ec2.RemovePrefixListEntry, 0, len(list)) + + for _, entry := range list { + m := entry.(map[string]interface{}) + output := ec2.RemovePrefixListEntry{} + output.Cidr = aws.String(m["cidr_block"].(string)) + result = append(result, &output) + } + + return result +} + +func flattenPrefixListEntries(entries []*ec2.PrefixListEntry) []interface{} { list := make([]interface{}, 0, len(entries)) for _, entry := range entries { @@ -315,7 +298,7 @@ func flattenPrefixListEntries(entries []*ec2.PrefixListEntry) *schema.Set { list = append(list, m) } - return schema.NewSet(awsPrefixListEntrySetHashFunc, list) + return list } func getManagedPrefixList( @@ -365,111 +348,3 @@ func getPrefixListEntries( return result, nil } - -func computePrefixListEntriesModification( - oldEntries []*ec2.PrefixListEntry, - newEntries []*ec2.AddPrefixListEntry, -) ([]*ec2.AddPrefixListEntry, []*ec2.RemovePrefixListEntry) { - adds := map[string]string{} // CIDR => Description - - removes := map[string]struct{}{} // set of CIDR - for _, oldEntry := range oldEntries { - oldCIDR := aws.StringValue(oldEntry.Cidr) - removes[oldCIDR] = struct{}{} - } - - for _, newEntry := range newEntries { - newCIDR := aws.StringValue(newEntry.Cidr) - newDescription := aws.StringValue(newEntry.Description) - - for _, oldEntry := range oldEntries { - oldCIDR := aws.StringValue(oldEntry.Cidr) - oldDescription := aws.StringValue(oldEntry.Description) - - if oldCIDR == newCIDR { - delete(removes, oldCIDR) - - if oldDescription != newDescription { - adds[oldCIDR] = newDescription - } - - goto nextNewEntry - } - } - - // reach this point when no matching oldEntry found - adds[newCIDR] = newDescription - - nextNewEntry: - } - - addList := make([]*ec2.AddPrefixListEntry, 0, len(adds)) - for cidr, description := range adds { - addList = append(addList, &ec2.AddPrefixListEntry{ - Cidr: aws.String(cidr), - Description: aws.String(description), - }) - } - sort.Slice(addList, func(i, j int) bool { - return aws.StringValue(addList[i].Cidr) < aws.StringValue(addList[j].Cidr) - }) - - removeList := make([]*ec2.RemovePrefixListEntry, 0, len(removes)) - for cidr := range removes { - removeList = append(removeList, &ec2.RemovePrefixListEntry{ - Cidr: aws.String(cidr), - }) - } - sort.Slice(removeList, func(i, j int) bool { - return aws.StringValue(removeList[i].Cidr) < aws.StringValue(removeList[j].Cidr) - }) - - return addList, removeList -} - -func waitUntilAwsManagedPrefixListSettled( - id string, - conn *ec2.EC2, - timeout time.Duration, -) error { - log.Printf("[INFO] Waiting for managed prefix list %s to settle...", id) - - err := resource.Retry(timeout, func() *resource.RetryError { - settled, err := isAwsManagedPrefixListSettled(id, conn) - switch { - case err != nil: - return resource.NonRetryableError(err) - case !settled: - return resource.RetryableError(errors.New("resource not yet settled")) - } - - return nil - }) - - if isResourceTimeoutError(err) { - return fmt.Errorf("timed out: %s", err) - } - - return nil -} - -func isAwsManagedPrefixListSettled(id string, conn *ec2.EC2) (bool, error) { - pl, ok, err := getManagedPrefixList(id, conn) - switch { - case err != nil: - return false, err - case !ok: - return true, nil - } - - switch state := aws.StringValue(pl.State); state { - case ec2.PrefixListStateCreateComplete, ec2.PrefixListStateModifyComplete, ec2.PrefixListStateDeleteComplete: - return true, nil - case ec2.PrefixListStateCreateInProgress, ec2.PrefixListStateModifyInProgress, ec2.PrefixListStateDeleteInProgress: - return false, nil - case ec2.PrefixListStateCreateFailed, ec2.PrefixListStateModifyFailed, ec2.PrefixListStateDeleteFailed: - return false, fmt.Errorf("terminal state %s indicates failure", state) - default: - return false, fmt.Errorf("unexpected state %s", state) - } -} diff --git a/aws/resource_aws_ec2_managed_prefix_list_test.go b/aws/resource_aws_ec2_managed_prefix_list_test.go index ec2667036d3..6c767623cb4 100644 --- a/aws/resource_aws_ec2_managed_prefix_list_test.go +++ b/aws/resource_aws_ec2_managed_prefix_list_test.go @@ -2,136 +2,16 @@ package aws import ( "fmt" - "reflect" "regexp" - "sort" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccAwsEc2ManagedPrefixList_computePrefixListEntriesModification(t *testing.T) { - type testEntry struct { - CIDR string - Description string - } - - tests := []struct { - name string - oldEntries []testEntry - newEntries []testEntry - expectedAdds []testEntry - expectedRemoves []testEntry - }{ - { - name: "add two", - oldEntries: []testEntry{}, - newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - expectedAdds: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - expectedRemoves: []testEntry{}, - }, - { - name: "remove one", - oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - newEntries: []testEntry{{"1.2.3.4/32", "test1"}}, - expectedAdds: []testEntry{}, - expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, - }, - { - name: "modify description of one", - oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2-1"}}, - expectedAdds: []testEntry{{"2.3.4.5/32", "test2-1"}}, - expectedRemoves: []testEntry{}, - }, - { - name: "add third", - oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}, {"3.4.5.6/32", "test3"}}, - expectedAdds: []testEntry{{"3.4.5.6/32", "test3"}}, - expectedRemoves: []testEntry{}, - }, - { - name: "add and remove one", - oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - newEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"3.4.5.6/32", "test3"}}, - expectedAdds: []testEntry{{"3.4.5.6/32", "test3"}}, - expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, - }, - { - name: "add and remove one with description change", - oldEntries: []testEntry{{"1.2.3.4/32", "test1"}, {"2.3.4.5/32", "test2"}}, - newEntries: []testEntry{{"1.2.3.4/32", "test1-1"}, {"3.4.5.6/32", "test3"}}, - expectedAdds: []testEntry{{"1.2.3.4/32", "test1-1"}, {"3.4.5.6/32", "test3"}}, - expectedRemoves: []testEntry{{"2.3.4.5/32", "test2"}}, - }, - { - name: "basic test update", - oldEntries: []testEntry{{"1.0.0.0/8", "Test1"}}, - newEntries: []testEntry{{"1.0.0.0/8", "Test1-1"}, {"2.2.0.0/16", "Test2"}}, - expectedAdds: []testEntry{{"1.0.0.0/8", "Test1-1"}, {"2.2.0.0/16", "Test2"}}, - expectedRemoves: []testEntry{}, - }, - } - - for _, test := range tests { - oldEntryList := []*ec2.PrefixListEntry(nil) - for _, entry := range test.oldEntries { - oldEntryList = append(oldEntryList, &ec2.PrefixListEntry{ - Cidr: aws.String(entry.CIDR), - Description: aws.String(entry.Description), - }) - } - - newEntryList := []*ec2.AddPrefixListEntry(nil) - for _, entry := range test.newEntries { - newEntryList = append(newEntryList, &ec2.AddPrefixListEntry{ - Cidr: aws.String(entry.CIDR), - Description: aws.String(entry.Description), - }) - } - - addList, removeList := computePrefixListEntriesModification(oldEntryList, newEntryList) - - if len(addList) != len(test.expectedAdds) { - t.Errorf("expected %d adds, got %d", len(test.expectedAdds), len(addList)) - } - - for i, added := range addList { - expected := test.expectedAdds[i] - - actualCidr := aws.StringValue(added.Cidr) - expectedCidr := expected.CIDR - if actualCidr != expectedCidr { - t.Errorf("add[%d]: expected cidr %s, got %s", i, expectedCidr, actualCidr) - } - - actualDesc := aws.StringValue(added.Description) - expectedDesc := expected.Description - if actualDesc != expectedDesc { - t.Errorf("add[%d]: expected description '%s', got '%s'", i, expectedDesc, actualDesc) - } - } - - if len(removeList) != len(test.expectedRemoves) { - t.Errorf("expected %d removes, got %d", len(test.expectedRemoves), len(removeList)) - } - - for i, removed := range removeList { - expected := test.expectedRemoves[i] - - actualCidr := aws.StringValue(removed.Cidr) - expectedCidr := expected.CIDR - if actualCidr != expectedCidr { - t.Errorf("add[%d]: expected cidr %s, got %s", i, expectedCidr, actualCidr) - } - } - } -} - func testAccCheckAwsEc2ManagedPrefixListDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).ec2conn @@ -169,52 +49,8 @@ func testAccCheckAwsEc2ManagedPrefixListVersion( func TestAccAwsEc2ManagedPrefixList_basic(t *testing.T) { resourceName := "aws_ec2_managed_prefix_list.test" pl, entries := ec2.ManagedPrefixList{}, []*ec2.PrefixListEntry(nil) - - checkAttributes := func(*terraform.State) error { - if actual := aws.StringValue(pl.AddressFamily); actual != "IPv4" { - return fmt.Errorf("bad address family: %s", actual) - } - - if actual := aws.Int64Value(pl.MaxEntries); actual != 5 { - return fmt.Errorf("bad max entries: %d", actual) - } - - if actual := aws.StringValue(pl.OwnerId); actual != testAccGetAccountID() { - return fmt.Errorf("bad owner id: %s", actual) - } - - if actual := aws.StringValue(pl.PrefixListName); actual != "tf-test-basic-create" { - return fmt.Errorf("bad name: %s", actual) - } - - sort.Slice(pl.Tags, func(i, j int) bool { - return aws.StringValue(pl.Tags[i].Key) < aws.StringValue(pl.Tags[j].Key) - }) - - expectTags := []*ec2.Tag{ - {Key: aws.String("Key1"), Value: aws.String("Value1")}, - {Key: aws.String("Key2"), Value: aws.String("Value2")}, - } - - if !reflect.DeepEqual(expectTags, pl.Tags) { - return fmt.Errorf("expected tags %#v, got %#v", expectTags, pl.Tags) - } - - sort.Slice(entries, func(i, j int) bool { - return aws.StringValue(entries[i].Cidr) < aws.StringValue(entries[j].Cidr) - }) - - expectEntries := []*ec2.PrefixListEntry{ - {Cidr: aws.String("1.0.0.0/8"), Description: aws.String("Test1")}, - {Cidr: aws.String("2.0.0.0/8"), Description: aws.String("Test2")}, - } - - if !reflect.DeepEqual(expectEntries, entries) { - return fmt.Errorf("expected entries %#v, got %#v", expectEntries, entries) - } - - return nil - } + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -222,12 +58,11 @@ func TestAccAwsEc2ManagedPrefixList_basic(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsEc2ManagedPrefixListConfig_basic_create, + Config: testAccAwsEc2ManagedPrefixListConfig_basic_create(rName1), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, &entries), - checkAttributes, - resource.TestCheckResourceAttr(resourceName, "name", "tf-test-basic-create"), + resource.TestCheckResourceAttr(resourceName, "name", rName1), testAccMatchResourceAttrRegionalARN(resourceName, "arn", "ec2", regexp.MustCompile(`prefix-list/pl-[[:xdigit:]]+`)), resource.TestCheckResourceAttr(resourceName, "address_family", "IPv4"), resource.TestCheckResourceAttr(resourceName, "max_entries", "5"), @@ -253,11 +88,11 @@ func TestAccAwsEc2ManagedPrefixList_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsEc2ManagedPrefixListConfig_basic_update, + Config: testAccAwsEc2ManagedPrefixListConfig_basic_update(rName2), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, &entries), - resource.TestCheckResourceAttr(resourceName, "name", "tf-test-basic-update"), + resource.TestCheckResourceAttr(resourceName, "name", rName2), resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "entry.*", map[string]string{ "cidr_block": "1.0.0.0/8", @@ -273,13 +108,19 @@ func TestAccAwsEc2ManagedPrefixList_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -const testAccAwsEc2ManagedPrefixListConfig_basic_create = ` +func testAccAwsEc2ManagedPrefixListConfig_basic_create(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-basic-create" + name = %[1]q address_family = "IPv4" max_entries = 5 @@ -298,11 +139,13 @@ resource "aws_ec2_managed_prefix_list" "test" { Key2 = "Value2" } } -` +`, rName) +} -const testAccAwsEc2ManagedPrefixListConfig_basic_update = ` +func testAccAwsEc2ManagedPrefixListConfig_basic_update(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-basic-update" + name = %[1]q address_family = "IPv4" max_entries = 5 @@ -321,7 +164,8 @@ resource "aws_ec2_managed_prefix_list" "test" { Key3 = "Value3" } } -` +`, rName) +} func testAccAwsEc2ManagedPrefixListExists( name string, @@ -368,6 +212,7 @@ func testAccAwsEc2ManagedPrefixListExists( func TestAccAwsEc2ManagedPrefixList_disappears(t *testing.T) { resourceName := "aws_ec2_managed_prefix_list.test" pl := ec2.ManagedPrefixList{} + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -375,7 +220,7 @@ func TestAccAwsEc2ManagedPrefixList_disappears(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsEc2ManagedPrefixListConfig_disappears, + Config: testAccAwsEc2ManagedPrefixListConfig_disappears(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), @@ -387,9 +232,10 @@ func TestAccAwsEc2ManagedPrefixList_disappears(t *testing.T) { }) } -const testAccAwsEc2ManagedPrefixListConfig_disappears = ` +func testAccAwsEc2ManagedPrefixListConfig_disappears(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-disappears" + name = %[1]q address_family = "IPv4" max_entries = 2 @@ -397,21 +243,14 @@ resource "aws_ec2_managed_prefix_list" "test" { cidr_block = "1.0.0.0/8" } } -` +`, rName) +} func TestAccAwsEc2ManagedPrefixList_name(t *testing.T) { resourceName := "aws_ec2_managed_prefix_list.test" pl := ec2.ManagedPrefixList{} - - checkName := func(name string) resource.TestCheckFunc { - return func(*terraform.State) error { - if actual := aws.StringValue(pl.PrefixListName); actual != name { - return fmt.Errorf("expected name %s, got %s", name, actual) - } - - return nil - } - } + rName1 := acctest.RandomWithPrefix("tf-acc-test") + rName2 := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -419,22 +258,25 @@ func TestAccAwsEc2ManagedPrefixList_name(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsEc2ManagedPrefixListConfig_name_create, + Config: testAccAwsEc2ManagedPrefixListConfig_name_create(rName1), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - resource.TestCheckResourceAttr(resourceName, "name", "tf-test-name-create"), - checkName("tf-test-name-create"), + resource.TestCheckResourceAttr(resourceName, "name", rName1), testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 1), ), }, { - Config: testAccAwsEc2ManagedPrefixListConfig_name_update, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAwsEc2ManagedPrefixListConfig_name_update(rName2), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - resource.TestCheckResourceAttr(resourceName, "name", "tf-test-name-update"), - checkName("tf-test-name-update"), + resource.TestCheckResourceAttr(resourceName, "name", rName2), testAccCheckAwsEc2ManagedPrefixListVersion(&pl, 1), ), }, @@ -447,54 +289,30 @@ func TestAccAwsEc2ManagedPrefixList_name(t *testing.T) { }) } -const testAccAwsEc2ManagedPrefixListConfig_name_create = ` +func testAccAwsEc2ManagedPrefixListConfig_name_create(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-name-create" + name = %[1]q address_family = "IPv4" max_entries = 5 } -` +`, rName) +} -const testAccAwsEc2ManagedPrefixListConfig_name_update = ` +func testAccAwsEc2ManagedPrefixListConfig_name_update(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-name-update" + name = %[1]q address_family = "IPv4" max_entries = 5 } -` +`, rName) +} func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { resourceName := "aws_ec2_managed_prefix_list.test" pl := ec2.ManagedPrefixList{} - - checkTags := func(m map[string]string) resource.TestCheckFunc { - return func(*terraform.State) error { - sort.Slice(pl.Tags, func(i, j int) bool { - return aws.StringValue(pl.Tags[i].Key) < aws.StringValue(pl.Tags[j].Key) - }) - - expectTags := []*ec2.Tag(nil) - - if m != nil { - for k, v := range m { - expectTags = append(expectTags, &ec2.Tag{ - Key: aws.String(k), - Value: aws.String(v), - }) - } - - sort.Slice(expectTags, func(i, j int) bool { - return aws.StringValue(expectTags[i].Key) < aws.StringValue(expectTags[j].Key) - }) - } - - if !reflect.DeepEqual(expectTags, pl.Tags) { - return fmt.Errorf("expected tags %#v, got %#v", expectTags, pl.Tags) - } - - return nil - } - } + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -502,11 +320,10 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsEc2ManagedPrefixListConfig_tags_none, + Config: testAccAwsEc2ManagedPrefixListConfig_tags_none(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - checkTags(nil), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -516,11 +333,10 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsEc2ManagedPrefixListConfig_tags_addSome, + Config: testAccAwsEc2ManagedPrefixListConfig_tags_addSome(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - checkTags(map[string]string{"Key1": "Value1", "Key2": "Value2", "Key3": "Value3"}), resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), resource.TestCheckResourceAttr(resourceName, "tags.Key1", "Value1"), resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2"), @@ -533,11 +349,10 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome, + Config: testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - checkTags(map[string]string{"Key2": "Value2-1", "Key3": "Value3"}), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.Key2", "Value2-1"), resource.TestCheckResourceAttr(resourceName, "tags.Key3", "Value3"), @@ -549,11 +364,10 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsEc2ManagedPrefixListConfig_tags_empty, + Config: testAccAwsEc2ManagedPrefixListConfig_tags_empty(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - checkTags(nil), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -563,11 +377,10 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccAwsEc2ManagedPrefixListConfig_tags_none, + Config: testAccAwsEc2ManagedPrefixListConfig_tags_none(rName), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &pl, nil), - checkTags(nil), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, @@ -580,17 +393,20 @@ func TestAccAwsEc2ManagedPrefixList_tags(t *testing.T) { }) } -const testAccAwsEc2ManagedPrefixListConfig_tags_none = ` +func testAccAwsEc2ManagedPrefixListConfig_tags_none(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" + name = %[1]q address_family = "IPv4" max_entries = 5 } -` +`, rName) +} -const testAccAwsEc2ManagedPrefixListConfig_tags_addSome = ` +func testAccAwsEc2ManagedPrefixListConfig_tags_addSome(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" + name = %[1]q address_family = "IPv4" max_entries = 5 @@ -600,11 +416,13 @@ resource "aws_ec2_managed_prefix_list" "test" { Key3 = "Value3" } } -` +`, rName) +} -const testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome = ` +func testAccAwsEc2ManagedPrefixListConfig_tags_dropOrModifySome(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" + name = %[1]q address_family = "IPv4" max_entries = 5 @@ -613,97 +431,24 @@ resource "aws_ec2_managed_prefix_list" "test" { Key3 = "Value3" } } -` - -const testAccAwsEc2ManagedPrefixListConfig_tags_empty = ` -resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" - address_family = "IPv4" - max_entries = 5 - tags = {} +`, rName) } -` -func TestAccAwsEc2ManagedPrefixList_entryConfigMode(t *testing.T) { - resourceName := "aws_ec2_managed_prefix_list.test" - prefixList := ec2.ManagedPrefixList{} - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, - Steps: []resource.TestStep{ - { - Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_blocks, - ResourceName: resourceName, - Check: resource.ComposeAggregateTestCheckFunc( - testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), - resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_noBlocks, - ResourceName: resourceName, - Check: resource.ComposeAggregateTestCheckFunc( - testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), - resource.TestCheckResourceAttr(resourceName, "entry.#", "2"), - ), - }, - { - Config: testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_zeroed, - ResourceName: resourceName, - Check: resource.ComposeAggregateTestCheckFunc( - testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), - resource.TestCheckResourceAttr(resourceName, "entry.#", "0"), - ), - }, - }, - }) -} - -const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_blocks = ` +func testAccAwsEc2ManagedPrefixListConfig_tags_empty(rName string) string { + return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" - max_entries = 5 + name = %[1]q address_family = "IPv4" - - entry { - cidr_block = "1.0.0.0/8" - description = "Entry1" - } - - entry { - cidr_block = "2.0.0.0/8" - description = "Entry2" - } -} -` - -const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_noBlocks = ` -resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" max_entries = 5 - address_family = "IPv4" + tags = {} } -` - -const testAccAwsEc2ManagedPrefixListConfig_entryConfigMode_zeroed = ` -resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" - max_entries = 5 - address_family = "IPv4" - entry = [] +`, rName) } -` func TestAccAwsEc2ManagedPrefixList_exceedLimit(t *testing.T) { resourceName := "aws_ec2_managed_prefix_list.test" prefixList := ec2.ManagedPrefixList{} + rName := acctest.RandomWithPrefix("tf-acc-test") resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -711,7 +456,7 @@ func TestAccAwsEc2ManagedPrefixList_exceedLimit(t *testing.T) { CheckDestroy: testAccCheckAwsEc2ManagedPrefixListDestroy, Steps: []resource.TestStep{ { - Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(2), + Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(rName, 2), ResourceName: resourceName, Check: resource.ComposeAggregateTestCheckFunc( testAccAwsEc2ManagedPrefixListExists(resourceName, &prefixList, nil), @@ -719,7 +464,7 @@ func TestAccAwsEc2ManagedPrefixList_exceedLimit(t *testing.T) { ), }, { - Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(3), + Config: testAccAwsEc2ManagedPrefixListConfig_exceedLimit(rName, 3), ResourceName: resourceName, ExpectError: regexp.MustCompile(`You've reached the maximum number of entries for the prefix list.`), }, @@ -727,7 +472,7 @@ func TestAccAwsEc2ManagedPrefixList_exceedLimit(t *testing.T) { }) } -func testAccAwsEc2ManagedPrefixListConfig_exceedLimit(count int) string { +func testAccAwsEc2ManagedPrefixListConfig_exceedLimit(rName string, count int) string { entries := `` for i := 0; i < count; i++ { entries += fmt.Sprintf(` @@ -740,11 +485,10 @@ func testAccAwsEc2ManagedPrefixListConfig_exceedLimit(count int) string { return fmt.Sprintf(` resource "aws_ec2_managed_prefix_list" "test" { - name = "tf-test-acc" + name = %[2]q address_family = "IPv4" max_entries = 2 %[1]s } -`, - entries) +`, entries, rName) } diff --git a/website/docs/r/ec2_managed_prefix_list.html.markdown b/website/docs/r/ec2_managed_prefix_list.html.markdown index d372d874d75..d0f25d54b9d 100644 --- a/website/docs/r/ec2_managed_prefix_list.html.markdown +++ b/website/docs/r/ec2_managed_prefix_list.html.markdown @@ -10,13 +10,6 @@ description: |- Provides a managed prefix list resource. -~> **NOTE on Prefix Lists and Prefix List Entries:** Terraform currently -provides both a standalone [Managed Prefix List Entry resource](ec2_managed_prefix_list_entry.html), -and a Prefix List resource 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 on `max_entries`:** When you reference a Prefix List in a resource, the maximum number of entries for the prefix lists counts as the same number of rules or entries for the resource. For example, if you create a prefix list with a maximum @@ -75,6 +68,7 @@ In addition to all arguments above, the following attributes are exported: * `id` - The ID of the prefix list. * `arn` - The ARN of the prefix list. * `owner_id` - The ID of the AWS account that owns this prefix list. +* `version` - The latest version of this prefix list. ## Import