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

r/ssm_maintenance_window_task - allow not setting targets #23756

Merged
merged 6 commits into from
Mar 21, 2022
Merged
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
11 changes: 11 additions & 0 deletions .changelog/23756.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```release-note:enhancement
resource/aws_ssm_maintenance_window_task: Add `arn` and `window_task_id` attributes.
```

```release-note:enhancement
resource/aws_ssm_maintenance_window_task: Add `cutoff_behavior` argument.
```

```release-note:bug
resource/aws_ssm_maintenance_window_task: Allow creating a window taks without targets.
```
85 changes: 70 additions & 15 deletions internal/service/ssm/maintenance_window_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"

"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"
Expand All @@ -28,21 +29,37 @@ func ResourceMaintenanceWindowTask() *schema.Resource {
},

Schema: map[string]*schema.Schema{
"arn": {
Type: schema.TypeString,
Computed: true,
},
"window_task_id": {
Type: schema.TypeString,
Computed: true,
},
"window_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"cutoff_behavior": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice(ssm.MaintenanceWindowTaskCutoffBehavior_Values(), false),
},

"max_concurrency": {
Type: schema.TypeString,
Required: true,
Optional: true,
Computed: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([1-9][0-9]*|[1-9][0-9]%|[1-9]%|100%)$`), "must be a number without leading zeros or a percentage between 1% and 100% without leading zeros and ending with the percentage symbol"),
},

"max_errors": {
Type: schema.TypeString,
Required: true,
Optional: true,
Computed: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^([1-9][0-9]*|[0]|[1-9][0-9]%|[0-9]%|100%)$`), "must be zero, a number without leading zeros, or a percentage between 1% and 100% without leading zeros and ending with the percentage symbol"),
},

Expand Down Expand Up @@ -656,11 +673,21 @@ func resourceMaintenanceWindowTaskCreate(d *schema.ResourceData, meta interface{
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)),
TaskArn: aws.String(d.Get("task_arn").(string)),
WindowId: aws.String(d.Get("window_id").(string)),
TaskType: aws.String(d.Get("task_type").(string)),
TaskArn: aws.String(d.Get("task_arn").(string)),
}

if v, ok := d.GetOk("max_errors"); ok {
params.MaxErrors = aws.String(v.(string))
}

if v, ok := d.GetOk("max_concurrency"); ok {
params.MaxConcurrency = aws.String(v.(string))
}

if v, ok := d.GetOk("cutoff_behavior"); ok {
params.CutoffBehavior = aws.String(v.(string))
}

if v, ok := d.GetOk("targets"); ok {
Expand Down Expand Up @@ -715,7 +742,9 @@ func resourceMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error getting Maintenance Window (%s) Task (%s): %s", windowID, d.Id(), err)
}

windowTaskID := aws.StringValue(resp.WindowTaskId)
d.Set("window_id", resp.WindowId)
d.Set("window_task_id", windowTaskID)
d.Set("max_concurrency", resp.MaxConcurrency)
d.Set("max_errors", resp.MaxErrors)
d.Set("task_type", resp.TaskType)
Expand All @@ -724,6 +753,7 @@ func resourceMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{})
d.Set("priority", resp.Priority)
d.Set("name", resp.Name)
d.Set("description", resp.Description)
d.Set("cutoff_behavior", resp.CutoffBehavior)

if resp.TaskInvocationParameters != nil {
if err := d.Set("task_invocation_parameters", flattenTaskInvocationParameters(resp.TaskInvocationParameters)); err != nil {
Expand All @@ -735,6 +765,15 @@ func resourceMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{})
return fmt.Errorf("Error setting targets error: %#v", err)
}

arn := arn.ARN{
Partition: meta.(*conns.AWSClient).Partition,
Service: "ssm",
Region: meta.(*conns.AWSClient).Region,
AccountID: meta.(*conns.AWSClient).AccountID,
Resource: fmt.Sprintf("windowtask/%s", windowTaskID),
}.String()
d.Set("arn", arn)

