Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: CloudFront Public Key #5737

Merged
merged 14 commits into from
Sep 12, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ func Provider() terraform.ResourceProvider {
"aws_cloudformation_stack": resourceAwsCloudFormationStack(),
"aws_cloudfront_distribution": resourceAwsCloudFrontDistribution(),
"aws_cloudfront_origin_access_identity": resourceAwsCloudFrontOriginAccessIdentity(),
"aws_cloudfront_public_key": resourceAwsCloudFrontPublicKey(),
"aws_cloudtrail": resourceAwsCloudTrail(),
"aws_cloudwatch_event_permission": resourceAwsCloudWatchEventPermission(),
"aws_cloudwatch_event_rule": resourceAwsCloudWatchEventRule(),
Expand Down
175 changes: 175 additions & 0 deletions aws/resource_aws_cloudfront_public_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsCloudFrontPublicKey() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCloudFrontPublicKeyCreate,
Read: resourceAwsCloudFrontPublicKeyRead,
Update: resourceAwsCloudFrontPublicKeyUpdate,
Delete: resourceAwsCloudFrontPublicKeyDelete,

Schema: map[string]*schema.Schema{
"caller_reference": {
Type: schema.TypeString,
Computed: true,
},
"comment": {
Type: schema.TypeString,
Optional: true,
},
"encoded_key": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name_prefix"},
ValidateFunc: validateCloudFrontPublicKeyName,
},
"name_prefix": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ConflictsWith: []string{"name"},
ValidateFunc: validateCloudFrontPublicKeyNamePrefix,
},
},
}
}

func resourceAwsCloudFrontPublicKeyCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudfrontconn

if v, ok := d.GetOk("name"); ok {
d.Set("name", v.(string))
} else if v, ok := d.GetOk("name_prefix"); ok {
d.Set("name", resource.PrefixedUniqueId(v.(string)))
} else {
d.Set("name", resource.PrefixedUniqueId("tf-"))
}

request := &cloudfront.CreatePublicKeyInput{
PublicKeyConfig: expandPublicKeyConfig(d),
}

log.Println("[DEBUG] Create CloudFront PublicKey:", request)

output, err := conn.CreatePublicKey(request)
if err != nil {
return fmt.Errorf("error creating CloudFront PublicKey: %s", err)
}

d.SetId(aws.StringValue(output.PublicKey.Id))
return resourceAwsCloudFrontPublicKeyRead(d, meta)
}

func resourceAwsCloudFrontPublicKeyRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudfrontconn
request := &cloudfront.GetPublicKeyInput{
Id: aws.String(d.Id()),
}

output, err := conn.GetPublicKey(request)
if err != nil {
if isAWSErr(err, cloudfront.ErrCodeNoSuchPublicKey, "") {
log.Printf("[WARN] No PublicKey found: %s, removing from state", d.Id())
d.SetId("")
return nil
}
return err
}

var publicKeyConfig *cloudfront.PublicKeyConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declaring this variable seems extraneous and should also be protected by nil checks to prevent panics, e.g.

if output == nil || output.PublicKey == nil || output.PublicKey.PublicKeyConfig == nil {
  log.Printf("[WARN] No PublicKey found: %s, removing from state", d.Id())
  d.SetId("")
  return nil
}
publicKeyConfig := output.PublicKey.PublicKeyConfig

publicKeyConfig = output.PublicKey.PublicKeyConfig

d.Set("encoded_key", publicKeyConfig.EncodedKey)
d.Set("name", publicKeyConfig.Name)

if publicKeyConfig.Comment != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should prefer to always call d.Set() as it can handle *string set to nil just fine and helps catch the case where the comment is removed outside of Terraform.

d.Set("comment", publicKeyConfig.Comment)
}

if publicKeyConfig.CallerReference != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here -- we should prefer to always call d.Set() as it can handle *string set to nil just fine.

d.Set("caller_reference", publicKeyConfig.CallerReference)
}

d.Set("etag", output.ETag)

