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 patch_source block to resource_aws_ssm_patch_baseline #11879

Merged
merged 2 commits into from
Feb 12, 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
83 changes: 83 additions & 0 deletions aws/resource_aws_ssm_patch_baseline.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,38 @@ func resourceAwsSsmPatchBaseline() *schema.Resource {
Type: schema.TypeBool,
Optional: true,
},

"patch_source": {
Type: schema.TypeList,
Optional: true,
MaxItems: 20,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_\-.]{3,50}$`), "see https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_PatchSource.html"),
},

"configuration": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 1024),
},

"products": {
Type: schema.TypeList,
Required: true,
MaxItems: 20,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringLenBetween(1, 128),
},
},
},
},
},

"tags": tagsSchema(),
},
}
Expand Down Expand Up @@ -203,6 +235,10 @@ func resourceAwsSsmPatchBaselineCreate(d *schema.ResourceData, meta interface{})
params.ApprovalRules = expandAwsSsmPatchRuleGroup(d)
}

if _, ok := d.GetOk("patch_source"); ok {
params.Sources = expandAwsSsmPatchSource(d)
}

if v, ok := d.GetOk("approved_patches_enable_non_security"); ok {
params.ApprovedPatchesEnableNonSecurity = aws.Bool(v.(bool))
}
Expand All @@ -212,6 +248,7 @@ func resourceAwsSsmPatchBaselineCreate(d *schema.ResourceData, meta interface{})
}

resp, err := conn.CreatePatchBaseline(params)

if err != nil {
return err
}
Expand Down Expand Up @@ -255,6 +292,10 @@ func resourceAwsSsmPatchBaselineUpdate(d *schema.ResourceData, meta interface{})
params.GlobalFilters = expandAwsSsmPatchFilterGroup(d)
}

if d.HasChange("patch_source") {
params.Sources = expandAwsSsmPatchSource(d)
}

if d.HasChange("approved_patches_enable_non_security") {
params.ApprovedPatchesEnableNonSecurity = aws.Bool(d.Get("approved_patches_enable_non_security").(bool))
}
Expand Down Expand Up @@ -315,6 +356,10 @@ func resourceAwsSsmPatchBaselineRead(d *schema.ResourceData, meta interface{}) e
return fmt.Errorf("Error setting approval rules error: %#v", err)
}

if err := d.Set("patch_source", flattenAwsSsmPatchSource(resp.Sources)); err != nil {
return fmt.Errorf("Error setting patch sources error: %#v", err)
}

arn := arn.ARN{
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Expand Down Expand Up @@ -452,3 +497,41 @@ func flattenAwsSsmPatchRuleGroup(group *ssm.PatchRuleGroup) []map[string]interfa

return result
}

func expandAwsSsmPatchSource(d *schema.ResourceData) []*ssm.PatchSource {
var sources []*ssm.PatchSource

sourceConfigs := d.Get("patch_source").([]interface{})

for _, sConfig := range sourceConfigs {
config := sConfig.(map[string]interface{})

source := &ssm.PatchSource{
Name: aws.String(config["name"].(string)),
Configuration: aws.String(config["configuration"].(string)),
Products: expandStringList(config["products"].([]interface{})),
}

sources = append(sources, source)
}

return sources
}

func flattenAwsSsmPatchSource(sources []*ssm.PatchSource) []map[string]interface{} {
if len(sources) == 0 {
return nil
}

result := make([]map[string]interface{}, 0, len(sources))

for _, source := range sources {
s := make(map[string]interface{})
s["name"] = *source.Name
s["configuration"] = *source.Configuration
s["products"] = flattenStringList(source.Products)
result = append(result, s)
}

return result
}
94 changes: 94 additions & 0 deletions aws/resource_aws_ssm_patch_baseline_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,62 @@ func TestAccAWSSSMPatchBaseline_OperatingSystem(t *testing.T) {
})
}

func TestAccAWSSSMPatchBaseline_PatchSources(t *testing.T) {
var before, after ssm.PatchBaselineIdentity
name := acctest.RandString(10)
resourceName := "aws_ssm_patch_baseline.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSSSMPatchBaselineDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSSSMPatchBaselineConfigWithPatchSource(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMPatchBaselineExists(resourceName, &before),
resource.TestCheckResourceAttr(resourceName, "patch_source.#", "1"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.name", "My-AL2017.09"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.configuration", "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.products.#", "1"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.products.0", "AmazonLinux2017.09"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccAWSSSMPatchBaselineConfigWithPatchSourceUpdated(name),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSSSMPatchBaselineExists(resourceName, &after),
resource.TestCheckResourceAttr(resourceName, "patch_source.#", "2"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.name", "My-AL2017.09"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.configuration", "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.products.#", "1"),
resource.TestCheckResourceAttr(resourceName, "patch_source.0.products.0", "AmazonLinux2017.09"),
resource.TestCheckResourceAttr(resourceName, "patch_source.1.name", "My-AL2018.03"),
resource.TestCheckResourceAttr(resourceName, "patch_source.1.configuration", "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"),
resource.TestCheckResourceAttr(resourceName, "patch_source.1.products.#", "1"),
resource.TestCheckResourceAttr(resourceName, "patch_source.1.products.0", "AmazonLinux2018.03"),
func(*terraform.State) error {
if *before.BaselineId != *after.BaselineId {
t.Fatal("Baseline IDs changed unexpectedly")
}
return nil
},
),
},
},
})
}

func TestAccAWSSSMPatchBaseline_ApprovedPatchesNonSec(t *testing.T) {
var ssmPatch ssm.PatchBaselineIdentity
name := acctest.RandString(10)
resourceName := "aws_ssm_patch_baseline.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Expand Down Expand Up @@ -410,6 +462,48 @@ resource "aws_ssm_patch_baseline" "test" {
`, rName)
}

