From 4c5c65a0ab823d6dcfec2fcdca966f5ac1902070 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Thu, 1 Mar 2018 23:27:40 -0500 Subject: [PATCH 1/2] resource/aws_ssm_activation: Prevent crash with expiration_date --- aws/resource_aws_ssm_activation.go | 13 ++++- aws/resource_aws_ssm_activation_test.go | 64 +++++++++++++++++++++ website/docs/r/ssm_activation.html.markdown | 2 +- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/aws/resource_aws_ssm_activation.go b/aws/resource_aws_ssm_activation.go index c23ca0eb208..0360739506e 100644 --- a/aws/resource_aws_ssm_activation.go +++ b/aws/resource_aws_ssm_activation.go @@ -37,6 +37,14 @@ func resourceAwsSsmActivation() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, + // When released, replace with upstream validation function: + // https://github.com/hashicorp/terraform/pull/17484 + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + if _, err := time.Parse(time.RFC3339, v.(string)); err != nil { + errors = append(errors, fmt.Errorf("%q: %s", k, err)) + } + return + }, }, "iam_role": { Type: schema.TypeString, @@ -77,8 +85,9 @@ func resourceAwsSsmActivationCreate(d *schema.ResourceData, meta interface{}) er activationInput.Description = aws.String(d.Get("description").(string)) } - if _, ok := d.GetOk("expiration_date"); ok { - activationInput.ExpirationDate = aws.Time(d.Get("expiration_date").(time.Time)) + if v, ok := d.GetOk("expiration_date"); ok { + t, _ := time.Parse(time.RFC3339, v.(string)) + activationInput.ExpirationDate = aws.Time(t) } if _, ok := d.GetOk("iam_role"); ok { diff --git a/aws/resource_aws_ssm_activation_test.go b/aws/resource_aws_ssm_activation_test.go index 6e66671b62a..b41f09c6c5a 100644 --- a/aws/resource_aws_ssm_activation_test.go +++ b/aws/resource_aws_ssm_activation_test.go @@ -2,7 +2,9 @@ package aws import ( "fmt" + "regexp" "testing" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ssm" @@ -29,6 +31,32 @@ func TestAccAWSSSMActivation_basic(t *testing.T) { }) } +func TestAccAWSSSMActivation_expirationDate(t *testing.T) { + rName := acctest.RandString(10) + expirationTime := time.Now().Add(48 * time.Hour) + expirationDateS := expirationTime.Format(time.RFC3339) + resourceName := "aws_ssm_activation.foo" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMActivationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMActivationConfig_expirationDate(rName, "2018-03-01"), + ExpectError: regexp.MustCompile(`cannot parse`), + }, + { + Config: testAccAWSSSMActivationConfig_expirationDate(rName, expirationDateS), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMActivationExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "expiration_date", expirationDateS), + ), + }, + }, + }) +} + func testAccCheckAWSSSMActivationExists(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -130,3 +158,39 @@ resource "aws_ssm_activation" "foo" { } `, rName, rName) } + +func testAccAWSSSMActivationConfig_expirationDate(rName, expirationDate string) string { + return fmt.Sprintf(` +resource "aws_iam_role" "test_role" { + name = "test_role-%[1]s" + assume_role_policy = < Date: Fri, 2 Mar 2018 09:22:21 -0500 Subject: [PATCH 2/2] Create validateRFC3339TimeString ValidateFunc instead of inline --- aws/resource_aws_ssm_activation.go | 15 ++---- aws/validators.go | 9 ++++ aws/validators_test.go | 73 ++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/aws/resource_aws_ssm_activation.go b/aws/resource_aws_ssm_activation.go index 0360739506e..8fb58671d0b 100644 --- a/aws/resource_aws_ssm_activation.go +++ b/aws/resource_aws_ssm_activation.go @@ -34,17 +34,10 @@ func resourceAwsSsmActivation() *schema.Resource { Computed: true, }, "expiration_date": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - // When released, replace with upstream validation function: - // https://github.com/hashicorp/terraform/pull/17484 - ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { - if _, err := time.Parse(time.RFC3339, v.(string)); err != nil { - errors = append(errors, fmt.Errorf("%q: %s", k, err)) - } - return - }, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validateRFC3339TimeString, }, "iam_role": { Type: schema.TypeString, diff --git a/aws/validators.go b/aws/validators.go index 086b5008d91..aa9bbf4ac67 100644 --- a/aws/validators.go +++ b/aws/validators.go @@ -22,6 +22,15 @@ import ( "github.com/hashicorp/terraform/helper/structure" ) +// When released, replace all usage with upstream validation function: +// https://github.com/hashicorp/terraform/pull/17484 +func validateRFC3339TimeString(v interface{}, k string) (ws []string, errors []error) { + if _, err := time.Parse(time.RFC3339, v.(string)); err != nil { + errors = append(errors, fmt.Errorf("%q: %s", k, err)) + } + return +} + func validateInstanceUserDataSize(v interface{}, k string) (ws []string, errors []error) { value := v.(string) length := len(value) diff --git a/aws/validators_test.go b/aws/validators_test.go index c2459c4fd1e..2724ced4f87 100644 --- a/aws/validators_test.go +++ b/aws/validators_test.go @@ -2,6 +2,7 @@ package aws import ( "fmt" + "regexp" "strings" "testing" @@ -9,6 +10,78 @@ import ( "github.com/aws/aws-sdk-go/service/s3" ) +func TestValidateRFC3339TimeString(t *testing.T) { + testCases := []struct { + val interface{} + expectedErr *regexp.Regexp + }{ + { + val: "2018-03-01T00:00:00Z", + }, + { + val: "2018-03-01T00:00:00-05:00", + }, + { + val: "2018-03-01T00:00:00+05:00", + }, + { + val: "03/01/2018", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`cannot parse "1/2018" as "2006"`)), + }, + { + val: "03-01-2018", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`cannot parse "1-2018" as "2006"`)), + }, + { + val: "2018-03-01", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`cannot parse "" as "T"`)), + }, + { + val: "2018-03-01T", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`cannot parse "" as "15"`)), + }, + { + val: "2018-03-01T00:00:00", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`cannot parse "" as "Z07:00"`)), + }, + { + val: "2018-03-01T00:00:00Z05:00", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`extra text: 05:00`)), + }, + { + val: "2018-03-01T00:00:00Z-05:00", + expectedErr: regexp.MustCompile(regexp.QuoteMeta(`extra text: -05:00`)), + }, + } + + matchErr := func(errs []error, r *regexp.Regexp) bool { + // err must match one provided + for _, err := range errs { + if r.MatchString(err.Error()) { + return true + } + } + + return false + } + + for i, tc := range testCases { + _, errs := validateRFC3339TimeString(tc.val, "test_property") + + if len(errs) == 0 && tc.expectedErr == nil { + continue + } + + if len(errs) != 0 && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + } + + if !matchErr(errs, tc.expectedErr) { + t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) + } + } +} + func TestValidateInstanceUserDataSize(t *testing.T) { validValues := []string{ "#!/bin/bash",