return nil
}

func resourceAwsCloudFrontPublicKeyUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudfrontconn

request := &cloudfront.UpdatePublicKeyInput{
Id: aws.String(d.Id()),
PublicKeyConfig: expandPublicKeyConfig(d),
IfMatch: aws.String(d.Get("etag").(string)),
}

_, err := conn.UpdatePublicKey(request)
if err != nil {
return fmt.Errorf("error updating CloudFront PublicKey (%s): %s", d.Id(), err)
}

return resourceAwsCloudFrontPublicKeyRead(d, meta)
}

func resourceAwsCloudFrontPublicKeyDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudfrontconn

request := &cloudfront.DeletePublicKeyInput{
Id: aws.String(d.Id()),
IfMatch: aws.String(d.Get("etag").(string)),
}

_, err := conn.DeletePublicKey(request)
if err != nil {
if isAWSErr(err, cloudfront.ErrCodeNoSuchPublicKey, "") {
log.Printf("[WARN] No PublicKey found: %s, removing from state", d.Id())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log line and the d.SetId("") are extraneous in the delete function. 👍

d.SetId("")
return nil
}
return err
}

return nil
}

func expandPublicKeyConfig(d *schema.ResourceData) *cloudfront.PublicKeyConfig {
publicKeyConfig := &cloudfront.PublicKeyConfig{
EncodedKey: aws.String(d.Get("encoded_key").(string)),
Name: aws.String(d.Get("name").(string)),
}

if v, ok := d.GetOk("comment"); ok {
publicKeyConfig.Comment = aws.String(v.(string))
}

if v, ok := d.GetOk("caller_reference"); ok {
publicKeyConfig.CallerReference = aws.String(v.(string))
} else {
publicKeyConfig.CallerReference = aws.String(time.Now().Format(time.RFC3339Nano))
}

return publicKeyConfig
}
156 changes: 156 additions & 0 deletions aws/resource_aws_cloudfront_public_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package aws

import (
"fmt"
"regexp"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudfront"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSCloudFrontPublicKey_basic(t *testing.T) {
rInt := acctest.RandInt()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontPublicKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudFrontPublicKeyConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFrontPublicKeyExistence("aws_cloudfront_public_key.example"),
resource.TestCheckResourceAttr("aws_cloudfront_public_key.example", "comment", "test key"),
resource.TestMatchResourceAttr("aws_cloudfront_public_key.example",
"caller_reference",
regexp.MustCompile("^20[0-9]{2}.*")),
resource.TestCheckResourceAttr("aws_cloudfront_public_key.example", "name", fmt.Sprintf("tf-acc-test-%d", rInt)),
),
},
},
})
}

func TestAccAWSCloudFrontPublicKey_namePrefix(t *testing.T) {
startsWithPrefix := regexp.MustCompile("^tf-acc-test-")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontPublicKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudFrontPublicKeyConfig_namePrefix(),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFrontPublicKeyExistence("aws_cloudfront_public_key.example"),
resource.TestMatchResourceAttr("aws_cloudfront_public_key.example", "name", startsWithPrefix),
),
},
},
})
}

func TestAccAWSCloudFrontPublicKey_update(t *testing.T) {
rInt := acctest.RandInt()

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudFrontPublicKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSCloudFrontPublicKeyConfig(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFrontPublicKeyExistence("aws_cloudfront_public_key.example"),
resource.TestCheckResourceAttr("aws_cloudfront_public_key.example", "comment", "test key"),
),
},
{
Config: testAccAWSCloudFrontPublicKeyConfigUpdate(rInt),
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudFrontPublicKeyExistence("aws_cloudfront_public_key.example"),
resource.TestCheckResourceAttr("aws_cloudfront_public_key.example", "comment", "test key1"),
),
},
},
})
}

