From bdbbf496c3f3d37f4c6a0e1f9a732156515280fe Mon Sep 17 00:00:00 2001 From: iwarapter Date: Wed, 22 May 2024 15:44:30 +0100 Subject: [PATCH] add support for aws shield subscription resource fixes hashicorp#21430 --- .../service/shield/service_package_gen.go | 4 + internal/service/shield/subscription.go | 209 ++++++++++++++++++ internal/service/shield/subscription_test.go | 90 ++++++++ .../docs/r/shield_subscription.html.markdown | 50 +++++ 4 files changed, 353 insertions(+) create mode 100644 internal/service/shield/subscription.go create mode 100644 internal/service/shield/subscription_test.go create mode 100644 website/docs/r/shield_subscription.html.markdown diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index 5ff95a39e3c..e326b4a7278 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -34,6 +34,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newProactiveEngagementResource, Name: "Proactive Engagement", }, + { + Factory: newResourceSubscription, + Name: "Subscription", + }, } } diff --git a/internal/service/shield/subscription.go b/internal/service/shield/subscription.go new file mode 100644 index 00000000000..6830d657e79 --- /dev/null +++ b/internal/service/shield/subscription.go @@ -0,0 +1,209 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "context" + "errors" + "github.com/aws/aws-sdk-go-v2/service/shield" + awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "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/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @FrameworkResource("aws_shield_subscription", name="Subscription") +func newResourceSubscription(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceSubscription{}, nil +} + +const ( + ResNameSubscription = "Subscription" +) + +type resourceSubscription struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceSubscription) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_shield_subscription" +} + +func (r *resourceSubscription) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: framework.IDAttribute(), + "auto_renew": schema.StringAttribute{ + Description: "Whether to automatically renew the subscription when it expires.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(awstypes.AutoRenewEnabled)), + Validators: []validator.String{ + stringvalidator.OneOf(string(awstypes.AutoRenewEnabled), string(awstypes.AutoRenewDisabled)), + }, + }, + }, + } +} + +func (r *resourceSubscription) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().ShieldClient(ctx) + + var plan resourceSubscriptionData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + plan.ID = types.StringValue(r.Meta().AccountID) + if plan.AutoRenew.Equal(types.StringValue(string(awstypes.AutoRenewDisabled))) { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameSubscription, plan.ID.String(), nil), + errors.New("subscription auto_renew flag cannot be changed earlier than 30 days before subscription end and later than 1 day before subscription end").Error(), + ) + return + } + + in := &shield.CreateSubscriptionInput{} + out, err := conn.CreateSubscription(ctx, in) + if err != nil { + // in error messages at this point. + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameSubscription, plan.ID.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameSubscription, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceSubscription) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().ShieldClient(ctx) + + var state resourceSubscriptionData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findSubscriptionByID(ctx, conn) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameSubscription, state.ID.String(), err), + err.Error(), + ) + return + } + state.AutoRenew = types.StringValue(string(out.AutoRenew)) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceSubscription) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().ShieldClient(ctx) + + var plan, state resourceSubscriptionData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.UpdateSubscriptionInput{ + AutoRenew: awstypes.AutoRenew(plan.AutoRenew.ValueString()), + } + + out, err := conn.UpdateSubscription(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameSubscription, plan.ID.String(), err), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameSubscription, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + plan.ID = types.StringValue(r.Meta().AccountID) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceSubscription) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().ShieldClient(ctx) + + var state resourceSubscriptionData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.DeleteSubscriptionInput{} + + _, err := conn.DeleteSubscription(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameSubscription, state.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceSubscription) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) +} + +func findSubscriptionByID(ctx context.Context, conn *shield.Client) (*awstypes.Subscription, error) { + in := &shield.DescribeSubscriptionInput{} + + out, err := conn.DescribeSubscription(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + return nil, err + } + + if out == nil || out.Subscription == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out.Subscription, nil +} + +type resourceSubscriptionData struct { + ID types.String `tfsdk:"id"` + AutoRenew types.String `tfsdk:"auto_renew"` +} diff --git a/internal/service/shield/subscription_test.go b/internal/service/shield/subscription_test.go new file mode 100644 index 00000000000..336d60b2c1b --- /dev/null +++ b/internal/service/shield/subscription_test.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield_test + +import ( + "context" + "errors" + "fmt" + awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/shield" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/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" + "github.com/hashicorp/terraform-provider-aws/names" + + tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" +) + +func TestAccShieldSubscription_basic(t *testing.T) { + ctx := acctest.Context(t) + t.Skipf("running this test signs up an account for $3000 yearly commitment to shield advanced") + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var subscription shield.DescribeSubscriptionOutput + resourceName := "aws_shield_subscription.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.ShieldEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.ShieldServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccSubscriptionConfig_basic(string(awstypes.AutoRenewEnabled)), + Check: resource.ComposeTestCheckFunc( + testAccCheckSubscriptionExists(ctx, resourceName, &subscription), + resource.TestCheckResourceAttr(resourceName, "auto_renew", string(awstypes.AutoRenewEnabled)), + ), + Destroy: false, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + Destroy: false, + }, + }, + }) +} + +func testAccCheckSubscriptionExists(ctx context.Context, name string, subscription *shield.DescribeSubscriptionOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameSubscription, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameSubscription, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) + resp, err := conn.DescribeSubscription(ctx, &shield.DescribeSubscriptionInput{}) + + if err != nil { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameSubscription, rs.Primary.ID, err) + } + + *subscription = *resp + + return nil + } +} + +func testAccSubscriptionConfig_basic(autoRenew string) string { + return fmt.Sprintf(` +resource "aws_shield_subscription" "test" { + auto_renew = %[1]q +} +`, autoRenew) +} diff --git a/website/docs/r/shield_subscription.html.markdown b/website/docs/r/shield_subscription.html.markdown new file mode 100644 index 00000000000..0c954d91bac --- /dev/null +++ b/website/docs/r/shield_subscription.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "Shield" +layout: "aws" +page_title: "AWS: aws_shield_subscription" +description: |- + Terraform resource for managing an AWS Shield Subscription. +--- + +# Resource: aws_shield_subscription + +Terraform resource for managing an AWS Shield Subscription. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_shield_subscription" "example" { + auto_renew = "ENABLED" +} +``` + +## Argument Reference + +The following arguments are optional: + +* `auto_renew` - (Optional) automated renewal for the subscription. Valid values are `ENABLED` or `DISABLED`. Default is `ENABLED`. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - Account ID is used. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Shield Subscription using the `AWS Account ID`. For example: + +```terraform +import { + to = aws_shield_subscription.example + id = "1234567890" +} +``` + +Using `terraform import`, import Shield Subscription using the `AWS Account ID`. For example: + +```console +% terraform import aws_shield_subscription.example 1234567890 +```