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

assign policy to a managementgroup #2490

Merged
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
85 changes: 73 additions & 12 deletions azurerm/resource_arm_policy_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"context"
"fmt"
"log"
"regexp"
"strings"

"time"

"strconv"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/policy"
"github.com/Azure/go-autorest/autorest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/structure"
Expand All @@ -25,7 +27,7 @@ func resourceArmPolicyDefinition() *schema.Resource {
Read: resourceArmPolicyDefinitionRead,
Delete: resourceArmPolicyDefinitionDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
State: resourceArmPolicyDefinitionImport,
},

Schema: map[string]*schema.Schema{
Expand Down Expand Up @@ -56,6 +58,12 @@ func resourceArmPolicyDefinition() *schema.Resource {
}, true),
},

"management_group_id": {
Type: schema.TypeString,
Optional: true,
lawrenae marked this conversation as resolved.
Show resolved Hide resolved
ForceNew: true,
},

"display_name": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -99,6 +107,7 @@ func resourceArmPolicyDefinitionCreateUpdate(d *schema.ResourceData, meta interf
mode := d.Get("mode").(string)
displayName := d.Get("display_name").(string)
description := d.Get("description").(string)
managementGroupID := d.Get("management_group_id").(string)

properties := policy.DefinitionProperties{
PolicyType: policy.Type(policyType),
Expand Down Expand Up @@ -136,7 +145,15 @@ func resourceArmPolicyDefinitionCreateUpdate(d *schema.ResourceData, meta interf
DefinitionProperties: &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 err
}

Expand All @@ -145,16 +162,16 @@ func resourceArmPolicyDefinitionCreateUpdate(d *schema.ResourceData, meta interf
stateConf := &resource.StateChangeConf{
Pending: []string{"404"},
Target: []string{"200"},
Refresh: policyDefinitionRefreshFunc(ctx, client, name),
Refresh: policyDefinitionRefreshFunc(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 Definition %q to become available: %s", name, err)
}

resp, err := client.Get(ctx, name)
resp, err := getPolicyDefinition(ctx, client, name, managementGroupID)
if err != nil {
return err
}
Expand All @@ -173,7 +190,10 @@ func resourceArmPolicyDefinitionRead(d *schema.ResourceData, meta interface{}) e
return err
}

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

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

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

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

if props := resp.DefinitionProperties; props != nil {
d.Set("policy_type", props.PolicyType)
Expand Down Expand Up @@ -235,7 +256,14 @@ func resourceArmPolicyDefinitionDelete(d *schema.ResourceData, meta interface{})
return err
}

resp, err := client.Delete(ctx, name)
managementGroupID := parseManagementGroupIdFromPolicyId(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) {
Expand All @@ -255,20 +283,53 @@ func parsePolicyDefinitionNameFromId(id string) (string, error) {
return "", fmt.Errorf("Azure Policy Definition Id is empty or not formatted correctly: %s", id)
}

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

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

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

return components[6], nil
return ""
}

func policyDefinitionRefreshFunc(ctx context.Context, client policy.DefinitionsClient, name string) resource.StateRefreshFunc {
func policyDefinitionRefreshFunc(ctx context.Context, client policy.DefinitionsClient, name string, managementGroupID string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(ctx, name)
res, err := getPolicyDefinition(ctx, client, name, managementGroupID)

if err != nil {
return nil, strconv.Itoa(res.StatusCode), fmt.Errorf("Error issuing read request in policyAssignmentRefreshFunc for Policy Assignment %q: %s", name, err)
}

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

func resourceArmPolicyDefinitionImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
if managementGroupID := parseManagementGroupIdFromPolicyId(d.Id()); managementGroupID != "" {
d.Set("management_group_id", managementGroupID)

if name, _ := parsePolicyDefinitionNameFromId(d.Id()); name != "" {
d.Set("name", name)
}

return []*schema.ResourceData{d}, nil
}

//import a subscription based policy as before
return schema.ImportStatePassthrough(d, meta)
}

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

return res, err
}
95 changes: 95 additions & 0 deletions azurerm/resource_arm_policy_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,58 @@ func TestAccAzureRMPolicyDefinition_basic(t *testing.T) {
})
}

func TestAccAzureRMPolicyDefinitionAtMgmtGroup_basic(t *testing.T) {
resourceName := "azurerm_policy_definition.test"
mgmtGroupName := "azurerm_management_group.test"

ri := acctest.RandInt()

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

func testCheckAzureRMPolicyDefinitionExistsInMgmtGroup(policyName string, managementGroupName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[policyName]
if !ok {
return fmt.Errorf("not found: %s", policyName)
}

policyName := rs.Primary.Attributes["name"]
managementGroupID := rs.Primary.Attributes["management_group_id"]

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

resp, err := client.GetAtManagementGroup(ctx, policyName, managementGroupID)
if err != nil {
return fmt.Errorf("Bad: GetAtManagementGroup on policyDefinitionsClient: %s", err)
}

if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("policy does not exist: %s", policyName)
}

return nil
}
}

func testCheckAzureRMPolicyDefinitionExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
Expand Down Expand Up @@ -122,3 +174,46 @@ PARAMETERS
}
`, ri, ri)
}

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

resource "azurerm_policy_definition" "test" {
name = "acctestpol-%d"
policy_type = "Custom"
mode = "All"
display_name = "acctestpol-%d"
management_group_id = "${azurerm_management_group.test.group_id}"

policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "audit"
}
}
POLICY_RULE

parameters = <<PARAMETERS
{
"allowedLocations": {
"type": "Array",
"metadata": {
"description": "The list of allowed locations for resources.",
"displayName": "Allowed locations",
"strongType": "location"
}
}
}
PARAMETERS
}
`, ri, ri, ri)
}
12 changes: 11 additions & 1 deletion website/docs/r/policy_definition.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ description: |-

# azurerm_policy_definition

Manages a policy rule definition. Policy definitions do not take effect until they are assigned to a scope using a Policy Assignment.
Manages a policy rule definition on a management group or your provider subscription.

Policy definitions do not take effect until they are assigned to a scope using a Policy Assignment.

## Example Usage

Expand Down Expand Up @@ -67,6 +69,10 @@ The following arguments are supported:

* `description` - (Optional) The description of the policy 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`.

* `policy_rule` - (Optional) The policy rule for the policy definition. This
is a json object representing the rule that contains an if and
a then block.
Expand All @@ -91,3 +97,7 @@ Policy Definitions can be imported using the `policy name`, e.g.
```shell
terraform import azurerm_policy_definition.testPolicy /subscriptions/<SUBSCRIPTION_ID>/providers/Microsoft.Authorization/policyDefinitions/<POLICY_NAME>
```
or
```shell
terraform import azurerm_policy_definition.testPolicy /providers/Microsoft.Management/managementgroups/<MANGAGEMENT_GROUP_ID>/providers/Microsoft.Authorization/policyDefinitions/<POLICY_NAME>
```