Skip to content

Commit

Permalink
Merge pull request #1253 from stack72/f-aws-ssm-association-1246
Browse files Browse the repository at this point in the history
resource/aws_ssm_association: Add support for OutputLocation and Schedule Expression
  • Loading branch information
grubernaut authored Jul 28, 2017
2 parents 7585e8f + 7d79ebc commit 6c9239b
Show file tree
Hide file tree
Showing 3 changed files with 203 additions and 0 deletions.
76 changes: 76 additions & 0 deletions aws/resource_aws_ssm_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ func resourceAwsSsmAssociation() *schema.Resource {
ForceNew: true,
Computed: true,
},
"schedule_expression": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"output_location": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"s3_bucket_name": {
Type: schema.TypeString,
Required: true,
},
"s3_key_prefix": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"targets": {
Type: schema.TypeList,
Optional: true,
Expand Down Expand Up @@ -74,6 +97,10 @@ func resourceAwsSsmAssociationCreate(d *schema.ResourceData, meta interface{}) e
assosciationInput.InstanceId = aws.String(v.(string))
}

if v, ok := d.GetOk("schedule_expression"); ok {
assosciationInput.ScheduleExpression = aws.String(v.(string))
}

if v, ok := d.GetOk("parameters"); ok {
assosciationInput.Parameters = expandSSMDocumentParameters(v.(map[string]interface{}))
}
Expand All @@ -82,6 +109,10 @@ func resourceAwsSsmAssociationCreate(d *schema.ResourceData, meta interface{}) e
assosciationInput.Targets = expandAwsSsmTargets(d)
}

if v, ok := d.GetOk("output_location"); ok {
assosciationInput.OutputLocation = expandSSMAssociationOutputLocation(v.([]interface{}))
}

resp, err := ssmconn.CreateAssociation(assosciationInput)
if err != nil {
return errwrap.Wrapf("[ERROR] Error creating SSM association: {{err}}", err)
Expand Down Expand Up @@ -120,11 +151,16 @@ func resourceAwsSsmAssociationRead(d *schema.ResourceData, meta interface{}) err
d.Set("name", association.Name)
d.Set("parameters", association.Parameters)
d.Set("association_id", association.AssociationId)
d.Set("schedule_expression", association.ScheduleExpression)

if err := d.Set("targets", flattenAwsSsmTargets(association.Targets)); err != nil {
return fmt.Errorf("[DEBUG] Error setting targets error: %#v", err)
}

if err := d.Set("output_location", flattenAwsSsmAssociationOutoutLocation(association.OutputLocation)); err != nil {
return fmt.Errorf("[DEBUG] Error setting output_location error: %#v", err)
}

return nil
}

Expand Down Expand Up @@ -156,3 +192,43 @@ func expandSSMDocumentParameters(params map[string]interface{}) map[string][]*st

return docParams
}

func expandSSMAssociationOutputLocation(config []interface{}) *ssm.InstanceAssociationOutputLocation {
if config == nil {
return nil
}

//We only allow 1 Item so we can grab the first in the list only
locationConfig := config[0].(map[string]interface{})

S3OutputLocation := &ssm.S3OutputLocation{
OutputS3BucketName: aws.String(locationConfig["s3_bucket_name"].(string)),
}

if v, ok := locationConfig["s3_key_prefix"]; ok {
S3OutputLocation.OutputS3KeyPrefix = aws.String(v.(string))
}

return &ssm.InstanceAssociationOutputLocation{
S3Location: S3OutputLocation,
}
}

func flattenAwsSsmAssociationOutoutLocation(location *ssm.InstanceAssociationOutputLocation) []map[string]interface{} {
if location == nil {
return nil
}

result := make([]map[string]interface{}, 0)
item := make(map[string]interface{})

item["s3_bucket_name"] = *location.S3Location.OutputS3BucketName

if location.S3Location.OutputS3KeyPrefix != nil {
item["s3_key_prefix"] = *location.S3Location.OutputS3KeyPrefix
}

result = append(result, item)

return result
}
120 changes: 120 additions & 0 deletions aws/resource_aws_ssm_association_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,46 @@ func TestAccAWSSSMAssociation_withTargets(t *testing.T) {
})
}

