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 ssm parameters features #1520

Merged
merged 9 commits into from
Mar 6, 2018
125 changes: 97 additions & 28 deletions aws/resource_aws_ssm_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
)

Expand All @@ -18,6 +17,7 @@ func resourceAwsSsmParameter() *schema.Resource {
Read: resourceAwsSsmParameterRead,
Update: resourceAwsSsmParameterPut,
Delete: resourceAwsSsmParameterDelete,
Exists: resourceAwsSmmParameterExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Expand All @@ -28,10 +28,13 @@ func resourceAwsSsmParameter() *schema.Resource {
Required: true,
ForceNew: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateSsmParameterType,
},
"value": {
Expand All @@ -47,35 +50,47 @@ func resourceAwsSsmParameter() *schema.Resource {
"key_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is key_id modifiable now? Can you please update TestAccAWSSSMParameter_secure_with_key to test this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes it works well, I tested it manually but I'll be adding tests of course.

Computed: true,
},
"overwrite": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"allowed_pattern": {
Type: schema.TypeString,
Optional: true,
},
"tags": tagsSchema(),
},
}
}

func resourceAwsSsmParameterRead(d *schema.ResourceData, meta interface{}) error {
func resourceAwsSmmParameterExists(d *schema.ResourceData, meta interface{}) (bool, error) {
ssmconn := meta.(*AWSClient).ssmconn

log.Printf("[DEBUG] Reading SSM Parameter: %s", d.Id())

paramInput := &ssm.GetParametersInput{
Names: []*string{
aws.String(d.Id()),
},
resp, err := ssmconn.GetParameters(&ssm.GetParametersInput{
Names: []*string{aws.String(d.Id())},
WithDecryption: aws.Bool(true),
})

if err != nil {
return false, err
}
return len(resp.InvalidParameters) == 0, nil
}

resp, err := ssmconn.GetParameters(paramInput)
func resourceAwsSsmParameterRead(d *schema.ResourceData, meta interface{}) error {
ssmconn := meta.(*AWSClient).ssmconn

log.Printf("[DEBUG] Reading SSM Parameter: %s", d.Id())

resp, err := ssmconn.GetParameters(&ssm.GetParametersInput{
Names: []*string{aws.String(d.Id())},
WithDecryption: aws.Bool(true),
})
if err != nil {
return errwrap.Wrapf("[ERROR] Error describing SSM parameter: {{err}}", err)
return fmt.Errorf("error getting SSM parameter: %s", err)
}

if len(resp.Parameters) == 0 {
log.Printf("[WARN] SSM Param %q not found, removing from state", d.Id())
d.SetId("")
Expand All @@ -87,6 +102,43 @@ func resourceAwsSsmParameterRead(d *schema.ResourceData, meta interface{}) error
d.Set("type", param.Type)
d.Set("value", param.Value)

describeParamsInput := &ssm.DescribeParametersInput{
Filters: []*ssm.ParametersFilter{
&ssm.ParametersFilter{
Key: aws.String("Name"),
Values: []*string{aws.String(d.Get("name").(string))},
},
},
}
detailedParameters := []*ssm.ParameterMetadata{}
err = ssmconn.DescribeParametersPages(describeParamsInput,
func(page *ssm.DescribeParametersOutput, lastPage bool) bool {
detailedParameters = append(detailedParameters, page.Parameters...)
return !lastPage
})
if err != nil {
return fmt.Errorf("error describing SSM parameter: %s", err)
}
if len(detailedParameters) == 0 {
log.Printf("[WARN] SSM Param %q not found, removing from state", d.Id())
d.SetId("")
return nil
}

detail := detailedParameters[0]
d.Set("key_id", detail.KeyId)
d.Set("description", detail.Description)
d.Set("allowed_pattern", detail.AllowedPattern)

if tagList, err := ssmconn.ListTagsForResource(&ssm.ListTagsForResourceInput{
ResourceId: aws.String(d.Get("name").(string)),
ResourceType: aws.String("Parameter"),
}); err != nil {
return fmt.Errorf("Failed to get SSM parameter tags for %s: %s", d.Get("name"), err)
} else {
d.Set("tags", tagsToMapSSM(tagList.TagList))
}

arn := arn.ARN{
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Expand All @@ -104,15 +156,12 @@ func resourceAwsSsmParameterDelete(d *schema.ResourceData, meta interface{}) err

log.Printf("[INFO] Deleting SSM Parameter: %s", d.Id())

paramInput := &ssm.DeleteParameterInput{
_, err := ssmconn.DeleteParameter(&ssm.DeleteParameterInput{
Name: aws.String(d.Get("name").(string)),
}

_, err := ssmconn.DeleteParameter(paramInput)
})
if err != nil {
return err
}

d.SetId("")

return nil
Expand All @@ -124,24 +173,44 @@ func resourceAwsSsmParameterPut(d *schema.ResourceData, meta interface{}) error
log.Printf("[INFO] Creating SSM Parameter: %s", d.Get("name").(string))

paramInput := &ssm.PutParameterInput{
Name: aws.String(d.Get("name").(string)),
Type: aws.String(d.Get("type").(string)),
Value: aws.String(d.Get("value").(string)),
Overwrite: aws.Bool(d.Get("overwrite").(bool)),
Name: aws.String(d.Get("name").(string)),
Type: aws.String(d.Get("type").(string)),
Value: aws.String(d.Get("value").(string)),
Overwrite: aws.Bool(shouldUpdateSsmParameter(d)),
AllowedPattern: aws.String(d.Get("allowed_pattern").(string)),
}

if d.HasChange("description") {
_, n := d.GetChange("description")
paramInput.Description = aws.String(n.(string))
}

if keyID, ok := d.GetOk("key_id"); ok {
log.Printf("[DEBUG] Setting key_id for SSM Parameter %s: %s", d.Get("name").(string), keyID.(string))
log.Printf("[DEBUG] Setting key_id for SSM Parameter %v: %s", d.Get("name"), keyID)
paramInput.SetKeyId(keyID.(string))
}

log.Printf("[DEBUG] Waiting for SSM Parameter %q to be updated", d.Get("name").(string))
_, err := ssmconn.PutParameter(paramInput)
log.Printf("[DEBUG] Waiting for SSM Parameter %v to be updated", d.Get("name"))
if _, err := ssmconn.PutParameter(paramInput); err != nil {
return fmt.Errorf("error creating SSM parameter: %s", err)
}

if err != nil {
return errwrap.Wrapf("[ERROR] Error creating SSM parameter: {{err}}", err)
if err := setTagsSSM(ssmconn, d, d.Get("name").(string), "Parameter"); err != nil {
return fmt.Errorf("error creating SSM parameter tags: %s", err)
}

d.SetId(d.Get("name").(string))

return resourceAwsSsmParameterRead(d, meta)
}

func shouldUpdateSsmParameter(d *schema.ResourceData) bool {
// If the user has specified a preference, return their preference
if value, ok := d.GetOkExists("overwrite"); ok {
return value.(bool)
}

// Since the user has not specified a preference, obey lifecycle rules
// if it is not a new resource, otherwise overwrite should be set to false.
return !d.IsNewResource()
}
140 changes: 130 additions & 10 deletions aws/resource_aws_ssm_parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,29 @@ func TestAccAWSSSMParameter_update(t *testing.T) {
})
}

func TestAccAWSSSMParameter_updateDescription(t *testing.T) {
var param ssm.Parameter
name := fmt.Sprintf("%s_%s", t.Name(), acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterBasicConfigOverwrite(name, "String", "bar"),
},
{
Config: testAccAWSSSMParameterBasicConfigOverwriteWithoutDescription(name, "String", "bar"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterExists("aws_ssm_parameter.foo", &param),
resource.TestCheckResourceAttr("aws_ssm_parameter.foo", "description", ""),
),
},
},
})
}

