Skip to content

Commit

Permalink
Merge pull request #35663 from justinpopa/f-aws_cloudfront_key_value_…
Browse files Browse the repository at this point in the history
…store

New Resource: aws_cloudfront_key_value_store
  • Loading branch information
ewbankkit authored Feb 13, 2024
2 parents 6465147 + bd62f46 commit bc5dfd1
Show file tree
Hide file tree
Showing 7 changed files with 578 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .changelog/35663.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_cloudfront_key_value_store
```
4 changes: 3 additions & 1 deletion internal/service/cloudfront/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package cloudfront
// Exports for use in tests only.
var (
ResourceContinuousDeploymentPolicy = newResourceContinuousDeploymentPolicy
ResourceKeyValueStore = newKeyValueStoreResource

FindPublicKeyByID = findPublicKeyByID
FindKeyValueStoreByName = findKeyValueStoreByName
FindPublicKeyByID = findPublicKeyByID
)
321 changes: 321 additions & 0 deletions internal/service/cloudfront/key_value_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,321 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package cloudfront

import (
"context"
"fmt"
"time"

"github.com/YakDriver/regexache"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
awstypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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/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/errs"
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
"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"
tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource(name="Key Value Store")
func newKeyValueStoreResource(context.Context) (resource.ResourceWithConfigure, error) {
r := &keyValueStoreResource{}

r.SetDefaultCreateTimeout(30 * time.Minute)

return r, nil
}

type keyValueStoreResource struct {
framework.ResourceWithConfigure
framework.WithImportByID
framework.WithTimeouts
}

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

func (r *keyValueStoreResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
names.AttrARN: framework.ARNAttributeComputedOnly(),
"comment": schema.StringAttribute{
Optional: true,
},
"etag": schema.StringAttribute{
Computed: true,
},
names.AttrID: framework.IDAttribute(),
"last_modified_time": schema.StringAttribute{
CustomType: fwtypes.TimestampType,
Computed: true,
},
names.AttrName: schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthBetween(1, 64),
stringvalidator.RegexMatches(
regexache.MustCompile(`^[a-zA-Z0-9-_]{1,64}$`),
"must contain only alphanumeric characters, hyphens, and underscores",
),
},
},
},
Blocks: map[string]schema.Block{
names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{
Create: true,
}),
},
}
}

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

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

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

name := aws.ToString(input.Name)
_, err := conn.CreateKeyValueStore(ctx, input)

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("creating CloudFront Key Value Store (%s)", name), err.Error())

return
}

// Set values for unknowns.
data.setID()

outputDKVS, err := waitKeyValueStoreCreated(ctx, conn, name, r.CreateTimeout(ctx, data.Timeouts))

if err != nil {
response.Diagnostics.AddError(fmt.Sprintf("waiting for CloudFront Key Value Store (%s) create", name), err.Error())

return
}

// Set values for unknowns after creation is complete.
response.Diagnostics.Append(fwflex.Flatten(ctx, outputDKVS.KeyValueStore, &data)...)
if response.Diagnostics.HasError() {
return
}

data.ETag = fwflex.StringToFramework(ctx, outputDKVS.ETag)
data.setID() // API response has a field named 'Id' which isn't the resource's ID.

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

func (r *keyValueStoreResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var data keyValueStoreResourceModel
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().CloudFrontClient(ctx)

output, err := findKeyValueStoreByName(ctx, conn, data.ID.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 Key Value Store (%s)", data.ID.ValueString()), err.Error())

return
}

response.Diagnostics.Append(fwflex.Flatten(ctx, output.KeyValueStore, &data)...)
if response.Diagnostics.HasError() {
return
}

data.ETag = fwflex.StringToFramework(ctx, output.ETag)
data.setID() // API response has a field named 'Id' which isn't the resource's ID.

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

func (r *keyValueStoreResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var old, new keyValueStoreResourceModel
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().CloudFrontClient(ctx)

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

input.IfMatch = fwflex.StringFromFramework(ctx, old.ETag)

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

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

return
}

response.Diagnostics.Append(fwflex.Flatten(ctx, output.KeyValueStore, &new)...)
if response.Diagnostics.HasError() {
return
}

new.ETag = fwflex.StringToFramework(ctx, output.ETag)
new.setID() // API response has a field named 'Id' which isn't the resource's ID.

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

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

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

input := &cloudfront.DeleteKeyValueStoreInput{
IfMatch: fwflex.StringFromFramework(ctx, data.ETag),
Name: fwflex.StringFromFramework(ctx, data.ID),
}

_, err := conn.DeleteKeyValueStore(ctx, input)

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

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

return
}
}

func findKeyValueStoreByName(ctx context.Context, conn *cloudfront.Client, name string) (*cloudfront.DescribeKeyValueStoreOutput, error) {
input := &cloudfront.DescribeKeyValueStoreInput{
Name: aws.String(name),
}

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

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

if err != nil {
return nil, err
}

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

return output, nil
}

func statusKeyValueStore(ctx context.Context, conn *cloudfront.Client, name string) retry.StateRefreshFunc {
return func() (interface{}, string, error) {
output, err := findKeyValueStoreByName(ctx, conn, name)

if tfresource.NotFound(err) {
return nil, "", nil
}

if err != nil {
return nil, "", err
}

return output, aws.ToString(output.KeyValueStore.Status), nil
}
}

func waitKeyValueStoreCreated(ctx context.Context, conn *cloudfront.Client, name string, timeout time.Duration) (*cloudfront.DescribeKeyValueStoreOutput, error) {
stateConf := &retry.StateChangeConf{
Pending: tfslices.Of("PROVISIONING"),
Target: tfslices.Of("READY"),
Refresh: statusKeyValueStore(ctx, conn, name),
Timeout: timeout,
}

outputRaw, err := stateConf.WaitForStateContext(ctx)

if output, ok := outputRaw.(*cloudfront.DescribeKeyValueStoreOutput); ok {
return output, err
}

return nil, err
}

type keyValueStoreResourceModel struct {
ARN types.String `tfsdk:"arn"`
Comment types.String `tfsdk:"comment"`
ETag types.String `tfsdk:"etag"`
ID types.String `tfsdk:"id"`
LastModifiedTime fwtypes.Timestamp `tfsdk:"last_modified_time"`
Name types.String `tfsdk:"name"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
}

func (data *keyValueStoreResourceModel) InitFromID() error {
data.Name = data.ID

return nil
}

func (data *keyValueStoreResourceModel) setID() {
data.ID = data.Name
}
Loading

0 comments on commit bc5dfd1

Please sign in to comment.