Skip to content

Commit

Permalink
Merge pull request #36534 from brittandeyoung/f-cloudfront_key_value_…
Browse files Browse the repository at this point in the history
…store_key

New Resource: `aws_cloudfrontkeyvaluestore_key`
  • Loading branch information
ewbankkit committed Mar 26, 2024
2 parents f7af145 + c69a552 commit b8f37b6
Show file tree
Hide file tree
Showing 10 changed files with 590 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .changelog/36534.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_cloudfrontkeyvaluestore_key
```
2 changes: 1 addition & 1 deletion .github/labeler-pr-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ service/cloudfront:
- 'website/**/cloudfront_*'
service/cloudfrontkeyvaluestore:
- 'internal/service/cloudfrontkeyvaluestore/**/*'
- 'website/**/cloudfront_keyvaluestore_*'
- 'website/**/cloudfrontkeyvaluestore_*'
service/cloudhsmv2:
- 'internal/service/cloudhsmv2/**/*'
- 'website/**/cloudhsm*'
Expand Down
4 changes: 4 additions & 0 deletions internal/errs/fwdiag/diags.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ func NewResourceNotFoundWarningDiagnostic(err error) diag.Diagnostic {
"Automatically removing from Terraform State instead of returning the error, which may trigger resource recreation. Original error: "+err.Error(),
)
}

func AsErr[T any](x T, diags diag.Diagnostics) (T, error) {
return x, DiagnosticsError(diags)
}
2 changes: 1 addition & 1 deletion internal/errs/fwdiag/must.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ import (
// [1]: https://pkg.go.dev/text/template#Must
// [2]: https://pkg.go.dev/regexp#MustCompile
func Must[T any](x T, diags diag.Diagnostics) T {
return errs.Must(x, DiagnosticsError(diags))
return errs.Must(AsErr(x, diags))
}
11 changes: 11 additions & 0 deletions internal/service/cloudfrontkeyvaluestore/exports_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package cloudfrontkeyvaluestore

// Exports for use in tests only.
var (
ResourceKey = newKeyResource

FindKeyByTwoPartKey = findKeyByTwoPartKey
)
319 changes: 319 additions & 0 deletions internal/service/cloudfrontkeyvaluestore/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package cloudfrontkeyvaluestore

import (
"context"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfrontkeyvaluestore"
awstypes "github.com/aws/aws-sdk-go-v2/service/cloudfrontkeyvaluestore/types"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"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"
fwflex "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(name="Key")
func newKeyResource(_ context.Context) (resource.ResourceWithConfigure, error) {
r := &keyResource{}

return r, nil
}

type keyResource struct {
framework.ResourceWithConfigure
framework.WithImportByID
}

func (*keyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "aws_cloudfrontkeyvaluestore_key"
}

func (r *keyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
names.AttrID: framework.IDAttribute(),
"key": schema.StringAttribute{
Required: true,
MarkdownDescription: "The key to put.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"key_value_store_arn": schema.StringAttribute{
CustomType: fwtypes.ARNType,
Required: true,
MarkdownDescription: "The Amazon Resource Name (ARN) of the Key Value Store.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"total_size_in_bytes": schema.Int64Attribute{
Computed: true,
MarkdownDescription: "Total size of the Key Value Store in bytes.",
},
"value": schema.StringAttribute{
Required: true,
MarkdownDescription: "The value to put.",
},
},
}
}

func (r *keyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var data keyResourceModel
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().CloudFrontKeyValueStoreClient(ctx)

etag, err := findETagByARN(ctx, conn, data.KvsARN.ValueString())

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("reading CloudFront KeyValueStore ETag (%s)", data.KvsARN.ValueString()), err.Error())

return
}

input := &cloudfrontkeyvaluestore.PutKeyInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...)
if response.Diagnostics.HasError() {
return
}

// Additional fields.
input.IfMatch = etag

output, err := conn.PutKey(ctx, input)

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("creating CloudFront KeyValueStore (%s) Key (%s)", data.KvsARN.ValueString(), data.Key.ValueString()), err.Error())

return
}

// Set values for unknowns.
data.TotalSizeInBytes = fwflex.Int64ToFramework(ctx, output.TotalSizeInBytes)
data.setID()

response.Diagnostics.Append(response.State.Set(ctx, data)...)
}

func (r *keyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var data keyResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

if err := data.InitFromID(); err != nil {
response.Diagnostics.AddError("parsing resource ID", err.Error())

return
}

conn := r.Meta().CloudFrontKeyValueStoreClient(ctx)

output, err := findKeyByTwoPartKey(ctx, conn, data.KvsARN.ValueString(), data.Key.ValueString())

if tfresource.NotFound(err) {
response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
response.State.RemoveResource(ctx)

return
}

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("reading CloudFront KeyValueStore Key (%s)", data.ID.ValueString()), err.Error())

return
}

// Set attributes for import.
response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...)
if response.Diagnostics.HasError() {
return
}

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

func (r *keyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var old, new keyResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &old)...)
if response.Diagnostics.HasError() {
return
}
response.Diagnostics.Append(request.Plan.Get(ctx, &new)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().CloudFrontKeyValueStoreClient(ctx)

if !new.Value.Equal(old.Value) {
etag, err := findETagByARN(ctx, conn, new.KvsARN.ValueString())

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("reading CloudFront KeyValueStore ETag (%s)", new.KvsARN.ValueString()), err.Error())

return
}

input := &cloudfrontkeyvaluestore.PutKeyInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...)
if response.Diagnostics.HasError() {
return
}

// Additional fields.
input.IfMatch = etag

output, err := conn.PutKey(ctx, input)

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("updating CloudFront KeyValueStore (%s) Key (%s)", new.KvsARN.ValueString(), new.Key.ValueString()), err.Error())

return
}

// Set values for unknowns.
new.TotalSizeInBytes = fwflex.Int64ToFramework(ctx, output.TotalSizeInBytes)
}

response.Diagnostics.Append(response.State.Set(ctx, &new)...)
}

func (r *keyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var data keyResourceModel
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().CloudFrontKeyValueStoreClient(ctx)

etag, err := findETagByARN(ctx, conn, data.KvsARN.ValueString())

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("reading CloudFront KeyValueStore ETag (%s)", data.KvsARN.ValueString()), err.Error())

return
}

_, err = conn.DeleteKey(ctx, &cloudfrontkeyvaluestore.DeleteKeyInput{
IfMatch: etag,
Key: fwflex.StringFromFramework(ctx, data.Key),
KvsARN: fwflex.StringFromFramework(ctx, data.KvsARN),
})

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return
}

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("deleting CloudFront KeyValueStore Key (%s)", data.ID.ValueString()), err.Error())

return
}
}

func findKeyByTwoPartKey(ctx context.Context, conn *cloudfrontkeyvaluestore.Client, kvsARN, key string) (*cloudfrontkeyvaluestore.GetKeyOutput, error) {
input := &cloudfrontkeyvaluestore.GetKeyInput{
Key: aws.String(key),
KvsARN: aws.String(kvsARN),
}

output, err := conn.GetKey(ctx, input)

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || output.Key == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}

func findETagByARN(ctx context.Context, conn *cloudfrontkeyvaluestore.Client, arn string) (*string, error) {
input := &cloudfrontkeyvaluestore.DescribeKeyValueStoreInput{
KvsARN: aws.String(arn),
}

output, err := conn.DescribeKeyValueStore(ctx, input)

if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || output.ETag == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output.ETag, nil
}

type keyResourceModel struct {
ID types.String `tfsdk:"id"`
Key types.String `tfsdk:"key"`
KvsARN fwtypes.ARN `tfsdk:"key_value_store_arn"`
TotalSizeInBytes types.Int64 `tfsdk:"total_size_in_bytes"`
Value types.String `tfsdk:"value"`
}

const (
keyResourceIDPartCount = 2
)

func (data *keyResourceModel) InitFromID() error {
id := data.ID.ValueString()
parts, err := flex.ExpandResourceId(id, keyResourceIDPartCount, false)
if err != nil {
return err
}

v, err := fwdiag.AsErr(fwtypes.ARNValue(parts[0]))
if err != nil {
return err
}

data.KvsARN = v
data.Key = types.StringValue(parts[1])

return nil
}

func (data *keyResourceModel) setID() {
data.ID = types.StringValue(errs.Must(flex.FlattenResourceId([]string{data.KvsARN.ValueString(), data.Key.ValueString()}, keyResourceIDPartCount, false)))
}
Loading

0 comments on commit b8f37b6

Please sign in to comment.