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

New data source: azurerm_policy_set_definition #6305

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
114 changes: 114 additions & 0 deletions azurerm/internal/services/policy/data_source_policy_set_definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package policy

import (
"encoding/json"
"fmt"
"time"

"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/policy"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
)

func dataSourceArmPolicySetDefinition() *schema.Resource {
return &schema.Resource{
Read: dataSourceArmPolicySetDefinitionRead,

Timeouts: &schema.ResourceTimeout{
Read: schema.DefaultTimeout(5 * time.Minute),
},

Schema: map[string]*schema.Schema{
"display_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsNotEmpty,
ExactlyOneOf: []string{"name", "display_name"},
},

"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringIsNotEmpty,
ExactlyOneOf: []string{"name", "display_name"},
},

"management_group_name": {
Type: schema.TypeString,
Optional: true,
},

"description": {
Type: schema.TypeString,
Computed: true,
},

"metadata": {
Type: schema.TypeString,
Computed: true,
},

"parameters": {
Type: schema.TypeString,
Computed: true,
},

"policy_definitions": {
Type: schema.TypeString,
Computed: true,
},

"policy_type": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func dataSourceArmPolicySetDefinitionRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients.Client).Policy.SetDefinitionsClient
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

name := d.Get("name").(string)
displayName := d.Get("display_name").(string)
managementGroupID := d.Get("management_group_name").(string)

var setDefinition policy.SetDefinition
var err error

// we marked `display_name` and `name` as `ExactlyOneOf`, therefore there will only be one of display_name and name that have non-empty value here
if displayName != "" {
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
setDefinition, err = getPolicySetDefinitionByDisplayName(ctx, client, displayName, managementGroupID)
if err != nil {
return fmt.Errorf("failed to read Policy Set Definition (Display Name %q): %+v", displayName, err)
}
}
if name != "" {
setDefinition, err = getPolicySetDefinitionByName(ctx, client, name, managementGroupID)
if err != nil {
return fmt.Errorf("failed to read Policy Set Definition %q: %+v", name, err)
}
}

d.SetId(*setDefinition.ID)
d.Set("name", setDefinition.Name)
d.Set("display_name", setDefinition.DisplayName)
d.Set("description", setDefinition.Description)
d.Set("policy_type", setDefinition.PolicyType)
d.Set("metadata", flattenJSON(setDefinition.Metadata))
d.Set("parameters", flattenJSON(setDefinition.Parameters))

definitionBytes, err := json.Marshal(setDefinition.PolicyDefinitions)
if err != nil {
return fmt.Errorf("unable to flatten JSON for `policy_defintions`: %+v", err)
}
d.Set("policy_definitions", string(definitionBytes))

return nil
}
6 changes: 3 additions & 3 deletions azurerm/internal/services/policy/parse/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ type PolicyDefinitionId struct {
PolicyScopeId
}

