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

resource/aws_ssm_activation: Prevent crash with expiration_date #3597

Merged
merged 2 commits into from
Mar 2, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 7 additions & 5 deletions aws/resource_aws_ssm_activation.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ func resourceAwsSsmActivation() *schema.Resource {
Computed: true,
},
"expiration_date": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validateRFC3339TimeString,
},
"iam_role": {
Type: schema.TypeString,
Expand Down Expand Up @@ -77,8 +78,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 {
Expand Down
64 changes: 64 additions & 0 deletions aws/resource_aws_ssm_activation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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]
Expand Down Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️ the use of %[1]s, 👍

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ssm.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_iam_role_policy_attachment" "test_attach" {
role = "${aws_iam_role.test_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
}

resource "aws_ssm_activation" "foo" {
name = "test_ssm_activation-%[1]s",
description = "Test"
expiration_date = "%[2]s"
iam_role = "${aws_iam_role.test_role.name}"
registration_limit = "5"
depends_on = ["aws_iam_role_policy_attachment.test_attach"]
}
`, rName, expirationDate)
}
9 changes: 9 additions & 0 deletions aws/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
73 changes: 73 additions & 0 deletions aws/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,86 @@ package aws

import (
"fmt"
"regexp"
"strings"
"testing"

"github.com/aws/aws-sdk-go/service/cognitoidentity"
"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",
Expand Down
2 changes: 1 addition & 1 deletion website/docs/r/ssm_activation.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The following arguments are supported:

* `name` - (Optional) The default name of the registerd managed instance.
* `description` - (Optional) The description of the resource that you want to register.
* `expiration_date` - (Optional) The date by which this activation request should expire. The default value is 24 hours.
* `expiration_date` - (Optional) A timestamp in [RFC3339 format](https://tools.ietf.org/html/rfc3339#section-5.8) by which this activation request should expire. The default value is 24 hours from resource creation time.
* `iam_role` - (Required) The IAM Role to attach to the managed instance.
* `registration_limit` - (Optional) The maximum number of managed instances you want to register. The default value is 1 instance.

Expand Down