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

provider/aws: New SSM Parameter resource #14043

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ func Provider() terraform.ResourceProvider {
"aws_ssm_activation": resourceAwsSsmActivation(),
"aws_ssm_association": resourceAwsSsmAssociation(),
"aws_ssm_document": resourceAwsSsmDocument(),
"aws_ssm_parameter": resourceAwsSsmParameter(),
"aws_spot_datafeed_subscription": resourceAwsSpotDataFeedSubscription(),
"aws_spot_instance_request": resourceAwsSpotInstanceRequest(),
"aws_spot_fleet_request": resourceAwsSpotFleetRequest(),
Expand Down
126 changes: 126 additions & 0 deletions builtin/providers/aws/resource_aws_ssm_parameter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package aws

import (
"fmt"
"log"
"time"

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

func resourceAwsSsmParameter() *schema.Resource {
return &schema.Resource{
Create: resourceAwsSsmParameterCreate,
Read: resourceAwsSsmParameterRead,
Update: resourceAwsSsmParameterUpdate,
Delete: resourceAwsSsmParameterDelete,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateSsmParameterType,
},
"value": {
Type: schema.TypeString,
Required: true,
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth marking this field as Sensitive as it may contain sensitive data?

},
},
}
}

func resourceAwsSsmParameterCreate(d *schema.ResourceData, meta interface{}) error {
return putAwsSSMParameter(d, meta, false)
}

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

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

paramInput := &ssm.GetParametersInput{
Names: []*string{
aws.String(d.Get("name").(string)),
},
WithDecryption: aws.Bool(true),
}

resp, err := ssmconn.GetParameters(paramInput)

if err != nil {
return errwrap.Wrapf("[ERROR] Error describing SSM parameter: {{err}}", err)
}

if len(resp.InvalidParameters) > 0 {
return fmt.Errorf("[ERROR] SSM Parameter %s is invalid", d.Id())
}

param := resp.Parameters[0]
d.Set("name", param.Name)
d.Set("type", param.Type)
d.Set("value", param.Value)

return nil
}

func resourceAwsSsmParameterUpdate(d *schema.ResourceData, meta interface{}) error {
return putAwsSSMParameter(d, meta, true)
}

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

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

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

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

d.SetId("")

return nil
}

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

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(overwrite),
Copy link
Member

Choose a reason for hiding this comment

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

Just FYI - there's d.IsNewResource() which can basically tell you whether this is being called from Create() or Update() and therefore you could reduce the number of arguments as you already have *schema.ResourceData there.

}

log.Printf("[DEBUG] Waiting for SSM Parameter %q to be updated", d.Get("name").(string))
err := resource.Retry(5*time.Minute, func() *resource.RetryError {
Copy link
Member

Choose a reason for hiding this comment

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

What exactly is the reason for retrying this operation if there's no retryable error? 🤔

_, err := ssmconn.PutParameter(paramInput)

if err != nil {
return resource.NonRetryableError(err)
}

d.SetId(d.Get("name").(string))
return nil
})

if err != nil {
return errwrap.Wrapf("[ERROR] Error creating SSM parameter: {{err}}", err)
}

return resourceAwsSsmParameterRead(d, meta)
}
148 changes: 148 additions & 0 deletions builtin/providers/aws/resource_aws_ssm_parameter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package aws

import (
"fmt"
"testing"

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

func TestAccAWSSSMParameter_basic(t *testing.T) {
name := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterBasicConfig(name, "bar"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterHasValue("aws_ssm_parameter.foo", "bar"),
),
},
},
})
}

func TestAccAWSSSMParameter_update(t *testing.T) {
name := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterBasicConfig(name, "bar"),
},
{
Config: testAccAWSSSMParameterBasicConfig(name, "baz"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterHasValue("aws_ssm_parameter.foo", "baz"),
),
},
},
})
}

func TestAccAWSSSMParameter_secure(t *testing.T) {
name := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMParameterDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMParameterSecureConfig(name, "secret"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMParameterHasValue("aws_ssm_parameter.secret_foo", "secret"),
),
},
},
})
}

func testAccCheckAWSSSMParameterHasValue(n string, v string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No SSM Parameter ID is set")
}

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

paramInput := &ssm.GetParametersInput{
Names: []*string{
aws.String(rs.Primary.Attributes["name"]),
},
WithDecryption: aws.Bool(true),
}

