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

Add pagination support (introduced in aws-sdk-go v1.33.21) and multiple baselines per patch group. #15213

Merged
merged 2 commits into from
Mar 17, 2021
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
7 changes: 7 additions & 0 deletions .changelog/15213.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:bug
resource/aws_ssm_patch_group: Allow for a single patch group to be registered with multiple patch baselines
```

```release-note:bug
resource/aws_ssm_patch_group: Replace `Provider produced inconsistent result after apply` with actual error message
```
29 changes: 29 additions & 0 deletions aws/internal/service/ssm/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,32 @@ func DocumentByName(conn *ssm.SSM, name string) (*ssm.DocumentDescription, error

return output.Document, nil
}

// PatchGroup returns matching SSM Patch Group by Patch Group and BaselineId.
func PatchGroup(conn *ssm.SSM, patchGroup, baselineId string) (*ssm.PatchGroupPatchBaselineMapping, error) {
input := &ssm.DescribePatchGroupsInput{}
var result *ssm.PatchGroupPatchBaselineMapping

err := conn.DescribePatchGroupsPages(input, func(page *ssm.DescribePatchGroupsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, mapping := range page.Mappings {
if mapping == nil {
continue
}

if aws.StringValue(mapping.PatchGroup) == patchGroup {
if mapping.BaselineIdentity != nil && aws.StringValue(mapping.BaselineIdentity.BaselineId) == baselineId {
result = mapping
return false
}
}
}

return !lastPage
})

return result, err
}
91 changes: 65 additions & 26 deletions aws/resource_aws_ssm_patch_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package aws
import (
"fmt"
"log"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/ssm/finder"
)

func resourceAwsSsmPatchGroup() *schema.Resource {
Expand All @@ -15,6 +18,15 @@ func resourceAwsSsmPatchGroup() *schema.Resource {
Read: resourceAwsSsmPatchGroupRead,
Delete: resourceAwsSsmPatchGroupDelete,

SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceAwsSsmPatchGroupV0().CoreConfigSchema().ImpliedType(),
Upgrade: resourceAwsSsmPatchGroupStateUpgradeV0,
Version: 0,
},
},

Schema: map[string]*schema.Schema{
"baseline_id": {
Type: schema.TypeString,
Expand All @@ -31,66 +43,93 @@ func resourceAwsSsmPatchGroup() *schema.Resource {
}

func resourceAwsSsmPatchGroupCreate(d *schema.ResourceData, meta interface{}) error {
ssmconn := meta.(*AWSClient).ssmconn
conn := meta.(*AWSClient).ssmconn

baselineId := d.Get("baseline_id").(string)
patchGroup := d.Get("patch_group").(string)

params := &ssm.RegisterPatchBaselineForPatchGroupInput{
BaselineId: aws.String(d.Get("baseline_id").(string)),
PatchGroup: aws.String(d.Get("patch_group").(string)),
BaselineId: aws.String(baselineId),
PatchGroup: aws.String(patchGroup),
}

resp, err := ssmconn.RegisterPatchBaselineForPatchGroup(params)
resp, err := conn.RegisterPatchBaselineForPatchGroup(params)
if err != nil {
return err
return fmt.Errorf("error registering SSM Patch Baseline (%s) for Patch Group (%s): %w", baselineId, patchGroup, err)
}

d.SetId(aws.StringValue(resp.PatchGroup))
d.SetId(fmt.Sprintf("%s,%s", aws.StringValue(resp.PatchGroup), aws.StringValue(resp.BaselineId)))

return resourceAwsSsmPatchGroupRead(d, meta)
}

func resourceAwsSsmPatchGroupRead(d *schema.ResourceData, meta interface{}) error {
ssmconn := meta.(*AWSClient).ssmconn

params := &ssm.DescribePatchGroupsInput{}
conn := meta.(*AWSClient).ssmconn

resp, err := ssmconn.DescribePatchGroups(params)
patchGroup, baselineId, err := parseSsmPatchGroupId(d.Id())
if err != nil {
return err
return fmt.Errorf("error parsing SSM Patch Group ID (%s): %w", d.Id(), err)
}

found := false
for _, t := range resp.Mappings {
if *t.PatchGroup == d.Id() {
found = true
group, err := finder.PatchGroup(conn, patchGroup, baselineId)

d.Set("patch_group", t.PatchGroup)
d.Set("baseline_id", t.BaselineIdentity.BaselineId)
}
if err != nil {
return fmt.Errorf("error reading SSM Patch Group (%s): %w", d.Id(), err)
}

if !found {
log.Printf("[INFO] Patch Group not found. Removing from state")
if group == nil {
if d.IsNewResource() {
return fmt.Errorf("error reading SSM Patch Group (%s): not found after creation", d.Id())
}

log.Printf("[WARN] SSM Patch Group (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

var groupBaselineId string
if group.BaselineIdentity != nil {
groupBaselineId = aws.StringValue(group.BaselineIdentity.BaselineId)
}

d.Set("baseline_id", groupBaselineId)
d.Set("patch_group", aws.StringValue(group.PatchGroup))

return nil

}

func resourceAwsSsmPatchGroupDelete(d *schema.ResourceData, meta interface{}) error {
ssmconn := meta.(*AWSClient).ssmconn
conn := meta.(*AWSClient).ssmconn

log.Printf("[INFO] Deleting SSM Patch Group: %s", d.Id())
patchGroup, baselineId, err := parseSsmPatchGroupId(d.Id())
if err != nil {
return fmt.Errorf("error parsing SSM Patch Group ID (%s): %w", d.Id(), err)
}

params := &ssm.DeregisterPatchBaselineForPatchGroupInput{
BaselineId: aws.String(d.Get("baseline_id").(string)),
PatchGroup: aws.String(d.Get("patch_group").(string)),
BaselineId: aws.String(baselineId),
PatchGroup: aws.String(patchGroup),
}

_, err := ssmconn.DeregisterPatchBaselineForPatchGroup(params)
_, err = conn.DeregisterPatchBaselineForPatchGroup(params)

if err != nil {
return fmt.Errorf("error deregistering SSM Patch Group (%s): %s", d.Id(), err)
if tfawserr.ErrCodeEquals(err, ssm.ErrCodeDoesNotExistException) {
return nil
}
return fmt.Errorf("error deregistering SSM Patch Baseline (%s) for Patch Group (%s): %w", baselineId, patchGroup, err)
}

return nil
}

func parseSsmPatchGroupId(id string) (string, string, error) {
parts := strings.SplitN(id, ",", 2)

if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", fmt.Errorf("please make sure ID is in format PATCH_GROUP,BASELINE_ID")
}

return parts[0], parts[1], nil
}
35 changes: 35 additions & 0 deletions aws/resource_aws_ssm_patch_group_migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package aws

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceAwsSsmPatchGroupV0() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"baseline_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"patch_group": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceAwsSsmPatchGroupStateUpgradeV0(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
if rawState == nil {
rawState = map[string]interface{}{}
}

rawState["id"] = fmt.Sprintf("%s,%s", rawState["patch_group"], rawState["baseline_id"])

return rawState, nil
}
37 changes: 37 additions & 0 deletions aws/resource_aws_ssm_patch_group_migrate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package aws

import (
"context"
"fmt"
"reflect"
"testing"
)

func testResourceAwsSsmPatchGroupStateDataV0() map[string]interface{} {
return map[string]interface{}{
"id": "testgroup",
"baseline_id": "pb-0c4e592064EXAMPLE",
"patch_group": "testgroup",
}
}

func testResourceAwsSsmPatchGroupStateDataV1() map[string]interface{} {
v0 := testResourceAwsSsmPatchGroupStateDataV0()
return map[string]interface{}{
"id": fmt.Sprintf("%s,%s", v0["patch_group"], v0["baseline_id"]),
"baseline_id": v0["baseline_id"],
"patch_group": v0["patch_group"],
}
}

func TestResourceAWSSSMPatchGroupStateUpgradeV0(t *testing.T) {
expected := testResourceAwsSsmPatchGroupStateDataV1()
actual, err := resourceAwsSsmPatchGroupStateUpgradeV0(context.Background(), testResourceAwsSsmPatchGroupStateDataV0(), nil)
if err != nil {
t.Fatalf("error migrating state: %s", err)
}

if !reflect.DeepEqual(expected, actual) {
t.Fatalf("\n\nexpected:\n\n%#v\n\ngot:\n\n%#v\n\n", expected, actual)
}
}
Loading