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

Support for Management Groups in azurerm_policy_set_definition #2618

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
70 changes: 59 additions & 11 deletions azurerm/resource_arm_policy_set_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"fmt"
"log"
"reflect"
"regexp"
"strconv"
"strings"
"time"

"github.com/Azure/go-autorest/autorest"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/policy"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
Expand Down Expand Up @@ -47,6 +50,12 @@ func resourceArmPolicySetDefinition() *schema.Resource {
}, false),
},

"management_group_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"display_name": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -106,6 +115,7 @@ func resourceArmPolicySetDefinitionCreateUpdate(d *schema.ResourceData, meta int
policyType := d.Get("policy_type").(string)
displayName := d.Get("display_name").(string)
description := d.Get("description").(string)
managementGroupID := d.Get("management_group_id").(string)

properties := policy.SetDefinitionProperties{
PolicyType: policy.Type(policyType),
Expand Down Expand Up @@ -143,7 +153,14 @@ func resourceArmPolicySetDefinitionCreateUpdate(d *schema.ResourceData, meta int
SetDefinitionProperties: &properties,
}

if _, err := client.CreateOrUpdate(ctx, name, definition); err != nil {
var err error
if managementGroupID == "" {
_, err = client.CreateOrUpdate(ctx, name, definition)
} else {
_, err = client.CreateOrUpdateAtManagementGroup(ctx, name, definition, managementGroupID)
}

if err != nil {
return fmt.Errorf("Error creating/updating Policy Set Definition %q: %s", name, err)
}

Expand All @@ -152,16 +169,18 @@ func resourceArmPolicySetDefinitionCreateUpdate(d *schema.ResourceData, meta int
stateConf := &resource.StateChangeConf{
Pending: []string{"404"},
Target: []string{"200"},
Refresh: policySetDefinitionRefreshFunc(ctx, client, name),
Refresh: policySetDefinitionRefreshFunc(ctx, client, name, managementGroupID),
Timeout: 5 * time.Minute,
MinTimeout: 10 * time.Second,
ContinuousTargetOccurence: 10,
}
if _, err := stateConf.WaitForState(); err != nil {

if _, err = stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Policy Set Definition %q to become available: %s", name, err)
}

resp, err := client.Get(ctx, name)
var resp policy.SetDefinition
resp, err = getPolicySetDefinition(ctx, client, name, managementGroupID)
if err != nil {
return fmt.Errorf("Error retrieving Policy Set Definition %q: %s", name, err)
}
Expand All @@ -180,7 +199,10 @@ func resourceArmPolicySetDefinitionRead(d *schema.ResourceData, meta interface{}
return err
}

resp, err := client.Get(ctx, name)
managementGroupID := parseManagementGroupIdFromPolicySetId(d.Id())

resp, err := getPolicySetDefinition(ctx, client, name, managementGroupID)

if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[INFO] Error reading Policy Set Definition %q - removing from state", d.Id())
Expand All @@ -192,6 +214,7 @@ func resourceArmPolicySetDefinitionRead(d *schema.ResourceData, meta interface{}
}

d.Set("name", resp.Name)
d.Set("management_group_id", managementGroupID)

if props := resp.SetDefinitionProperties; props != nil {
d.Set("policy_type", string(props.PolicyType))
Expand Down Expand Up @@ -241,7 +264,15 @@ func resourceArmPolicySetDefinitionDelete(d *schema.ResourceData, meta interface
return err
}

resp, err := client.Delete(ctx, name)
managementGroupID := parseManagementGroupIdFromPolicySetId(d.Id())

var resp autorest.Response
if managementGroupID == "" {
resp, err = client.Delete(ctx, name)
} else {
resp, err = client.DeleteAtManagementGroup(ctx, name, managementGroupID)
}

if err != nil {
if utils.ResponseWasNotFound(resp) {
return nil
Expand All @@ -260,20 +291,37 @@ func parsePolicySetDefinitionNameFromId(id string) (string, error) {
return "", fmt.Errorf("Azure Policy Set Definition Id is empty or not formatted correctly: %s", id)
}

if len(components) != 7 {
return "", fmt.Errorf("Azure Policy Set Definition Id should have 6 segments, got %d: '%s'", len(components)-1, id)
return components[len(components)-1], nil
}

func parseManagementGroupIdFromPolicySetId(id string) string {
r, _ := regexp.Compile("managementgroups/(.+)/providers/.*$")

if r.MatchString(id) {
matches := r.FindAllStringSubmatch(id, -1)[0]
return matches[1]
}

return components[6], nil
return ""
}

func policySetDefinitionRefreshFunc(ctx context.Context, client policy.SetDefinitionsClient, name string) resource.StateRefreshFunc {
func policySetDefinitionRefreshFunc(ctx context.Context, client policy.SetDefinitionsClient, name string, managementGroupId string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, name)
res, err := getPolicySetDefinition(ctx, client, name, managementGroupId)
if err != nil {
return nil, strconv.Itoa(res.StatusCode), fmt.Errorf("Error issuing read request in policySetDefinitionRefreshFunc for Policy Set Definition %q: %s", name, err)
}

return res, strconv.Itoa(res.StatusCode), nil
}
}

func getPolicySetDefinition(ctx context.Context, client policy.SetDefinitionsClient, name string, managementGroupID string) (res policy.SetDefinition, err error) {
if managementGroupID == "" {
res, err = client.Get(ctx, name)
} else {
res, err = client.GetAtManagementGroup(ctx, name, managementGroupID)
}

return res, err
}
108 changes: 97 additions & 11 deletions azurerm/resource_arm_policy_set_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package azurerm

import (
"fmt"
"net/http"
"testing"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/policy"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"

"github.com/hashicorp/terraform/terraform"

"github.com/hashicorp/terraform/helper/acctest"
Expand Down Expand Up @@ -61,6 +63,31 @@ func TestAccAzureRMPolicySetDefinition_custom(t *testing.T) {
})
}

func TestAccAzureRMPolicySetDefinition_ManagementGroup(t *testing.T) {
resourceName := "azurerm_policy_set_definition.test"

ri := acctest.RandInt()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMPolicySetDefinitionDestroy,
Steps: []resource.TestStep{
{
Config: testAzureRMPolicySetDefinition_ManagementGroup(ri),
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMPolicySetDefinitionExists(resourceName),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAzureRMPolicySetDefinition_builtIn(ri int) string {
return fmt.Sprintf(`
resource "azurerm_policy_set_definition" "test" {
Expand Down Expand Up @@ -167,6 +194,47 @@ POLICY_DEFINITIONS
`, ri, ri, ri, ri)
}

func testAzureRMPolicySetDefinition_ManagementGroup(ri int) string {
return fmt.Sprintf(`
resource "azurerm_management_group" "test" {
display_name = "acctestmg-%d"
}

resource "azurerm_policy_set_definition" "test" {
name = "acctestpolset-%d"
policy_type = "Custom"
display_name = "acctestpolset-%d"
management_group_id = "${azurerm_management_group.test.group_id}"

parameters = <<PARAMETERS
{
"allowedLocations": {
"type": "Array",
"metadata": {
"description": "The list of allowed locations for resources.",
"displayName": "Allowed locations",
"strongType": "location"
}
}
}
PARAMETERS

policy_definitions = <<POLICY_DEFINITIONS
[
{
"parameters": {
"listOfAllowedLocations": {
"value": "[parameters('allowedLocations')]"
}
},
"policyDefinitionId": "/providers/Microsoft.Authorization/policyDefinitions/e765b5de-1225-4ba3-bd56-1ac6695af988"
}
]
POLICY_DEFINITIONS
}
`, ri, ri, ri)
}

func testCheckAzureRMPolicySetDefinitionExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
Expand All @@ -175,17 +243,25 @@ func testCheckAzureRMPolicySetDefinitionExists(resourceName string) resource.Tes
}

policySetName := rs.Primary.Attributes["name"]
managementGroupId := rs.Primary.Attributes["management_group_id"]

client := testAccProvider.Meta().(*ArmClient).policySetDefinitionsClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext

resp, err := client.Get(ctx, policySetName)
if err != nil {
return fmt.Errorf("Bad: Get on policySetDefinitionsClient: %s", err)
var err error
var resp policy.SetDefinition
if managementGroupId != "" {
resp, err = client.GetAtManagementGroup(ctx, policySetName, managementGroupId)
} else {
resp, err = client.Get(ctx, policySetName)
}

if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("policy set definition does not exist: %s", policySetName)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("policy set definition does not exist: %s", policySetName)
} else {
return fmt.Errorf("Bad: Get on policySetDefinitionsClient: %s", err)
}
}

return nil
Expand All @@ -201,16 +277,26 @@ func testCheckAzureRMPolicySetDefinitionDestroy(s *terraform.State) error {
continue
}

name := rs.Primary.Attributes["name"]
policySetName := rs.Primary.Attributes["name"]
managementGroupId := rs.Primary.Attributes["management_group_id"]

resp, err := client.Get(ctx, name)
if err != nil {
return nil
var err error
var resp policy.SetDefinition
if managementGroupId != "" {
resp, err = client.GetAtManagementGroup(ctx, policySetName, managementGroupId)
} else {
resp, err = client.Get(ctx, policySetName)
}

if resp.StatusCode != http.StatusNotFound {
if err == nil {
return fmt.Errorf("policy set definition still exists: %s", *resp.Name)
}

if utils.ResponseWasNotFound(resp.Response) {
return nil
} else {
return err
}
}

return nil
Expand Down
8 changes: 8 additions & 0 deletions website/docs/r/policy_set_definition.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ The following arguments are supported:

* `description` - (Optional) The description of the policy set definition.

* `management_group_id` - (Optional) The ID of the Management Group where this policy should be defined. Changing this forces a new resource to be created.

~> **Note:** if you are using `azurerm_management_group` to assign a value to `management_group_id`, be sure to use `.group_id` and not `.id`.

* `metadata` - (Optional) The metadata for the policy set definition. This is a json object representing additional metadata that should be stored with the policy definition.

* `parameters` - (Optional) Parameters for the policy set definition. This field is a json object that allows you to parameterize your policy definition.
Expand All @@ -79,3 +83,7 @@ Policy Set Definitions can be imported using the Resource ID, e.g.
```shell
terraform import azurerm_policy_set_definition.test /subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/testPolicySet
```
or
```shell
terraform import azurerm_policy_set_definition.test /providers/Microsoft.Management/managementgroups/my-mgmt-group-id/providers/Microsoft.Authorization/policySetDefinitions/testPolicySet
```