// TODO: This paring function is currently suppressing every case difference due to github issue: https://github.com/Azure/azure-rest-api-specs/issues/8353
// TODO: This parsing function is currently suppressing every case difference due to github issue: https://github.com/Azure/azure-rest-api-specs/issues/8353
func PolicyDefinitionID(input string) (*PolicyDefinitionId, error) {
// in general, the id of a definition should be:
// {scope}/providers/Microsoft.PolicyInsights/remediations/{name}
// {scope}/providers/Microsoft.Authorization/policyDefinitions/{name}
regex := regexp.MustCompile(`/providers/[Mm]icrosoft\.[Aa]uthorization/policy[Dd]efinitions/`)
if !regex.MatchString(input) {
return nil, fmt.Errorf("unable to parse Policy Definition ID %q", input)
Expand All @@ -28,7 +28,7 @@ func PolicyDefinitionID(input string) (*PolicyDefinitionId, error) {
scope := segments[0]
name := segments[1]
if name == "" {
return nil, fmt.Errorf("unable to parse Policy Definition ID %q: assignment name is empty", input)
return nil, fmt.Errorf("unable to parse Policy Definition ID %q: definition name is empty", input)
}

scopeId, err := PolicyScopeID(scope)
Expand Down
43 changes: 43 additions & 0 deletions azurerm/internal/services/policy/parse/set_definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package parse

import (
"fmt"
"regexp"
)

type PolicySetDefinitionId struct {
Name string
PolicyScopeId
}

// TODO: This parsing function is currently suppressing case difference due to github issue: https://github.com/Azure/azure-rest-api-specs/issues/8353
func PolicySetDefinitionID(input string) (*PolicySetDefinitionId, error) {
// in general, the id of a set definition should be:
// {scope}/providers/Microsoft.Authorization/policySetDefinitions/set1
regex := regexp.MustCompile(`/providers/[Mm]icrosoft\.[Aa]uthorization/policy[Ss]et[Dd]efinitions/`)
if !regex.MatchString(input) {
return nil, fmt.Errorf("unable to parse Policy Set Definition ID %q", input)
}

segments := regex.Split(input, -1)

if len(segments) != 2 {
return nil, fmt.Errorf("unable to parse Policy Set Definition ID %q: Expected 2 segments after split", input)
}

scope := segments[0]
name := segments[1]
if name == "" {
return nil, fmt.Errorf("unable to parse Policy Set Definition ID %q: set definition name is empty", input)
}

scopeId, err := PolicyScopeID(scope)
if err != nil {
return nil, fmt.Errorf("unable to parse Policy Set Definition ID %q: %+v", input, err)
}

return &PolicySetDefinitionId{
Name: name,
PolicyScopeId: scopeId,
}, nil
}
85 changes: 85 additions & 0 deletions azurerm/internal/services/policy/parse/set_definition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package parse

import (
"reflect"
"testing"
)

func TestPolicySetDefinitionID(t *testing.T) {
testData := []struct {
Name string
Input string
Error bool
Expected *PolicySetDefinitionId
}{
{
Name: "empty",
Input: "",
Error: true,
},
{
Name: "regular policy set definition",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/set1",
Expected: &PolicySetDefinitionId{
Name: "set1",
PolicyScopeId: ScopeAtSubscription{
scopeId: "/subscriptions/00000000-0000-0000-0000-000000000000",
SubscriptionId: "00000000-0000-0000-0000-000000000000",
},
},
},
{
Name: "regular policy set definition but no name",
Input: "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/",
Error: true,
},
{
Name: "policy set definition in management group",
Input: "/providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/set1",
Expected: &PolicySetDefinitionId{
Name: "set1",
PolicyScopeId: ScopeAtManagementGroup{
scopeId: "/providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000",
ManagementGroupId: "00000000-0000-0000-0000-000000000000",
},
},
},
{
Name: "policy set definition in management group with inconsistent casing",
Input: "/providers/Microsoft.Management/managementgroups/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/set1",
Expected: &PolicySetDefinitionId{
Name: "set1",
PolicyScopeId: ScopeAtManagementGroup{
scopeId: "/providers/Microsoft.Management/managementgroups/00000000-0000-0000-0000-000000000000",
ManagementGroupId: "00000000-0000-0000-0000-000000000000",
},
},
},
{
Name: "policy set definition in management group but no name",
Input: "/providers/Microsoft.Management/managementgroups/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policySetDefinitions/",
Error: true,
},
}

for _, v := range testData {
t.Logf("[DEBUG] Testing %q", v.Name)

actual, err := PolicySetDefinitionID(v.Input)
if err != nil {
if v.Error {
continue
}

t.Fatalf("Expected a value but got an error: %+v", err)
}

if actual.Name != v.Expected.Name {
t.Fatalf("Expected %q but got %q", v.Expected.Name, actual.Name)
}

if !reflect.DeepEqual(v.Expected.PolicyScopeId, actual.PolicyScopeId) {
t.Fatalf("Expected %+v but got %+v", v.Expected.PolicyScopeId, actual.PolicyScopeId)
}
}
}
48 changes: 48 additions & 0 deletions azurerm/internal/services/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,51 @@ func getPolicyDefinitionByName(ctx context.Context, client *policy.DefinitionsCl

return res, err
}

func getPolicySetDefinitionByName(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
}

func getPolicySetDefinitionByDisplayName(ctx context.Context, client *policy.SetDefinitionsClient, displayName, managementGroupID string) (policy.SetDefinition, error) {
var setDefinitions policy.SetDefinitionListResultIterator
var err error

if managementGroupID != "" {
setDefinitions, err = client.ListByManagementGroupComplete(ctx, managementGroupID)
} else {
setDefinitions, err = client.ListComplete(ctx)
}
if err != nil {
return policy.SetDefinition{}, fmt.Errorf("failed to load Policy Set Definition List: %+v", err)
}

var results []policy.SetDefinition
for setDefinitions.NotDone() {
def := setDefinitions.Value()
if def.DisplayName != nil && *def.DisplayName == displayName && def.ID != nil {
results = append(results, def)
}

if err := setDefinitions.NextWithContext(ctx); err != nil {
return policy.SetDefinition{}, fmt.Errorf("failed to load Policy Set Definition List: %s", err)
}
}

// throw error when we found none
if len(results) == 0 {
return policy.SetDefinition{}, fmt.Errorf("failed to load Policy Set Definition List: could not find policy '%s'", displayName)
}

// throw error when we found more than one
if len(results) > 1 {
return policy.SetDefinition{}, fmt.Errorf("failed to load Policy Set Definition List: found more than one policy set definition '%s'", displayName)
}

return results[0], nil
}
3 changes: 2 additions & 1 deletion azurerm/internal/services/policy/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ func (r Registration) WebsiteCategories() []string {
// SupportedDataSources returns the supported Data Sources supported by this Service
func (r Registration) SupportedDataSources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azurerm_policy_definition": dataSourceArmPolicyDefinition(),
"azurerm_policy_definition": dataSourceArmPolicyDefinition(),
"azurerm_policy_set_definition": dataSourceArmPolicySetDefinition(),
}
}

Expand Down
15 changes: 12 additions & 3 deletions azurerm/internal/services/policy/resource_arm_policy_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate"
azSchema "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)
Expand All @@ -26,9 +29,11 @@ func resourceArmPolicyAssignment() *schema.Resource {
Update: resourceArmPolicyAssignmentCreateUpdate,
Read: resourceArmPolicyAssignmentRead,
Delete: resourceArmPolicyAssignmentDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Importer: azSchema.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.PolicyAssignmentID(id)
return err
}),

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Minute),
Expand All @@ -54,6 +59,10 @@ func resourceArmPolicyAssignment() *schema.Resource {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.Any(
validate.PolicyDefinitionID,
validate.PolicySetDefinitionID,
),
},

"description": {
Expand Down
Loading