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 1 commit
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
141 changes: 141 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,141 @@
package policy

import (
"context"
"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_id": {
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
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_id").(string)

var setDefinition policy.SetDefinition
var err error

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 = getPolicySetDefinition(ctx, client, name, managementGroupID)
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
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
}

func getPolicySetDefinitionByDisplayName(ctx context.Context, client *policy.SetDefinitionsClient, displayName, managementGroupID string) (policy.SetDefinition, error) {
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
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 Definition List: %+v", err)
}

for setDefinitions.NotDone() {
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
def := setDefinitions.Value()
if def.DisplayName != nil && *def.DisplayName == displayName && def.ID != nil {
return def, nil
}

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

return policy.SetDefinition{}, fmt.Errorf("failed to load Policy Definition List: could not find policy '%s'", displayName)
}
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/policyDefinition/{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/`)
ArcturusZhang marked this conversation as resolved.
Show resolved Hide resolved
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)
}
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ 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"
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 @@ -30,9 +32,11 @@ func resourceArmPolicySetDefinition() *schema.Resource {
Update: resourceArmPolicySetDefinitionCreateUpdate,
Read: resourceArmPolicySetDefinitionRead,
Delete: resourceArmPolicySetDefinitionDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

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

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(30 * time.Minute),
Expand Down
Loading