func TestAccAWSSSMAssociation_withOutputLocation(t *testing.T) {
name := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMAssociationBasicConfigWithOutPutLocation(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMAssociationExists("aws_ssm_association.foo"),
resource.TestCheckResourceAttr(
"aws_ssm_association.foo", "output_location.0.s3_bucket_name", fmt.Sprintf("tf-acc-test-ssmoutput-%s", name)),
resource.TestCheckResourceAttr(
"aws_ssm_association.foo", "output_location.0.s3_key_prefix", "SSMAssociation"),
),
},
},
})
}

func TestAccAWSSSMAssociation_withScheduleExpression(t *testing.T) {
name := acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMAssociationDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMAssociationBasicConfigWithScheduleExpression(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMAssociationExists("aws_ssm_association.foo"),
resource.TestCheckResourceAttr(
"aws_ssm_association.foo", "schedule_expression", "cron(0 16 ? * TUE *)"),
),
},
},
})
}

func testAccCheckAWSSSMAssociationExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand Down Expand Up @@ -186,3 +226,83 @@ resource "aws_ssm_association" "foo" {
}
`, rName, rName, rName)
}

func testAccAWSSSMAssociationBasicConfigWithScheduleExpression(rName string) string {
return fmt.Sprintf(`
resource "aws_ssm_document" "foo_document" {
name = "test_document_association-%s",
document_type = "Command"
content = <<DOC
{
"schemaVersion": "1.2",
"description": "Check ip configuration of a Linux instance.",
"parameters": {
},
"runtimeConfig": {
"aws:runShellScript": {
"properties": [
{
"id": "0.aws:runShellScript",
"runCommand": ["ifconfig"]
}
]
}
}
}
DOC
}
resource "aws_ssm_association" "foo" {
name = "${aws_ssm_document.foo_document.name}",
schedule_expression = "cron(0 16 ? * TUE *)"
targets {
key = "tag:Name"
values = ["acceptanceTest"]
}
}`, rName)
}

func testAccAWSSSMAssociationBasicConfigWithOutPutLocation(rName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "output_location" {
bucket = "tf-acc-test-ssmoutput-%s"
force_destroy = true
}
resource "aws_ssm_document" "foo_document" {
name = "test_document_association-%s",
document_type = "Command"
content = <<DOC
{
"schemaVersion": "1.2",
"description": "Check ip configuration of a Linux instance.",
"parameters": {
},
"runtimeConfig": {
"aws:runShellScript": {
"properties": [
{
"id": "0.aws:runShellScript",
"runCommand": ["ifconfig"]
}
]
}
}
}
DOC
}
resource "aws_ssm_association" "foo" {
name = "${aws_ssm_document.foo_document.name}",
targets {
key = "tag:Name"
values = ["acceptanceTest"]
}
output_location {
s3_bucket_name = "${aws_s3_bucket.output_location.id}"
s3_key_prefix = "SSMAssociation"
}
}`, rName, rName)
}
7 changes: 7 additions & 0 deletions website/docs/r/ssm_association.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ The following arguments are supported:
* `instance_id` - (Optional) The instance id to apply an SSM document to.
* `parameters` - (Optional) Additional parameters to pass to the SSM document.
* `targets` - (Optional) The targets (either instances or tags). Instances are specified using Key=instanceids,Values=instanceid1,instanceid2. Tags are specified using Key=tag name,Values=tag value. Only 1 target is currently supported by AWS.
* `schedule_expression` - (Optional) A cron expression when the association will be applied to the target(s).
* `output_location` - (Optional) An output location block. OutputLocation documented below.

Output Location (`output_location`) is an S3 bucket where you want to store the results of this association:

* `s3_bucket_name` - (Required) The S3 bucket name.
* `s3_key_prefix` - (Optional) The S3 bucket prefix. Results stored in the root if not configured.

## Attributes Reference

Expand Down

0 comments on commit 6c9239b

Please sign in to comment.