mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-09 23:54:17 -06:00
70a90cc1f4
This commits changes the behaviour in a case there was an error while interacting with EC2 tags related to the CloudFormation Distribution resource, fixing the issue with nil pointer dereference when despite an error being present code path to handle tags was executed. Also, a small re-factor of the `validateHTTP` helper method, and a unit test added for it. Signed-off-by: Krzysztof Wilczynski <krzysztof.wilczynski@linux.com>
672 lines
18 KiB
Go
672 lines
18 KiB
Go
package aws
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/service/cloudfront"
|
|
"github.com/hashicorp/errwrap"
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
func resourceAwsCloudFrontDistribution() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourceAwsCloudFrontDistributionCreate,
|
|
Read: resourceAwsCloudFrontDistributionRead,
|
|
Update: resourceAwsCloudFrontDistributionUpdate,
|
|
Delete: resourceAwsCloudFrontDistributionDelete,
|
|
Importer: &schema.ResourceImporter{
|
|
State: resourceAwsCloudFrontDistributionImport,
|
|
},
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"arn": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"aliases": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
Set: aliasesHash,
|
|
},
|
|
"cache_behavior": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Set: cacheBehaviorHash,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"allowed_methods": {
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"cached_methods": {
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"compress": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
},
|
|
"default_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"forwarded_values": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: forwardedValuesHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"cookies": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: cookiePreferenceHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"forward": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"whitelisted_names": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"headers": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"query_string": {
|
|
Type: schema.TypeBool,
|
|
Required: true,
|
|
},
|
|
"query_string_cache_keys": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"max_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"min_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"path_pattern": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"smooth_streaming": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
},
|
|
"target_origin_id": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"trusted_signers": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"viewer_protocol_policy": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"comment": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
"custom_error_response": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Set: customErrorResponseHash,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"error_caching_min_ttl": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
},
|
|
"error_code": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"response_code": {
|
|
Type: schema.TypeInt,
|
|
Optional: true,
|
|
},
|
|
"response_page_path": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"default_cache_behavior": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: defaultCacheBehaviorHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"allowed_methods": {
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"cached_methods": {
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"compress": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
},
|
|
"default_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"forwarded_values": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: forwardedValuesHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"cookies": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Set: cookiePreferenceHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"forward": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"whitelisted_names": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"headers": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"query_string": {
|
|
Type: schema.TypeBool,
|
|
Required: true,
|
|
},
|
|
"query_string_cache_keys": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"max_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"min_ttl": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"smooth_streaming": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
},
|
|
"target_origin_id": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"trusted_signers": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"viewer_protocol_policy": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"default_root_object": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
"enabled": {
|
|
Type: schema.TypeBool,
|
|
Required: true,
|
|
},
|
|
"http_version": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: "http2",
|
|
ValidateFunc: validateHTTP,
|
|
},
|
|
"logging_config": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Set: loggingConfigHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"bucket": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"include_cookies": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
},
|
|
"prefix": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: "",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"origin": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: originHash,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"custom_origin_config": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ConflictsWith: []string{"origin.s3_origin_config"},
|
|
Set: customOriginConfigHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"http_port": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"https_port": {
|
|
Type: schema.TypeInt,
|
|
Required: true,
|
|
},
|
|
"origin_protocol_policy": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"origin_ssl_protocols": {
|
|
Type: schema.TypeList,
|
|
Required: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"domain_name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"custom_header": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
Set: originCustomHeaderHash,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"value": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"origin_id": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
"origin_path": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
"s3_origin_config": {
|
|
Type: schema.TypeSet,
|
|
Optional: true,
|
|
ConflictsWith: []string{"origin.custom_origin_config"},
|
|
Set: s3OriginConfigHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"origin_access_identity": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"price_class": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: "PriceClass_All",
|
|
},
|
|
"restrictions": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: restrictionsHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"geo_restriction": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: geoRestrictionHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"locations": {
|
|
Type: schema.TypeList,
|
|
Optional: true,
|
|
Elem: &schema.Schema{Type: schema.TypeString},
|
|
},
|
|
"restriction_type": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"viewer_certificate": {
|
|
Type: schema.TypeSet,
|
|
Required: true,
|
|
Set: viewerCertificateHash,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"acm_certificate_arn": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ConflictsWith: []string{"viewer_certificate.cloudfront_default_certificate", "viewer_certificate.iam_certificate_id"},
|
|
},
|
|
"cloudfront_default_certificate": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
ConflictsWith: []string{"viewer_certificate.acm_certificate_arn", "viewer_certificate.iam_certificate_id"},
|
|
},
|
|
"iam_certificate_id": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
ConflictsWith: []string{"viewer_certificate.acm_certificate_arn", "viewer_certificate.cloudfront_default_certificate"},
|
|
},
|
|
"minimum_protocol_version": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
Default: "SSLv3",
|
|
},
|
|
"ssl_support_method": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"web_acl_id": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
"caller_reference": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"status": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"active_trusted_signers": {
|
|
Type: schema.TypeMap,
|
|
Computed: true,
|
|
},
|
|
"domain_name": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"last_modified_time": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"in_progress_validation_batches": {
|
|
Type: schema.TypeInt,
|
|
Computed: true,
|
|
},
|
|
"etag": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"hosted_zone_id": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
// retain_on_delete is a non-API attribute that may help facilitate speedy
|
|
// deletion of a resoruce. It's mainly here for testing purposes, so
|
|
// enable at your own risk.
|
|
"retain_on_delete": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: false,
|
|
},
|
|
|
|
"tags": tagsSchema(),
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourceAwsCloudFrontDistributionCreate(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).cloudfrontconn
|
|
|
|
params := &cloudfront.CreateDistributionWithTagsInput{
|
|
DistributionConfigWithTags: &cloudfront.DistributionConfigWithTags{
|
|
DistributionConfig: expandDistributionConfig(d),
|
|
Tags: tagsFromMapCloudFront(d.Get("tags").(map[string]interface{})),
|
|
},
|
|
}
|
|
|
|
resp, err := conn.CreateDistributionWithTags(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.SetId(*resp.Distribution.Id)
|
|
return resourceAwsCloudFrontDistributionRead(d, meta)
|
|
}
|
|
|
|
func resourceAwsCloudFrontDistributionRead(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).cloudfrontconn
|
|
params := &cloudfront.GetDistributionInput{
|
|
Id: aws.String(d.Id()),
|
|
}
|
|
|
|
resp, err := conn.GetDistribution(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update attributes from DistributionConfig
|
|
err = flattenDistributionConfig(d, resp.Distribution.DistributionConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Update other attributes outside of DistributionConfig
|
|
d.SetId(*resp.Distribution.Id)
|
|
err = d.Set("active_trusted_signers", flattenActiveTrustedSigners(resp.Distribution.ActiveTrustedSigners))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.Set("status", resp.Distribution.Status)
|
|
d.Set("domain_name", resp.Distribution.DomainName)
|
|
d.Set("last_modified_time", aws.String(resp.Distribution.LastModifiedTime.String()))
|
|
d.Set("in_progress_validation_batches", resp.Distribution.InProgressInvalidationBatches)
|
|
d.Set("etag", resp.ETag)
|
|
d.Set("arn", resp.Distribution.ARN)
|
|
|
|
tagResp, err := conn.ListTagsForResource(&cloudfront.ListTagsForResourceInput{
|
|
Resource: aws.String(d.Get("arn").(string)),
|
|
})
|
|
|
|
if err != nil {
|
|
return errwrap.Wrapf(fmt.Sprintf(
|
|
"Error retrieving EC2 tags for CloudFront Distribution %q (ARN: %q): {{err}}",
|
|
d.Id(), d.Get("arn").(string)), err)
|
|
}
|
|
|
|
if err := d.Set("tags", tagsToMapCloudFront(tagResp.Tags)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourceAwsCloudFrontDistributionUpdate(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).cloudfrontconn
|
|
params := &cloudfront.UpdateDistributionInput{
|
|
Id: aws.String(d.Id()),
|
|
DistributionConfig: expandDistributionConfig(d),
|
|
IfMatch: aws.String(d.Get("etag").(string)),
|
|
}
|
|
_, err := conn.UpdateDistribution(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := setTagsCloudFront(conn, d, d.Get("arn").(string)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return resourceAwsCloudFrontDistributionRead(d, meta)
|
|
}
|
|
|
|
func resourceAwsCloudFrontDistributionDelete(d *schema.ResourceData, meta interface{}) error {
|
|
conn := meta.(*AWSClient).cloudfrontconn
|
|
|
|
// manually disable the distribution first
|
|
d.Set("enabled", false)
|
|
err := resourceAwsCloudFrontDistributionUpdate(d, meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// skip delete if retain_on_delete is enabled
|
|
if d.Get("retain_on_delete").(bool) {
|
|
log.Printf("[WARN] Removing CloudFront Distribution ID %q with `retain_on_delete` set. Please delete this distribution manually.", d.Id())
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
// Distribution needs to be in deployed state again before it can be deleted.
|
|
err = resourceAwsCloudFrontDistributionWaitUntilDeployed(d.Id(), meta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// now delete
|
|
params := &cloudfront.DeleteDistributionInput{
|
|
Id: aws.String(d.Id()),
|
|
IfMatch: aws.String(d.Get("etag").(string)),
|
|
}
|
|
|
|
_, err = conn.DeleteDistribution(params)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Done
|
|
d.SetId("")
|
|
return nil
|
|
}
|
|
|
|
// resourceAwsCloudFrontWebDistributionWaitUntilDeployed blocks until the
|
|
// distribution is deployed. It currently takes exactly 15 minutes to deploy
|
|
// but that might change in the future.
|
|
func resourceAwsCloudFrontDistributionWaitUntilDeployed(id string, meta interface{}) error {
|
|
stateConf := &resource.StateChangeConf{
|
|
Pending: []string{"InProgress", "Deployed"},
|
|
Target: []string{"Deployed"},
|
|
Refresh: resourceAwsCloudFrontWebDistributionStateRefreshFunc(id, meta),
|
|
Timeout: 40 * time.Minute,
|
|
MinTimeout: 15 * time.Second,
|
|
Delay: 10 * time.Minute,
|
|
}
|
|
|
|
_, err := stateConf.WaitForState()
|
|
return err
|
|
}
|
|
|
|
// The refresh function for resourceAwsCloudFrontWebDistributionWaitUntilDeployed.
|
|
func resourceAwsCloudFrontWebDistributionStateRefreshFunc(id string, meta interface{}) resource.StateRefreshFunc {
|
|
return func() (interface{}, string, error) {
|
|
conn := meta.(*AWSClient).cloudfrontconn
|
|
params := &cloudfront.GetDistributionInput{
|
|
Id: aws.String(id),
|
|
}
|
|
|
|
resp, err := conn.GetDistribution(params)
|
|
if err != nil {
|
|
log.Printf("[WARN] Error retrieving CloudFront Distribution %q details: %s", id, err)
|
|
return nil, "", err
|
|
}
|
|
|
|
if resp == nil {
|
|
return nil, "", nil
|
|
}
|
|
|
|
return resp.Distribution, *resp.Distribution.Status, nil
|
|
}
|
|
}
|
|
|
|
// validateHTTP ensures that the http_version resource parameter is
|
|
// correct.
|
|
func validateHTTP(v interface{}, k string) (ws []string, errors []error) {
|
|
value := v.(string)
|
|
|
|
if value != "http1.1" && value != "http2" {
|
|
errors = append(errors, fmt.Errorf(
|
|
"%q contains an invalid HTTP version parameter %q. Valid parameters are either %q or %q.",
|
|
k, value, "http1.1", "http2"))
|
|
}
|
|
return
|
|
}
|