Skip to content

Commit

Permalink
New Resource: awsex_cloudfront_distribution_invalidations (#12)
Browse files Browse the repository at this point in the history
* Create reusable funcs from invalidation

* Added awsex_cloudfront_distribution_invalidations

* acc test

* fix tests

* docs

* can have 0 distribution ids
  • Loading branch information
BSick7 authored Oct 1, 2024
1 parent 18ddc62 commit 64996f0
Show file tree
Hide file tree
Showing 10 changed files with 498 additions and 92 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
## 0.1.3 (Oct 01, 2024)

FEATURES:
- New Resource: `awsex_cloudfront_distribution_invalidations`

## 0.1.2 (Sep 14, 2024)

BUG FIXES:
- Fixed `triggers` so that they force a new resource when changed.

## 0.1.1 (Sep 14, 2024)

BUG FIXES:
- Fixed caller reference for create invalidation.

Expand Down
12 changes: 11 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
page_title: "awsex Provider"
subcategory: ""
description: |-
This is a Terraform provider to extend the functionality of the AWS Provider https://registry.terraform.io/providers/hashicorp/aws/latest/docs.
At time of creation, Hashicorp had few review resources dedicated to the provider.
As a result, there are over 400 open pull requests, many older than 4 years.
The purpose of this provider is to rapidly augment the official provider.
This can also be used to rapidly experiment with new resources.
---

# awsex Provider

This is a Terraform provider to extend the functionality of the [AWS Provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs).

At time of creation, Hashicorp had few review resources dedicated to the provider.
As a result, there are over 400 open pull requests, many older than 4 years.

The purpose of this provider is to rapidly augment the official provider.
This can also be used to rapidly experiment with new resources.



Expand Down
38 changes: 38 additions & 0 deletions docs/resources/cloudfront_distribution_invalidations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "awsex_cloudfront_distribution_invalidations Resource - awsex"
subcategory: ""
description: |-
---

# awsex_cloudfront_distribution_invalidations (Resource)





<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `distribution_ids` (Set of String) A list of Cloudfront Distribution IDs where an invalidation will be created.
- `paths` (Set of String) A list of paths to invalidate. Each path *must* start with `/`.

### Optional

- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
- `triggers` (Map of String) A map of triggers that, when changed, will force Terraform to create a new invalidation.

### Read-Only

- `id` (String) The ID of the invalidations.
- `statuses` (Map of String) The status of each invalidation indexed by the Cloudfront Distribution ID.

<a id="nestedblock--timeouts"></a>
### Nested Schema for `timeouts`

Optional:

- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
75 changes: 75 additions & 0 deletions internal/provider/cloudfront/invalidation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package cloudfront

import (
"context"
"errors"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cftypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-provider-awsex/internal/conns"
"time"
)

func CreateInvalidation(ctx context.Context, client *conns.Client, distributionId string, paths []string, createTimeout time.Duration) (*cftypes.Invalidation, diag.Diagnostics) {
var diags diag.Diagnostics

input := &cloudfront.CreateInvalidationInput{
DistributionId: &distributionId,
InvalidationBatch: &cftypes.InvalidationBatch{
CallerReference: aws.String(uuid.NewString()),
Paths: &cftypes.Paths{
Quantity: aws.Int32(int32(len(paths))),
Items: paths,
},
},
}
cfClient := client.Cloudfront()
out, err := cfClient.CreateInvalidation(ctx, input)
if err != nil {
diags.AddError("Error creating AWS Cloudfront Invalidation", err.Error())
return nil, diags
}
if out != nil && out.Invalidation != nil && out.Invalidation.Id != nil {
tflog.Trace(ctx, "Created Cloudfront Invalidation")
} else {
diags.AddWarning("Unable to create AWS Cloudfront Invalidation.", "AWS did not create an invalidation and gave no reason")
return nil, diags
}

waiter := cloudfront.NewInvalidationCompletedWaiter(cfClient)
res, err := waiter.WaitForOutput(ctx, &cloudfront.GetInvalidationInput{
DistributionId: aws.String(distributionId),
Id: out.Invalidation.Id,
}, createTimeout)
if err != nil {
diags.AddError("Error waiting for creation of AWS Cloudfront Invalidation", err.Error())
return out.Invalidation, diags
} else if res != nil && res.Invalidation != nil {
return res.Invalidation, diags
}

return out.Invalidation, diags
}

func FindInvalidation(ctx context.Context, client *conns.Client, distributionId string, id string) (*cftypes.Invalidation, diag.Diagnostics) {
var diags diag.Diagnostics

input := &cloudfront.GetInvalidationInput{
DistributionId: &distributionId,
Id: &id,
}
out, err := client.Cloudfront().GetInvalidation(ctx, input)
if err != nil {
var nsi *cftypes.NoSuchInvalidation
if !errors.As(err, &nsi) {
diags.AddError("error getting AWS Invalidation", err.Error())
}
}
if out != nil {
return out.Invalidation, diags
}
return nil, diags
}
81 changes: 81 additions & 0 deletions internal/provider/cloudfront/invalidations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cloudfront

import (
"context"
cftypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-provider-awsex/internal/conns"
"sync"
"time"
)

type invalidationResult struct {
DistributionId string
Invalidation *cftypes.Invalidation
Diags diag.Diagnostics
}

func CreateInvalidations(ctx context.Context, client *conns.Client, distributionIds []string, paths []string,
createTimeout time.Duration) (map[string]*cftypes.Invalidation, diag.Diagnostics) {
ch := make(chan invalidationResult, len(distributionIds))

var wg sync.WaitGroup
for _, distributionId := range distributionIds {
wg.Add(1)
go func(distributionId string) {
defer wg.Done()
inval, diags := CreateInvalidation(ctx, client, distributionId, paths, createTimeout)
ch <- invalidationResult{
DistributionId: distributionId,
Invalidation: inval,
Diags: diags,
}

}(distributionId)
}

go func() {
wg.Wait()
close(ch)
}()

results := make(map[string]*cftypes.Invalidation)
var diags diag.Diagnostics
for cur := range ch {
results[cur.DistributionId] = cur.Invalidation
diags.Append(cur.Diags...)
}
return results, diags
}

func FindInvalidations(ctx context.Context, client *conns.Client, ids map[string]string) (map[string]*cftypes.Invalidation, diag.Diagnostics) {
ch := make(chan invalidationResult, len(ids))

var wg sync.WaitGroup
for distributionId, id := range ids {
wg.Add(1)
go func(distributionId, id string) {
defer wg.Done()
inval, diags := FindInvalidation(ctx, client, distributionId, id)
ch <- invalidationResult{
DistributionId: distributionId,
Invalidation: inval,
Diags: diags,
}

}(distributionId, id)
}

go func() {
wg.Wait()
close(ch)
}()

results := make(map[string]*cftypes.Invalidation)
var diags diag.Diagnostics
for cur := range ch {
results[cur.DistributionId] = cur.Invalidation
diags.Append(cur.Diags...)
}
return results, diags
}
98 changes: 13 additions & 85 deletions internal/provider/cloudfront_distribution_invalidation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@ package provider

import (
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cftypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
Expand All @@ -20,8 +14,8 @@ import (
"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-log/tflog"
"github.com/hashicorp/terraform-provider-awsex/internal/conns"
"github.com/hashicorp/terraform-provider-awsex/internal/provider/cloudfront"
"regexp"
"time"
)
Expand Down Expand Up @@ -130,12 +124,22 @@ func (r *CloudfrontDistributionInvalidationResource) Create(ctx context.Context,
return
}

data, diags := r.createInvalidation(ctx, data)
createTimeout, diags := data.Timeouts.Create(ctx, 30*time.Minute)
response.Diagnostics.Append(diags...)
paths := make([]string, 0)
response.Diagnostics.Append(data.Paths.ElementsAs(ctx, &paths, false)...)
if response.Diagnostics.HasError() {
return
}

inval, diags := cloudfront.CreateInvalidation(ctx, r.client, data.DistributionId.ValueString(), paths, createTimeout)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
}

data.Id = types.StringPointerValue(inval.Id)
data.Status = types.StringPointerValue(inval.Status)
if data.Triggers.IsUnknown() {
data.Triggers = types.MapNull(types.StringType)
}
Expand All @@ -150,7 +154,7 @@ func (r *CloudfrontDistributionInvalidationResource) Read(ctx context.Context, r
return
}

inval, diags := r.findInvalidation(ctx, data)
inval, diags := cloudfront.FindInvalidation(ctx, r.client, data.DistributionId.ValueString(), data.Id.ValueString())
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
return
Expand All @@ -171,79 +175,3 @@ func (r *CloudfrontDistributionInvalidationResource) Update(ctx context.Context,

func (r *CloudfrontDistributionInvalidationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
}

func (r *CloudfrontDistributionInvalidationResource) createInvalidation(ctx context.Context, data CloudfrontDistributionInvalidationModel) (CloudfrontDistributionInvalidationModel, diag.Diagnostics) {
var diags diag.Diagnostics

distributionId := data.DistributionId.ValueString()

paths := make([]string, 0)
diags = append(diags, data.Paths.ElementsAs(ctx, &paths, false)...)
if diags.HasError() {
return data, diags
}

input := &cloudfront.CreateInvalidationInput{
DistributionId: &distributionId,
InvalidationBatch: &cftypes.InvalidationBatch{
CallerReference: aws.String(uuid.NewString()),
Paths: &cftypes.Paths{
Quantity: aws.Int32(int32(len(paths))),
Items: paths,
},
},
}
client := r.client.Cloudfront()
out, err := client.CreateInvalidation(ctx, input)
if err != nil {
diags.AddError("Error creating AWS Cloudfront Invalidation", err.Error())
return data, diags
}
if out != nil && out.Invalidation != nil && out.Invalidation.Id != nil {
data.Id = types.StringValue(*out.Invalidation.Id)
tflog.Trace(ctx, "Created Cloudfront Invalidation")
} else {
diags.AddWarning("Unable to create AWS Cloudfront Invalidation.", "AWS did not create an invalidation and gave no reason")
}

createTimeout, diags := data.Timeouts.Create(ctx, 30*time.Minute)
if diags.HasError() {
return data, diags
}

waiter := cloudfront.NewInvalidationCompletedWaiter(client)
res, err := waiter.WaitForOutput(ctx, &cloudfront.GetInvalidationInput{
DistributionId: aws.String(distributionId),
Id: out.Invalidation.Id,
}, createTimeout)
if err != nil {
diags.AddError("Error waiting for creation of AWS Cloudfront Invalidation", err.Error())
return data, diags
} else if res.Invalidation != nil {
data.Status = types.StringPointerValue(res.Invalidation.Status)
}

return data, diags
}

func (r *CloudfrontDistributionInvalidationResource) findInvalidation(ctx context.Context, data CloudfrontDistributionInvalidationModel) (*cftypes.Invalidation, diag.Diagnostics) {
var diags diag.Diagnostics
var inval *cftypes.Invalidation

input := &cloudfront.GetInvalidationInput{
DistributionId: data.DistributionId.ValueStringPointer(),
Id: data.Id.ValueStringPointer(),
}
client := r.client.Cloudfront()
out, err := client.GetInvalidation(ctx, input)
if err != nil {
var nsi *cftypes.NoSuchInvalidation
if !errors.As(err, &nsi) {
diags.AddError("error getting AWS Invalidation", err.Error())
}
}
if out != nil && out.Invalidation != nil {
inval = out.Invalidation
}
return inval, diags
}
Loading

0 comments on commit 64996f0

Please sign in to comment.