diff --git a/aws/resource_aws_ssm_maintenance_window_task.go b/aws/resource_aws_ssm_maintenance_window_task.go index 39aad047b7af..34513f900e61 100644 --- a/aws/resource_aws_ssm_maintenance_window_task.go +++ b/aws/resource_aws_ssm_maintenance_window_task.go @@ -3,6 +3,9 @@ package aws import ( "fmt" "log" + "regexp" + "sort" + "strings" "github.com/hashicorp/terraform/helper/validation" @@ -15,7 +18,11 @@ func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { return &schema.Resource{ Create: resourceAwsSsmMaintenanceWindowTaskCreate, Read: resourceAwsSsmMaintenanceWindowTaskRead, + Update: resourceAwsSsmMaintenanceWindowTaskUpdate, Delete: resourceAwsSsmMaintenanceWindowTaskDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsSsmMaintenanceWindowTaskImport, + }, Schema: map[string]*schema.Schema{ "window_id": { @@ -27,13 +34,11 @@ func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { "max_concurrency": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "max_errors": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "task_type": { @@ -45,30 +50,25 @@ func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { "task_arn": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "service_role_arn": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "targets": { Type: schema.TypeList, Required: true, - ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "values": { Type: schema.TypeList, Required: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, }, @@ -78,28 +78,26 @@ func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { "name": { Type: schema.TypeString, Optional: true, - ForceNew: true, ValidateFunc: validateAwsSSMMaintenanceWindowTaskName, }, "description": { Type: schema.TypeString, Optional: true, - ForceNew: true, ValidateFunc: validation.StringLenBetween(1, 128), }, "priority": { Type: schema.TypeInt, Optional: true, - ForceNew: true, }, "logging_info": { - Type: schema.TypeList, - MaxItems: 1, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ConflictsWith: []string{"task_invocation_parameters"}, + Deprecated: "use 'task_invocation_parameters' argument instead", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "s3_bucket_name": { @@ -119,25 +117,213 @@ func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { }, "task_parameters": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"task_invocation_parameters"}, + Deprecated: "use 'task_invocation_parameters' argument instead", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, - ForceNew: true, }, "values": { Type: schema.TypeList, Required: true, - ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, }, }, }, + + "task_invocation_parameters": { + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"task_parameters", "logging_info"}, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "automation_parameters": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "document_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("([$]LATEST|[$]DEFAULT|^[1-9][0-9]*$)"), "see https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_MaintenanceWindowAutomationParameters.html"), + }, + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + }, + }, + }, + + "lambda_parameters": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "client_context": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 8000), + }, + + "payload": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(0, 4096), + }, + + "qualifier": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + }, + }, + }, + + "run_command_parameters": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "comment": { + Type: schema.TypeString, + Optional: true, + }, + + "document_hash": { + Type: schema.TypeString, + Optional: true, + }, + + "document_hash_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + ssm.DocumentHashTypeSha256, + ssm.DocumentHashTypeSha1, + }, false), + }, + + "notification_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "notification_arn": { + Type: schema.TypeString, + Optional: true, + }, + + "notification_events": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "notification_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + ssm.NotificationTypeCommand, + ssm.NotificationTypeInvocation, + }, false), + }, + }, + }, + }, + + "output_s3_bucket": { + Type: schema.TypeString, + Optional: true, + }, + + "output_s3_key_prefix": { + Type: schema.TypeString, + Optional: true, + }, + + "parameter": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + + "service_role_arn": { + Type: schema.TypeString, + Optional: true, + }, + + "timeout_seconds": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + + "step_functions_parameters": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "input": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + ValidateFunc: validation.StringLenBetween(0, 4096), + }, + + "name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 80), + }, + }, + }, + }, + }, + }, + }, }, } } @@ -195,6 +381,264 @@ func flattenAwsSsmTaskParameters(taskParameters map[string]*ssm.MaintenanceWindo return result } +func expandAwsSsmTaskInvocationParameters(config []interface{}) *ssm.MaintenanceWindowTaskInvocationParameters { + params := &ssm.MaintenanceWindowTaskInvocationParameters{} + for _, v := range config { + paramConfig := v.(map[string]interface{}) + if attr, ok := paramConfig["automation_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { + params.Automation = expandAwsSsmTaskInvocationAutomationParameters(attr.([]interface{})) + } + if attr, ok := paramConfig["lambda_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { + params.Lambda = expandAwsSsmTaskInvocationLambdaParameters(attr.([]interface{})) + } + if attr, ok := paramConfig["run_command_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { + params.RunCommand = expandAwsSsmTaskInvocationRunCommandParameters(attr.([]interface{})) + } + if attr, ok := paramConfig["step_functions_parameters"]; ok && len(attr.([]interface{})) > 0 && attr.([]interface{})[0] != nil { + params.StepFunctions = expandAwsSsmTaskInvocationStepFunctionsParameters(attr.([]interface{})) + } + } + return params +} + +func flattenAwsSsmTaskInvocationParameters(parameters *ssm.MaintenanceWindowTaskInvocationParameters) []interface{} { + result := make(map[string]interface{}) + if parameters.Automation != nil { + result["automation_parameters"] = flattenAwsSsmTaskInvocationAutomationParameters(parameters.Automation) + } + + if parameters.Lambda != nil { + result["lambda_parameters"] = flattenAwsSsmTaskInvocationLambdaParameters(parameters.Lambda) + } + + if parameters.RunCommand != nil { + result["run_command_parameters"] = flattenAwsSsmTaskInvocationRunCommandParameters(parameters.RunCommand) + } + + if parameters.StepFunctions != nil { + result["step_functions_parameters"] = flattenAwsSsmTaskInvocationStepFunctionsParameters(parameters.StepFunctions) + } + + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationAutomationParameters(config []interface{}) *ssm.MaintenanceWindowAutomationParameters { + params := &ssm.MaintenanceWindowAutomationParameters{} + configParam := config[0].(map[string]interface{}) + if attr, ok := configParam["document_version"]; ok && len(attr.(string)) != 0 { + params.DocumentVersion = aws.String(attr.(string)) + } + if attr, ok := configParam["parameter"]; ok && len(attr.(*schema.Set).List()) > 0 { + params.Parameters = expandAwsSsmTaskInvocationCommonParameters(attr.(*schema.Set).List()) + } + + return params +} + +func flattenAwsSsmTaskInvocationAutomationParameters(parameters *ssm.MaintenanceWindowAutomationParameters) []interface{} { + result := make(map[string]interface{}) + + if parameters.DocumentVersion != nil { + result["document_version"] = aws.StringValue(parameters.DocumentVersion) + } + if parameters.Parameters != nil { + result["parameter"] = flattenAwsSsmTaskInvocationCommonParameters(parameters.Parameters) + } + + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationLambdaParameters(config []interface{}) *ssm.MaintenanceWindowLambdaParameters { + params := &ssm.MaintenanceWindowLambdaParameters{} + configParam := config[0].(map[string]interface{}) + if attr, ok := configParam["client_context"]; ok && len(attr.(string)) != 0 { + params.ClientContext = aws.String(attr.(string)) + } + if attr, ok := configParam["payload"]; ok && len(attr.(string)) != 0 { + params.Payload = []byte(attr.(string)) + } + if attr, ok := configParam["qualifier"]; ok && len(attr.(string)) != 0 { + params.Qualifier = aws.String(attr.(string)) + } + return params +} + +func flattenAwsSsmTaskInvocationLambdaParameters(parameters *ssm.MaintenanceWindowLambdaParameters) []interface{} { + result := make(map[string]interface{}) + + if parameters.ClientContext != nil { + result["client_context"] = aws.StringValue(parameters.ClientContext) + } + if parameters.Payload != nil { + result["payload"] = string(parameters.Payload) + } + if parameters.Qualifier != nil { + result["qualifier"] = aws.StringValue(parameters.Qualifier) + } + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationRunCommandParameters(config []interface{}) *ssm.MaintenanceWindowRunCommandParameters { + params := &ssm.MaintenanceWindowRunCommandParameters{} + configParam := config[0].(map[string]interface{}) + if attr, ok := configParam["comment"]; ok && len(attr.(string)) != 0 { + params.Comment = aws.String(attr.(string)) + } + if attr, ok := configParam["document_hash"]; ok && len(attr.(string)) != 0 { + params.DocumentHash = aws.String(attr.(string)) + } + if attr, ok := configParam["document_hash_type"]; ok && len(attr.(string)) != 0 { + params.DocumentHashType = aws.String(attr.(string)) + } + if attr, ok := configParam["notification_config"]; ok && len(attr.([]interface{})) > 0 { + params.NotificationConfig = expandAwsSsmTaskInvocationRunCommandParametersNotificationConfig(attr.([]interface{})) + } + if attr, ok := configParam["output_s3_bucket"]; ok && len(attr.(string)) != 0 { + params.OutputS3BucketName = aws.String(attr.(string)) + } + if attr, ok := configParam["output_s3_key_prefix"]; ok && len(attr.(string)) != 0 { + params.OutputS3KeyPrefix = aws.String(attr.(string)) + } + if attr, ok := configParam["parameter"]; ok && len(attr.(*schema.Set).List()) > 0 { + params.Parameters = expandAwsSsmTaskInvocationCommonParameters(attr.(*schema.Set).List()) + } + if attr, ok := configParam["service_role_arn"]; ok && len(attr.(string)) != 0 { + params.ServiceRoleArn = aws.String(attr.(string)) + } + if attr, ok := configParam["timeout_seconds"]; ok && attr.(int) != 0 { + params.TimeoutSeconds = aws.Int64(int64(attr.(int))) + } + return params +} + +func flattenAwsSsmTaskInvocationRunCommandParameters(parameters *ssm.MaintenanceWindowRunCommandParameters) []interface{} { + result := make(map[string]interface{}) + + if parameters.Comment != nil { + result["comment"] = aws.StringValue(parameters.Comment) + } + if parameters.DocumentHash != nil { + result["document_hash"] = aws.StringValue(parameters.DocumentHash) + } + if parameters.DocumentHashType != nil { + result["document_hash_type"] = aws.StringValue(parameters.DocumentHashType) + } + if parameters.NotificationConfig != nil { + result["notification_config"] = flattenAwsSsmTaskInvocationRunCommandParametersNotificationConfig(parameters.NotificationConfig) + } + if parameters.OutputS3BucketName != nil { + result["output_s3_bucket"] = aws.StringValue(parameters.OutputS3BucketName) + } + if parameters.OutputS3KeyPrefix != nil { + result["output_s3_key_prefix"] = aws.StringValue(parameters.OutputS3KeyPrefix) + } + if parameters.Parameters != nil { + result["parameter"] = flattenAwsSsmTaskInvocationCommonParameters(parameters.Parameters) + } + if parameters.ServiceRoleArn != nil { + result["service_role_arn"] = aws.StringValue(parameters.ServiceRoleArn) + } + if parameters.TimeoutSeconds != nil { + result["timeout_seconds"] = aws.Int64Value(parameters.TimeoutSeconds) + } + + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationStepFunctionsParameters(config []interface{}) *ssm.MaintenanceWindowStepFunctionsParameters { + params := &ssm.MaintenanceWindowStepFunctionsParameters{} + configParam := config[0].(map[string]interface{}) + + if attr, ok := configParam["input"]; ok && len(attr.(string)) != 0 { + params.Input = aws.String(attr.(string)) + } + if attr, ok := configParam["name"]; ok && len(attr.(string)) != 0 { + params.Name = aws.String(attr.(string)) + } + + return params +} + +func flattenAwsSsmTaskInvocationStepFunctionsParameters(parameters *ssm.MaintenanceWindowStepFunctionsParameters) []interface{} { + result := make(map[string]interface{}) + + if parameters.Input != nil { + result["input"] = aws.StringValue(parameters.Input) + } + if parameters.Name != nil { + result["name"] = aws.StringValue(parameters.Name) + } + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationRunCommandParametersNotificationConfig(config []interface{}) *ssm.NotificationConfig { + params := &ssm.NotificationConfig{} + configParam := config[0].(map[string]interface{}) + + if attr, ok := configParam["notification_arn"]; ok && len(attr.(string)) != 0 { + params.NotificationArn = aws.String(attr.(string)) + } + if attr, ok := configParam["notification_events"]; ok && len(attr.([]interface{})) > 0 { + params.NotificationEvents = expandStringList(attr.([]interface{})) + } + if attr, ok := configParam["notification_type"]; ok && len(attr.(string)) != 0 { + params.NotificationType = aws.String(attr.(string)) + } + + return params +} + +func flattenAwsSsmTaskInvocationRunCommandParametersNotificationConfig(config *ssm.NotificationConfig) []interface{} { + result := make(map[string]interface{}) + + if config.NotificationArn != nil { + result["notification_arn"] = aws.StringValue(config.NotificationArn) + } + if config.NotificationEvents != nil { + result["notification_events"] = flattenStringList(config.NotificationEvents) + } + if config.NotificationType != nil { + result["notification_type"] = aws.StringValue(config.NotificationType) + } + + return []interface{}{result} +} + +func expandAwsSsmTaskInvocationCommonParameters(config []interface{}) map[string][]*string { + params := make(map[string][]*string) + + for _, v := range config { + paramConfig := v.(map[string]interface{}) + params[paramConfig["name"].(string)] = expandStringList(paramConfig["values"].([]interface{})) + } + + return params +} + +func flattenAwsSsmTaskInvocationCommonParameters(parameters map[string][]*string) []interface{} { + attributes := make([]interface{}, 0, len(parameters)) + + keys := make([]string, 0, len(parameters)) + for k := range parameters { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, key := range keys { + values := make([]string, 0) + for _, value := range parameters[key] { + values = append(values, aws.StringValue(value)) + } + params := map[string]interface{}{ + "name": key, + "values": values, + } + attributes = append(attributes, params) + } + + return attributes +} + func resourceAwsSsmMaintenanceWindowTaskCreate(d *schema.ResourceData, meta interface{}) error { ssmconn := meta.(*AWSClient).ssmconn @@ -230,6 +674,10 @@ func resourceAwsSsmMaintenanceWindowTaskCreate(d *schema.ResourceData, meta inte params.TaskParameters = expandAwsSsmTaskParameters(v.([]interface{})) } + if v, ok := d.GetOk("task_invocation_parameters"); ok { + params.TaskInvocationParameters = expandAwsSsmTaskInvocationParameters(v.([]interface{})) + } + resp, err := ssmconn.RegisterTaskWithMaintenanceWindow(params) if err != nil { return err @@ -242,56 +690,108 @@ func resourceAwsSsmMaintenanceWindowTaskCreate(d *schema.ResourceData, meta inte func resourceAwsSsmMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{}) error { ssmconn := meta.(*AWSClient).ssmconn + windowID := d.Get("window_id").(string) - params := &ssm.DescribeMaintenanceWindowTasksInput{ - WindowId: aws.String(d.Get("window_id").(string)), + params := &ssm.GetMaintenanceWindowTaskInput{ + WindowId: aws.String(windowID), + WindowTaskId: aws.String(d.Id()), + } + resp, err := ssmconn.GetMaintenanceWindowTask(params) + if isAWSErr(err, ssm.ErrCodeDoesNotExistException, "") { + log.Printf("[WARN] Maintenance Window (%s) Task (%s) not found, removing from state", windowID, d.Id()) + d.SetId("") + return nil } - - resp, err := ssmconn.DescribeMaintenanceWindowTasks(params) if err != nil { - return err + return fmt.Errorf("Error getting Maintenance Window (%s) Task (%s): %s", windowID, d.Id(), err) } - found := false - for _, t := range resp.Tasks { - if *t.WindowTaskId == d.Id() { - found = true - - d.Set("window_id", t.WindowId) - d.Set("max_concurrency", t.MaxConcurrency) - d.Set("max_errors", t.MaxErrors) - d.Set("task_type", t.Type) - d.Set("service_role_arn", t.ServiceRoleArn) - d.Set("task_arn", t.TaskArn) - d.Set("priority", t.Priority) - d.Set("name", t.Name) - d.Set("description", t.Description) - - if t.LoggingInfo != nil { - if err := d.Set("logging_info", flattenAwsSsmMaintenanceWindowLoggingInfo(t.LoggingInfo)); err != nil { - return fmt.Errorf("Error setting logging_info error: %#v", err) - } - } - - if t.TaskParameters != nil { - if err := d.Set("task_parameters", flattenAwsSsmTaskParameters(t.TaskParameters)); err != nil { - return fmt.Errorf("Error setting task_parameters error: %#v", err) - } - } - - if err := d.Set("targets", flattenAwsSsmTargets(t.Targets)); err != nil { - return fmt.Errorf("Error setting targets error: %#v", err) - } + d.Set("window_id", resp.WindowId) + d.Set("max_concurrency", resp.MaxConcurrency) + d.Set("max_errors", resp.MaxErrors) + d.Set("task_type", resp.TaskType) + d.Set("service_role_arn", resp.ServiceRoleArn) + d.Set("task_arn", resp.TaskArn) + d.Set("priority", resp.Priority) + d.Set("name", resp.Name) + d.Set("description", resp.Description) + + if resp.LoggingInfo != nil { + if err := d.Set("logging_info", flattenAwsSsmMaintenanceWindowLoggingInfo(resp.LoggingInfo)); err != nil { + return fmt.Errorf("Error setting logging_info error: %#v", err) } } - if !found { - log.Printf("[INFO] Maintenance Window Target not found. Removing from state") + if resp.TaskParameters != nil { + if err := d.Set("task_parameters", flattenAwsSsmTaskParameters(resp.TaskParameters)); err != nil { + return fmt.Errorf("Error setting task_parameters error: %#v", err) + } + } + + if resp.TaskInvocationParameters != nil { + if err := d.Set("task_invocation_parameters", flattenAwsSsmTaskInvocationParameters(resp.TaskInvocationParameters)); err != nil { + return fmt.Errorf("Error setting task_invocation_parameters error: %#v", err) + } + } + + if err := d.Set("targets", flattenAwsSsmTargets(resp.Targets)); err != nil { + return fmt.Errorf("Error setting targets error: %#v", err) + } + + return nil +} + +func resourceAwsSsmMaintenanceWindowTaskUpdate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + windowID := d.Get("window_id").(string) + + params := &ssm.UpdateMaintenanceWindowTaskInput{ + WindowId: aws.String(windowID), + WindowTaskId: aws.String(d.Id()), + MaxConcurrency: aws.String(d.Get("max_concurrency").(string)), + MaxErrors: aws.String(d.Get("max_errors").(string)), + ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)), + TaskArn: aws.String(d.Get("task_arn").(string)), + Targets: expandAwsSsmTargets(d.Get("targets").([]interface{})), + Replace: aws.Bool(true), + } + + if v, ok := d.GetOk("name"); ok { + params.Name = aws.String(v.(string)) + } + + if v, ok := d.GetOk("description"); ok { + params.Description = aws.String(v.(string)) + } + + if v, ok := d.GetOk("priority"); ok { + params.Priority = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("logging_info"); ok { + params.LoggingInfo = expandAwsSsmMaintenanceWindowLoggingInfo(v.([]interface{})) + } + + if v, ok := d.GetOk("task_parameters"); ok { + params.TaskParameters = expandAwsSsmTaskParameters(v.([]interface{})) + } + + if v, ok := d.GetOk("task_invocation_parameters"); ok { + params.TaskInvocationParameters = expandAwsSsmTaskInvocationParameters(v.([]interface{})) + } + + _, err := ssmconn.UpdateMaintenanceWindowTask(params) + if isAWSErr(err, ssm.ErrCodeDoesNotExistException, "") { + log.Printf("[WARN] Maintenance Window (%s) Task (%s) not found, removing from state", windowID, d.Id()) d.SetId("") return nil } - return nil + if err != nil { + return fmt.Errorf("Error updating Maintenance Window (%s) Task (%s): %s", windowID, d.Id(), err) + } + + return resourceAwsSsmMaintenanceWindowTaskRead(d, meta) } func resourceAwsSsmMaintenanceWindowTaskDelete(d *schema.ResourceData, meta interface{}) error { @@ -305,9 +805,27 @@ func resourceAwsSsmMaintenanceWindowTaskDelete(d *schema.ResourceData, meta inte } _, err := ssmconn.DeregisterTaskFromMaintenanceWindow(params) + if isAWSErr(err, ssm.ErrCodeDoesNotExistException, "") { + return nil + } if err != nil { return fmt.Errorf("error deregistering SSM Maintenance Window Task (%s): %s", d.Id(), err) } return nil } + +func resourceAwsSsmMaintenanceWindowTaskImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.SplitN(d.Id(), "/", 2) + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected /", d.Id()) + } + + windowID := idParts[0] + windowTaskID := idParts[1] + + d.Set("window_id", windowID) + d.SetId(windowTaskID) + + return []*schema.ResourceData{d}, nil +} diff --git a/aws/resource_aws_ssm_maintenance_window_task_test.go b/aws/resource_aws_ssm_maintenance_window_task_test.go index 396eb00ec5b4..1197f02a50e2 100644 --- a/aws/resource_aws_ssm_maintenance_window_task_test.go +++ b/aws/resource_aws_ssm_maintenance_window_task_test.go @@ -13,7 +13,8 @@ import ( ) func TestAccAWSSSMMaintenanceWindowTask_basic(t *testing.T) { - var task ssm.MaintenanceWindowTask + var before, after ssm.MaintenanceWindowTask + resourceName := "aws_ssm_maintenance_window_task.target" name := acctest.RandString(10) resource.ParallelTest(t, resource.TestCase{ @@ -24,9 +25,29 @@ func TestAccAWSSSMMaintenanceWindowTask_basic(t *testing.T) { { Config: testAccAWSSSMMaintenanceWindowTaskBasicConfig(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSSMMaintenanceWindowTaskExists("aws_ssm_maintenance_window_task.target", &task), + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &before), + ), + }, + { + Config: testAccAWSSSMMaintenanceWindowTaskBasicConfigUpdate(name, "test description", "RUN_COMMAND", "AWS-InstallPowerShellModule", 3, 3, 2), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &after), + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("maintenance-window-task-%s", name)), + resource.TestCheckResourceAttr(resourceName, "description", "test description"), + resource.TestCheckResourceAttr(resourceName, "task_type", "RUN_COMMAND"), + resource.TestCheckResourceAttr(resourceName, "task_arn", "AWS-InstallPowerShellModule"), + resource.TestCheckResourceAttr(resourceName, "priority", "3"), + resource.TestCheckResourceAttr(resourceName, "max_concurrency", "3"), + resource.TestCheckResourceAttr(resourceName, "max_errors", "2"), + testAccCheckAwsSsmWindowsTaskNotRecreated(t, &before, &after), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, }, }) } @@ -34,6 +55,8 @@ func TestAccAWSSSMMaintenanceWindowTask_basic(t *testing.T) { func TestAccAWSSSMMaintenanceWindowTask_updateForcesNewResource(t *testing.T) { var before, after ssm.MaintenanceWindowTask name := acctest.RandString(10) + resourceName := "aws_ssm_maintenance_window_task.target" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, @@ -42,22 +65,171 @@ func TestAccAWSSSMMaintenanceWindowTask_updateForcesNewResource(t *testing.T) { { Config: testAccAWSSSMMaintenanceWindowTaskBasicConfig(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSSMMaintenanceWindowTaskExists("aws_ssm_maintenance_window_task.target", &before), + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &before), ), }, { Config: testAccAWSSSMMaintenanceWindowTaskBasicConfigUpdated(name), Check: resource.ComposeTestCheckFunc( - testAccCheckAWSSSMMaintenanceWindowTaskExists("aws_ssm_maintenance_window_task.target", &after), - resource.TestCheckResourceAttr("aws_ssm_maintenance_window_task.target", "name", "TestMaintenanceWindowTask"), - resource.TestCheckResourceAttr("aws_ssm_maintenance_window_task.target", "description", "This resource is for test purpose only"), + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &after), + resource.TestCheckResourceAttr(resourceName, "name", "TestMaintenanceWindowTask"), + resource.TestCheckResourceAttr(resourceName, "description", "This resource is for test purpose only"), testAccCheckAwsSsmWindowsTaskRecreated(t, &before, &after), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSSMMaintenanceWindowTask_TaskInvocationAutomationParameters(t *testing.T) { + var task ssm.MaintenanceWindowTask + resourceName := "aws_ssm_maintenance_window_task.target" + + name := acctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskAutomationConfig(name, "$DEFAULT"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.automation_parameters.0.document_version", "$DEFAULT"), + ), + }, + { + Config: testAccAWSSSMMaintenanceWindowTaskAutomationConfigUpdate(name, "$LATEST"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.automation_parameters.0.document_version", "$LATEST"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, }, }) } +func TestAccAWSSSMMaintenanceWindowTask_TaskInvocationLambdaParameters(t *testing.T) { + var task ssm.MaintenanceWindowTask + resourceName := "aws_ssm_maintenance_window_task.target" + rString := acctest.RandString(8) + rInt := acctest.RandInt() + + funcName := fmt.Sprintf("tf_acc_lambda_func_tags_%s", rString) + policyName := fmt.Sprintf("tf_acc_policy_lambda_func_tags_%s", rString) + roleName := fmt.Sprintf("tf_acc_role_lambda_func_tags_%s", rString) + sgName := fmt.Sprintf("tf_acc_sg_lambda_func_tags_%s", rString) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskLambdaConfig(funcName, policyName, roleName, sgName, rString, rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSSMMaintenanceWindowTask_TaskInvocationRunCommandParameters(t *testing.T) { + var task ssm.MaintenanceWindowTask + resourceName := "aws_ssm_maintenance_window_task.target" + serviceRoleResourceName := "aws_iam_role.test" + s3BucketResourceName := "aws_s3_bucket.foo" + + name := acctest.RandString(10) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskRunCommandConfig(name, "test comment", 30), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + resource.TestCheckResourceAttrPair(resourceName, "service_role_arn", serviceRoleResourceName, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "task_invocation_parameters.0.run_command_parameters.0.service_role_arn", serviceRoleResourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.run_command_parameters.0.comment", "test comment"), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.run_command_parameters.0.timeout_seconds", "30"), + ), + }, + { + Config: testAccAWSSSMMaintenanceWindowTaskRunCommandConfigUpdate(name, "test comment update", 60), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.run_command_parameters.0.comment", "test comment update"), + resource.TestCheckResourceAttr(resourceName, "task_invocation_parameters.0.run_command_parameters.0.timeout_seconds", "60"), + resource.TestCheckResourceAttrPair(resourceName, "task_invocation_parameters.0.run_command_parameters.0.output_s3_bucket", s3BucketResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSSSMMaintenanceWindowTask_TaskInvocationStepFunctionParameters(t *testing.T) { + var task ssm.MaintenanceWindowTask + resourceName := "aws_ssm_maintenance_window_task.target" + rString := acctest.RandString(8) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskStepFunctionConfig(rString), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists(resourceName, &task), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAwsSsmWindowsTaskNotRecreated(t *testing.T, + before, after *ssm.MaintenanceWindowTask) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(before.WindowTaskId) != aws.StringValue(after.WindowTaskId) { + t.Fatalf("Unexpected change of Windows Task IDs, but both were %s and %s", aws.StringValue(before.WindowTaskId), aws.StringValue(after.WindowTaskId)) + } + return nil + } +} + func testAccCheckAwsSsmWindowsTaskRecreated(t *testing.T, before, after *ssm.MaintenanceWindowTask) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -129,44 +301,34 @@ func testAccCheckAWSSSMMaintenanceWindowTaskDestroy(s *terraform.State) error { return nil } -func testAccAWSSSMMaintenanceWindowTaskBasicConfig(rName string) string { - return fmt.Sprintf(` -resource "aws_ssm_maintenance_window" "foo" { - name = "maintenance-window-%s" - schedule = "cron(0 16 ? * TUE *)" - duration = 3 - cutoff = 1 -} - -resource "aws_ssm_maintenance_window_task" "target" { - window_id = "${aws_ssm_maintenance_window.foo.id}" - task_type = "RUN_COMMAND" - task_arn = "AWS-RunShellScript" - priority = 1 - service_role_arn = "${aws_iam_role.ssm_role.arn}" - max_concurrency = "2" - max_errors = "1" +func testAccAWSSSMMaintenanceWindowTaskImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("Not found: %s", resourceName) + } - targets { - key = "InstanceIds" - values = ["${aws_instance.foo.id}"] - } + return fmt.Sprintf("%s/%s", rs.Primary.Attributes["window_id"], rs.Primary.ID), nil + } +} - task_parameters { - name = "commands" - values = ["pwd"] - } +func testAccAWSSSMMaintenanceWindowTaskConfigBase(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "test" { + cutoff = 1 + duration = 3 + name = %[1]q + schedule = "cron(0 16 ? * TUE *)" } -resource "aws_instance" "foo" { +resource "aws_instance" "test" { ami = "ami-4fccb37f" instance_type = "m1.small" } -resource "aws_iam_role" "ssm_role" { - name = "ssm-role-%s" - +resource "aws_iam_role" "test" { + name = %[1]q assume_role_policy = </ +```