From e18f8df8454d859ee8d32591b213ca9718be09b4 Mon Sep 17 00:00:00 2001 From: Paul Stack Date: Thu, 4 May 2017 20:36:28 +0300 Subject: [PATCH] provider/aws: Add support for aws_ssm_maintenance_window (#14087) * provider/aws: Add support for aws_ssm_maintenance_window Fixes: #14027 ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSSSMMaintenanceWindow_basic' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/04/29 13:38:19 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSSMMaintenanceWindow_basic -timeout 120m === RUN TestAccAWSSSMMaintenanceWindow_basic --- PASS: TestAccAWSSSMMaintenanceWindow_basic (51.69s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 51.711s ``` * provider/aws: Add documentation for aws_ssm_maintenance_window * provider/aws: Add support for aws_ssm_maintenance_window_target ``` % make testacc TEST=./builtin/providers/aws TESTARGS='-run=TestAccAWSSSMMaintenanceWindowTarget' ==> Checking that code complies with gofmt requirements... go generate $(go list ./... | grep -v /terraform/vendor/) 2017/04/29 16:38:22 Generated command/internal_plugin_list.go TF_ACC=1 go test ./builtin/providers/aws -v -run=TestAccAWSSSMMaintenanceWindowTarget -timeout 120m === RUN TestAccAWSSSMMaintenanceWindowTarget_basic --- PASS: TestAccAWSSSMMaintenanceWindowTarget_basic (34.68s) PASS ok github.com/hashicorp/terraform/builtin/providers/aws 34.701s ``` * provider/aws: Adding the documentation for aws_ssm_maintenance_window_target * provider/aws: Add support for aws_ssm_maintenance_window_task * provider/aws: Documentation for aws_ssm_maintenance_window_task --- builtin/providers/aws/provider.go | 3 + .../resource_aws_ssm_maintenance_window.go | 168 +++++++++++++ ...ource_aws_ssm_maintenance_window_target.go | 179 ++++++++++++++ ..._aws_ssm_maintenance_window_target_test.go | 122 ++++++++++ ...esource_aws_ssm_maintenance_window_task.go | 230 ++++++++++++++++++ ...ce_aws_ssm_maintenance_window_task_test.go | 158 ++++++++++++ ...esource_aws_ssm_maintenance_window_test.go | 141 +++++++++++ .../r/ssm_maintenance_window.html.markdown | 38 +++ ...sm_maintenance_window_target.html.markdown | 46 ++++ .../ssm_maintenance_window_task.html.markdown | 68 ++++++ website/source/layouts/aws.erb | 12 + 11 files changed, 1165 insertions(+) create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_target.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_target_test.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_test.go create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e6165f33e..d9905ef47 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -422,6 +422,9 @@ func Provider() terraform.ResourceProvider { "aws_ssm_activation": resourceAwsSsmActivation(), "aws_ssm_association": resourceAwsSsmAssociation(), "aws_ssm_document": resourceAwsSsmDocument(), + "aws_ssm_maintenance_window": resourceAwsSsmMaintenanceWindow(), + "aws_ssm_maintenance_window_target": resourceAwsSsmMaintenanceWindowTarget(), + "aws_ssm_maintenance_window_task": resourceAwsSsmMaintenanceWindowTask(), "aws_spot_datafeed_subscription": resourceAwsSpotDataFeedSubscription(), "aws_spot_instance_request": resourceAwsSpotInstanceRequest(), "aws_spot_fleet_request": resourceAwsSpotFleetRequest(), diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window.go new file mode 100644 index 000000000..f35f9a9bd --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window.go @@ -0,0 +1,168 @@ +package aws + +import ( + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSsmMaintenanceWindow() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSsmMaintenanceWindowCreate, + Read: resourceAwsSsmMaintenanceWindowRead, + Update: resourceAwsSsmMaintenanceWindowUpdate, + Delete: resourceAwsSsmMaintenanceWindowDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "schedule": { + Type: schema.TypeString, + Required: true, + }, + + "duration": { + Type: schema.TypeInt, + Required: true, + }, + + "cutoff": { + Type: schema.TypeInt, + Required: true, + }, + + "allow_unassociated_targets": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + } +} + +func resourceAwsSsmMaintenanceWindowCreate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.CreateMaintenanceWindowInput{ + Name: aws.String(d.Get("name").(string)), + Schedule: aws.String(d.Get("schedule").(string)), + Duration: aws.Int64(int64(d.Get("duration").(int))), + Cutoff: aws.Int64(int64(d.Get("cutoff").(int))), + AllowUnassociatedTargets: aws.Bool(d.Get("allow_unassociated_targets").(bool)), + } + + resp, err := ssmconn.CreateMaintenanceWindow(params) + if err != nil { + return err + } + + d.SetId(*resp.WindowId) + + return resourceAwsSsmMaintenanceWindowRead(d, meta) +} + +func resourceAwsSsmMaintenanceWindowUpdate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.UpdateMaintenanceWindowInput{ + WindowId: aws.String(d.Id()), + } + + if d.HasChange("name") { + params.Name = aws.String(d.Get("name").(string)) + } + + if d.HasChange("schedule") { + params.Schedule = aws.String(d.Get("schedule").(string)) + } + + if d.HasChange("duration") { + params.Duration = aws.Int64(int64(d.Get("duration").(int))) + } + + if d.HasChange("cutoff") { + params.Cutoff = aws.Int64(int64(d.Get("cutoff").(int))) + } + + if d.HasChange("allow_unassociated_targets") { + params.AllowUnassociatedTargets = aws.Bool(d.Get("allow_unassociated_targets").(bool)) + } + + if d.HasChange("enabled") { + params.Enabled = aws.Bool(d.Get("enabled").(bool)) + } + + _, err := ssmconn.UpdateMaintenanceWindow(params) + if err != nil { + return err + } + + return resourceAwsSsmMaintenanceWindowRead(d, meta) +} + +func resourceAwsSsmMaintenanceWindowRead(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.DescribeMaintenanceWindowsInput{ + Filters: []*ssm.MaintenanceWindowFilter{ + { + Key: aws.String("Name"), + Values: []*string{aws.String(d.Get("name").(string))}, + }, + }, + } + + resp, err := ssmconn.DescribeMaintenanceWindows(params) + if err != nil { + return err + } + + found := false + + for _, window := range resp.WindowIdentities { + if *window.WindowId == d.Id() { + found = true + + d.Set("name", window.Name) + d.Set("cutoff", window.Cutoff) + d.Set("duration", window.Duration) + d.Set("enabled", window.Enabled) + } + } + + if !found { + log.Printf("[INFO] Cannot find the SSM Maintenance Window %q. Removing from state", d.Get("name").(string)) + d.SetId("") + return nil + } + + return nil +} + +func resourceAwsSsmMaintenanceWindowDelete(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Deleting SSM Maintenance Window: %s", d.Id()) + + params := &ssm.DeleteMaintenanceWindowInput{ + WindowId: aws.String(d.Id()), + } + + _, err := ssmconn.DeleteMaintenanceWindow(params) + if err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_target.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_target.go new file mode 100644 index 000000000..d11d52cde --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_target.go @@ -0,0 +1,179 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSsmMaintenanceWindowTarget() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSsmMaintenanceWindowTargetCreate, + Read: resourceAwsSsmMaintenanceWindowTargetRead, + Delete: resourceAwsSsmMaintenanceWindowTargetDelete, + + Schema: map[string]*schema.Schema{ + "window_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "targets": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + + "owner_information": { + Type: schema.TypeString, + ForceNew: true, + Optional: true, + }, + }, + } +} + +func expandAwsSsmMaintenanceWindowTargets(d *schema.ResourceData) []*ssm.Target { + var targets []*ssm.Target + + targetConfig := d.Get("targets").([]interface{}) + + for _, tConfig := range targetConfig { + config := tConfig.(map[string]interface{}) + + target := &ssm.Target{ + Key: aws.String(config["key"].(string)), + Values: expandStringList(config["values"].([]interface{})), + } + + targets = append(targets, target) + } + + return targets +} + +func flattenAwsSsmMaintenanceWindowTargets(targets []*ssm.Target) []map[string]interface{} { + if len(targets) == 0 { + return nil + } + + result := make([]map[string]interface{}, 0, len(targets)) + target := targets[0] + + t := make(map[string]interface{}) + t["key"] = *target.Key + t["values"] = flattenStringList(target.Values) + + result = append(result, t) + + return result +} + +func resourceAwsSsmMaintenanceWindowTargetCreate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Registering SSM Maintenance Window Target") + + params := &ssm.RegisterTargetWithMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + ResourceType: aws.String(d.Get("resource_type").(string)), + Targets: expandAwsSsmMaintenanceWindowTargets(d), + } + + if v, ok := d.GetOk("owner_information"); ok { + params.OwnerInformation = aws.String(v.(string)) + } + + resp, err := ssmconn.RegisterTargetWithMaintenanceWindow(params) + if err != nil { + return err + } + + d.SetId(*resp.WindowTargetId) + + return resourceAwsSsmMaintenanceWindowTargetRead(d, meta) +} + +func resourceAwsSsmMaintenanceWindowTargetRead(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.DescribeMaintenanceWindowTargetsInput{ + WindowId: aws.String(d.Get("window_id").(string)), + Filters: []*ssm.MaintenanceWindowFilter{ + { + Key: aws.String("WindowTargetId"), + Values: []*string{aws.String(d.Id())}, + }, + }, + } + + resp, err := ssmconn.DescribeMaintenanceWindowTargets(params) + if err != nil { + return err + } + + found := false + for _, t := range resp.Targets { + if *t.WindowTargetId == d.Id() { + found = true + + d.Set("owner_information", t.OwnerInformation) + d.Set("window_id", t.WindowId) + d.Set("resource_type", t.ResourceType) + + if err := d.Set("targets", flattenAwsSsmMaintenanceWindowTargets(t.Targets)); err != nil { + return fmt.Errorf("[DEBUG] Error setting targets error: %#v", err) + } + } + } + + if !found { + log.Printf("[INFO] Maintenance Window Target not found. Removing from state") + d.SetId("") + return nil + } + + return nil +} + +func resourceAwsSsmMaintenanceWindowTargetDelete(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Deregistering SSM Maintenance Window Target: %s", d.Id()) + + params := &ssm.DeregisterTargetFromMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + WindowTargetId: aws.String(d.Id()), + } + + _, err := ssmconn.DeregisterTargetFromMaintenanceWindow(params) + if err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_target_test.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_target_test.go new file mode 100644 index 000000000..8f02a0154 --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_target_test.go @@ -0,0 +1,122 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "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 TestAccAWSSSMMaintenanceWindowTarget_basic(t *testing.T) { + name := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTargetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTargetBasicConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTargetExists("aws_ssm_maintenance_window_target.target"), + ), + }, + }, + }) +} + +func testAccCheckAWSSSMMaintenanceWindowTargetExists(n 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 Maintenance Window Target Window ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + resp, err := conn.DescribeMaintenanceWindowTargets(&ssm.DescribeMaintenanceWindowTargetsInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + Filters: []*ssm.MaintenanceWindowFilter{ + { + Key: aws.String("WindowTargetId"), + Values: []*string{aws.String(rs.Primary.ID)}, + }, + }, + }) + if err != nil { + return err + } + + for _, i := range resp.Targets { + if *i.WindowTargetId == rs.Primary.ID { + return nil + } + } + + return fmt.Errorf("No AWS SSM Maintenance window target found") + } +} + +func testAccCheckAWSSSMMaintenanceWindowTargetDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssm_maintenance_window_target" { + continue + } + + out, err := conn.DescribeMaintenanceWindowTargets(&ssm.DescribeMaintenanceWindowTargetsInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + Filters: []*ssm.MaintenanceWindowFilter{ + { + Key: aws.String("WindowTargetId"), + Values: []*string{aws.String(rs.Primary.ID)}, + }, + }, + }) + + if err != nil { + // Verify the error is what we want + if ae, ok := err.(awserr.Error); ok && ae.Code() == "DoesNotExistException" { + continue + } + return err + } + + if len(out.Targets) > 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Target to be gone, but was still found") + } + + return nil + } + + return nil +} + +func testAccAWSSSMMaintenanceWindowTargetBasicConfig(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_target" "target" { + window_id = "${aws_ssm_maintenance_window.foo.id}" + resource_type = "INSTANCE" + targets { + key = "tag:Name" + values = ["acceptance_test"] + } +} +`, rName) +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go new file mode 100644 index 000000000..23e955afa --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go @@ -0,0 +1,230 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSsmMaintenanceWindowTaskCreate, + Read: resourceAwsSsmMaintenanceWindowTaskRead, + Delete: resourceAwsSsmMaintenanceWindowTaskDelete, + + Schema: map[string]*schema.Schema{ + "window_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "max_concurrency": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "max_errors": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "task_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "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, + }, + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + + "priority": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "logging_info": { + 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_region": { + Type: schema.TypeString, + Required: true, + }, + "s3_bucket_prefix": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func expandAwsSsmMaintenanceWindowLoggingInfo(config []interface{}) *ssm.LoggingInfo { + + loggingConfig := config[0].(map[string]interface{}) + + loggingInfo := &ssm.LoggingInfo{ + S3BucketName: aws.String(loggingConfig["s3_bucket_name"].(string)), + S3Region: aws.String(loggingConfig["s3_region"].(string)), + } + + if s := loggingConfig["s3_bucket_prefix"].(string); s != "" { + loggingInfo.S3KeyPrefix = aws.String(s) + } + + return loggingInfo +} + +func flattenAwsSsmMaintenanceWindowLoggingInfo(loggingInfo *ssm.LoggingInfo) []interface{} { + + result := make(map[string]interface{}) + result["s3_bucket_name"] = *loggingInfo.S3BucketName + result["s3_region"] = *loggingInfo.S3Region + + if loggingInfo.S3KeyPrefix != nil { + result["s3_bucket_prefix"] = *loggingInfo.S3KeyPrefix + } + + return []interface{}{result} +} + +func resourceAwsSsmMaintenanceWindowTaskCreate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Registering SSM Maintenance Window Task") + + params := &ssm.RegisterTaskWithMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + MaxConcurrency: aws.String(d.Get("max_concurrency").(string)), + MaxErrors: aws.String(d.Get("max_errors").(string)), + TaskType: aws.String(d.Get("task_type").(string)), + ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)), + TaskArn: aws.String(d.Get("task_arn").(string)), + Targets: expandAwsSsmMaintenanceWindowTargets(d), + } + + 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{})) + } + + resp, err := ssmconn.RegisterTaskWithMaintenanceWindow(params) + if err != nil { + return err + } + + d.SetId(*resp.WindowTaskId) + + return resourceAwsSsmMaintenanceWindowTaskRead(d, meta) +} + +func resourceAwsSsmMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(d.Get("window_id").(string)), + } + + resp, err := ssmconn.DescribeMaintenanceWindowTasks(params) + if err != nil { + return 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) + + if t.LoggingInfo != nil { + if err := d.Set("logging_info", flattenAwsSsmMaintenanceWindowLoggingInfo(t.LoggingInfo)); err != nil { + return fmt.Errorf("[DEBUG] Error setting logging_info error: %#v", err) + } + } + + if err := d.Set("targets", flattenAwsSsmMaintenanceWindowTargets(t.Targets)); err != nil { + return fmt.Errorf("[DEBUG] Error setting targets error: %#v", err) + } + } + } + + if !found { + log.Printf("[INFO] Maintenance Window Target not found. Removing from state") + d.SetId("") + return nil + } + + return nil +} + +func resourceAwsSsmMaintenanceWindowTaskDelete(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Deregistering SSM Maintenance Window Task: %s", d.Id()) + + params := &ssm.DeregisterTaskFromMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + WindowTaskId: aws.String(d.Id()), + } + + _, err := ssmconn.DeregisterTaskFromMaintenanceWindow(params) + if err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go new file mode 100644 index 000000000..797d75766 --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go @@ -0,0 +1,158 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "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 TestAccAWSSSMMaintenanceWindowTask_basic(t *testing.T) { + name := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskBasicConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists("aws_ssm_maintenance_window_task.target"), + ), + }, + }, + }) +} + +func testAccCheckAWSSSMMaintenanceWindowTaskExists(n 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 Maintenance Window Task Window ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + resp, err := conn.DescribeMaintenanceWindowTasks(&ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + }) + if err != nil { + return err + } + + for _, i := range resp.Tasks { + if *i.WindowTaskId == rs.Primary.ID { + return nil + } + } + + return fmt.Errorf("No AWS SSM Maintenance window task found") + } +} + +func testAccCheckAWSSSMMaintenanceWindowTaskDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssm_maintenance_window_target" { + continue + } + + out, err := conn.DescribeMaintenanceWindowTasks(&ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + }) + + if err != nil { + // Verify the error is what we want + if ae, ok := err.(awserr.Error); ok && ae.Code() == "DoesNotExistException" { + continue + } + return err + } + + if len(out.Tasks) > 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Task to be gone, but was still found") + } + + return nil + } + + 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" + targets { + key = "InstanceIds" + values = ["${aws_instance.foo.id}"] + } +} + +resource "aws_instance" "foo" { + ami = "ami-4fccb37f" + + instance_type = "m1.small" +} + +resource "aws_iam_role" "ssm_role" { + name = "ssm-role-%s" + + assume_role_policy = < 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Document to be gone, but was still found") + } + + return nil + } + + return nil +} + +func testAccAWSSSMMaintenanceWindowBasicConfig(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 +} + +`, rName) +} + +func testAccAWSSSMMaintenanceWindowBasicConfigUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "foo" { + name = "updated-maintenance-window-%s" + schedule = "cron(0 16 ? * WED *)" + duration = 10 + cutoff = 8 +} + +`, rName) +} diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown new file mode 100644 index 000000000..d48b7de16 --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window" +sidebar_current: "docs-aws-resource-ssm-maintenance-window" +description: |- + Provides an SSM Maintenance Window resource +--- + +# aws_ssm_maintenance_window + +Provides an SSM Maintenance Window resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "production" { + name = "maintenance-window-application" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the maintenance window. +* `schedule` - (Required) The schedule of the Maintenance Window in the form of a [cron](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-maintenance-cron.html) or rate expression. +* `cutoff` - (Required) The number of hours before the end of the Maintenance Window that Systems Manager stops scheduling new tasks for execution. +* `duration` - (Required) The duration of the Maintenance Window in hours. +* `allow_unregistered_targets` - (Optional) Whether targets must be registered with the Maintenance Window before tasks can be defined for those targets. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown new file mode 100644 index 000000000..beff191ee --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown @@ -0,0 +1,46 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window_target" +sidebar_current: "docs-aws-resource-ssm-maintenance-window-target" +description: |- + Provides an SSM Maintenance Window Target resource +--- + +# aws_ssm_maintenance_window_target + +Provides an SSM Maintenance Window Target resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "window" { + name = "maintenance-window-webapp" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +resource "aws_ssm_maintenance_window_target" "target1" { + window_id = "${aws_ssm_maintenance_window.window.id}" + resource_type = "INSTANCE" + targets { + key = "tag:Name" + values = ["acceptance_test"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `window_id` - (Required) The Id of the maintenance window to register the target with. +* `resource_type` - (Required) The type of target being registered with the Maintenance Window. Possible values `INSTANCE`. +* `targets` - (Required) 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. +* `owner_information` - (Optional) User-provided value that will be included in any CloudWatch events raised while running tasks for these targets in this Maintenance Window. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window target. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown new file mode 100644 index 000000000..2ed3b50a4 --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window_task" +sidebar_current: "docs-aws-resource-ssm-maintenance-window-task" +description: |- + Provides an SSM Maintenance Window Task resource +--- + +# aws_ssm_maintenance_window_task + +Provides an SSM Maintenance Window Task resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "window" { + 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.window.id}" + task_type = "RUN_COMMAND" + task_arn = "AWS-RunShellScript" + priority = 1 + service_role_arn = "arn:aws:iam::187416307283:role/service-role/AWS_Events_Invoke_Run_Command_112316643" + max_concurrency = "2" + max_errors = "1" + targets { + key = "InstanceIds" + values = ["${aws_instance.instance.id}"] + } +} + +resource "aws_instance" "instance" { + ami = "ami-4fccb37f" + + instance_type = "m1.small" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `window_id` - (Required) The Id of the maintenance window to register the task with. +* `max_concurrency` - (Required) The maximum number of targets this task can be run for in parallel. +* `max_errors` - (Required) The maximum number of errors allowed before this task stops being scheduled. +* `task_type` - (Required) The type of task being registered. The only allowed value is `RUN_COMMAND`. +* `task_arn` - (Required) The ARN of the task to execute. +* `service_role_arn` - (Required) The role that should be assumed when executing the task. +* `targets` - (Required) 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. +* `priority` - (Optional) The priority of the task in the Maintenance Window, the lower the number the higher the priority. Tasks in a Maintenance Window are scheduled in priority order with tasks that have the same priority scheduled in parallel. +* `logging_info` - (Optional) A structure containing information about an Amazon S3 bucket to write instance-level logs to. Documented below. + +`logging_info` supports the following: + +* `s3_bucket_name` - (Required) +* `s3_region` - (Required) +* `s3_bucket_prefix` - (Optional) + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window task. \ No newline at end of file diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index ec6633463..8afac7765 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -1249,6 +1249,18 @@ aws_ssm_document + > + aws_ssm_maintenance_window + + + > + aws_ssm_maintenance_window_target + + + > + aws_ssm_maintenance_window_task + +