Skip to content

Commit

Permalink
Merge pull request #17732 from DrFaust92/r/ssm_association_validations
Browse files Browse the repository at this point in the history
r/ssm_association - validations + wait for association argument + arn attribute
  • Loading branch information
ewbankkit authored Feb 13, 2022
2 parents de59e32 + dcc1901 commit e889d9b
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 204 deletions.
11 changes: 11 additions & 0 deletions .changelog/17732.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_ssm_association: Add plan time validation to `association_name`, `document_version`, `schedule_expression`, `output_location.s3_bucket_name`, `output_location.s3_key_prefix`, `targets.key`, `targets.values`, `automation_target_parameter_name`
```

```release-note:enhancement
resource/aws_ssm_association: Add `wait_for_success_timeout_seconds` argument
```

```release-note:enhancement
resource/aws_ssm_association: Add `arn` attribute
```
117 changes: 72 additions & 45 deletions internal/service/ssm/association.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import (
"fmt"
"log"
"regexp"
"time"

"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/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
)

func ResourceAssociation() *schema.Resource {
Expand All @@ -28,6 +31,10 @@ func ResourceAssociation() *schema.Resource {
SchemaVersion: 1,

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"apply_only_at_cron_interval": {
Type: schema.TypeBool,
Default: false,
Expand All @@ -36,20 +43,26 @@ func ResourceAssociation() *schema.Resource {
"association_name": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.All(
validation.StringLenBetween(3, 128),
validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_\-.]{3,128}$`), "must contain only alphanumeric, underscore, hyphen, or period characters"),
),
},
"association_id": {
Type: schema.TypeString,
Computed: true,
},
"instance_id": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Deprecated: "use 'targets' argument instead. https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_CreateAssociation.html#systemsmanager-CreateAssociation-request-InstanceId",
},
"document_version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([$]LATEST|[$]DEFAULT|^[1-9][0-9]*$)$`), ""),
},
"max_concurrency": {
Type: schema.TypeString,
Expand All @@ -73,8 +86,9 @@ func ResourceAssociation() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString},
},
"schedule_expression": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},
"output_location": {
Type: schema.TypeList,
Expand All @@ -83,12 +97,14 @@ func ResourceAssociation() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"s3_bucket_name": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(3, 63),
},
"s3_key_prefix": {
Type: schema.TypeString,
Optional: true,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 500),
},
"s3_region": {
Type: schema.TypeString,
Expand All @@ -106,30 +122,31 @@ func ResourceAssociation() *schema.Resource {
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"key": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 163),
},
"values": {
Type: schema.TypeList,
Required: true,
MaxItems: 50,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"compliance_severity": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
ssm.ComplianceSeverityUnspecified,
ssm.ComplianceSeverityLow,
ssm.ComplianceSeverityMedium,
ssm.ComplianceSeverityHigh,
ssm.ComplianceSeverityCritical,
}, false),
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(ssm.ComplianceSeverity_Values(), false),
},
"automation_target_parameter_name": {
Type: schema.TypeString,
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(1, 50),
},
"wait_for_success_timeout_seconds": {
Type: schema.TypeInt,
Optional: true,
},
},
Expand Down Expand Up @@ -169,8 +186,8 @@ func resourceAssociationCreate(d *schema.ResourceData, meta interface{}) error {
associationInput.Parameters = expandSSMDocumentParameters(v.(map[string]interface{}))
}

if _, ok := d.GetOk("targets"); ok {
associationInput.Targets = expandTargets(d.Get("targets").([]interface{}))
if v, ok := d.GetOk("targets"); ok {
associationInput.Targets = expandTargets(v.([]interface{}))
}

if v, ok := d.GetOk("output_location"); ok {
Expand All @@ -195,15 +212,22 @@ func resourceAssociationCreate(d *schema.ResourceData, meta interface{}) error {

resp, err := conn.CreateAssociation(associationInput)
if err != nil {
return fmt.Errorf("Error creating SSM association: %s", err)
return fmt.Errorf("Error creating SSM association: %w", err)
}

if resp.AssociationDescription == nil {
return fmt.Errorf("AssociationDescription was nil")
}

d.SetId(aws.StringValue(resp.AssociationDescription.AssociationId))
d.Set("association_id", resp.AssociationDescription.AssociationId)

if v, ok := d.GetOk("wait_for_success_timeout_seconds"); ok {
dur, _ := time.ParseDuration(fmt.Sprintf("%ds", v.(int)))
_, err = waitAssociationSuccess(conn, d.Id(), dur)
if err != nil {
return fmt.Errorf("error waiting for SSM Association (%s) to be Success: %w", d.Id(), err)
}
}

return resourceAssociationRead(d, meta)
}
Expand All @@ -213,24 +237,24 @@ func resourceAssociationRead(d *schema.ResourceData, meta interface{}) error {

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

params := &ssm.DescribeAssociationInput{
AssociationId: aws.String(d.Id()),
}

resp, err := conn.DescribeAssociation(params)

association, err := FindAssociationById(conn, d.Id())
if err != nil {
if tfawserr.ErrMessageContains(err, ssm.ErrCodeAssociationDoesNotExist, "") {
if !d.IsNewResource() && tfresource.NotFound(err) {
d.SetId("")
log.Printf("[WARN] Unable to find SSM Association (%s); removing from state", d.Id())
return nil
}
return fmt.Errorf("Error reading SSM association: %s", err)
}
if resp.AssociationDescription == nil {
return fmt.Errorf("AssociationDescription was nil")
return fmt.Errorf("error reading SSM Association (%s): %w", d.Id(), err)
}

association := resp.AssociationDescription
arn := arn.ARN{
Partition: meta.(*conns.AWSClient).Partition,
Service: "ssm",
Region: meta.(*conns.AWSClient).Region,
AccountID: meta.(*conns.AWSClient).AccountID,
Resource: fmt.Sprintf("association/%s", aws.StringValue(association.AssociationId)),
}.String()
d.Set("arn", arn)
d.Set("apply_only_at_cron_interval", association.ApplyOnlyAtCronInterval)
d.Set("association_name", association.AssociationName)
d.Set("instance_id", association.InstanceId)
Expand All @@ -248,11 +272,11 @@ func resourceAssociationRead(d *schema.ResourceData, meta interface{}) error {
}

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

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

return nil
Expand All @@ -264,7 +288,7 @@ func resourceAssociationUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] SSM Association update: %s", d.Id())

associationInput := &ssm.UpdateAssociationInput{
AssociationId: aws.String(d.Get("association_id").(string)),
AssociationId: aws.String(d.Id()),
}

if v, ok := d.GetOk("apply_only_at_cron_interval"); ok {
Expand Down Expand Up @@ -326,13 +350,16 @@ func resourceAssociationDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Deleting SSM Association: %s", d.Id())

params := &ssm.DeleteAssociationInput{
AssociationId: aws.String(d.Get("association_id").(string)),
AssociationId: aws.String(d.Id()),
}

_, err := conn.DeleteAssociation(params)

if err != nil {
return fmt.Errorf("Error deleting SSM association: %s", err)
if tfawserr.ErrCodeContains(err, ssm.ErrCodeAssociationDoesNotExist) {
return nil
}
return fmt.Errorf("Error deleting SSM association: %w", err)
}

return nil
Expand Down
Loading

0 comments on commit e889d9b

Please sign in to comment.