From 6ab9798276a8f4e200b3ac579bd614608cc28738 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:12:39 +0100 Subject: [PATCH 01/10] Add VPC Lattice Service resource --- internal/service/vpclattice/export_tests.go | 6 + internal/service/vpclattice/find.go | 36 ++ internal/service/vpclattice/generate.go | 4 + internal/service/vpclattice/service.go | 354 ++++++++++++++++++ .../service/vpclattice/service_package_gen.go | 6 +- internal/service/vpclattice/service_test.go | 156 ++++++++ internal/service/vpclattice/status.go | 24 ++ .../vpclattice/{tags.go => tags_gen.go} | 3 +- internal/service/vpclattice/wait.go | 45 +++ names/names.go | 1 + .../docs/r/vpclattice_service.html.markdown | 60 +++ 11 files changed, 693 insertions(+), 2 deletions(-) create mode 100644 internal/service/vpclattice/export_tests.go create mode 100644 internal/service/vpclattice/find.go create mode 100644 internal/service/vpclattice/generate.go create mode 100644 internal/service/vpclattice/service.go create mode 100644 internal/service/vpclattice/service_test.go create mode 100644 internal/service/vpclattice/status.go rename internal/service/vpclattice/{tags.go => tags_gen.go} (97%) create mode 100644 internal/service/vpclattice/wait.go create mode 100644 website/docs/r/vpclattice_service.html.markdown diff --git a/internal/service/vpclattice/export_tests.go b/internal/service/vpclattice/export_tests.go new file mode 100644 index 00000000000..82d2b024c4a --- /dev/null +++ b/internal/service/vpclattice/export_tests.go @@ -0,0 +1,6 @@ +package vpclattice + +// Exports for use in tests only. +var ( + ResourceService = newResourceService +) diff --git a/internal/service/vpclattice/find.go b/internal/service/vpclattice/find.go new file mode 100644 index 00000000000..7c4a3a47f7b --- /dev/null +++ b/internal/service/vpclattice/find.go @@ -0,0 +1,36 @@ +package vpclattice + +import ( + "context" + "errors" + + "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/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func findServiceByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetServiceOutput, error) { + in := &vpclattice.GetServiceInput{ + ServiceIdentifier: aws.String(id), + } + out, err := conn.GetService(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/generate.go b/internal/service/vpclattice/generate.go new file mode 100644 index 00000000000..61423534b2e --- /dev/null +++ b/internal/service/vpclattice/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ServiceTagsMap -KVTValues -SkipTypesImp -ListTags -UpdateTags +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package vpclattice diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go new file mode 100644 index 00000000000..59abcf1d725 --- /dev/null +++ b/internal/service/vpclattice/service.go @@ -0,0 +1,354 @@ +package vpclattice + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + awstypes "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource +// @Tags(identifierAttribute="arn") +func newResourceService(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceService{}, nil +} + +type resourceServiceData struct { + ARN types.String `tfsdk:"arn"` + AuthType types.String `tfsdk:"auth_type"` + CertificateARN types.String `tfsdk:"certificate_arn"` + CustomDomainName types.String `tfsdk:"custom_domain_name"` + DnsEntry types.List `tfsdk:"dns_entry"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Status types.String `tfsdk:"status"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +const ( + ResNameService = "Service" + serviceCreateTimeout = 30 * time.Minute + serviceDeleteTimeout = 30 * time.Minute +) + +type resourceService struct { + framework.ResourceWithConfigure +} + +func (r *resourceService) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_vpclattice_service" +} + +func (r *resourceService) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": framework.ARNAttributeComputedOnly(), + "auth_type": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Type of IAM policy. Either `NONE` or `AWS_IAM`", + Validators: []validator.String{ + enum.FrameworkValidate[awstypes.AuthType](), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "certificate_arn": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Amazon Resource Name (ARN) of the certificate", + }, + "custom_domain_name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Custom domain name of the service", + Validators: []validator.String{ + stringvalidator.LengthBetween(3, 255), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + }, + "id": framework.IDAttribute(), + "name": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Name of the service. The name must be unique within the account. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen.", + Validators: []validator.String{ + stringvalidator.LengthBetween(3, 40), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "status": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Status of the service. If the status is `CREATE_FAILED`, you will have to delete and recreate the service.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + Blocks: map[string]schema.Block{ + "dns_entry": schema.ListNestedBlock{ + MarkdownDescription: "Public DNS name of the service", + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "domain_name": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Domain name of the service", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "hosted_zone_id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "ID of the hosted zone", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: false, + Delete: true, + }), + }, + } +} + +func (r *resourceService) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan, state resourceServiceData + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + + if resp.Diagnostics.HasError() { + return + } + + createTimeout, diags := plan.Timeouts.Create(ctx, serviceCreateTimeout) // nosemgrep:ci.semgrep.migrate.direct-CRUD-calls + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, createTimeout) + defer cancel() + + conn := r.Meta().VPCLatticeClient() + + in := &vpclattice.CreateServiceInput{ + ClientToken: aws.String(id.UniqueId()), + Name: plan.Name.ValueStringPointer(), + } + + if !plan.AuthType.IsNull() { + in.AuthType = awstypes.AuthType(plan.AuthType.ValueString()) + } + + if !plan.CertificateARN.IsNull() { + in.CertificateArn = plan.CertificateARN.ValueStringPointer() + } + + if !plan.CustomDomainName.IsNull() { + in.CustomDomainName = plan.CustomDomainName.ValueStringPointer() + } + + out, err := conn.CreateService(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.VPCLattice, create.ErrActionCreating, ResNameService, plan.Name.String(), nil), + err.Error(), + ) + return + } + + if _, err := waitServiceCreated(ctx, conn, *out.Id, createTimeout); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameService, plan.Name.String(), nil), + err.Error(), + ) + return + } + + state = plan + state.ARN = flex.StringToFramework(ctx, out.Arn) + state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) + state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) + state.CustomDomainName = flex.StringToFramework(ctx, out.CustomDomainName) + // state.DnsEntry = flattenDNSEntry(ctx, out.DnsEntry) + state.ID = flex.StringToFramework(ctx, out.Id) + state.Name = flex.StringToFramework(ctx, out.Name) + state.Status = flex.StringToFramework(ctx, (*string)(&out.Status)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceService) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().VPCLatticeClient() + + var state resourceServiceData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findServiceByID(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + + state.ARN = flex.StringToFramework(ctx, out.Arn) + state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) + state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) + state.CustomDomainName = flex.StringToFramework(ctx, out.CustomDomainName) + // state.DnsEntry = flattenDNSEntry(ctx, out.DnsEntry) + state.ID = flex.StringToFramework(ctx, out.Id) + state.Name = flex.StringToFramework(ctx, out.Name) + state.Status = flex.StringToFramework(ctx, (*string)(&out.Status)) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceService) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().VPCLatticeClient() + + var plan, state resourceServiceData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.AuthType.Equal(state.AuthType) || + !plan.CertificateARN.Equal(state.CertificateARN) { + input := &vpclattice.UpdateServiceInput{ + ServiceIdentifier: plan.ID.ValueStringPointer(), + } + + if !plan.AuthType.Equal(state.AuthType) { + input.AuthType = awstypes.AuthType(plan.AuthType.ValueString()) + } + + if !plan.CertificateARN.Equal(state.CertificateARN) { + input.CertificateArn = plan.CertificateARN.ValueStringPointer() + } + + out, err := conn.UpdateService(ctx, input) + + if err != nil { + resp.Diagnostics.AddError(fmt.Sprintf("updating Security Policy (%s)", plan.Name.ValueString()), err.Error()) + return + } + + state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) + state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceService) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().VPCLatticeClient() + + var state resourceServiceData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + deleteTimeout, diags := state.Timeouts.Delete(ctx, serviceDeleteTimeout) // nosemgrep:ci.semgrep.migrate.direct-CRUD-calls + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + ctx, cancel := context.WithTimeout(ctx, deleteTimeout) + defer cancel() + + _, err := conn.DeleteService(ctx, &vpclattice.DeleteServiceInput{ + ServiceIdentifier: flex.StringFromFramework(ctx, state.ID), + }) + if err != nil { + var nfe *awstypes.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.VPCLattice, create.ErrActionDeleting, ResNameService, state.Name.String(), nil), + err.Error(), + ) + } + + if _, err := waitServiceDeleted(ctx, conn, state.ID.ValueString(), deleteTimeout); err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.VPCLattice, create.ErrActionWaitingForDeletion, ResNameService, state.Name.String(), nil), + err.Error(), + ) + return + } +} + +func (r *resourceService) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + +} + +var dnsEntryAttrs = map[string]attr.Type{ + "domain_name": types.StringType, + "hosted_zone_id": types.StringType, +} + +func flattenDNSEntry(ctx context.Context, dns *awstypes.DnsEntry) types.List { + elemType := types.ObjectType{AttrTypes: dnsEntryAttrs} + + if dns == nil { + return types.ListNull(elemType) + } + + attrs := map[string]attr.Value{} + attrs["domain_name"] = flex.StringToFramework(ctx, dns.DomainName) + attrs["hosted_zone_id"] = flex.StringToFramework(ctx, dns.HostedZoneId) + + vals := types.ObjectValueMust(dnsEntryAttrs, attrs) + + return types.ListValueMust(elemType, []attr.Value{vals}) +} diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 1066a04587a..58b1ccd5de3 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -16,7 +16,11 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceService, + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/internal/service/vpclattice/service_test.go b/internal/service/vpclattice/service_test.go new file mode 100644 index 00000000000..c58b1e0973d --- /dev/null +++ b/internal/service/vpclattice/service_test.go @@ -0,0 +1,156 @@ +package vpclattice_test + +import ( + "context" + "errors" + "fmt" + "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 TestAccVPCLatticeService_basic(t *testing.T) { + ctx := acctest.Context(t) + var service vpclattice.GetServiceOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service.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: testAccCheckServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccService_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "name", rName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccVPCLatticeService_disappears(t *testing.T) { + ctx := acctest.Context(t) + var service vpclattice.GetServiceOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service.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: testAccCheckServiceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccService_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(resourceName, &service), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceService, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckServiceDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + ctx := context.Background() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_vpclattice_service" { + continue + } + + _, err := conn.GetService(ctx, &vpclattice.GetServiceInput{ + ServiceIdentifier: 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.ResNameService, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil +} + +func testAccCheckServiceExists(name string, service *vpclattice.GetServiceOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameService, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameService, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + ctx := context.Background() + resp, err := conn.GetService(ctx, &vpclattice.GetServiceInput{ + ServiceIdentifier: aws.String(rs.Primary.ID), + }) + + if err != nil { + return create.Error(names.VPCLattice, create.ErrActionCheckingExistence, tfvpclattice.ResNameService, rs.Primary.ID, err) + } + + *service = *resp + + return nil + } +} + +func testAccPreCheck(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() + + input := &vpclattice.ListServicesInput{} + _, err := conn.ListServices(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccService_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q +} +`, rName) +} diff --git a/internal/service/vpclattice/status.go b/internal/service/vpclattice/status.go new file mode 100644 index 00000000000..f0b14a94685 --- /dev/null +++ b/internal/service/vpclattice/status.go @@ -0,0 +1,24 @@ +package vpclattice + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/vpclattice" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func statusService(ctx context.Context, conn *vpclattice.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findServiceByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Status), nil + } +} diff --git a/internal/service/vpclattice/tags.go b/internal/service/vpclattice/tags_gen.go similarity index 97% rename from internal/service/vpclattice/tags.go rename to internal/service/vpclattice/tags_gen.go index a36ba769cc6..acecf314216 100644 --- a/internal/service/vpclattice/tags.go +++ b/internal/service/vpclattice/tags_gen.go @@ -1,3 +1,4 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. package vpclattice import ( @@ -44,7 +45,7 @@ func (p *servicePackage) ListTags(ctx context.Context, meta any, identifier stri return nil } -// map[string]*string handling +// map[string]string handling // Tags returns vpclattice service tags. func Tags(tags tftags.KeyValueTags) map[string]string { diff --git a/internal/service/vpclattice/wait.go b/internal/service/vpclattice/wait.go new file mode 100644 index 00000000000..771ca385e2d --- /dev/null +++ b/internal/service/vpclattice/wait.go @@ -0,0 +1,45 @@ +package vpclattice + +import ( + "context" + "time" + + "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/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/enum" +) + +func waitServiceCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ServiceStatusCreateInProgress), + Target: enum.Slice(types.ServiceStatusActive), + Refresh: statusService(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { + return out, err + } + + return nil, err +} + +func waitServiceDeleted(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ServiceStatusDeleteInProgress, types.ServiceStatusActive), + Target: []string{}, + Refresh: statusService(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { + return out, err + } + + return nil, err +} diff --git a/names/names.go b/names/names.go index 04f923d6c35..5f1d1651b5d 100644 --- a/names/names.go +++ b/names/names.go @@ -44,6 +44,7 @@ const ( SSMContactsEndpointId = "ssm-contacts" SSMIncidentsEndpointID = "ssm-incidents" TranscribeEndpointID = "transcribe" + VPCLatticeEndpointID = "vpc-lattice" ) // Type ServiceDatum corresponds closely to columns in `names_data.csv` and are diff --git a/website/docs/r/vpclattice_service.html.markdown b/website/docs/r/vpclattice_service.html.markdown new file mode 100644 index 00000000000..700e1f05a8e --- /dev/null +++ b/website/docs/r/vpclattice_service.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "VPC Lattice" +layout: "aws" +page_title: "AWS: aws_vpclattice_service" +description: |- + Terraform resource for managing an AWS VPC Lattice Service. +--- + +# Resource: aws_vpclattice_service + +Terraform resource for managing an AWS VPC Lattice Service. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_vpclattice_service" "example" { + name = "example" + auth_type = "AWS_IAM" + custom_domain_name = "example.com" +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Name of the service. The name must be unique within the account. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen.Must be between 3 and 40 characters in length. + +The following arguments are optional: + +* `auth_type` - (Optional) Type of IAM policy. Either `NONE` or `AWS_IAM`. +* `certificate_arn` - (Optional) Amazon Resource Name (ARN) of the certificate. +* `custom_domain_name` - (Optional) Custom domain name of the service. +* `tags` - (Optional) A map of tags to assign to the service. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#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. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `dns_entry` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. +* `id` - Unique identifier for the service. +* `status` - Status of the service. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +VPC Lattice Service can be imported using the `id`, e.g., + +``` +$ terraform import aws_vpclattice_service.example svc-06728e2357ea55f8a +``` From 3348c2bd5a92ece93fb05e6f14785b33930b4159 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:53:39 +0100 Subject: [PATCH 02/10] Fix linting issues --- internal/service/vpclattice/service.go | 33 +++++++++---------- .../service/vpclattice/service_package_gen.go | 3 ++ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go index 59abcf1d725..e3691238960 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -12,7 +12,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -28,7 +27,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" ) @@ -329,26 +327,25 @@ func (r *resourceService) Delete(ctx context.Context, req resource.DeleteRequest func (r *resourceService) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) - } -var dnsEntryAttrs = map[string]attr.Type{ - "domain_name": types.StringType, - "hosted_zone_id": types.StringType, -} +// var dnsEntryAttrs = map[string]attr.Type{ +// "domain_name": types.StringType, +// "hosted_zone_id": types.StringType, +// } -func flattenDNSEntry(ctx context.Context, dns *awstypes.DnsEntry) types.List { - elemType := types.ObjectType{AttrTypes: dnsEntryAttrs} +// func flattenDNSEntry(ctx context.Context, dns *awstypes.DnsEntry) types.List { +// elemType := types.ObjectType{AttrTypes: dnsEntryAttrs} - if dns == nil { - return types.ListNull(elemType) - } +// if dns == nil { +// return types.ListNull(elemType) +// } - attrs := map[string]attr.Value{} - attrs["domain_name"] = flex.StringToFramework(ctx, dns.DomainName) - attrs["hosted_zone_id"] = flex.StringToFramework(ctx, dns.HostedZoneId) +// attrs := map[string]attr.Value{} +// attrs["domain_name"] = flex.StringToFramework(ctx, dns.DomainName) +// attrs["hosted_zone_id"] = flex.StringToFramework(ctx, dns.HostedZoneId) - vals := types.ObjectValueMust(dnsEntryAttrs, attrs) +// vals := types.ObjectValueMust(dnsEntryAttrs, attrs) - return types.ListValueMust(elemType, []attr.Value{vals}) -} +// return types.ListValueMust(elemType, []attr.Value{vals}) +// } diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 58b1ccd5de3..1aaf787d7c7 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -19,6 +19,9 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic return []*types.ServicePackageFrameworkResource{ { Factory: newResourceService, + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, }, } } From 2297112a374d2ef76e6ab9df521ce95ad53823f9 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:40:19 +0100 Subject: [PATCH 03/10] Use SDK instead of framework; add some more tests --- internal/service/vpclattice/export_tests.go | 6 - internal/service/vpclattice/find.go | 36 -- internal/service/vpclattice/service.go | 490 +++++++++--------- .../service/vpclattice/service_package_gen.go | 16 +- internal/service/vpclattice/service_test.go | 186 ++++++- internal/service/vpclattice/status.go | 24 - internal/service/vpclattice/wait.go | 45 -- .../docs/r/vpclattice_service.html.markdown | 3 +- 8 files changed, 402 insertions(+), 404 deletions(-) delete mode 100644 internal/service/vpclattice/export_tests.go delete mode 100644 internal/service/vpclattice/find.go delete mode 100644 internal/service/vpclattice/status.go delete mode 100644 internal/service/vpclattice/wait.go diff --git a/internal/service/vpclattice/export_tests.go b/internal/service/vpclattice/export_tests.go deleted file mode 100644 index 82d2b024c4a..00000000000 --- a/internal/service/vpclattice/export_tests.go +++ /dev/null @@ -1,6 +0,0 @@ -package vpclattice - -// Exports for use in tests only. -var ( - ResourceService = newResourceService -) diff --git a/internal/service/vpclattice/find.go b/internal/service/vpclattice/find.go deleted file mode 100644 index 7c4a3a47f7b..00000000000 --- a/internal/service/vpclattice/find.go +++ /dev/null @@ -1,36 +0,0 @@ -package vpclattice - -import ( - "context" - "errors" - - "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/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func findServiceByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetServiceOutput, error) { - in := &vpclattice.GetServiceInput{ - ServiceIdentifier: aws.String(id), - } - out, err := conn.GetService(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.go b/internal/service/vpclattice/service.go index e3691238960..c730bcf1d60 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -3,349 +3,329 @@ package vpclattice import ( "context" "errors" - "fmt" + "log" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/vpclattice" - awstypes "github.com/aws/aws-sdk-go-v2/service/vpclattice/types" - "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" + "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" - "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" - "github.com/hashicorp/terraform-provider-aws/internal/flex" - "github.com/hashicorp/terraform-provider-aws/internal/framework" + 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" ) -// @FrameworkResource -// @Tags(identifierAttribute="arn") -func newResourceService(_ context.Context) (resource.ResourceWithConfigure, error) { - return &resourceService{}, nil -} - -type resourceServiceData struct { - ARN types.String `tfsdk:"arn"` - AuthType types.String `tfsdk:"auth_type"` - CertificateARN types.String `tfsdk:"certificate_arn"` - CustomDomainName types.String `tfsdk:"custom_domain_name"` - DnsEntry types.List `tfsdk:"dns_entry"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Status types.String `tfsdk:"status"` - Timeouts timeouts.Value `tfsdk:"timeouts"` -} - -const ( - ResNameService = "Service" - serviceCreateTimeout = 30 * time.Minute - serviceDeleteTimeout = 30 * time.Minute -) +// @SDKResource("aws_vpclattice_service") +func ResourceService() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceServiceCreate, + ReadWithoutTimeout: resourceServiceRead, + UpdateWithoutTimeout: resourceServiceUpdate, + DeleteWithoutTimeout: resourceServiceDelete, -type resourceService struct { - framework.ResourceWithConfigure -} + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, -func (r *resourceService) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_vpclattice_service" -} + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), + }, -func (r *resourceService) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "arn": framework.ARNAttributeComputedOnly(), - "auth_type": schema.StringAttribute{ - Optional: true, - Computed: true, - MarkdownDescription: "Type of IAM policy. Either `NONE` or `AWS_IAM`", - Validators: []validator.String{ - enum.FrameworkValidate[awstypes.AuthType](), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, }, - "certificate_arn": schema.StringAttribute{ - Optional: true, - MarkdownDescription: "Amazon Resource Name (ARN) of the certificate", + "auth_type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[types.AuthType](), }, - "custom_domain_name": schema.StringAttribute{ - Optional: true, - MarkdownDescription: "Custom domain name of the service", - Validators: []validator.String{ - stringvalidator.LengthBetween(3, 255), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplaceIfConfigured(), - }, + "certificate_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, }, - "id": framework.IDAttribute(), - "name": schema.StringAttribute{ - Required: true, - MarkdownDescription: "Name of the service. The name must be unique within the account. The valid characters are a-z, 0-9, and hyphens (-). You can't use a hyphen as the first or last character, or immediately after another hyphen.", - Validators: []validator.String{ - stringvalidator.LengthBetween(3, 40), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, + "custom_domain_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 255), }, - "status": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Status of the service. If the status is `CREATE_FAILED`, you will have to delete and recreate the service.", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, + "dns_entry": { + Type: schema.TypeMap, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - }, - Blocks: map[string]schema.Block{ - "dns_entry": schema.ListNestedBlock{ - MarkdownDescription: "Public DNS name of the service", - Validators: []validator.List{ - listvalidator.SizeAtMost(1), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "domain_name": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Domain name of the service", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "hosted_zone_id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "ID of the hosted zone", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - }, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), - }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(3, 40), + }, + "status": { + Type: schema.TypeString, + Computed: true, }, - "timeouts": timeouts.Block(ctx, timeouts.Opts{ - Create: true, - Update: false, - Delete: true, - }), + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), }, + + CustomizeDiff: verify.SetTagsDiff, } } -func (r *resourceService) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan, state resourceServiceData +const ( + ResNameService = "Service" +) - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) +func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() - if resp.Diagnostics.HasError() { - return + in := &vpclattice.CreateServiceInput{ + ClientToken: aws.String(id.UniqueId()), + Name: aws.String(d.Get("name").(string)), } - createTimeout, diags := plan.Timeouts.Create(ctx, serviceCreateTimeout) // nosemgrep:ci.semgrep.migrate.direct-CRUD-calls + if v, ok := d.GetOk("auth_type"); ok { + in.AuthType = types.AuthType(v.(string)) + } - resp.Diagnostics.Append(diags...) + if v, ok := d.GetOk("certificate_arn"); ok { + in.CertificateArn = aws.String(v.(string)) + } - if resp.Diagnostics.HasError() { - return + if v, ok := d.GetOk("custom_domain_name"); ok { + in.CustomDomainName = aws.String(v.(string)) } - ctx, cancel := context.WithTimeout(ctx, createTimeout) - defer cancel() + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) - conn := r.Meta().VPCLatticeClient() + if len(tags) > 0 { + in.Tags = Tags(tags.IgnoreAWS()) + } - in := &vpclattice.CreateServiceInput{ - ClientToken: aws.String(id.UniqueId()), - Name: plan.Name.ValueStringPointer(), + out, err := conn.CreateService(ctx, in) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, d.Get("name").(string), err) } - if !plan.AuthType.IsNull() { - in.AuthType = awstypes.AuthType(plan.AuthType.ValueString()) + if out == nil { + return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, d.Get("name").(string), errors.New("empty output")) } - if !plan.CertificateARN.IsNull() { - in.CertificateArn = plan.CertificateARN.ValueStringPointer() + d.SetId(aws.ToString(out.Id)) + + if _, err := waitServiceCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameService, d.Id(), err) } - if !plan.CustomDomainName.IsNull() { - in.CustomDomainName = plan.CustomDomainName.ValueStringPointer() + return resourceServiceRead(ctx, d, meta) +} + +func resourceServiceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + out, err := findServiceByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] VPCLattice Service (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil } - out, err := conn.CreateService(ctx, in) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.VPCLattice, create.ErrActionCreating, ResNameService, plan.Name.String(), nil), - err.Error(), - ) - return + return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameService, d.Id(), err) } - if _, err := waitServiceCreated(ctx, conn, *out.Id, createTimeout); err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.VPCLattice, create.ErrActionWaitingForCreation, ResNameService, plan.Name.String(), nil), - err.Error(), - ) - return + d.Set("arn", out.Arn) + d.Set("auth_type", out.AuthType) + d.Set("certificate_arn", out.CertificateArn) + d.Set("custom_domain_name", out.CustomDomainName) + d.Set("name", out.Name) + d.Set("status", out.Status) + + if err := d.Set("dns_entry", flattenDNSEntry(out.DnsEntry)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) } - state = plan - state.ARN = flex.StringToFramework(ctx, out.Arn) - state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) - state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) - state.CustomDomainName = flex.StringToFramework(ctx, out.CustomDomainName) - // state.DnsEntry = flattenDNSEntry(ctx, out.DnsEntry) - state.ID = flex.StringToFramework(ctx, out.Id) - state.Name = flex.StringToFramework(ctx, out.Name) - state.Status = flex.StringToFramework(ctx, (*string)(&out.Status)) - - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} + tags, err := ListTags(ctx, conn, *out.Arn) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameService, d.Id(), err) + } -func (r *resourceService) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().VPCLatticeClient() + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - var state resourceServiceData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) } - out, err := findServiceByID(ctx, conn, state.ID.ValueString()) - if tfresource.NotFound(err) { - resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) - resp.State.RemoveResource(ctx) - return + if err := d.Set("tags_all", tags.Map()); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) } - state.ARN = flex.StringToFramework(ctx, out.Arn) - state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) - state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) - state.CustomDomainName = flex.StringToFramework(ctx, out.CustomDomainName) - // state.DnsEntry = flattenDNSEntry(ctx, out.DnsEntry) - state.ID = flex.StringToFramework(ctx, out.Id) - state.Name = flex.StringToFramework(ctx, out.Name) - state.Status = flex.StringToFramework(ctx, (*string)(&out.Status)) - - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + return nil } -func (r *resourceService) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().VPCLatticeClient() +func resourceServiceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() - var plan, state resourceServiceData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } + if d.HasChangesExcept("tags", "tags_all") { + in := &vpclattice.UpdateServiceInput{ + ServiceIdentifier: aws.String(d.Id()), + } - if !plan.AuthType.Equal(state.AuthType) || - !plan.CertificateARN.Equal(state.CertificateARN) { - input := &vpclattice.UpdateServiceInput{ - ServiceIdentifier: plan.ID.ValueStringPointer(), + if d.HasChanges("auth_type") { + in.AuthType = types.AuthType(d.Get("auth_type").(string)) } - if !plan.AuthType.Equal(state.AuthType) { - input.AuthType = awstypes.AuthType(plan.AuthType.ValueString()) + if d.HasChanges("certificate_arn") { + in.CertificateArn = aws.String(d.Get("certificate_arn").(string)) } - if !plan.CertificateARN.Equal(state.CertificateARN) { - input.CertificateArn = plan.CertificateARN.ValueStringPointer() + log.Printf("[DEBUG] Updating VPCLattice Service (%s): %#v", d.Id(), in) + _, err := conn.UpdateService(ctx, in) + if err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionUpdating, ResNameService, d.Id(), err) } + } - out, err := conn.UpdateService(ctx, input) + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { + return diag.Errorf("updating %s %s (%s) tags: %s", names.VPCLattice, ResNameService, d.Id(), err) + } + } - if err != nil { - resp.Diagnostics.AddError(fmt.Sprintf("updating Security Policy (%s)", plan.Name.ValueString()), err.Error()) - return + return resourceServiceRead(ctx, d, meta) +} + +func resourceServiceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).VPCLatticeClient() + + log.Printf("[INFO] Deleting VPCLattice Service %s", d.Id()) + + _, err := conn.DeleteService(ctx, &vpclattice.DeleteServiceInput{ + ServiceIdentifier: aws.String(d.Id()), + }) + + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil } - state.AuthType = flex.StringToFramework(ctx, (*string)(&out.AuthType)) - state.CertificateARN = flex.StringToFramework(ctx, out.CertificateArn) + return create.DiagError(names.VPCLattice, create.ErrActionDeleting, ResNameService, d.Id(), err) + } + + if _, err := waitServiceDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return create.DiagError(names.VPCLattice, create.ErrActionWaitingForDeletion, ResNameService, d.Id(), err) } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + return nil } -func (r *resourceService) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().VPCLatticeClient() +func waitServiceCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ServiceStatusCreateInProgress), + Target: enum.Slice(types.ServiceStatusActive), + Refresh: statusService(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } - var state resourceServiceData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { + return out, err } - deleteTimeout, diags := state.Timeouts.Delete(ctx, serviceDeleteTimeout) // nosemgrep:ci.semgrep.migrate.direct-CRUD-calls + return nil, err +} - resp.Diagnostics.Append(diags...) +func waitServiceDeleted(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ServiceStatusDeleteInProgress, types.ServiceStatusActive), + Target: []string{}, + Refresh: statusService(ctx, conn, id), + Timeout: timeout, + } - if resp.Diagnostics.HasError() { - return + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { + return out, err } - ctx, cancel := context.WithTimeout(ctx, deleteTimeout) - defer cancel() + return nil, err +} - _, err := conn.DeleteService(ctx, &vpclattice.DeleteServiceInput{ - ServiceIdentifier: flex.StringFromFramework(ctx, state.ID), - }) +func statusService(ctx context.Context, conn *vpclattice.Client, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := findServiceByID(ctx, conn, id) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.Status), nil + } +} + +func findServiceByID(ctx context.Context, conn *vpclattice.Client, id string) (*vpclattice.GetServiceOutput, error) { + in := &vpclattice.GetServiceInput{ + ServiceIdentifier: aws.String(id), + } + out, err := conn.GetService(ctx, in) if err != nil { - var nfe *awstypes.ResourceNotFoundException + var nfe *types.ResourceNotFoundException if errors.As(err, &nfe) { - return + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.VPCLattice, create.ErrActionDeleting, ResNameService, state.Name.String(), nil), - err.Error(), - ) + + return nil, err } - if _, err := waitServiceDeleted(ctx, conn, state.ID.ValueString(), deleteTimeout); err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.VPCLattice, create.ErrActionWaitingForDeletion, ResNameService, state.Name.String(), nil), - err.Error(), - ) - return + if out == nil { + return nil, tfresource.NewEmptyResultError(in) } -} -func (r *resourceService) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + return out, nil } -// var dnsEntryAttrs = map[string]attr.Type{ -// "domain_name": types.StringType, -// "hosted_zone_id": types.StringType, -// } - -// func flattenDNSEntry(ctx context.Context, dns *awstypes.DnsEntry) types.List { -// elemType := types.ObjectType{AttrTypes: dnsEntryAttrs} +func flattenDNSEntry(apiObject *types.DnsEntry) map[string]interface{} { + if apiObject == nil { + return nil + } -// if dns == nil { -// return types.ListNull(elemType) -// } + m := map[string]interface{}{} -// attrs := map[string]attr.Value{} -// attrs["domain_name"] = flex.StringToFramework(ctx, dns.DomainName) -// attrs["hosted_zone_id"] = flex.StringToFramework(ctx, dns.HostedZoneId) + if v := apiObject.DomainName; v != nil { + m["domain_name"] = aws.ToString(v) + } -// vals := types.ObjectValueMust(dnsEntryAttrs, attrs) + if v := apiObject.HostedZoneId; v != nil { + m["hosted_zone_id"] = aws.ToString(v) + } -// return types.ListValueMust(elemType, []attr.Value{vals}) -// } + return m +} diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 1aaf787d7c7..28ef7791132 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -16,14 +16,7 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{ - { - Factory: newResourceService, - Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: "arn", - }, - }, - } + return []*types.ServicePackageFrameworkResource{} } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { @@ -31,7 +24,12 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac } func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { - return []*types.ServicePackageSDKResource{} + return []*types.ServicePackageSDKResource{ + { + Factory: ResourceService, + TypeName: "aws_vpclattice_service", + }, + } } func (p *servicePackage) ServicePackageName() string { diff --git a/internal/service/vpclattice/service_test.go b/internal/service/vpclattice/service_test.go index c58b1e0973d..4574cb357fe 100644 --- a/internal/service/vpclattice/service_test.go +++ b/internal/service/vpclattice/service_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "regexp" "testing" "github.com/aws/aws-sdk-go-v2/aws" @@ -21,6 +22,7 @@ import ( func TestAccVPCLatticeService_basic(t *testing.T) { ctx := acctest.Context(t) + var service vpclattice.GetServiceOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_vpclattice_service.test" @@ -33,13 +35,14 @@ func TestAccVPCLatticeService_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckServiceDestroy, + CheckDestroy: testAccCheckServiceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccService_basic(rName), + Config: testAccServiceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckServiceExists(resourceName, &service), + testAccCheckServiceExists(ctx, resourceName, &service), resource.TestCheckResourceAttr(resourceName, "name", rName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("service/.+$")), ), }, { @@ -53,6 +56,7 @@ func TestAccVPCLatticeService_basic(t *testing.T) { func TestAccVPCLatticeService_disappears(t *testing.T) { ctx := acctest.Context(t) + var service vpclattice.GetServiceOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_vpclattice_service.test" @@ -65,13 +69,13 @@ func TestAccVPCLatticeService_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.VPCLatticeEndpointID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckServiceDestroy, + CheckDestroy: testAccCheckServiceDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccService_basic(rName), + Config: testAccServiceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckServiceExists(resourceName, &service), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceService, resourceName), + testAccCheckServiceExists(ctx, resourceName, &service), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfvpclattice.ResourceService(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -79,33 +83,115 @@ func TestAccVPCLatticeService_disappears(t *testing.T) { }) } -func testAccCheckServiceDestroy(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() - ctx := context.Background() +func TestAccVPCLatticeService_full(t *testing.T) { + ctx := acctest.Context(t) - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpclattice_service" { - continue - } + var service vpclattice.GetServiceOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service.test" - _, err := conn.GetService(ctx, &vpclattice.GetServiceInput{ - ServiceIdentifier: aws.String(rs.Primary.ID), - }) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil + 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: testAccCheckServiceDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccServiceConfig_full(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "auth_type", "AWS_IAM"), + resource.TestCheckResourceAttr(resourceName, "custom_domain_name", "example.com"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "vpc-lattice", regexp.MustCompile("service/.+$")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, + }, + }) +} + +func TestAccVPCLatticeService_tags(t *testing.T) { + ctx := acctest.Context(t) + var service1, service2, service3 vpclattice.GetServiceOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_vpclattice_service.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: testAccServiceConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccServiceConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccServiceConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckServiceExists(ctx, resourceName, &service3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckServiceDestroy(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" { + continue + } + + _, err := conn.GetService(ctx, &vpclattice.GetServiceInput{ + ServiceIdentifier: aws.String(rs.Primary.ID), + }) + if err != nil { + var nfe *types.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil + } + return err } - return err + + return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameService, rs.Primary.ID, errors.New("not destroyed")) } - return create.Error(names.VPCLattice, create.ErrActionCheckingDestroyed, tfvpclattice.ResNameService, rs.Primary.ID, errors.New("not destroyed")) + return nil } - - return nil } -func testAccCheckServiceExists(name string, service *vpclattice.GetServiceOutput) resource.TestCheckFunc { +func testAccCheckServiceExists(ctx context.Context, name string, service *vpclattice.GetServiceOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -117,7 +203,6 @@ func testAccCheckServiceExists(name string, service *vpclattice.GetServiceOutput } conn := acctest.Provider.Meta().(*conns.AWSClient).VPCLatticeClient() - ctx := context.Background() resp, err := conn.GetService(ctx, &vpclattice.GetServiceInput{ ServiceIdentifier: aws.String(rs.Primary.ID), }) @@ -147,10 +232,55 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { } } -func testAccService_basic(rName string) string { +// func testAccCheckServiceNotRecreated(before, after *vpclattice.GetServiceOutput) resource.TestCheckFunc { +// return func(s *terraform.State) error { +// if before, after := before.Id, after.Id; before != after { +// return create.Error(names.VPCLattice, create.ErrActionCheckingNotRecreated, tfvpclattice.ResNameService, *before, errors.New("recreated")) +// } + +// return nil +// } +// } + +func testAccServiceConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_vpclattice_service" "test" { name = %[1]q } `, rName) } + +func testAccServiceConfig_full(rName string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q + auth_type = "AWS_IAM" + custom_domain_name = "example.com" +} +`, rName) +} + +func testAccServiceConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccServiceConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_vpclattice_service" "test" { + name = %[1]q + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/vpclattice/status.go b/internal/service/vpclattice/status.go deleted file mode 100644 index f0b14a94685..00000000000 --- a/internal/service/vpclattice/status.go +++ /dev/null @@ -1,24 +0,0 @@ -package vpclattice - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/service/vpclattice" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" -) - -func statusService(ctx context.Context, conn *vpclattice.Client, id string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := findServiceByID(ctx, conn, id) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return out, string(out.Status), nil - } -} diff --git a/internal/service/vpclattice/wait.go b/internal/service/vpclattice/wait.go deleted file mode 100644 index 771ca385e2d..00000000000 --- a/internal/service/vpclattice/wait.go +++ /dev/null @@ -1,45 +0,0 @@ -package vpclattice - -import ( - "context" - "time" - - "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/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/enum" -) - -func waitServiceCreated(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(types.ServiceStatusCreateInProgress), - Target: enum.Slice(types.ServiceStatusActive), - Refresh: statusService(ctx, conn, id), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { - return out, err - } - - return nil, err -} - -func waitServiceDeleted(ctx context.Context, conn *vpclattice.Client, id string, timeout time.Duration) (*vpclattice.GetServiceOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(types.ServiceStatusDeleteInProgress, types.ServiceStatusActive), - Target: []string{}, - Refresh: statusService(ctx, conn, id), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*vpclattice.GetServiceOutput); ok { - return out, err - } - - return nil, err -} diff --git a/website/docs/r/vpclattice_service.html.markdown b/website/docs/r/vpclattice_service.html.markdown index 700e1f05a8e..a73c3948781 100644 --- a/website/docs/r/vpclattice_service.html.markdown +++ b/website/docs/r/vpclattice_service.html.markdown @@ -33,7 +33,7 @@ The following arguments are optional: * `auth_type` - (Optional) Type of IAM policy. Either `NONE` or `AWS_IAM`. * `certificate_arn` - (Optional) Amazon Resource Name (ARN) of the certificate. * `custom_domain_name` - (Optional) Custom domain name of the service. -* `tags` - (Optional) A map of tags to assign to the service. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. +* `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 @@ -43,6 +43,7 @@ In addition to all arguments above, the following attributes are exported: * `dns_entry` - Concise description. Do not begin the description with "An", "The", "Defines", "Indicates", or "Specifies," as these are verbose. In other words, "Indicates the amount of storage," can be rewritten as "Amount of storage," without losing any information. * `id` - Unique identifier for the service. * `status` - Status of the service. +* `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). ## Timeouts From 15b126a536a565be89f91f5dbfdb05de867ec39c Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:43:20 +0100 Subject: [PATCH 04/10] Update default timeouts in docs --- website/docs/r/vpclattice_service.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/r/vpclattice_service.html.markdown b/website/docs/r/vpclattice_service.html.markdown index a73c3948781..02c18e7d8b0 100644 --- a/website/docs/r/vpclattice_service.html.markdown +++ b/website/docs/r/vpclattice_service.html.markdown @@ -49,8 +49,8 @@ In addition to all arguments above, the following attributes are exported: [Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): -* `create` - (Default `30m`) -* `delete` - (Default `30m`) +* `create` - (Default `5m`) +* `delete` - (Default `5m`) ## Import From e1fc79c7afd95c6a80635d4e65cacf3fd5b688cb Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:47:04 +0100 Subject: [PATCH 05/10] Lint --- internal/service/vpclattice/service_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/service/vpclattice/service_test.go b/internal/service/vpclattice/service_test.go index 4574cb357fe..6a72e486166 100644 --- a/internal/service/vpclattice/service_test.go +++ b/internal/service/vpclattice/service_test.go @@ -266,7 +266,7 @@ resource "aws_vpclattice_service" "test" { name = %[1]q tags = { - %[2]q = %[3]q + %[2]q = %[3]q } } `, rName, tagKey1, tagValue1) @@ -276,10 +276,10 @@ func testAccServiceConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 st return fmt.Sprintf(` resource "aws_vpclattice_service" "test" { name = %[1]q - + tags = { - %[2]q = %[3]q - %[4]q = %[5]q + %[2]q = %[3]q + %[4]q = %[5]q } } `, rName, tagKey1, tagValue1, tagKey2, tagValue2) From ca76314b03e70969c45028a13ab73d446b9bc641 Mon Sep 17 00:00:00 2001 From: Matt Burgess <549318+mattburgess@users.noreply.github.com> Date: Wed, 5 Apr 2023 12:24:22 +0100 Subject: [PATCH 06/10] Fix tag gen --- internal/service/vpclattice/service.go | 1 + internal/service/vpclattice/service_package_gen.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go index c730bcf1d60..1e8692f4e66 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -24,6 +24,7 @@ import ( ) // @SDKResource("aws_vpclattice_service") +// @Tags(identifier_attribute="arn") func ResourceService() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceServiceCreate, diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 28ef7791132..79472c41b56 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -28,6 +28,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceService, TypeName: "aws_vpclattice_service", + Tags: &types.ServicePackageResourceTags{}, }, } } From 5f83f4b8407c223c7d51ddfbb874e5ca17bfc11f Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 5 Apr 2023 11:04:08 -0400 Subject: [PATCH 07/10] Add CHANGELOG entry. --- .changelog/30429.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/30429.txt diff --git a/.changelog/30429.txt b/.changelog/30429.txt new file mode 100644 index 00000000000..2b509514d16 --- /dev/null +++ b/.changelog/30429.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_vpclattice_service +``` \ No newline at end of file From 7456f43d975bb69af3d6dd1b92c3e4c792e877c2 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 5 Apr 2023 11:08:01 -0400 Subject: [PATCH 08/10] r/aws_vpclattice_service: Transparent tagging. --- internal/service/vpclattice/service.go | 43 +++---------------- .../service/vpclattice/service_package_gen.go | 1 + 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go index 1e8692f4e66..0520302a854 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -23,7 +23,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_vpclattice_service") +// @SDKResource("aws_vpclattice_service", name="Service") // @Tags(identifier_attribute="arn") func ResourceService() *schema.Resource { return &schema.Resource{ @@ -43,7 +43,7 @@ func ResourceService() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "arn": { + names.AttrARN: { Type: schema.TypeString, Computed: true, }, @@ -79,8 +79,8 @@ func ResourceService() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -97,6 +97,7 @@ func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, meta int in := &vpclattice.CreateServiceInput{ ClientToken: aws.String(id.UniqueId()), Name: aws.String(d.Get("name").(string)), + Tags: GetTagsIn(ctx), } if v, ok := d.GetOk("auth_type"); ok { @@ -111,13 +112,6 @@ func resourceServiceCreate(ctx context.Context, d *schema.ResourceData, meta int in.CustomDomainName = aws.String(v.(string)) } - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - tags := defaultTagsConfig.MergeTags(tftags.New(ctx, d.Get("tags").(map[string]interface{}))) - - if len(tags) > 0 { - in.Tags = Tags(tags.IgnoreAWS()) - } - out, err := conn.CreateService(ctx, in) if err != nil { return create.DiagError(names.VPCLattice, create.ErrActionCreating, ResNameService, d.Get("name").(string), err) @@ -162,23 +156,6 @@ func resourceServiceRead(ctx context.Context, d *schema.ResourceData, meta inter return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) } - tags, err := ListTags(ctx, conn, *out.Arn) - if err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionReading, ResNameService, d.Id(), err) - } - - defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig - ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) - } - - if err := d.Set("tags_all", tags.Map()); err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) - } - return nil } @@ -205,21 +182,13 @@ func resourceServiceUpdate(ctx context.Context, d *schema.ResourceData, meta int } } - if d.HasChange("tags_all") { - o, n := d.GetChange("tags_all") - if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { - return diag.Errorf("updating %s %s (%s) tags: %s", names.VPCLattice, ResNameService, d.Id(), err) - } - } - return resourceServiceRead(ctx, d, meta) } func resourceServiceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { conn := meta.(*conns.AWSClient).VPCLatticeClient() - log.Printf("[INFO] Deleting VPCLattice Service %s", d.Id()) - + log.Printf("[INFO] Deleting VPCLattice Service: %s", d.Id()) _, err := conn.DeleteService(ctx, &vpclattice.DeleteServiceInput{ ServiceIdentifier: aws.String(d.Id()), }) diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 79472c41b56..36f06021671 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -28,6 +28,7 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka { Factory: ResourceService, TypeName: "aws_vpclattice_service", + Name: "Service", Tags: &types.ServicePackageResourceTags{}, }, } From 1caeafee04f749ade249f2be1b2a99184d320c9e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 5 Apr 2023 11:47:10 -0400 Subject: [PATCH 09/10] r/aws_vpclattice_service: Make 'dns_entry' have schema structure. --- internal/service/vpclattice/service.go | 34 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go index 0520302a854..6a75ac55f6a 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -65,9 +65,20 @@ func ResourceService() *schema.Resource { ValidateFunc: validation.StringLenBetween(3, 255), }, "dns_entry": { - Type: schema.TypeMap, + Type: schema.TypeList, Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "domain_name": { + Type: schema.TypeString, + Computed: true, + }, + "hosted_zone_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, }, "name": { Type: schema.TypeString, @@ -149,13 +160,16 @@ func resourceServiceRead(ctx context.Context, d *schema.ResourceData, meta inter d.Set("auth_type", out.AuthType) d.Set("certificate_arn", out.CertificateArn) d.Set("custom_domain_name", out.CustomDomainName) + if out.DnsEntry != nil { + if err := d.Set("dns_entry", []interface{}{flattenDNSEntry(out.DnsEntry)}); err != nil { + return diag.Errorf("setting dns_entry: %s", err) + } + } else { + d.Set("dns_entry", nil) + } d.Set("name", out.Name) d.Set("status", out.Status) - if err := d.Set("dns_entry", flattenDNSEntry(out.DnsEntry)); err != nil { - return create.DiagError(names.VPCLattice, create.ErrActionSetting, ResNameService, d.Id(), err) - } - return nil } @@ -287,15 +301,15 @@ func flattenDNSEntry(apiObject *types.DnsEntry) map[string]interface{} { return nil } - m := map[string]interface{}{} + tfMap := map[string]interface{}{} if v := apiObject.DomainName; v != nil { - m["domain_name"] = aws.ToString(v) + tfMap["domain_name"] = aws.ToString(v) } if v := apiObject.HostedZoneId; v != nil { - m["hosted_zone_id"] = aws.ToString(v) + tfMap["hosted_zone_id"] = aws.ToString(v) } - return m + return tfMap } From 38d830f45ef09f24cb16e65cc890e080c44d7b32 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 5 Apr 2023 11:55:24 -0400 Subject: [PATCH 10/10] Fix typo. --- internal/service/vpclattice/service.go | 2 +- internal/service/vpclattice/service_package_gen.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/service/vpclattice/service.go b/internal/service/vpclattice/service.go index 6a75ac55f6a..8482ae329e8 100644 --- a/internal/service/vpclattice/service.go +++ b/internal/service/vpclattice/service.go @@ -24,7 +24,7 @@ import ( ) // @SDKResource("aws_vpclattice_service", name="Service") -// @Tags(identifier_attribute="arn") +// @Tags(identifierAttribute="arn") func ResourceService() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceServiceCreate, diff --git a/internal/service/vpclattice/service_package_gen.go b/internal/service/vpclattice/service_package_gen.go index 36f06021671..da348e17ccf 100644 --- a/internal/service/vpclattice/service_package_gen.go +++ b/internal/service/vpclattice/service_package_gen.go @@ -29,7 +29,9 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceService, TypeName: "aws_vpclattice_service", Name: "Service", - Tags: &types.ServicePackageResourceTags{}, + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "arn", + }, }, } }