return nil
}

Expand All @@ -743,20 +782,36 @@ func resourceMaintenanceWindowTaskUpdate(d *schema.ResourceData, meta interface{
windowID := d.Get("window_id").(string)

params := &ssm.UpdateMaintenanceWindowTaskInput{
Priority: aws.Int64(int64(d.Get("priority").(int))),
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)),
TaskArn: aws.String(d.Get("task_arn").(string)),
Targets: expandTargets(d.Get("targets").([]interface{})),
Replace: aws.Bool(true),
Priority: aws.Int64(int64(d.Get("priority").(int))),
WindowId: aws.String(windowID),
WindowTaskId: aws.String(d.Id()),
TaskArn: aws.String(d.Get("task_arn").(string)),
Replace: aws.Bool(true),
}

if v, ok := d.GetOk("service_role_arn"); ok {
params.ServiceRoleArn = aws.String(v.(string))
}

if v, ok := d.GetOk("max_errors"); ok {
params.MaxErrors = aws.String(v.(string))
}

if v, ok := d.GetOk("max_concurrency"); ok {
params.MaxConcurrency = aws.String(v.(string))
}

if v, ok := d.GetOk("targets"); ok {
params.Targets = expandTargets(v.([]interface{}))
} else {
params.MaxConcurrency = nil
params.MaxErrors = nil
}

if v, ok := d.GetOk("cutoff_behavior"); ok {
params.CutoffBehavior = aws.String(v.(string))
}

if v, ok := d.GetOk("name"); ok {
params.Name = aws.String(v.(string))
}
Expand Down
95 changes: 95 additions & 0 deletions internal/service/ssm/maintenance_window_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ssm_test

import (
"fmt"
"regexp"
"testing"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -30,6 +31,10 @@ func TestAccSSMMaintenanceWindowTask_basic(t *testing.T) {
Config: testAccMaintenanceWindowTaskBasicConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckMaintenanceWindowTaskExists(resourceName, &before),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "ssm", regexp.MustCompile(`windowtask/.+`)),
resource.TestCheckResourceAttrSet(resourceName, "window_task_id"),
resource.TestCheckResourceAttrPair(resourceName, "window_id", "aws_ssm_maintenance_window.test", "id"),
resource.TestCheckResourceAttr(resourceName, "targets.#", "1"),
),
},
{
Expand All @@ -56,6 +61,69 @@ func TestAccSSMMaintenanceWindowTask_basic(t *testing.T) {
})
}

func TestAccSSMMaintenanceWindowTask_noTarget(t *testing.T) {
var before ssm.MaintenanceWindowTask
resourceName := "aws_ssm_maintenance_window_task.test"

rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, ssm.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckMaintenanceWindowTaskDestroy,
Steps: []resource.TestStep{
{
Config: testAccMaintenanceWindowTaskNoTargetConfig(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckMaintenanceWindowTaskExists(resourceName, &before),
resource.TestCheckResourceAttr(resourceName, "targets.#", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccMaintenanceWindowTaskImportStateIdFunc(resourceName),
ImportStateVerify: true,
},
},
})
}

func TestAccSSMMaintenanceWindowTask_cutoff(t *testing.T) {
var before ssm.MaintenanceWindowTask
resourceName := "aws_ssm_maintenance_window_task.test"

rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, ssm.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckMaintenanceWindowTaskDestroy,
Steps: []resource.TestStep{
{
Config: testAccMaintenanceWindowTaskCutoffConfig(rName, "CANCEL_TASK"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMaintenanceWindowTaskExists(resourceName, &before),
resource.TestCheckResourceAttr(resourceName, "cutoff_behavior", "CANCEL_TASK"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: testAccMaintenanceWindowTaskImportStateIdFunc(resourceName),
ImportStateVerify: true,
},
{
Config: testAccMaintenanceWindowTaskCutoffConfig(rName, "CONTINUE_TASK"),
Check: resource.ComposeTestCheckFunc(
testAccCheckMaintenanceWindowTaskExists(resourceName, &before),
resource.TestCheckResourceAttr(resourceName, "cutoff_behavior", "CONTINUE_TASK"),
),
},
},
})
}