func testAccAWSSSMPatchBaselineConfigWithPatchSource(rName string) string {
return fmt.Sprintf(`
resource "aws_ssm_patch_baseline" "foo" {
name = %[1]q
description = "Baseline containing all updates approved for production systems"
approved_patches_compliance_level = "CRITICAL"
approved_patches = ["test123"]
operating_system = "AMAZON_LINUX"

patch_source {
name = "My-AL2017.09"
configuration = "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"
products = ["AmazonLinux2017.09"]
}
}
`, rName)
}

func testAccAWSSSMPatchBaselineConfigWithPatchSourceUpdated(rName string) string {
return fmt.Sprintf(`
resource "aws_ssm_patch_baseline" "foo" {
name = %[1]q
description = "Baseline containing all updates approved for production systems"
approved_patches_compliance_level = "CRITICAL"
approved_patches = ["test123"]
operating_system = "AMAZON_LINUX"

patch_source {
name = "My-AL2017.09"
configuration = "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"
products = ["AmazonLinux2017.09"]
}

patch_source {
name = "My-AL2018.03"
configuration = "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"
products = ["AmazonLinux2018.03"]
}
}
`, rName)
}

func testAccAWSSSMPatchBaselineBasicConfigApprovedPatchesNonSec(rName string) string {
return fmt.Sprintf(`
resource "aws_ssm_patch_baseline" "test" {
Expand Down
26 changes: 26 additions & 0 deletions website/docs/r/ssm_patch_baseline.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ resource "aws_ssm_patch_baseline" "windows_os_apps" {
}
```

Advanced usage, specifying alternate patch source repository

```hcl
resource "aws_ssm_patch_baseline" "al_2017_09" {
name = "Amazon-Linux-2017.09"
description = "My patch repository for Amazon Linux 2017.09"
operating_system = "AMAZON_LINUX"

approval_rule {
...
}

patch_source {
name = "My-AL2017.09"
configuration = "[amzn-main] \nname=amzn-main-Base\nmirrorlist=http://repo./$awsregion./$awsdomain//$releasever/main/mirror.list //nmirrorlist_expire=300//nmetadata_expire=300 \npriority=10 \nfailovermethod=priority \nfastestmirror_enabled=0 \ngpgcheck=1 \ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-amazon-ga \nenabled=1 \nretries=3 \ntimeout=5\nreport_instanceid=yes"
products = ["AmazonLinux2017.09"]
}
}
```

## Argument Reference

Expand All @@ -132,6 +151,7 @@ The following arguments are supported:
* `rejected_patches` - (Optional) A list of rejected patches.
* `global_filter` - (Optional) A set of global filters used to exclude patches from the baseline. Up to 4 global filters can be specified using Key/Value pairs. Valid Keys are `PRODUCT | CLASSIFICATION | MSRC_SEVERITY | PATCH_ID`.
* `approval_rule` - (Optional) A set of rules used to include patches in the baseline. up to 10 approval rules can be specified. Each approval_rule block requires the fields documented below.
* `patch_source` - (Optional) A list of alternate source repositories to retrieve patches from. Each patch_source block requires the fields documented below. Applies to Linux instances only.
* `rejected_patches_action` - (Optional) The action for Patch Manager to take on patches included in the `rejected_patches` list. Allow values are `ALLOW_AS_DEPENDENCY` and `BLOCK`.
* `approved_patches_enable_non_security` - (Optional) Indicates whether the list of approved patches includes non-security updates that should be applied to the instances. Applies to Linux instances only.

Expand All @@ -144,6 +164,12 @@ The `approval_rule` block supports:
* `enable_non_security` - (Optional) Boolean enabling the application of non-security updates. The default value is 'false'. Valid for Linux instances only.
* `tags` - (Optional) A map of tags to assign to the resource.

The `patch_source` block supports:

* `name` - (Required) The name specified to identify the patch source.
* `configuration` - (Required) The value of the yum repo configuration.
* `products` - (Required) The specific operating system versions a patch repository applies to, such as `"Ubuntu16.04"`, `"AmazonLinux2016.09"`, `"RedhatEnterpriseLinux7.2"` or `"Suse12.7"`. For lists of supported product values, see [PatchFilter](https://docs.aws.amazon.com/systems-manager/latest/APIReference/API_PatchFilter.html).

## Attributes Reference

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