-
Notifications
You must be signed in to change notification settings - Fork 9.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37637 from iwarapter/f-aws_shield_subscription
add support for aws shield subscription resource fixes #21430
- Loading branch information
Showing
5 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
```release-note:new-resource | ||
aws_shield_subscription | ||
``` | ||
```release-note:note | ||
resource/aws_shield_subscription: Because we cannot easily test this functionality, it is best effort and we ask for community help in testing | ||
``` |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// 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/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/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/framework/flex" | ||
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" | ||
"github.com/hashicorp/terraform-provider-aws/internal/tfresource" | ||
"github.com/hashicorp/terraform-provider-aws/names" | ||
) | ||
|
||
// @FrameworkResource("aws_shield_subscription", name="Subscription") | ||
func newResourceSubscription(_ context.Context) (resource.ResourceWithConfigure, error) { | ||
return &resourceSubscription{}, nil | ||
} | ||
|
||
const ( | ||
ResNameSubscription = "Subscription" | ||
) | ||
|
||
type resourceSubscription struct { | ||
framework.ResourceWithConfigure | ||
} | ||
|
||
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{ | ||
"auto_renew": schema.StringAttribute{ | ||
Description: "Whether to automatically renew the subscription when it expires.", | ||
Optional: true, | ||
Computed: true, | ||
CustomType: fwtypes.StringEnumType[awstypes.AutoRenew](), | ||
Default: stringdefault.StaticString(string(awstypes.AutoRenewEnabled)), | ||
}, | ||
names.AttrID: framework.IDAttribute(), | ||
names.AttrSkipDestroy: schema.BoolAttribute{ | ||
Optional: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
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{} | ||
_, err := conn.CreateSubscription(ctx, in) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameSubscription, plan.ID.String(), err), | ||
err.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 errs.IsA[*retry.NotFoundError](err) { | ||
resp.State.RemoveResource(ctx) | ||
return | ||
} | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
create.ProblemStandardMessage(names.Shield, create.ErrActionReading, ResNameSubscription, state.ID.String(), err), | ||
err.Error(), | ||
) | ||
return | ||
} | ||
|
||
resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) | ||
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 | ||
} | ||
|
||
if !plan.AutoRenew.Equal(state.AutoRenew) { | ||
in := &shield.UpdateSubscriptionInput{ | ||
AutoRenew: awstypes.AutoRenew(plan.AutoRenew.ValueString()), | ||
} | ||
|
||
_, 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 | ||
} | ||
} | ||
|
||
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 | ||
} | ||
|
||
if state.SkipDestroy.ValueBool() { | ||
return | ||
} | ||
|
||
in := &shield.UpdateSubscriptionInput{ | ||
AutoRenew: awstypes.AutoRenewDisabled, | ||
} | ||
|
||
_, err := conn.UpdateSubscription(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 { | ||
AutoRenew fwtypes.StringEnum[awstypes.AutoRenew] `tfsdk:"auto_renew"` | ||
ID types.String `tfsdk:"id"` | ||
SkipDestroy types.Bool `tfsdk:"skip_destroy"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package shield_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"testing" | ||
|
||
"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-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" | ||
tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" | ||
"github.com/hashicorp/terraform-provider-aws/names" | ||
) | ||
|
||
func TestAccShieldSubscription_basic(t *testing.T) { | ||
ctx := acctest.Context(t) //nolint:staticcheck // will be used when hardcoded skip is commented | ||
if testing.Short() { | ||
t.Skip("skipping long-running test in short mode") | ||
} | ||
|
||
// Due to the high cost of this subscription, we hardcode this test to | ||
// skip rather than gating behind an environment variable. | ||
// Run this test be removing the line below. | ||
t.Skipf("running this test requires a yearly commitment to AWS Shield Advanced with a $3000 monthly fee in the associated account") | ||
|
||
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)), | ||
), | ||
}, | ||
{ | ||
ResourceName: resourceName, | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
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 | ||
skip_destroy = true | ||
} | ||
`, autoRenew) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
--- | ||
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. | ||
|
||
~> This resource creates a subscription to AWS Shield Advanced, which requires a 1 year subscription commitment with a monthly fee. Refer to the [AWS Shield Pricing](https://aws.amazon.com/shield/pricing/) page for more details. | ||
|
||
~> Destruction of this resource will set `auto_renew` to `DISABLED`. Automatic renewal can only be disabled during the last 30 days of a subscription. To unsubscribe outside of this window, you must contact AWS Support. Set `skip_destroy` to `true` to skip modifying the `auto_renew` argument during destruction. | ||
|
||
## Example Usage | ||
|
||
### Basic Usage | ||
|
||
```terraform | ||
resource "aws_shield_subscription" "example" { | ||
auto_renew = "ENABLED" | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are optional: | ||
|
||
* `auto_renew` - (Optional) Toggle for automated renewal of the subscription. Valid values are `ENABLED` or `DISABLED`. Default is `ENABLED`. | ||
* `skip_destroy` - (Optional) Skip attempting to disable automated renewal upon destruction. If set to `true`, the `auto_renew` value will be left as-is and the resource will simply be removed from state. | ||
|
||
## Attribute Reference | ||
|
||
This resource exports the following attributes in addition to the arguments above: | ||
|
||
* `id` - AWS Account ID. | ||
|
||
## 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 `id`. For example: | ||
|
||
```terraform | ||
import { | ||
to = aws_shield_subscription.example | ||
id = "012345678901" | ||
} | ||
``` | ||
|
||
Using `terraform import`, import Shield Subscription using the `id`. For example: | ||
|
||
```console | ||
% terraform import aws_shield_subscription.example 012345678901 | ||
``` |