func TestAccAWSSSMParameter_changeNameForcesNew(t *testing.T) {
var beforeParam, afterParam ssm.Parameter
before := fmt.Sprintf("%s_%s", t.Name(), acctest.RandString(10))
Expand Down Expand Up @@ -145,6 +168,7 @@ func TestAccAWSSSMParameter_secure(t *testing.T) {
testAccCheckAWSSSMParameterExists("aws_ssm_parameter.foo", &param),
resource.TestCheckResourceAttr("aws_ssm_parameter.foo", "value", "secret"),
resource.TestCheckResourceAttr("aws_ssm_parameter.foo", "type", "SecureString"),
resource.TestCheckResourceAttr("aws_ssm_parameter.foo", "key_id", "alias/aws/ssm"), // Default SSM key id
),
},
},
Expand All @@ -153,19 +177,53 @@ func TestAccAWSSSMParameter_secure(t *testing.T) {

func TestAccAWSSSMParameter_secure_with_key(t *testing.T) {
var param ssm.Parameter
name := fmt.Sprintf("%s_%s", t.Name(), acctest.RandString(10))
randString := acctest.RandString(10)
name := fmt.Sprintf("%s_%s", t.Name(), randString)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterSecureConfigWithKey(name, "secret"),
Config: testAccAWSSSMParameterSecureConfigWithKey(name, "secret", randString),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterExists("aws_ssm_parameter.secret_foo", &param),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "value", "secret"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "type", "SecureString"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "key_id", "alias/"+randString),
),
},
},
})
}