func testAccCheckCloudFrontPublicKeyExistence(r string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[r]
if !ok {
return fmt.Errorf("Not found: %s", r)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Id is set")
}

conn := testAccProvider.Meta().(*AWSClient).cloudfrontconn

params := &cloudfront.GetPublicKeyInput{
Id: aws.String(rs.Primary.ID),
}

_, err := conn.GetPublicKey(params)
if err != nil {
return fmt.Errorf("Error retrieving CloudFront PublicKey: %s", err)
}
return nil
}
}

func testAccCheckCloudFrontPublicKeyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudfrontconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudfront_public_key" {
continue
}

params := &cloudfront.GetPublicKeyInput{
Id: aws.String(rs.Primary.ID),
}

_, err := conn.GetPublicKey(params)
if err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should prefer to catch the specific error we want and ensure we return other errors, e.g.

_, err := conn.GetPublicKey(params)
if isAWSErr(err, cloudfront.ErrCodeNoSuchPublicKey, "") {
  continue
}
if err != nil {
  return err
}
return fmt.Errorf("CloudFront PublicKey (%s) was not deleted", rs.Primary.ID)

return fmt.Errorf("CloudFront PublicKey was not deleted")
}
}

return nil
}

func testAccAWSCloudFrontPublicKeyConfig(rInt int) string {
return fmt.Sprintf(`
resource "aws_cloudfront_public_key" "example" {
comment = "test key"
encoded_key = "${file("test-fixtures/cloudfront-public-key.pem")}"
name = "tf-acc-test-%d"
}
`, rInt)
}

func testAccAWSCloudFrontPublicKeyConfig_namePrefix() string {
return fmt.Sprintf(`
resource "aws_cloudfront_public_key" "example" {
comment = "test key"
encoded_key = "${file("test-fixtures/cloudfront-public-key.pem")}"
name_prefix = "tf-acc-test-"
}
`)
}

func testAccAWSCloudFrontPublicKeyConfigUpdate(rInt int) string {
return fmt.Sprintf(`
resource "aws_cloudfront_public_key" "example" {
comment = "test key1"
encoded_key = "${file("test-fixtures/cloudfront-public-key.pem")}"
name = "tf-acc-test-%d"
}
`, rInt)
}
9 changes: 9 additions & 0 deletions aws/test-fixtures/cloudfront-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtZCjGTEV/ttumSJBnsc2
SUzPY/wJjfNchT2mjWivg/S7HuwKp1tDHizxrXTVuZLdDKceVcSclS7otzwfmGxM
Gjk2/CM2hEMThT86q76TrbH6hvGa25n8piBOkhwbwdbvmg3DRJiLR9bqw+nAPt/n
1ggTcwazm1Bw7y112Ardop+buWirS3w2C6au2OdloaaLz5N1eHEHQuRpnmD+UoVR
OgGeaLaU7FxKkpOps4Giu4vgjcefGlM3MrqG4FAzDMtgGZdJm4U+bldYmk0+J1yv
JA0FGd9g9GhjHMT9UznxXccw7PhHQsXn4lQfOn47uO9KIq170t8FeHKEzbCMsmyA
2QIDAQAB
-----END PUBLIC KEY-----
27 changes: 27 additions & 0 deletions aws/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -1974,3 +1974,30 @@ func validateNeptuneEventSubscriptionNamePrefix(v interface{}, k string) (ws []s
}
return
}

func validateCloudFrontPublicKeyName(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9A-Za-z_-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters, underscores and hyphens allowed in %q", k))
}
if len(value) > 128 {
errors = append(errors, fmt.Errorf(
"%q cannot be greater than 128 characters", k))
}
return
}

func validateCloudFrontPublicKeyNamePrefix(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if !regexp.MustCompile(`^[0-9A-Za-z_-]+$`).MatchString(value) {
errors = append(errors, fmt.Errorf(
"only alphanumeric characters, underscores and hyphens allowed in %q", k))
}
prefixMaxLength := 128 - resource.UniqueIDSuffixLength
if len(value) > prefixMaxLength {
errors = append(errors, fmt.Errorf(
"%q cannot be greater than %d characters", k, prefixMaxLength))
}
return
}
Loading