resp, _ := conn.GetParameters(paramInput)

if len(resp.Parameters) == 0 {
return fmt.Errorf("Expected AWS SSM Parameter to be created, but wasn't found")
}

parameterValue := resp.Parameters[0].Value

if *parameterValue != v {
return fmt.Errorf("Expected AWS SSM Parameter to have value %s but had %s", v, *parameterValue)
}

return nil
}
}

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

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

paramInput := &ssm.GetParametersInput{
Names: []*string{
aws.String(rs.Primary.Attributes["name"]),
},
}

resp, _ := conn.GetParameters(paramInput)

if len(resp.Parameters) > 0 {
return fmt.Errorf("Expected AWS SSM Parameter to be gone, but was still found")
}

return nil
}

return fmt.Errorf("Default error in SSM Parameter Test")
}

func testAccAWSSSMParameterBasicConfig(rName string, value string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "foo" {
name = "test_parameter-%s"
type = "String"
value = "%s"
}
`, rName, value)
}

func testAccAWSSSMParameterSecureConfig(rName string, value string) string {
return fmt.Sprintf(`
resource "aws_ssm_parameter" "secret_foo" {
name = "test_secure_parameter-%s"
type = "SecureString"
value = "%s"
}
`, rName, value)
}
14 changes: 14 additions & 0 deletions builtin/providers/aws/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -1301,3 +1301,17 @@ func validateWafMetricName(v interface{}, k string) (ws []string, errors []error
}
return
}

func validateSsmParameterType(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
types := map[string]bool{
"String": true,
"StringList": true,
"SecureString": true,
}

if !types[value] {
errors = append(errors, fmt.Errorf("Parameter type %s is invalid. Valid types are String, StringList or SecureString", value))
}
return
}
26 changes: 26 additions & 0 deletions builtin/providers/aws/validators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2209,3 +2209,29 @@ func TestValidateWafMetricName(t *testing.T) {
}
}
}

func TestValidateSsmParameterType(t *testing.T) {
validTypes := []string{
"String",
"StringList",
"SecureString",
}
for _, v := range validTypes {
_, errors := validateSsmParameterType(v, "name")
if len(errors) != 0 {
t.Fatalf("%q should be a valid SSM parameter type: %q", v, errors)
}
}

invalidTypes := []string{
"foo",
"string",
"Securestring",
}
for _, v := range invalidTypes {
_, errors := validateSsmParameterType(v, "name")
if len(errors) == 0 {
t.Fatalf("%q should be an invalid SSM parameter type", v)
}
}
}
65 changes: 65 additions & 0 deletions website/source/docs/providers/aws/r/ssm_parameter.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
layout: "aws"
page_title: "AWS: aws_ssm_parameter"
sidebar_current: "docs-aws-resource-ssm-parameter"
description: |-
Provides an SSM Parameter resource
---

# aws\_ssm\_parameter

Provides an SSM Parameter resource.

## Example Usage

To store a basic string parameter:

```hcl
resource "aws_ssm_parameter" "foo" {
name = "foo"
type = "String"
value = "bar"
}
```

To store an encrypted string using the default SSM KMS key:

```hcl
resource "aws_db_instance" "default" {
allocated_storage = 10
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7.16"
instance_class = "db.t2.micro"
name = "mydb"
username = "foo"
password = "${var.database_master_password}"
db_subnet_group_name = "my_database_subnet_group"
parameter_group_name = "default.mysql5.7"
}

resource "aws_ssm_parameter" "secret" {
name = "${var.environment}/database/password/master"
type = "SecureString"
value = "${var.database_master_password}"
}
```

~> **Note:** The unencrypted value of a SecureString will be stored in the raw state as plain-text.
[Read more about sensitive data in state](/docs/state/sensitive-data.html).

## Argument Reference

The following arguments are supported:

* `name` - (Required) The name of the parameter.
* `type` - (Required) The type of the parameter. Valid types are `String`, `StringList` and `SecureString`.
* `value` - (Required) The value of the parameter.

## Attributes Reference

The following attributes are exported:

* `name` - (Required) The name of the parameter.
* `type` - (Required) The type of the parameter. Valid types are `String`, `StringList` and `SecureString`.
* `value` - (Required) The value of the parameter.
Loading