func TestAccSSMMaintenanceWindowTask_noRole(t *testing.T) {
var task ssm.MaintenanceWindowTask
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
Expand Down Expand Up @@ -562,6 +630,33 @@ resource "aws_ssm_maintenance_window_task" "test" {
`)
}

func testAccMaintenanceWindowTaskNoTargetConfig(rName string) string {
return fmt.Sprintf(testAccMaintenanceWindowTaskBaseConfig(rName) + `

resource "aws_ssm_maintenance_window_task" "test" {
window_id = aws_ssm_maintenance_window.test.id
task_type = "AUTOMATION"
task_arn = "AWS-RunShellScript"
priority = 1
service_role_arn = aws_iam_role.test.arn
}
`)
}

func testAccMaintenanceWindowTaskCutoffConfig(rName, cutoff string) string {
return fmt.Sprintf(testAccMaintenanceWindowTaskBaseConfig(rName)+`

resource "aws_ssm_maintenance_window_task" "test" {
window_id = aws_ssm_maintenance_window.test.id
task_type = "AUTOMATION"
task_arn = "AWS-RunShellScript"
priority = 1
service_role_arn = aws_iam_role.test.arn
cutoff_behavior = %[1]q
}
`, cutoff)
}

func testAccMaintenanceWindowTaskBasicUpdateConfig(rName, description, taskType, taskArn string, priority, maxConcurrency, maxErrors int) string {
return fmt.Sprintf(testAccMaintenanceWindowTaskBaseConfig(rName)+`

Expand Down
9 changes: 6 additions & 3 deletions website/docs/r/ssm_maintenance_window_task.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,15 @@ resource "aws_ssm_maintenance_window_task" "example" {
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.
* `max_concurrency` - (Optional) The maximum number of targets this task can be run for in parallel.
* `max_errors` - (Optional) The maximum number of errors allowed before this task stops being scheduled.
* `cutoff_behavior` - (Optional) Indicates whether tasks should continue to run after the cutoff time specified in the maintenance windows is reached. Valid values are `CONTINUE_TASK` and `CANCEL_TASK`.
* `task_type` - (Required) The type of task being registered. Valid values: `AUTOMATION`, `LAMBDA`, `RUN_COMMAND` or `STEP_FUNCTIONS`.
* `task_arn` - (Required) The ARN of the task to execute.
* `service_role_arn` - (Optional) The role that should be assumed when executing the task. If a role is not provided, Systems Manager uses your account's service-linked role. If no service-linked role for Systems Manager exists in your account, it is created for you.
* `name` - (Optional) The name of the maintenance window task.
* `description` - (Optional) The description of the maintenance window task.
* `targets` - (Required) The targets (either instances or window target ids). Instances are specified using Key=InstanceIds,Values=instanceid1,instanceid2. Window target ids are specified using Key=WindowTargetIds,Values=window target id1, window target id2.
* `targets` - (Optional) The targets (either instances or window target ids). Instances are specified using Key=InstanceIds,Values=instanceid1,instanceid2. Window target ids are specified using Key=WindowTargetIds,Values=window target id1, window target id2.
* `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.
* `task_invocation_parameters` - (Optional) Configuration block with parameters for task execution.

Expand Down Expand Up @@ -201,7 +202,9 @@ The following arguments are supported:

In addition to all arguments above, the following attributes are exported:

* `arn` - The ARN of the maintenance window task.
* `id` - The ID of the maintenance window task.
* `window_task_id` - The ID of the maintenance window task.

## Import

Expand Down