From 4d532fce6e81d6fe17153bcd0791a6876395cfc4 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:50:03 +0100 Subject: [PATCH 1/2] Add VPCLatticeServiceNetwork resource --- .../service/vpclattice/service_network.go | 181 ++++++++++++ .../vpclattice/service_network_test.go | 270 ++++++++++++++++++ .../service/vpclattice/service_package_gen.go | 8 + .../vpclattice_service_network.html.markdown | 48 ++++ 4 files changed, 507 insertions(+) create mode 100644 internal/service/vpclattice/service_network.go create mode 100644 internal/service/vpclattice/service_network_test.go create mode 100644 website/docs/r/vpclattice_service_network.html.markdown diff --git a/internal/service/vpclattice/service_network.go b/internal/service/vpclattice/service_network.go new file mode 100644 index 00000000000..77242d1f8ce --- /dev/null +++ b/internal/service/vpclattice/service_network.go @@ -0,0 +1,181 @@ +package vpclattice + +import ( + "context" + "errors" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_vpclattice_service_network", name="ServiceNetwork") +// @Tags(identifierAttribute="arn") +func ResourceServiceNetwork() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceServiceNetworkCreate, + ReadWithoutTimeout: resourceServiceNetworkRead, + UpdateWithoutTimeout: resourceServiceNetworkUpdate, + DeleteWithoutTimeout: resourceServiceNetworkDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + names.AttrARN: { + Type: schema.TypeString, + Computed: true, + }, + "auth_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[types.AuthType](), + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 63), + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +const ( + ResNameServiceNetwork = "Service Network" +) + +func resourceServiceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + in := &vpclattice.CreateServiceNetworkInput{ + ClientToken: aws.String(id.UniqueId()), + Name: aws.String(d.Get("name").(string)), + Tags: GetTagsIn(ctx), + } + + if v, ok := d.GetOk("auth_type"); ok { + in.AuthType = types.AuthType(v.(string)) + } + + out, err := conn.CreateServiceNetwork(ctx, in) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameServiceNetwork, d.Get("name").(string), err) + } + + if out == nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameServiceNetwork, d.Get("name").(string), errors.New("empty output")) + } + + d.SetId(aws.ToString(out.Id)) + + return resourceServiceNetworkRead(ctx, d, meta) +} + +func resourceServiceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + out, err := findServiceNetworkByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] VPCLattice ServiceNetwork (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameServiceNetwork, d.Id(), err) + } + + d.Set("arn", out.Arn) + d.Set("auth_type", out.AuthType) + d.Set("name", out.Name) + + return nil +} + +func resourceServiceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + if d.HasChangesExcept("tags", "tags_all") { + in := &vpclattice.UpdateServiceNetworkInput{ + ServiceNetworkIdentifier: aws.String(d.Id()), + } + + if d.HasChanges("auth_type") { + in.AuthType = types.AuthType(d.Get("auth_type").(string)) + } + + log.Printf("[DEBUG] Updating VPCLattice ServiceNetwork (%s): %#v", d.Id(), in) + _, err := conn.UpdateServiceNetwork(ctx, in) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameServiceNetwork, d.Id(), err) + } + } + + return resourceServiceNetworkRead(ctx, d, meta) +} + +func resourceServiceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + log.Printf("[INFO] Deleting VPCLattice ServiceNetwork %s", d.Id()) + + _, err := conn.DeleteServiceNetwork(ctx, &vpclattice.DeleteServiceNetworkInput{ + ServiceNetworkIdentifier: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + + return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameServiceNetwork, d.Id(), err) + } + + return nil +} + +func findServiceNetworkByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetServiceNetworkOutput, error) { + in := &vpclattice.GetServiceNetworkInput{ + ServiceNetworkIdentifier: aws.String(id), + } + out, err := conn.GetServiceNetwork(ctx, in) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out, nil +} diff --git a/internal/service/vpclattice/service_network_test.go b/internal/service/vpclattice/service_network_test.go new file mode 100644 index 00000000000..ef3f04e8724 --- /dev/null +++ b/internal/service/vpclattice/service_network_test.go @@ -0,0 +1,270 @@ +package vpclattice_test + +import ( + "context" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" + sdkacctest "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" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfvpclattice "github.com/hashicorp/terraform-provider-aws/internal/service/vpclattice" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccVPCLatticeServiceNetwork_basic(t *testing.T) { + ctx := acctest.Context(t) + + var servicenetwork vpclattice.GetServiceNetworkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceNetworkDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &servicenetwork), + resource.TestCheckResourceAttr(resourceName, "name", rName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("servicenetwork/.+$")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCLatticeServiceNetwork_disappears(t *testing.T) { + ctx := acctest.Context(t) + + var servicenetwork vpclattice.GetServiceNetworkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceNetworkDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &servicenetwork), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceServiceNetwork(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccVPCLatticeServiceNetwork_full(t *testing.T) { + ctx := acctest.Context(t) + + var servicenetwork vpclattice.GetServiceNetworkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.VPCLatticeEndpointID) + testAccPreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceNetworkDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkConfig_full(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &servicenetwork), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "auth_type", "AWS_IAM"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("servicenetwork/.+$")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCLatticeServiceNetwork_tags(t *testing.T) { + ctx := acctest.Context(t) + var serviceNetwork1, serviceNetwork2, serviceNetwork3 vpclattice.GetServiceNetworkOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service_network.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServiceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceNetworkConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &serviceNetwork1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccServiceNetworkConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &serviceNetwork2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccServiceNetworkConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceNetworkExists(ctx, resourceName, &serviceNetwork3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckServiceNetworkDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpclattice_service_network" { + continue + } + + _, err := conn.GetServiceNetwork(ctx, &vpclattice.GetServiceNetworkInput{ + ServiceNetworkIdentifier: aws.String(rs.Primary.ID), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err + } + + return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameServiceNetwork, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckServiceNetworkExists(ctx context.Context, name string, servicenetwork *vpclattice.GetServiceNetworkOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameServiceNetwork, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameServiceNetwork, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + resp, err := conn.GetServiceNetwork(ctx, &vpclattice.GetServiceNetworkInput{ + ServiceNetworkIdentifier: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameServiceNetwork, rs.Primary.ID, err) + } + + *servicenetwork = *resp + + return nil + } +} + +// func testAccCheckServiceNetworkNotRecreated(before, after *vpclattice.DescribeServiceNetworkResponse) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if before, after := aws.StringValue(before.ServiceNetworkId), aws.StringValue(after.ServiceNetworkId); before != after { +// return create.Error(names.VPCLattice, create.ErrActionCheckingNotRecreated, tfvpclattice.ResNameServiceNetwork, aws.StringValue(before.ServiceNetworkId), errors.New("recreated")) +// } + +// return nil +// } +// } + +func testAccServiceNetworkConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service_network" "test" { + name = %[1]q +} +`, rName) +} + +func testAccServiceNetworkConfig_full(rName string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service_network" "test" { + name = %[1]q + auth_type = "AWS_IAM" +} +`, rName) +} + +func testAccServiceNetworkConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service_network" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccServiceNetworkConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service_network" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index da348e17ccf..125523d8eb7 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -33,6 +33,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "arn", }, }, + { + Factory: ResourceServiceNetwork, + TypeName: "aws_vpclattice_service_network", + Name: "ServiceNetwork", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, + }, } } diff --git a/website/docs/r/vpclattice_service_network.html.markdown b/website/docs/r/vpclattice_service_network.html.markdown new file mode 100644 index 00000000000..9993b1a1fd9 --- /dev/null +++ b/website/docs/r/vpclattice_service_network.html.markdown @@ -0,0 +1,48 @@ +--- +subcategory: "VPC Lattice" +layout: "aws" +page_title: "AWS: aws_vpclattice_service_network" +description: |- + Terraform resource for managing an AWS VPC Lattice Service Network. +--- + +# Resource: aws_vpclattice_service_network + +Terraform resource for managing an AWS VPC Lattice Service Network. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_vpclattice_service_network" "example" { + name = "example" + auth_type = "AWS_IAM" +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Name of the service network + +The following arguments are optional: + +* `auth_type` - (Optional) Type of IAM policy. Either `NONE` or `AWS_IAM`. +* `tags` - (Optional) Key-value mapping of resource tags. If configured with a provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - ARN of the Service Network. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](/docs/providers/aws/index.html#default_tags-configuration-block). + +## Import + +VPC Lattice Service Network can be imported using the `id`, e.g., + +``` +$ terraform import aws_vpclattice_service_network.example sn-0158f91c1e3358dba +``` From 9ff7e2cb4407b2476dee0c05860a75ceeb2b5b23 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 19:27:09 +0100 Subject: [PATCH 2/2] Add Changelog --- .changelog/35969.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/35969.txt diff --git a/.changelog/35969.txt b/.changelog/35969.txt new file mode 100644 index 00000000000..74224e67ac0 --- /dev/null +++ b/.changelog/35969.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_vpclattice_service_network +```