func TestAccAWSSSMParameter_secure_keyUpdate(t *testing.T) {
var param ssm.Parameter
randString := acctest.RandString(10)
name := fmt.Sprintf("%s_%s", t.Name(), randString)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterSecureConfig(name, "secret"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterExists("aws_ssm_parameter.secret_foo", &param),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "value", "secret"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "type", "SecureString"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "key_id", "alias/aws/ssm"), // Default SSM key id
),
},
{
Config: testAccAWSSSMParameterSecureConfigWithKey(name, "secret", randString),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterExists("aws_ssm_parameter.secret_foo", &param),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "value", "secret"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "type", "SecureString"),
resource.TestCheckResourceAttr("aws_ssm_parameter.secret_foo", "key_id", "alias/"+randString),
),
},
},
Expand Down Expand Up @@ -273,26 +331,88 @@ resource "aws_ssm_parameter" "foo" {
func testAccAWSSSMParameterBasicConfigOverwrite(rName, pType, value string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "foo" {
name = "%s"
type = "%s"
value = "%s"
name = "test_parameter-%[1]s"
description = "description for parameter %[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.

Since description is an optional field, we should probably give it its own acceptance test for adding it, updating it, and removing it in configuration. Especially given the API bug mentioned in the code.

type = "%[2]s"
value = "%[3]s"
overwrite = true
}
`, rName, pType, value)
}

func testAccAWSSSMParameterSecureConfigWithKey(rName string, value string) string {
func testAccAWSSSMParameterBasicConfigOverwriteWithoutDescription(rName, pType, value string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "foo" {
name = "test_parameter-%[1]s"
type = "%[2]s"
value = "%[3]s"
overwrite = true
}
`, rName, pType, value)
}

func testAccAWSSSMParameterSecureConfig(rName string, value string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "secret_foo" {
name = "test_secure_parameter-%s"
name = "test_secure_parameter-%[1]s"
description = "description for parameter %[1]s"
type = "SecureString"
value = "%s"
key_id = "${aws_kms_key.test_key.id}"
value = "%[2]s"
}
`, rName, value)
}

func testAccAWSSSMParameterSecureConfigWithKey(rName string, value string, keyAlias string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "secret_foo" {
name = "test_secure_parameter-%[1]s"
description = "description for parameter %[1]s"
type = "SecureString"
value = "%[2]s"
key_id = "alias/%[3]s"
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nitpick: ${aws_kms_alias.test_alias.name} is a little easier to understand here instead of depends_on 😉

depends_on = ["aws_kms_alias.test_alias"]
}

resource "aws_kms_key" "test_key" {
description = "KMS key 1"
deletion_window_in_days = 7
}
`, rName, value)

resource "aws_kms_alias" "test_alias" {
name = "alias/%[3]s"
target_key_id = "${aws_kms_key.test_key.id}"
}
`, rName, value, keyAlias)
}

func TestAWSSSMParameterShouldUpdate(t *testing.T) {
data := resourceAwsSsmParameter().TestResourceData()
failure := false

if !shouldUpdateSsmParameter(data) {
t.Logf("Existing resources should be overwritten if the values don't match!")
failure = true
}

data.MarkNewResource()
if shouldUpdateSsmParameter(data) {
t.Logf("New resources must never be overwritten, this will overwrite parameters created outside of the system")
failure = true
}

data = resourceAwsSsmParameter().TestResourceData()
data.Set("overwrite", true)
if !shouldUpdateSsmParameter(data) {
t.Logf("Resources should always be overwritten if the user requests it")
failure = true
}

data.Set("overwrite", false)
if shouldUpdateSsmParameter(data) {
t.Logf("Resources should never be overwritten if the user requests it")
failure = true
}
if failure {
t.Fail()
}
}
Loading