From d7c6ed3243d44f1f4e5ffdbdbf47a459dd30a230 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 09:52:24 +0200 Subject: [PATCH 01/30] r/policy_assignment: matching the behaviour of the identity block with other resources --- .../policy/policy_assignment_resource.go | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 9ac2ea17cbec..ad260fca3dfd 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -79,7 +79,6 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { "identity": { Type: pluginsdk.TypeList, Optional: true, - Computed: true, MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ @@ -88,7 +87,6 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { Optional: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{ - string(policy.None), string(policy.SystemAssigned), }, false), }, @@ -351,29 +349,35 @@ func expandAzureRmPolicyIdentity(input []interface{}) *policy.Identity { if len(input) == 0 || input[0] == nil { return nil } - identity := input[0].(map[string]interface{}) + identity := input[0].(map[string]interface{}) return &policy.Identity{ Type: policy.ResourceIdentityType(identity["type"].(string)), } } func flattenAzureRmPolicyIdentity(identity *policy.Identity) []interface{} { - if identity == nil { + if identity == nil || identity.Type == policy.None { return make([]interface{}, 0) } - result := make(map[string]interface{}) - result["type"] = string(identity.Type) + principalId := "" if identity.PrincipalID != nil { - result["principal_id"] = *identity.PrincipalID + principalId = *identity.PrincipalID } + tenantId := "" if identity.TenantID != nil { - result["tenant_id"] = *identity.TenantID + tenantId = *identity.TenantID } - return []interface{}{result} + return []interface{}{ + map[string]interface{}{ + "principal_id": principalId, + "tenant_id": tenantId, + "type": string(identity.Type), + }, + } } func expandAzureRmPolicyNotScopes(input []interface{}) *[]string { From ed3c08bb290c936b29c1300c32895f1735443bfc Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 10:39:45 +0200 Subject: [PATCH 02/30] r/policy_assignment: refactoring --- .../services/policy/parse/assignment.go | 31 +++++- .../policy/policy_assignment_resource.go | 104 +++++++++--------- .../docs/r/policy_assignment.html.markdown | 16 ++- 3 files changed, 89 insertions(+), 62 deletions(-) diff --git a/azurerm/internal/services/policy/parse/assignment.go b/azurerm/internal/services/policy/parse/assignment.go index 80d608fbbc48..173d72c788b2 100644 --- a/azurerm/internal/services/policy/parse/assignment.go +++ b/azurerm/internal/services/policy/parse/assignment.go @@ -3,13 +3,41 @@ package parse import ( "fmt" "regexp" + "strings" ) type PolicyAssignmentId struct { - Name string + Name string + Scope string PolicyScopeId } +func (id PolicyAssignmentId) String() string { + segments := []string{ + fmt.Sprintf("Assignment Name %q", id.Name), + fmt.Sprintf("Scope %q", id.Scope), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Policy Assignment ID", segmentsStr) +} + +func (id PolicyAssignmentId) ID() string { + fmtString := "%s/providers/Microsoft.Authorization/policyAssignment/%s" + return fmt.Sprintf(fmtString, id.Scope, id.Name) +} + +func NewPolicyAssignmentId(scope, name string) (*PolicyAssignmentId, error) { + scopeId, err := PolicyScopeID(scope) + if err != nil { + return nil, fmt.Errorf("parsing Policy Scope ID %q: %+v", scope, err) + } + + return &PolicyAssignmentId{ + Name: name, + PolicyScopeId: scopeId, + }, nil +} + // TODO: This paring function is currently suppressing every case difference due to github issue: https://github.com/Azure/azure-rest-api-specs/issues/8353 func PolicyAssignmentID(input string) (*PolicyAssignmentId, error) { // in general, the id of a assignment should be: @@ -38,6 +66,7 @@ func PolicyAssignmentID(input string) (*PolicyAssignmentId, error) { return &PolicyAssignmentId{ Name: name, + Scope: scope, PolicyScopeId: scopeId, }, nil } diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index ad260fca3dfd..4b84072c24b4 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -75,7 +75,6 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { "location": azure.SchemaLocationOptional(), - //lintignore:XS003 "identity": { Type: pluginsdk.TypeList, Optional: true, @@ -119,7 +118,9 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { "not_scopes": { Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{Type: pluginsdk.TypeString}, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, }, "metadata": { @@ -163,19 +164,21 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) defer cancel() - name := d.Get("name").(string) - scope := d.Get("scope").(string) + id, err := parse.NewPolicyAssignmentId(d.Get("scope").(string), d.Get("name").(string)) + if err != nil { + return err + } if d.IsNewResource() { - existing, err := client.Get(ctx, scope, name) + existing, err := client.Get(ctx, id.Scope, id.Name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing Policy Assignment %q: %s", name, err) + return fmt.Errorf("checking for presence of existing %s: %+v", *id, err) } } - if existing.ID != nil && *existing.ID != "" { - return tf.ImportAsExistsError("azurerm_policy_assignment", *existing.ID) + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_policy_assignment", id.ID()) } } @@ -183,7 +186,7 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int AssignmentProperties: &policy.AssignmentProperties{ PolicyDefinitionID: utils.String(d.Get("policy_definition_id").(string)), DisplayName: utils.String(d.Get("display_name").(string)), - Scope: utils.String(scope), + Scope: utils.String(id.Scope), EnforcementMode: convertEnforcementMode(d.Get("enforcement_mode").(bool)), }, } @@ -192,17 +195,17 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int assignment.AssignmentProperties.Description = utils.String(v) } + if v := d.Get("location").(string); v != "" { + assignment.Location = utils.String(azure.NormalizeLocation(v)) + } + if v, ok := d.GetOk("identity"); ok { - if location := d.Get("location").(string); location == "" { + if assignment.Location == nil { return fmt.Errorf("`location` must be set when `identity` is assigned") } assignment.Identity = expandAzureRmPolicyIdentity(v.([]interface{})) } - if v := d.Get("location").(string); v != "" { - assignment.Location = utils.String(azure.NormalizeLocation(v)) - } - if v := d.Get("parameters").(string); v != "" { expandedParams, err := expandParameterValuesValueFromString(v) if err != nil { @@ -224,16 +227,16 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int assignment.AssignmentProperties.NotScopes = expandAzureRmPolicyNotScopes(v.([]interface{})) } - if _, err := client.Create(ctx, scope, name, assignment); err != nil { - return fmt.Errorf("creating/updating Policy Assignment %q (Scope %q): %+v", name, scope, err) + if _, err := client.Create(ctx, id.Scope, id.Name, assignment); err != nil { + return fmt.Errorf("creating/updating %s: %+v", *id, err) } // Policy Assignments are eventually consistent; wait for them to stabilize - log.Printf("[DEBUG] Waiting for Policy Assignment %q to become available", name) + log.Printf("[DEBUG] Waiting for %s to become available", *id) stateConf := &pluginsdk.StateChangeConf{ Pending: []string{"404"}, Target: []string{"200"}, - Refresh: policyAssignmentRefreshFunc(ctx, client, scope, name), + Refresh: policyAssignmentRefreshFunc(ctx, client, *id), MinTimeout: 10 * time.Second, ContinuousTargetOccurence: 10, } @@ -245,19 +248,10 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int } if _, err := stateConf.WaitForStateContext(ctx); err != nil { - return fmt.Errorf("waiting for Policy Assignment %q to become available: %s", name, err) + return fmt.Errorf("waiting for %s to become available: %s", id, err) } - resp, err := client.Get(ctx, scope, name) - if err != nil { - return fmt.Errorf("retrieving Policy Assignment %q (Scope %q): %+v", name, scope, err) - } - - if resp.ID == nil || *resp.ID == "" { - return fmt.Errorf("empty or nil ID returned for Policy Assignment %q (Scope %q)", name, scope) - } - d.SetId(*resp.ID) - + d.SetId(id.ID()) return resourceArmPolicyAssignmentRead(d, meta) } @@ -266,20 +260,24 @@ func resourceArmPolicyAssignmentRead(d *pluginsdk.ResourceData, meta interface{} ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id := d.Id() + id, err := parse.PolicyAssignmentID(d.Id()) + if err != nil { + return err + } - resp, err := client.GetByID(ctx, id) + resp, err := client.Get(ctx, id.Scope, id.Name) if err != nil { if utils.ResponseWasNotFound(resp.Response) { - log.Printf("[INFO] Error reading Policy Assignment %q - removing from state", id) + log.Printf("[INFO] %s was not found - removing from state", *id) d.SetId("") return nil } - return fmt.Errorf("reading Policy Assignment %q: %+v", id, err) + return fmt.Errorf("reading %s: %+v", *id, err) } - d.Set("name", resp.Name) + d.Set("name", id.Name) + d.Set("scope", id.Scope) if err := d.Set("identity", flattenAzureRmPolicyIdentity(resp.Identity)); err != nil { return fmt.Errorf("setting `identity`: %+v", err) @@ -290,24 +288,18 @@ func resourceArmPolicyAssignmentRead(d *pluginsdk.ResourceData, meta interface{} } if props := resp.AssignmentProperties; props != nil { - d.Set("scope", props.Scope) d.Set("policy_definition_id", props.PolicyDefinitionID) d.Set("description", props.Description) d.Set("display_name", props.DisplayName) d.Set("enforcement_mode", props.EnforcementMode == policy.Default) + d.Set("metadata", flattenJSON(props.Metadata)) - if metadataStr := flattenJSON(props.Metadata); metadataStr != "" { - d.Set("metadata", metadataStr) + json, err := flattenParameterValuesValueToString(props.Parameters) + if err != nil { + return fmt.Errorf("serializing JSON from `parameters`: %+v", err) } - if params := props.Parameters; params != nil { - json, err := flattenParameterValuesValueToString(params) - if err != nil { - return fmt.Errorf("serializing JSON from `parameters`: %+v", err) - } - - d.Set("parameters", json) - } + d.Set("parameters", json) d.Set("not_scopes", props.NotScopes) } @@ -320,25 +312,25 @@ func resourceArmPolicyAssignmentDelete(d *pluginsdk.ResourceData, meta interface ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) defer cancel() - id := d.Id() - - resp, err := client.DeleteByID(ctx, id) + id, err := parse.PolicyAssignmentID(d.Id()) if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - return nil - } + return err + } + if _, err := client.Delete(ctx, id.Scope, id.Name); err != nil { return fmt.Errorf("deleting Policy Assignment %q: %+v", id, err) } + // TODO: presumably we want to wait for this to be fully gone too? + return nil } -func policyAssignmentRefreshFunc(ctx context.Context, client *policy.AssignmentsClient, scope string, name string) pluginsdk.StateRefreshFunc { +func policyAssignmentRefreshFunc(ctx context.Context, client *policy.AssignmentsClient, id parse.PolicyAssignmentId) pluginsdk.StateRefreshFunc { return func() (interface{}, string, error) { - res, err := client.Get(ctx, scope, name) + res, err := client.Get(ctx, id.Scope, id.Name) if err != nil { - return nil, strconv.Itoa(res.StatusCode), fmt.Errorf("issuing read request in policyAssignmentRefreshFunc for Policy Assignment %q (Scope: %q): %s", name, scope, err) + return nil, strconv.Itoa(res.StatusCode), fmt.Errorf("polling for %s: %+v", id, err) } return res, strconv.Itoa(res.StatusCode), nil @@ -347,7 +339,9 @@ func policyAssignmentRefreshFunc(ctx context.Context, client *policy.Assignments func expandAzureRmPolicyIdentity(input []interface{}) *policy.Identity { if len(input) == 0 || input[0] == nil { - return nil + return &policy.Identity{ + Type: policy.None, + } } identity := input[0].(map[string]interface{}) diff --git a/website/docs/r/policy_assignment.html.markdown b/website/docs/r/policy_assignment.html.markdown index dc7d13d36c05..fa9436ff9a92 100644 --- a/website/docs/r/policy_assignment.html.markdown +++ b/website/docs/r/policy_assignment.html.markdown @@ -88,14 +88,20 @@ The following arguments are supported: * `policy_definition_id` - (Required) The ID of the Policy Definition to be applied at the specified Scope. -* `identity` - (Optional) An `identity` block. - -* `location` - (Optional) The Azure location where this policy assignment should exist. This is required when an Identity is assigned. Changing this forces a new resource to be created. +--- * `description` - (Optional) A description to use for this Policy Assignment. Changing this forces a new resource to be created. * `display_name` - (Optional) A friendly display name to use for this Policy Assignment. Changing this forces a new resource to be created. +* `enforcement_mode`- (Optional) Can be set to 'true' or 'false' to control whether the assignment is enforced (true) or not (false). Default is 'true'. + +* `location` - (Optional) The Azure location where this policy assignment should exist. This is required when an Identity is assigned. Changing this forces a new resource to be created. + +* `identity` - (Optional) An `identity` block. + +-> **Note:** When `identity` is set the `location` field must also be set. + * `metadata` - (Optional) The metadata for the policy assignment. This is a JSON string representing additional metadata that should be stored with the policy assignment. * `parameters` - (Optional) Parameters for the policy definition. This field is a JSON string that maps to the Parameters field from the Policy Definition. Changing this forces a new resource to be created. @@ -104,13 +110,11 @@ The following arguments are supported: * `not_scopes` - (Optional) A list of the Policy Assignment's excluded scopes. The list must contain Resource IDs (such as Subscriptions e.g. `/subscriptions/00000000-0000-0000-000000000000` or Resource Groups e.g.`/subscriptions/00000000-0000-0000-000000000000/resourceGroups/myResourceGroup`). -* `enforcement_mode`- (Optional) Can be set to 'true' or 'false' to control whether the assignment is enforced (true) or not (false). Default is 'true'. - --- An `identity` block supports the following: -* `type` - (Required) The Managed Service Identity Type of this Policy Assignment. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you), or `None` (no use of a Managed Service Identity). +* `type` - (Required) The type of Managed Identity for this Policy Assignment. Possible values are `SystemAssigned` (where Azure will generate a Service Principal for you). ~> **NOTE:** When `type` is set to `SystemAssigned`, identity the Principal ID can be retrieved after the policy has been assigned. From 0a990e3ca8c755c77dd0aa99a7238d89feb9fd98 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 11:00:34 +0200 Subject: [PATCH 03/30] policy: refactoring out the common metadata --- azurerm/internal/services/policy/metadata.go | 45 +++++++++++++++++++ .../policy/policy_assignment_resource.go | 35 +-------------- .../policy/policy_definition_resource.go | 33 +------------- 3 files changed, 47 insertions(+), 66 deletions(-) create mode 100644 azurerm/internal/services/policy/metadata.go diff --git a/azurerm/internal/services/policy/metadata.go b/azurerm/internal/services/policy/metadata.go new file mode 100644 index 000000000000..633ac5d39763 --- /dev/null +++ b/azurerm/internal/services/policy/metadata.go @@ -0,0 +1,45 @@ +package policy + +import ( + "encoding/json" + "reflect" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" +) + +func metadataDiffSuppressFunc(_, old, new string, _ *pluginsdk.ResourceData) bool { + var oldPolicyAssignmentsMetadata map[string]interface{} + errOld := json.Unmarshal([]byte(old), &oldPolicyAssignmentsMetadata) + if errOld != nil { + return false + } + + var newPolicyAssignmentsMetadata map[string]interface{} + if new != "" { + errNew := json.Unmarshal([]byte(new), &newPolicyAssignmentsMetadata) + if errNew != nil { + return false + } + } + + // Ignore the following keys if they're found in the metadata JSON + ignoreKeys := [5]string{"assignedBy", "createdBy", "createdOn", "updatedBy", "updatedOn"} + for _, key := range ignoreKeys { + delete(oldPolicyAssignmentsMetadata, key) + delete(newPolicyAssignmentsMetadata, key) + } + + return reflect.DeepEqual(oldPolicyAssignmentsMetadata, newPolicyAssignmentsMetadata) +} + +func metadataSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: metadataDiffSuppressFunc, + } +} diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 4b84072c24b4..4679f22267e9 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -2,10 +2,8 @@ package policy import ( "context" - "encoding/json" "fmt" "log" - "reflect" "strconv" "time" @@ -123,42 +121,11 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { }, }, - "metadata": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: policyAssignmentsMetadataDiffSuppressFunc, - }, + "metadata": metadataSchema(), }, } } -func policyAssignmentsMetadataDiffSuppressFunc(_, old, new string, _ *pluginsdk.ResourceData) bool { - var oldPolicyAssignmentsMetadata map[string]interface{} - errOld := json.Unmarshal([]byte(old), &oldPolicyAssignmentsMetadata) - if errOld != nil { - return false - } - - var newPolicyAssignmentsMetadata map[string]interface{} - if new != "" { - errNew := json.Unmarshal([]byte(new), &newPolicyAssignmentsMetadata) - if errNew != nil { - return false - } - } - - // Ignore the following keys if they're found in the metadata JSON - ignoreKeys := [5]string{"assignedBy", "createdBy", "createdOn", "updatedBy", "updatedOn"} - for _, key := range ignoreKeys { - delete(oldPolicyAssignmentsMetadata, key) - delete(newPolicyAssignmentsMetadata, key) - } - - return reflect.DeepEqual(oldPolicyAssignmentsMetadata, newPolicyAssignmentsMetadata) -} - func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Policy.AssignmentsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) diff --git a/azurerm/internal/services/policy/policy_definition_resource.go b/azurerm/internal/services/policy/policy_definition_resource.go index ca5e4ebcd88a..00d3335bf51b 100644 --- a/azurerm/internal/services/policy/policy_definition_resource.go +++ b/azurerm/internal/services/policy/policy_definition_resource.go @@ -2,10 +2,8 @@ package policy import ( "context" - "encoding/json" "fmt" "log" - "reflect" "strconv" "time" @@ -118,40 +116,11 @@ func resourceArmPolicyDefinition() *pluginsdk.Resource { DiffSuppressFunc: pluginsdk.SuppressJsonDiff, }, - "metadata": { - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: policyDefinitionsMetadataDiffSuppressFunc, - }, + "metadata": metadataSchema(), }, } } -func policyDefinitionsMetadataDiffSuppressFunc(_, old, new string, _ *pluginsdk.ResourceData) bool { - var oldPolicyDefinitionsMetadata map[string]interface{} - errOld := json.Unmarshal([]byte(old), &oldPolicyDefinitionsMetadata) - if errOld != nil { - return false - } - - var newPolicyDefinitionsMetadata map[string]interface{} - errNew := json.Unmarshal([]byte(new), &newPolicyDefinitionsMetadata) - if errNew != nil { - return false - } - - // Ignore the following keys if they're found in the metadata JSON - ignoreKeys := [4]string{"createdBy", "createdOn", "updatedBy", "updatedOn"} - for _, key := range ignoreKeys { - delete(oldPolicyDefinitionsMetadata, key) - delete(newPolicyDefinitionsMetadata, key) - } - - return reflect.DeepEqual(oldPolicyDefinitionsMetadata, newPolicyDefinitionsMetadata) -} - func resourceArmPolicyDefinitionCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Policy.DefinitionsClient ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) From f9fe94593dac5bbd508ccbf044ca6a0f7d91e71e Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 11:11:14 +0200 Subject: [PATCH 04/30] r/policy_assignment: refactoring to use the identity block --- .../policy/policy_assignment_resource.go | 87 ++++++------------- 1 file changed, 28 insertions(+), 59 deletions(-) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 4679f22267e9..244791e4b8ae 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -7,6 +7,10 @@ import ( "strconv" "time" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/identity" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" @@ -19,6 +23,8 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) +type policyAssignmentIdentity = identity.SystemAssigned + func resourceArmPolicyAssignment() *pluginsdk.Resource { return &pluginsdk.Resource{ Create: resourceArmPolicyAssignmentCreateUpdate, @@ -73,31 +79,7 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { "location": azure.SchemaLocationOptional(), - "identity": { - Type: pluginsdk.TypeList, - Optional: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "type": { - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - string(policy.SystemAssigned), - }, false), - }, - "principal_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - "tenant_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, + "identity": policyAssignmentIdentity{}.Schema(), "parameters": { Type: pluginsdk.TypeString, @@ -170,7 +152,11 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int if assignment.Location == nil { return fmt.Errorf("`location` must be set when `identity` is assigned") } - assignment.Identity = expandAzureRmPolicyIdentity(v.([]interface{})) + identity, err := expandAzureRmPolicyIdentity(v.([]interface{})) + if err != nil { + + } + assignment.Identity = identity } if v := d.Get("parameters").(string); v != "" { @@ -245,15 +231,12 @@ func resourceArmPolicyAssignmentRead(d *pluginsdk.ResourceData, meta interface{} d.Set("name", id.Name) d.Set("scope", id.Scope) + d.Set("location", location.NormalizeNilable(resp.Location)) if err := d.Set("identity", flattenAzureRmPolicyIdentity(resp.Identity)); err != nil { return fmt.Errorf("setting `identity`: %+v", err) } - if location := resp.Location; location != nil { - d.Set("location", azure.NormalizeLocation(*location)) - } - if props := resp.AssignmentProperties; props != nil { d.Set("policy_definition_id", props.PolicyDefinitionID) d.Set("description", props.Description) @@ -304,41 +287,27 @@ func policyAssignmentRefreshFunc(ctx context.Context, client *policy.Assignments } } -func expandAzureRmPolicyIdentity(input []interface{}) *policy.Identity { - if len(input) == 0 || input[0] == nil { - return &policy.Identity{ - Type: policy.None, - } +func expandAzureRmPolicyIdentity(input []interface{}) (*policy.Identity, error) { + expanded, err := policyAssignmentIdentity{}.Expand(input) + if err != nil { + return nil, err } - identity := input[0].(map[string]interface{}) return &policy.Identity{ - Type: policy.ResourceIdentityType(identity["type"].(string)), - } + Type: policy.ResourceIdentityType(expanded.Type), + }, nil } -func flattenAzureRmPolicyIdentity(identity *policy.Identity) []interface{} { - if identity == nil || identity.Type == policy.None { - return make([]interface{}, 0) - } - - principalId := "" - if identity.PrincipalID != nil { - principalId = *identity.PrincipalID - } - - tenantId := "" - if identity.TenantID != nil { - tenantId = *identity.TenantID - } - - return []interface{}{ - map[string]interface{}{ - "principal_id": principalId, - "tenant_id": tenantId, - "type": string(identity.Type), - }, +func flattenAzureRmPolicyIdentity(input *policy.Identity) []interface{} { + var config *identity.ExpandedConfig + if input != nil { + config = &identity.ExpandedConfig{ + Type: string(input.Type), + PrincipalId: input.PrincipalID, + TenantId: input.TenantID, + } } + return policyAssignmentIdentity{}.Flatten(config) } func expandAzureRmPolicyNotScopes(input []interface{}) *[]string { From 0c5fa1947d9dda58e8cc182660e8c1d9b4b6ae67 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 11:14:19 +0200 Subject: [PATCH 05/30] r/policy_assignment: waiting consistently until the policy assignment is deleted --- .../policy/policy_assignment_resource.go | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 244791e4b8ae..aa9a1ffd59d1 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -186,20 +186,19 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int // Policy Assignments are eventually consistent; wait for them to stabilize log.Printf("[DEBUG] Waiting for %s to become available", *id) + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context was missing a deadline") + } stateConf := &pluginsdk.StateChangeConf{ Pending: []string{"404"}, Target: []string{"200"}, Refresh: policyAssignmentRefreshFunc(ctx, client, *id), MinTimeout: 10 * time.Second, ContinuousTargetOccurence: 10, + PollInterval: 5 * time.Second, + Timeout: time.Until(deadline), } - - if d.IsNewResource() { - stateConf.Timeout = d.Timeout(pluginsdk.TimeoutCreate) - } else { - stateConf.Timeout = d.Timeout(pluginsdk.TimeoutUpdate) - } - if _, err := stateConf.WaitForStateContext(ctx); err != nil { return fmt.Errorf("waiting for %s to become available: %s", id, err) } @@ -250,7 +249,6 @@ func resourceArmPolicyAssignmentRead(d *pluginsdk.ResourceData, meta interface{} } d.Set("parameters", json) - d.Set("not_scopes", props.NotScopes) } @@ -271,7 +269,24 @@ func resourceArmPolicyAssignmentDelete(d *pluginsdk.ResourceData, meta interface return fmt.Errorf("deleting Policy Assignment %q: %+v", id, err) } - // TODO: presumably we want to wait for this to be fully gone too? + // Policy Assignments are eventually consistent; wait for it to be gone + log.Printf("[DEBUG] Waiting for %s to finish deleting", *id) + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context was missing a deadline") + } + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"200"}, + Target: []string{"404"}, + Refresh: policyAssignmentRefreshFunc(ctx, client, *id), + MinTimeout: 10 * time.Second, + ContinuousTargetOccurence: 10, + PollInterval: 5 * time.Second, + Timeout: time.Until(deadline), + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for the deletion of %s: %+v", *id, err) + } return nil } From 16b13b390a2bd01846f6fcff2e8da22f091d8c82 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 13:16:27 +0200 Subject: [PATCH 06/30] r/policy_assignment: support for delta updates --- .../policy/policy_assignment_resource.go | 137 ++++++++++++++++-- 1 file changed, 123 insertions(+), 14 deletions(-) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index aa9a1ffd59d1..8371c3542407 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -27,8 +27,8 @@ type policyAssignmentIdentity = identity.SystemAssigned func resourceArmPolicyAssignment() *pluginsdk.Resource { return &pluginsdk.Resource{ - Create: resourceArmPolicyAssignmentCreateUpdate, - Update: resourceArmPolicyAssignmentCreateUpdate, + Create: resourceArmPolicyAssignmentCreate, + Update: resourceArmPolicyAssignmentUpdate, Read: resourceArmPolicyAssignmentRead, Delete: resourceArmPolicyAssignmentDelete, @@ -108,9 +108,9 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { } } -func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { +func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Policy.AssignmentsClient - ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() id, err := parse.NewPolicyAssignmentId(d.Get("scope").(string), d.Get("name").(string)) @@ -118,19 +118,17 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int return err } - if d.IsNewResource() { - existing, err := client.Get(ctx, id.Scope, id.Name) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing %s: %+v", *id, err) - } - } - + existing, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return tf.ImportAsExistsError("azurerm_policy_assignment", id.ID()) + return fmt.Errorf("checking for presence of existing %s: %+v", *id, err) } } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_policy_assignment", id.ID()) + } + assignment := policy.Assignment{ AssignmentProperties: &policy.AssignmentProperties{ PolicyDefinitionID: utils.String(d.Get("policy_definition_id").(string)), @@ -154,7 +152,7 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int } identity, err := expandAzureRmPolicyIdentity(v.([]interface{})) if err != nil { - + return fmt.Errorf("expanding `identity`: %+v", err) } assignment.Identity = identity } @@ -207,6 +205,117 @@ func resourceArmPolicyAssignmentCreateUpdate(d *pluginsdk.ResourceData, meta int return resourceArmPolicyAssignmentRead(d, meta) } +func resourceArmPolicyAssignmentUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Policy.AssignmentsClient + ctx, cancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.PolicyAssignmentID(d.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + if existing.AssignmentProperties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", *id) + } + + update := policy.Assignment{ + Location: existing.Location, + AssignmentProperties: existing.AssignmentProperties, + } + if existing.Identity != nil { + update.Identity = &policy.Identity{ + Type: existing.Identity.Type, + } + } + + if d.HasChange("description") { + update.AssignmentProperties.Description = utils.String(d.Get("description").(string)) + } + if d.HasChange("display_name") { + update.AssignmentProperties.DisplayName = utils.String(d.Get("display_name").(string)) + } + if d.HasChange("enforcement_mode") { + update.AssignmentProperties.EnforcementMode = convertEnforcementMode(d.Get("enforcement_mode").(bool)) + } + if d.HasChange("location") { + update.Location = utils.String(d.Get("location").(string)) + } + if d.HasChange("policy_definition_id") { + update.AssignmentProperties.PolicyDefinitionID = utils.String(d.Get("policy_definition_id").(string)) + } + + if d.HasChange("identity") { + if update.Location == nil { + return fmt.Errorf("`location` must be set when `identity` is assigned") + } + identityRaw := d.Get("identity").([]interface{}) + identity, err := expandAzureRmPolicyIdentity(identityRaw) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + update.Identity = identity + } + + if d.HasChange("metadata") { + v := d.Get("metadata").(string) + update.AssignmentProperties.Metadata = map[string]interface{}{} + if v != "" { + metaData, err := pluginsdk.ExpandJsonFromString(v) + if err != nil { + return fmt.Errorf("parsing metadata: %+v", err) + } + update.AssignmentProperties.Metadata = &metaData + } + } + + if d.HasChange("not_scopes") { + update.AssignmentProperties.NotScopes = expandAzureRmPolicyNotScopes(d.Get("not_scopes").([]interface{})) + } + + if d.HasChange("parameters") { + update.AssignmentProperties.Parameters = map[string]*policy.ParameterValuesValue{} + + if v := d.Get("parameters").(string); v != "" { + expandedParams, err := expandParameterValuesValueFromString(v) + if err != nil { + return fmt.Errorf("expanding JSON for `parameters` %q: %+v", v, err) + } + update.AssignmentProperties.Parameters = expandedParams + } + } + + // NOTE: there isn't an Update endpoint + if _, err := client.Create(ctx, id.Scope, id.Name, update); err != nil { + return fmt.Errorf("creating/updating %s: %+v", *id, err) + } + + // Policy Assignments are eventually consistent; wait for them to stabilize + log.Printf("[DEBUG] Waiting for %s to become available", *id) + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context was missing a deadline") + } + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"404"}, + Target: []string{"200"}, + Refresh: policyAssignmentRefreshFunc(ctx, client, *id), + MinTimeout: 10 * time.Second, + ContinuousTargetOccurence: 10, + PollInterval: 5 * time.Second, + Timeout: time.Until(deadline), + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return fmt.Errorf("waiting for %s to become available: %s", id, err) + } + + return resourceArmPolicyAssignmentRead(d, meta) +} + func resourceArmPolicyAssignmentRead(d *pluginsdk.ResourceData, meta interface{}) error { client := meta.(*clients.Client).Policy.AssignmentsClient ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) From 529f1b5d98dbb38bbc078150b24b58c136d51dbf Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 14:12:14 +0200 Subject: [PATCH 07/30] r/policy_assignment: fixing an issue when parsing the ID --- .../services/policy/parse/assignment.go | 3 +- .../policy/policy_assignment_resource.go | 79 ++++++++----------- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/azurerm/internal/services/policy/parse/assignment.go b/azurerm/internal/services/policy/parse/assignment.go index 173d72c788b2..36a228d20a15 100644 --- a/azurerm/internal/services/policy/parse/assignment.go +++ b/azurerm/internal/services/policy/parse/assignment.go @@ -22,7 +22,7 @@ func (id PolicyAssignmentId) String() string { } func (id PolicyAssignmentId) ID() string { - fmtString := "%s/providers/Microsoft.Authorization/policyAssignment/%s" + fmtString := "%s/providers/Microsoft.Authorization/policyAssignments/%s" return fmt.Sprintf(fmtString, id.Scope, id.Name) } @@ -35,6 +35,7 @@ func NewPolicyAssignmentId(scope, name string) (*PolicyAssignmentId, error) { return &PolicyAssignmentId{ Name: name, PolicyScopeId: scopeId, + Scope: scope, }, nil } diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 8371c3542407..858b9f469722 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -183,21 +183,8 @@ func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface } // Policy Assignments are eventually consistent; wait for them to stabilize - log.Printf("[DEBUG] Waiting for %s to become available", *id) - deadline, ok := ctx.Deadline() - if !ok { - return fmt.Errorf("context was missing a deadline") - } - stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"404"}, - Target: []string{"200"}, - Refresh: policyAssignmentRefreshFunc(ctx, client, *id), - MinTimeout: 10 * time.Second, - ContinuousTargetOccurence: 10, - PollInterval: 5 * time.Second, - Timeout: time.Until(deadline), - } - if _, err := stateConf.WaitForStateContext(ctx); err != nil { + log.Printf("[DEBUG] Waiting for %s to become available..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, true); err != nil { return fmt.Errorf("waiting for %s to become available: %s", id, err) } @@ -295,21 +282,8 @@ func resourceArmPolicyAssignmentUpdate(d *pluginsdk.ResourceData, meta interface } // Policy Assignments are eventually consistent; wait for them to stabilize - log.Printf("[DEBUG] Waiting for %s to become available", *id) - deadline, ok := ctx.Deadline() - if !ok { - return fmt.Errorf("context was missing a deadline") - } - stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"404"}, - Target: []string{"200"}, - Refresh: policyAssignmentRefreshFunc(ctx, client, *id), - MinTimeout: 10 * time.Second, - ContinuousTargetOccurence: 10, - PollInterval: 5 * time.Second, - Timeout: time.Until(deadline), - } - if _, err := stateConf.WaitForStateContext(ctx); err != nil { + log.Printf("[DEBUG] Waiting for %s to become available..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, true); err != nil { return fmt.Errorf("waiting for %s to become available: %s", id, err) } @@ -379,38 +353,51 @@ func resourceArmPolicyAssignmentDelete(d *pluginsdk.ResourceData, meta interface } // Policy Assignments are eventually consistent; wait for it to be gone - log.Printf("[DEBUG] Waiting for %s to finish deleting", *id) + log.Printf("[DEBUG] Waiting for %s to disappear..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, false); err != nil { + return fmt.Errorf("waiting for the deletion of %s: %s", id, err) + } + + return nil +} + +func waitForPolicyAssignmentToStabilize(ctx context.Context, client *policy.AssignmentsClient, id parse.PolicyAssignmentId, shouldExist bool) error { deadline, ok := ctx.Deadline() if !ok { return fmt.Errorf("context was missing a deadline") } stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"200"}, - Target: []string{"404"}, - Refresh: policyAssignmentRefreshFunc(ctx, client, *id), + Pending: []string{"404"}, + Target: []string{"200"}, + Refresh: func() (interface{}, string, error) { + resp, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return resp, strconv.Itoa(resp.StatusCode), nil + } + + return nil, strconv.Itoa(resp.StatusCode), fmt.Errorf("polling for %s: %+v", id, err) + } + + return resp, strconv.Itoa(resp.StatusCode), nil + }, MinTimeout: 10 * time.Second, ContinuousTargetOccurence: 10, PollInterval: 5 * time.Second, Timeout: time.Until(deadline), } + if !shouldExist { + stateConf.Pending = []string{"200"} + stateConf.Target = []string{"404"} + } + if _, err := stateConf.WaitForStateContext(ctx); err != nil { - return fmt.Errorf("waiting for the deletion of %s: %+v", *id, err) + return err } return nil } -func policyAssignmentRefreshFunc(ctx context.Context, client *policy.AssignmentsClient, id parse.PolicyAssignmentId) pluginsdk.StateRefreshFunc { - return func() (interface{}, string, error) { - res, err := client.Get(ctx, id.Scope, id.Name) - if err != nil { - return nil, strconv.Itoa(res.StatusCode), fmt.Errorf("polling for %s: %+v", id, err) - } - - return res, strconv.Itoa(res.StatusCode), nil - } -} - func expandAzureRmPolicyIdentity(input []interface{}) (*policy.Identity, error) { expanded, err := policyAssignmentIdentity{}.Expand(input) if err != nil { From 1d392bbd6cada4a0fe5e08340e9a39c2d2f07cdb Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 15:15:29 +0200 Subject: [PATCH 08/30] r/policy_assignment: removing the unnecessary `PolicyScopeId` from the PolicyAssignmentId --- .../services/policy/parse/assignment.go | 25 +++++-------------- .../policy/policy_assignment_resource.go | 11 +++----- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/azurerm/internal/services/policy/parse/assignment.go b/azurerm/internal/services/policy/parse/assignment.go index 36a228d20a15..8271411eeb4e 100644 --- a/azurerm/internal/services/policy/parse/assignment.go +++ b/azurerm/internal/services/policy/parse/assignment.go @@ -9,7 +9,6 @@ import ( type PolicyAssignmentId struct { Name string Scope string - PolicyScopeId } func (id PolicyAssignmentId) String() string { @@ -26,17 +25,11 @@ func (id PolicyAssignmentId) ID() string { return fmt.Sprintf(fmtString, id.Scope, id.Name) } -func NewPolicyAssignmentId(scope, name string) (*PolicyAssignmentId, error) { - scopeId, err := PolicyScopeID(scope) - if err != nil { - return nil, fmt.Errorf("parsing Policy Scope ID %q: %+v", scope, err) +func NewPolicyAssignmentId(scope, name string) PolicyAssignmentId { + return PolicyAssignmentId{ + Name: name, + Scope: scope, } - - return &PolicyAssignmentId{ - Name: name, - PolicyScopeId: scopeId, - Scope: scope, - }, nil } // TODO: This paring function is currently suppressing every case difference due to github issue: https://github.com/Azure/azure-rest-api-specs/issues/8353 @@ -60,14 +53,8 @@ func PolicyAssignmentID(input string) (*PolicyAssignmentId, error) { return nil, fmt.Errorf("unable to parse Policy Assignment ID %q: assignment name is empty", input) } - scopeId, err := PolicyScopeID(scope) - if err != nil { - return nil, fmt.Errorf("unable to parse Policy Assignment ID %q: %+v", input, err) - } - return &PolicyAssignmentId{ - Name: name, - Scope: scope, - PolicyScopeId: scopeId, + Name: name, + Scope: scope, }, nil } diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 858b9f469722..d15731dfb597 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -113,15 +113,12 @@ func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.NewPolicyAssignmentId(d.Get("scope").(string), d.Get("name").(string)) - if err != nil { - return err - } + id := parse.NewPolicyAssignmentId(d.Get("scope").(string), d.Get("name").(string)) existing, err := client.Get(ctx, id.Scope, id.Name) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for presence of existing %s: %+v", *id, err) + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) } } @@ -179,12 +176,12 @@ func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface } if _, err := client.Create(ctx, id.Scope, id.Name, assignment); err != nil { - return fmt.Errorf("creating/updating %s: %+v", *id, err) + return fmt.Errorf("creating/updating %s: %+v", id, err) } // Policy Assignments are eventually consistent; wait for them to stabilize log.Printf("[DEBUG] Waiting for %s to become available..", id) - if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, true); err != nil { + if err := waitForPolicyAssignmentToStabilize(ctx, client, id, true); err != nil { return fmt.Errorf("waiting for %s to become available: %s", id, err) } From ea6aed82c4f13e2514a5ae804c2998a973b2eeaa Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 15:16:12 +0200 Subject: [PATCH 09/30] r/policy_assignment: updating the log messages for create/update --- .../internal/services/policy/policy_assignment_resource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index d15731dfb597..60a02d6b6a97 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -176,7 +176,7 @@ func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface } if _, err := client.Create(ctx, id.Scope, id.Name, assignment); err != nil { - return fmt.Errorf("creating/updating %s: %+v", id, err) + return fmt.Errorf("creating %s: %+v", id, err) } // Policy Assignments are eventually consistent; wait for them to stabilize @@ -275,7 +275,7 @@ func resourceArmPolicyAssignmentUpdate(d *pluginsdk.ResourceData, meta interface // NOTE: there isn't an Update endpoint if _, err := client.Create(ctx, id.Scope, id.Name, update); err != nil { - return fmt.Errorf("creating/updating %s: %+v", *id, err) + return fmt.Errorf("updating %s: %+v", *id, err) } // Policy Assignments are eventually consistent; wait for them to stabilize From fd653d1776b48306a918fd408139dbd0f43567b4 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Tue, 22 Jun 2021 15:22:24 +0200 Subject: [PATCH 10/30] r/policy_assignment: updating the tests --- .../services/policy/parse/assignment_test.go | 41 ++++++------------- .../policy/policy_assignment_resource.go | 6 +-- 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/azurerm/internal/services/policy/parse/assignment_test.go b/azurerm/internal/services/policy/parse/assignment_test.go index 979e1b78ec80..015d771f7e9e 100644 --- a/azurerm/internal/services/policy/parse/assignment_test.go +++ b/azurerm/internal/services/policy/parse/assignment_test.go @@ -1,7 +1,6 @@ package parse import ( - "reflect" "testing" ) @@ -21,12 +20,8 @@ func TestPolicyAssignmentID(t *testing.T) { Name: "policy assignment in resource group", Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Authorization/policyAssignments/assignment1", Expected: &PolicyAssignmentId{ - Name: "assignment1", - PolicyScopeId: ScopeAtResourceGroup{ - scopeId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", - SubscriptionId: "00000000-0000-0000-0000-000000000000", - ResourceGroup: "foo", - }, + Name: "assignment1", + Scope: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", }, }, { @@ -38,23 +33,16 @@ func TestPolicyAssignmentID(t *testing.T) { Name: "the returned value of policy assignment id may not keep its casing", Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.authorization/policyassignments/assignment1", Expected: &PolicyAssignmentId{ - Name: "assignment1", - PolicyScopeId: ScopeAtResourceGroup{ - scopeId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", - SubscriptionId: "00000000-0000-0000-0000-000000000000", - ResourceGroup: "foo", - }, + Name: "assignment1", + Scope: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo", }, }, { Name: "policy assignment in subscription", Input: "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment1", Expected: &PolicyAssignmentId{ - Name: "assignment1", - PolicyScopeId: ScopeAtSubscription{ - scopeId: "/subscriptions/00000000-0000-0000-0000-000000000000", - SubscriptionId: "00000000-0000-0000-0000-000000000000", - }, + Name: "assignment1", + Scope: "/subscriptions/00000000-0000-0000-0000-000000000000", }, }, { @@ -66,11 +54,8 @@ func TestPolicyAssignmentID(t *testing.T) { Name: "policy assignment in management group", Input: "/providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment1", Expected: &PolicyAssignmentId{ - Name: "assignment1", - PolicyScopeId: ScopeAtManagementGroup{ - scopeId: "/providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000", - ManagementGroupName: "00000000-0000-0000-0000-000000000000", - }, + Name: "assignment1", + Scope: "/providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000", }, }, { @@ -82,10 +67,8 @@ func TestPolicyAssignmentID(t *testing.T) { Name: "policy assignment in resource", Input: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Compute/virtualMachines/vm1/providers/Microsoft.Authorization/policyAssignments/assignment1", Expected: &PolicyAssignmentId{ - Name: "assignment1", - PolicyScopeId: ScopeAtResource{ - scopeId: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Compute/virtualMachines/vm1", - }, + Name: "assignment1", + Scope: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/foo/providers/Microsoft.Compute/virtualMachines/vm1", }, }, { @@ -111,8 +94,8 @@ func TestPolicyAssignmentID(t *testing.T) { 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) + if v.Expected.Scope != actual.Scope { + t.Fatalf("Expected %+v but got %+v", v.Expected.Scope, actual.Scope) } } } diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 60a02d6b6a97..5379f6706fee 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -7,14 +7,12 @@ import ( "strconv" "time" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/identity" - - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" - "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "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/identity" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" From dd9faf3a4a1faa2ec3e761887e5a69c157e1c927 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 23 Jun 2021 11:28:50 +0200 Subject: [PATCH 11/30] fixing the azure schema uri --- .../logic/logic_app_workflow_resource.go | 2 +- ...ed_application_definition_resource_test.go | 4 ++-- .../policy/policy_assignment_resource_test.go | 6 +++--- ...group_template_deployment_resource_test.go | 4 ++-- ...group_template_deployment_resource_test.go | 20 +++++++++---------- ...ption_template_deployment_resource_test.go | 12 +++++------ .../template_deployment_resource_test.go | 16 +++++++-------- ...enant_template_deployment_resource_test.go | 4 ++-- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/azurerm/internal/services/logic/logic_app_workflow_resource.go b/azurerm/internal/services/logic/logic_app_workflow_resource.go index 314cc9a17a5a..8b5e53e08c7a 100644 --- a/azurerm/internal/services/logic/logic_app_workflow_resource.go +++ b/azurerm/internal/services/logic/logic_app_workflow_resource.go @@ -81,7 +81,7 @@ func resourceLogicAppWorkflow() *pluginsdk.Resource { Type: pluginsdk.TypeString, Optional: true, ForceNew: true, - Default: "https://pluginsdk.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + Default: "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", }, "workflow_version": { diff --git a/azurerm/internal/services/managedapplications/managed_application_definition_resource_test.go b/azurerm/internal/services/managedapplications/managed_application_definition_resource_test.go index 33b371b415f8..b5219f53f9bd 100644 --- a/azurerm/internal/services/managedapplications/managed_application_definition_resource_test.go +++ b/azurerm/internal/services/managedapplications/managed_application_definition_resource_test.go @@ -167,7 +167,7 @@ resource "azurerm_managed_application_definition" "test" { create_ui_definition = < Date: Wed, 23 Jun 2021 11:31:46 +0200 Subject: [PATCH 12/30] sdk: support for Typed Resources without models --- azurerm/internal/sdk/service_registration.go | 3 --- azurerm/internal/sdk/wrapper_data_source.go | 6 ++++-- azurerm/internal/sdk/wrapper_resource.go | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/azurerm/internal/sdk/service_registration.go b/azurerm/internal/sdk/service_registration.go index 94a283b4e725..269c24a0a32e 100644 --- a/azurerm/internal/sdk/service_registration.go +++ b/azurerm/internal/sdk/service_registration.go @@ -11,9 +11,6 @@ type TypedServiceRegistration interface { // Name is the name of this Service Name() string - // PackagePath is the relative path to this package - PackagePath() string - // DataSources returns a list of Data Sources supported by this Service DataSources() []DataSource diff --git a/azurerm/internal/sdk/wrapper_data_source.go b/azurerm/internal/sdk/wrapper_data_source.go index 16fee73ad135..9f8e79bbaff7 100644 --- a/azurerm/internal/sdk/wrapper_data_source.go +++ b/azurerm/internal/sdk/wrapper_data_source.go @@ -31,8 +31,10 @@ func (dw *DataSourceWrapper) DataSource() (*schema.Resource, error) { } modelObj := dw.dataSource.ModelObject() - if err := ValidateModelObject(&modelObj); err != nil { - return nil, fmt.Errorf("validating model for %q: %+v", dw.dataSource.ResourceType(), err) + if modelObj != nil { + if err := ValidateModelObject(&modelObj); err != nil { + return nil, fmt.Errorf("validating model for %q: %+v", dw.dataSource.ResourceType(), err) + } } d := func(duration time.Duration) *time.Duration { diff --git a/azurerm/internal/sdk/wrapper_resource.go b/azurerm/internal/sdk/wrapper_resource.go index 48509a3b9c53..206515256077 100644 --- a/azurerm/internal/sdk/wrapper_resource.go +++ b/azurerm/internal/sdk/wrapper_resource.go @@ -34,8 +34,10 @@ func (rw *ResourceWrapper) Resource() (*schema.Resource, error) { } modelObj := rw.resource.ModelObject() - if err := ValidateModelObject(&modelObj); err != nil { - return nil, fmt.Errorf("validating model for %q: %+v", rw.resource.ResourceType(), err) + if modelObj != nil { + if err := ValidateModelObject(&modelObj); err != nil { + return nil, fmt.Errorf("validating model for %q: %+v", rw.resource.ResourceType(), err) + } } d := func(duration time.Duration) *time.Duration { From 2090e3229fc4d7dbf2e1fd1ab13a7a2542e67975 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 23 Jun 2021 12:12:09 +0200 Subject: [PATCH 13/30] policy: supporting typed resources --- azurerm/internal/provider/services.go | 1 + azurerm/internal/services/policy/registration.go | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/azurerm/internal/provider/services.go b/azurerm/internal/provider/services.go index a74a4f7983dd..cf8732f360af 100644 --- a/azurerm/internal/provider/services.go +++ b/azurerm/internal/provider/services.go @@ -103,6 +103,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { return []sdk.TypedServiceRegistration{ eventhub.Registration{}, loadbalancer.Registration{}, + policy.Registration{}, resource.Registration{}, web.Registration{}, } diff --git a/azurerm/internal/services/policy/registration.go b/azurerm/internal/services/policy/registration.go index 616723e21f80..0dc6b1af4f4a 100644 --- a/azurerm/internal/services/policy/registration.go +++ b/azurerm/internal/services/policy/registration.go @@ -1,11 +1,23 @@ package policy import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" ) +var _ sdk.TypedServiceRegistration = Registration{} +var _ sdk.UntypedServiceRegistration = Registration{} + type Registration struct{} +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{} +} + +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{} +} + // Name is the name of this Service func (r Registration) Name() string { return "Policy" From e05980fbf4fe2731e56937e9ba0c7a34d703ebad Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 23 Jun 2021 12:12:31 +0200 Subject: [PATCH 14/30] New Resource: `azurerm_resource_group_policy_assignment` ``` $ TF_ACC=1 go test -v ./azurerm/internal/services/policy -run=TestAccResourceGroupPolicyAssignment -timeout=60m === RUN TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicy === PAUSE TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicy === RUN TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicySet === PAUSE TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicySet === RUN TestAccResourceGroupPolicyAssignment_basicWithCustomPolicy === PAUSE TestAccResourceGroupPolicyAssignment_basicWithCustomPolicy === RUN TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyComplete === PAUSE TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicy === CONT TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccResourceGroupPolicyAssignment_basicWithCustomPolicy === CONT TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicySet --- PASS: TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicySet (391.22s) --- PASS: TestAccResourceGroupPolicyAssignment_basicWithCustomPolicy (458.61s) --- PASS: TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyComplete (515.47s) --- PASS: TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicy (522.90s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy 522.980s ``` --- .../policy/assignment_base_resource.go | 376 +++++++++++++++++ .../assignment_resource_group_resource.go | 58 +++ ...assignment_resource_group_resource_test.go | 388 ++++++++++++++++++ .../policy/parse/resource_group_assignment.go | 69 ++++ .../parse/resource_group_assignment_test.go | 112 +++++ .../policy/policy_assignment_resource.go | 2 +- .../internal/services/policy/registration.go | 4 +- .../internal/services/policy/resourceids.go | 1 + .../validate/resource_group_assignment_id.go | 23 ++ .../resource_group_assignment_id_test.go | 76 ++++ 10 files changed, 1107 insertions(+), 2 deletions(-) create mode 100644 azurerm/internal/services/policy/assignment_base_resource.go create mode 100644 azurerm/internal/services/policy/assignment_resource_group_resource.go create mode 100644 azurerm/internal/services/policy/assignment_resource_group_resource_test.go create mode 100644 azurerm/internal/services/policy/parse/resource_group_assignment.go create mode 100644 azurerm/internal/services/policy/parse/resource_group_assignment_test.go create mode 100644 azurerm/internal/services/policy/validate/resource_group_assignment_id.go create mode 100644 azurerm/internal/services/policy/validate/resource_group_assignment_id_test.go diff --git a/azurerm/internal/services/policy/assignment_base_resource.go b/azurerm/internal/services/policy/assignment_base_resource.go new file mode 100644 index 000000000000..02082b3372b7 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_base_resource.go @@ -0,0 +1,376 @@ +package policy + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" + + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/identity" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" +) + +type policyAssignmentIdentity = identity.SystemAssigned + +type assignmentBaseResource struct { + resourceName string +} + +func (br assignmentBaseResource) createFunc(resourceName, scopeFieldName string) sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policy.AssignmentsClient + id := parse.NewPolicyAssignmentId(metadata.ResourceData.Get(scopeFieldName).(string), metadata.ResourceData.Get("name").(string)) + existing, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + if !utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError(resourceName, id.ID()) + } + + assignment := policy.Assignment{ + AssignmentProperties: &policy.AssignmentProperties{ + PolicyDefinitionID: utils.String(metadata.ResourceData.Get("policy_definition_id").(string)), + DisplayName: utils.String(metadata.ResourceData.Get("display_name").(string)), + Scope: utils.String(id.Scope), + EnforcementMode: convertEnforcementMode(metadata.ResourceData.Get("enforce").(bool)), + }, + } + + if v := metadata.ResourceData.Get("description").(string); v != "" { + assignment.AssignmentProperties.Description = utils.String(v) + } + + if v := metadata.ResourceData.Get("location").(string); v != "" { + assignment.Location = utils.String(azure.NormalizeLocation(v)) + } + + if v, ok := metadata.ResourceData.GetOk("identity"); ok { + if assignment.Location == nil { + return fmt.Errorf("`location` must be set when `identity` is assigned") + } + identity, err := expandAzureRmPolicyIdentity(v.([]interface{})) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + assignment.Identity = identity + } + + if v := metadata.ResourceData.Get("parameters").(string); v != "" { + expandedParams, err := expandParameterValuesValueFromString(v) + if err != nil { + return fmt.Errorf("expanding JSON for `parameters` %q: %+v", v, err) + } + + assignment.AssignmentProperties.Parameters = expandedParams + } + + if metaDataString := metadata.ResourceData.Get("metadata").(string); metaDataString != "" { + metaData, err := pluginsdk.ExpandJsonFromString(metaDataString) + if err != nil { + return fmt.Errorf("unable to parse metadata: %s", err) + } + assignment.AssignmentProperties.Metadata = &metaData + } + + if v, ok := metadata.ResourceData.GetOk("not_scopes"); ok { + assignment.AssignmentProperties.NotScopes = expandAzureRmPolicyNotScopes(v.([]interface{})) + } + + if _, err := client.Create(ctx, id.Scope, id.Name, assignment); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + // Policy Assignments are eventually consistent; wait for them to stabilize + log.Printf("[DEBUG] Waiting for %s to become available..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, id, true); err != nil { + return fmt.Errorf("waiting for %s to become available: %s", id, err) + } + + metadata.SetID(id) + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (br assignmentBaseResource) deleteFunc() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policy.AssignmentsClient + + id, err := parse.PolicyAssignmentID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err := client.Delete(ctx, id.Scope, id.Name); err != nil { + return fmt.Errorf("deleting Policy Assignment %q: %+v", id, err) + } + + // Policy Assignments are eventually consistent; wait for it to be gone + log.Printf("[DEBUG] Waiting for %s to disappear..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, false); err != nil { + return fmt.Errorf("waiting for the deletion of %s: %s", id, err) + } + + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (br assignmentBaseResource) readFunc(scopeFieldName string) sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policy.AssignmentsClient + + id, err := parse.PolicyAssignmentID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("reading %s: %+v", *id, err) + } + + metadata.ResourceData.Set("name", id.Name) + metadata.ResourceData.Set(scopeFieldName, id.Scope) + metadata.ResourceData.Set("location", location.NormalizeNilable(resp.Location)) + + if err := metadata.ResourceData.Set("identity", flattenAzureRmPolicyIdentity(resp.Identity)); err != nil { + return fmt.Errorf("setting `identity`: %+v", err) + } + + if props := resp.AssignmentProperties; props != nil { + metadata.ResourceData.Set("description", props.Description) + metadata.ResourceData.Set("display_name", props.DisplayName) + metadata.ResourceData.Set("enforce", props.EnforcementMode == policy.Default) + metadata.ResourceData.Set("not_scopes", props.NotScopes) + metadata.ResourceData.Set("policy_definition_id", props.PolicyDefinitionID) + + flattenedMetaData, err := br.flattenMetaData(props.Metadata) + if err != nil { + return fmt.Errorf("serializing `metadata`: %+v", err) + } + metadata.ResourceData.Set("metadata", *flattenedMetaData) + + json, err := flattenParameterValuesValueToString(props.Parameters) + if err != nil { + return fmt.Errorf("serializing JSON from `parameters`: %+v", err) + } + metadata.ResourceData.Set("parameters", json) + } + + return nil + }, + Timeout: 5 * time.Minute, + } +} + +func (br assignmentBaseResource) flattenMetaData(input interface{}) (*string, error) { + // TODO: fixme + out := flattenJSON(input) + return &out, nil +} + +func (br assignmentBaseResource) flattenParameterValues(input map[string]*policy.ParameterValuesValue) (*string, error) { + // TODO: fixme + out, err := flattenParameterValuesValueToString(input) + if err != nil { + return nil, err + } + return &out, nil +} + +func (br assignmentBaseResource) updateFunc() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policy.AssignmentsClient + + id, err := parse.PolicyAssignmentID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + if existing.AssignmentProperties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", *id) + } + + update := policy.Assignment{ + Location: existing.Location, + AssignmentProperties: existing.AssignmentProperties, + } + if existing.Identity != nil { + update.Identity = &policy.Identity{ + Type: existing.Identity.Type, + } + } + + if metadata.ResourceData.HasChange("description") { + update.AssignmentProperties.Description = utils.String(metadata.ResourceData.Get("description").(string)) + } + if metadata.ResourceData.HasChange("display_name") { + update.AssignmentProperties.DisplayName = utils.String(metadata.ResourceData.Get("display_name").(string)) + } + if metadata.ResourceData.HasChange("enforce") { + update.AssignmentProperties.EnforcementMode = convertEnforcementMode(metadata.ResourceData.Get("enforce").(bool)) + } + if metadata.ResourceData.HasChange("location") { + update.Location = utils.String(metadata.ResourceData.Get("location").(string)) + } + if metadata.ResourceData.HasChange("policy_definition_id") { + update.AssignmentProperties.PolicyDefinitionID = utils.String(metadata.ResourceData.Get("policy_definition_id").(string)) + } + + if metadata.ResourceData.HasChange("identity") { + if update.Location == nil { + return fmt.Errorf("`location` must be set when `identity` is assigned") + } + identityRaw := metadata.ResourceData.Get("identity").([]interface{}) + identity, err := expandAzureRmPolicyIdentity(identityRaw) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + update.Identity = identity + } + + if metadata.ResourceData.HasChange("metadata") { + v := metadata.ResourceData.Get("metadata").(string) + update.AssignmentProperties.Metadata = map[string]interface{}{} + if v != "" { + metaData, err := pluginsdk.ExpandJsonFromString(v) + if err != nil { + return fmt.Errorf("parsing metadata: %+v", err) + } + update.AssignmentProperties.Metadata = &metaData + } + } + + if metadata.ResourceData.HasChange("not_scopes") { + update.AssignmentProperties.NotScopes = expandAzureRmPolicyNotScopes(metadata.ResourceData.Get("not_scopes").([]interface{})) + } + + if metadata.ResourceData.HasChange("parameters") { + update.AssignmentProperties.Parameters = map[string]*policy.ParameterValuesValue{} + + if v := metadata.ResourceData.Get("parameters").(string); v != "" { + expandedParams, err := expandParameterValuesValueFromString(v) + if err != nil { + return fmt.Errorf("expanding JSON for `parameters` %q: %+v", v, err) + } + update.AssignmentProperties.Parameters = expandedParams + } + } + + // NOTE: there isn't an Update endpoint + if _, err := client.Create(ctx, id.Scope, id.Name, update); err != nil { + return fmt.Errorf("updating %s: %+v", *id, err) + } + + // Policy Assignments are eventually consistent; wait for them to stabilize + log.Printf("[DEBUG] Waiting for %s to become available..", id) + if err := waitForPolicyAssignmentToStabilize(ctx, client, *id, true); err != nil { + return fmt.Errorf("waiting for %s to become available: %s", id, err) + } + + return nil + }, + Timeout: 30 * time.Minute, + } +} + +func (br assignmentBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[string]*pluginsdk.Schema { + output := map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotWhiteSpace, + }, + + "policy_definition_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.Any( + validate.PolicyDefinitionID, + validate.PolicySetDefinitionID, + ), + }, + + "description": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "display_name": { + Type: pluginsdk.TypeString, + Optional: true, + }, + + "location": azure.SchemaLocationOptional(), + + "identity": policyAssignmentIdentity{}.Schema(), + + "enforce": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + + "metadata": metadataSchema(), + + "not_scopes": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + + "parameters": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsJSON, + DiffSuppressFunc: pluginsdk.SuppressJsonDiff, + }, + } + + for k, v := range fields { + output[k] = v + } + + return output +} + +func (br assignmentBaseResource) attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} diff --git a/azurerm/internal/services/policy/assignment_resource_group_resource.go b/azurerm/internal/services/policy/assignment_resource_group_resource.go new file mode 100644 index 000000000000..59986d3ab162 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_resource_group_resource.go @@ -0,0 +1,58 @@ +package policy + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" + resourceValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" +) + +var _ sdk.ResourceWithUpdate = ResourceGroupAssignmentResource{} + +type ResourceGroupAssignmentResource struct { + base assignmentBaseResource +} + +func (r ResourceGroupAssignmentResource) Arguments() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "resource_group_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: resourceValidate.ResourceGroupID, + }, + } + return r.base.arguments(schema) +} + +func (r ResourceGroupAssignmentResource) Attributes() map[string]*pluginsdk.Schema { + return r.base.attributes() +} + +func (r ResourceGroupAssignmentResource) Create() sdk.ResourceFunc { + return r.base.createFunc(r.ResourceType(), "resource_group_id") +} + +func (r ResourceGroupAssignmentResource) Delete() sdk.ResourceFunc { + return r.base.deleteFunc() +} + +func (r ResourceGroupAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.ResourceGroupAssignmentID +} + +func (r ResourceGroupAssignmentResource) ModelObject() interface{} { + return nil +} + +func (r ResourceGroupAssignmentResource) Read() sdk.ResourceFunc { + return r.base.readFunc("resource_group_id") +} + +func (r ResourceGroupAssignmentResource) ResourceType() string { + return "azurerm_resource_group_policy_assignment" +} + +func (r ResourceGroupAssignmentResource) Update() sdk.ResourceFunc { + return r.base.updateFunc() +} diff --git a/azurerm/internal/services/policy/assignment_resource_group_resource_test.go b/azurerm/internal/services/policy/assignment_resource_group_resource_test.go new file mode 100644 index 000000000000..e5ff9841f3c1 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_resource_group_resource_test.go @@ -0,0 +1,388 @@ +package policy_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type ResourceGroupAssignmentTestResource struct{} + +func TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_policy_assignment", "test") + r := ResourceGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourceGroupPolicyAssignment_basicWithBuiltInPolicySet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_policy_assignment", "test") + r := ResourceGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourceGroupPolicyAssignment_basicWithCustomPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_policy_assignment", "test") + r := ResourceGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_policy_assignment", "test") + r := ResourceGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + data.RequiresImportErrorStep(r.withCustomPolicyRequiresImport), + }) +} + +func TestAccResourceGroupPolicyAssignment_basicWithCustomPolicyComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_group_policy_assignment", "test") + r := ResourceGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r ResourceGroupAssignmentTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PolicyAssignmentID(state.ID) + if err != nil { + return nil, err + } + + assignment, err := client.Policy.AssignmentsClient.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(assignment.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return utils.Bool(true), nil +} + +func (r ResourceGroupAssignmentTestResource) withBuiltInPolicyBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ azurerm_resource_group.test.location ] + } + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) withBuiltInPolicyUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ azurerm_resource_group.test.location, %[3]q ] + } + }) +} +`, template, data.RandomInteger, data.Locations.Secondary) +} + +func (r ResourceGroupAssignmentTestResource) withBuiltInPolicySetBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = azurerm_resource_group.test.location + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) withBuiltInPolicySetUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = azurerm_resource_group.test.location + + identity { + type = "SystemAssigned" + } + + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) withCustomPolicyBasic(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = azurerm_policy_definition.test.id +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) withCustomPolicyComplete(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + // NOTE: we could include parameters here but it's tested extensively elsewhere + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = azurerm_policy_definition.test.id + description = "This is a policy assignment from an acceptance test" + display_name = "AccTest Policy %[2]d" + enforce = false + not_scopes = [ + format("%%s/virtualMachines/testvm1", azurerm_resource_group.test.id) + ] + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) withCustomPolicyRequiresImport(data acceptance.TestData) string { + template := r.withCustomPolicyBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_resource_group_policy_assignment" "import" { + name = azurerm_resource_group_policy_assignment.test.name + resource_group_id = azurerm_resource_group_policy_assignment.test.resource_group_id + policy_definition_id = azurerm_resource_group_policy_assignment.test.policy_definition_id +} +`, template) +} + +func (r ResourceGroupAssignmentTestResource) withCustomPolicyUpdated(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_group_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_group_id = azurerm_resource_group.test.id + policy_definition_id = azurerm_policy_definition.test.id + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceGroupAssignmentTestResource) templateWithCustomPolicy(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_policy_definition" "test" { + name = "acctestpol-%[2]d" + policy_type = "Custom" + mode = "All" + display_name = "acctestpol-%[2]d" + + policy_rule = < Date: Wed, 23 Jun 2021 12:35:15 +0200 Subject: [PATCH 15/30] New Resource: `azurerm_subscription_policy_assignment` ``` $ TF_ACC=1 go test -v ./azurerm/internal/services/policy -run=TestAccSubscriptionPolicyAssignment -timeout=60m === RUN TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicy === PAUSE TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicy === RUN TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicySet === PAUSE TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicySet === RUN TestAccSubscriptionPolicyAssignment_basicWithCustomPolicy === PAUSE TestAccSubscriptionPolicyAssignment_basicWithCustomPolicy === RUN TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyComplete === PAUSE TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyComplete === RUN TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyRequiresImport === PAUSE TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyRequiresImport === CONT TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicy === CONT TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicySet === CONT TestAccSubscriptionPolicyAssignment_basicWithCustomPolicy --- PASS: TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicySet (341.21s) === CONT TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyRequiresImport --- PASS: TestAccSubscriptionPolicyAssignment_basicWithCustomPolicy (430.73s) --- PASS: TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyComplete (484.83s) --- PASS: TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicy (487.97s) --- PASS: TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyRequiresImport (287.82s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy 629.114s ``` --- .../assignment_subscription_resource.go | 58 +++ .../assignment_subscription_resource_test.go | 382 ++++++++++++++++++ .../policy/parse/subscription_assignment.go | 61 +++ .../parse/subscription_assignment_test.go | 96 +++++ .../internal/services/policy/registration.go | 1 + .../internal/services/policy/resourceids.go | 1 + .../validate/subscription_assignment_id.go | 23 ++ .../subscription_assignment_id_test.go | 64 +++ 8 files changed, 686 insertions(+) create mode 100644 azurerm/internal/services/policy/assignment_subscription_resource.go create mode 100644 azurerm/internal/services/policy/assignment_subscription_resource_test.go create mode 100644 azurerm/internal/services/policy/parse/subscription_assignment.go create mode 100644 azurerm/internal/services/policy/parse/subscription_assignment_test.go create mode 100644 azurerm/internal/services/policy/validate/subscription_assignment_id.go create mode 100644 azurerm/internal/services/policy/validate/subscription_assignment_id_test.go diff --git a/azurerm/internal/services/policy/assignment_subscription_resource.go b/azurerm/internal/services/policy/assignment_subscription_resource.go new file mode 100644 index 000000000000..8b0c4d3c21e4 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_subscription_resource.go @@ -0,0 +1,58 @@ +package policy + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" + subscriptionValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/subscription/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" +) + +var _ sdk.ResourceWithUpdate = SubscriptionAssignmentResource{} + +type SubscriptionAssignmentResource struct { + base assignmentBaseResource +} + +func (r SubscriptionAssignmentResource) Arguments() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "subscription_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: subscriptionValidate.SubscriptionID, + }, + } + return r.base.arguments(schema) +} + +func (r SubscriptionAssignmentResource) Attributes() map[string]*pluginsdk.Schema { + return r.base.attributes() +} + +func (r SubscriptionAssignmentResource) Create() sdk.ResourceFunc { + return r.base.createFunc(r.ResourceType(), "subscription_id") +} + +func (r SubscriptionAssignmentResource) Delete() sdk.ResourceFunc { + return r.base.deleteFunc() +} + +func (r SubscriptionAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.SubscriptionAssignmentID +} + +func (r SubscriptionAssignmentResource) ModelObject() interface{} { + return nil +} + +func (r SubscriptionAssignmentResource) Read() sdk.ResourceFunc { + return r.base.readFunc("subscription_id") +} + +func (r SubscriptionAssignmentResource) ResourceType() string { + return "azurerm_subscription_policy_assignment" +} + +func (r SubscriptionAssignmentResource) Update() sdk.ResourceFunc { + return r.base.updateFunc() +} diff --git a/azurerm/internal/services/policy/assignment_subscription_resource_test.go b/azurerm/internal/services/policy/assignment_subscription_resource_test.go new file mode 100644 index 000000000000..fd7f38c816d0 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_subscription_resource_test.go @@ -0,0 +1,382 @@ +package policy_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type SubscriptionAssignmentTestResource struct{} + +func TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_policy_assignment", "test") + r := SubscriptionAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubscriptionPolicyAssignment_basicWithBuiltInPolicySet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_policy_assignment", "test") + r := SubscriptionAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubscriptionPolicyAssignment_basicWithCustomPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_policy_assignment", "test") + r := SubscriptionAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_policy_assignment", "test") + r := SubscriptionAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSubscriptionPolicyAssignment_basicWithCustomPolicyRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_subscription_policy_assignment", "test") + r := SubscriptionAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + data.RequiresImportErrorStep(r.withCustomPolicyRequiresImport), + }) +} + +func (r SubscriptionAssignmentTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PolicyAssignmentID(state.ID) + if err != nil { + return nil, err + } + + assignment, err := client.Policy.AssignmentsClient.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(assignment.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return utils.Bool(true), nil +} + +func (r SubscriptionAssignmentTestResource) withBuiltInPolicyBasic(data acceptance.TestData) string { + template := r.template() + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ %q ] + } + }) +} +`, template, data.RandomInteger, data.Locations.Primary) +} + +func (r SubscriptionAssignmentTestResource) withBuiltInPolicyUpdated(data acceptance.TestData) string { + template := r.template() + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ %[3]q, %[4]q ] + } + }) +} +`, template, data.RandomInteger, data.Locations.Primary, data.Locations.Secondary) +} + +func (r SubscriptionAssignmentTestResource) withBuiltInPolicySetBasic(data acceptance.TestData) string { + template := r.template() + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = %[3]q + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger, data.Locations.Primary) +} + +func (r SubscriptionAssignmentTestResource) withBuiltInPolicySetUpdated(data acceptance.TestData) string { + template := r.template() + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = %[3]q + + identity { + type = "SystemAssigned" + } + + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger, data.Locations.Primary) +} + +func (r SubscriptionAssignmentTestResource) withCustomPolicyBasic(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = azurerm_policy_definition.test.id +} +`, template, data.RandomInteger) +} + +func (r SubscriptionAssignmentTestResource) withCustomPolicyComplete(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + // NOTE: we could include parameters here but it's tested extensively elsewhere + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = azurerm_policy_definition.test.id + description = "This is a policy assignment from an acceptance test" + display_name = "AccTest Policy %[2]d" + enforce = false + not_scopes = [ + format("%%s/resourceGroups/blah", data.azurerm_subscription.test.id) + ] + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r SubscriptionAssignmentTestResource) withCustomPolicyRequiresImport(data acceptance.TestData) string { + template := r.withCustomPolicyBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_subscription_policy_assignment" "import" { + name = azurerm_subscription_policy_assignment.test.name + subscription_id = azurerm_subscription_policy_assignment.test.subscription_id + policy_definition_id = azurerm_subscription_policy_assignment.test.policy_definition_id +} +`, template) +} +func (r SubscriptionAssignmentTestResource) withCustomPolicyUpdated(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_subscription_policy_assignment" "test" { + name = "acctestpa-%[2]d" + subscription_id = data.azurerm_subscription.test.id + policy_definition_id = azurerm_policy_definition.test.id + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r SubscriptionAssignmentTestResource) templateWithCustomPolicy(data acceptance.TestData) string { + template := r.template() + return fmt.Sprintf(` +%[1]s + +resource "azurerm_policy_definition" "test" { + name = "acctestpol-%[2]d" + policy_type = "Custom" + mode = "All" + display_name = "acctestpol-%[2]d" + + policy_rule = < Date: Wed, 23 Jun 2021 13:05:50 +0200 Subject: [PATCH 16/30] policy: assignment name can vary per resource --- .../internal/services/policy/assignment_base_resource.go | 8 +------- .../services/policy/assignment_resource_group_resource.go | 7 +++++++ .../services/policy/assignment_subscription_resource.go | 7 +++++++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/azurerm/internal/services/policy/assignment_base_resource.go b/azurerm/internal/services/policy/assignment_base_resource.go index 02082b3372b7..af8b7c8d76d8 100644 --- a/azurerm/internal/services/policy/assignment_base_resource.go +++ b/azurerm/internal/services/policy/assignment_base_resource.go @@ -308,13 +308,7 @@ func (br assignmentBaseResource) updateFunc() sdk.ResourceFunc { func (br assignmentBaseResource) arguments(fields map[string]*pluginsdk.Schema) map[string]*pluginsdk.Schema { output := map[string]*pluginsdk.Schema{ - "name": { - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotWhiteSpace, - }, - + // NOTE: `name` isn't included since it varies depending on the resource, so it's expected to be passed in "policy_definition_id": { Type: pluginsdk.TypeString, Required: true, diff --git a/azurerm/internal/services/policy/assignment_resource_group_resource.go b/azurerm/internal/services/policy/assignment_resource_group_resource.go index 59986d3ab162..4a3f106026fb 100644 --- a/azurerm/internal/services/policy/assignment_resource_group_resource.go +++ b/azurerm/internal/services/policy/assignment_resource_group_resource.go @@ -5,6 +5,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" resourceValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" ) var _ sdk.ResourceWithUpdate = ResourceGroupAssignmentResource{} @@ -15,6 +16,12 @@ type ResourceGroupAssignmentResource struct { func (r ResourceGroupAssignmentResource) Arguments() map[string]*pluginsdk.Schema { schema := map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotWhiteSpace, + }, "resource_group_id": { Type: pluginsdk.TypeString, Required: true, diff --git a/azurerm/internal/services/policy/assignment_subscription_resource.go b/azurerm/internal/services/policy/assignment_subscription_resource.go index 8b0c4d3c21e4..6f9e9badbbce 100644 --- a/azurerm/internal/services/policy/assignment_subscription_resource.go +++ b/azurerm/internal/services/policy/assignment_subscription_resource.go @@ -5,6 +5,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" subscriptionValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/subscription/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" ) var _ sdk.ResourceWithUpdate = SubscriptionAssignmentResource{} @@ -15,6 +16,12 @@ type SubscriptionAssignmentResource struct { func (r SubscriptionAssignmentResource) Arguments() map[string]*pluginsdk.Schema { schema := map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotWhiteSpace, + }, "subscription_id": { Type: pluginsdk.TypeString, Required: true, From 618501a96f6df9c3709ee4e6ca36c353f42df7dc Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 23 Jun 2021 13:44:32 +0200 Subject: [PATCH 17/30] New Resource: `azurerm_management_group_policy_assignment` ``` $ TF_ACC=1 go test -v ./azurerm/internal/services/policy -run=TestAccManagementGroupPolicyAssignment_ -timeout=60m === RUN TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicy === PAUSE TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicy === RUN TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicySet === PAUSE TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicySet === RUN TestAccManagementGroupPolicyAssignment_basicWithCustomPolicy === PAUSE TestAccManagementGroupPolicyAssignment_basicWithCustomPolicy === RUN TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyRequiresImport === PAUSE TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyRequiresImport === RUN TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete === PAUSE TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicy === CONT TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyRequiresImport === CONT TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccManagementGroupPolicyAssignment_basicWithCustomPolicy --- PASS: TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyRequiresImport (376.83s) === CONT TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicySet --- PASS: TestAccManagementGroupPolicyAssignment_basicWithCustomPolicy (467.15s) --- PASS: TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete (534.50s) --- PASS: TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicy (568.46s) --- PASS: TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicySet (383.26s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy 760.164s ``` --- .../assignment_management_group_resource.go | 69 ++++ ...signment_management_group_resource_test.go | 387 ++++++++++++++++++ .../parse/management_group_assignment.go | 58 +++ .../parse/management_group_assignment_test.go | 94 +++++ .../internal/services/policy/registration.go | 1 + .../management_group_assignment_id.go | 21 + .../management_group_assignment_id_test.go | 62 +++ 7 files changed, 692 insertions(+) create mode 100644 azurerm/internal/services/policy/assignment_management_group_resource.go create mode 100644 azurerm/internal/services/policy/assignment_management_group_resource_test.go create mode 100644 azurerm/internal/services/policy/parse/management_group_assignment.go create mode 100644 azurerm/internal/services/policy/parse/management_group_assignment_test.go create mode 100644 azurerm/internal/services/policy/validate/management_group_assignment_id.go create mode 100644 azurerm/internal/services/policy/validate/management_group_assignment_id_test.go diff --git a/azurerm/internal/services/policy/assignment_management_group_resource.go b/azurerm/internal/services/policy/assignment_management_group_resource.go new file mode 100644 index 000000000000..b9c56a08059f --- /dev/null +++ b/azurerm/internal/services/policy/assignment_management_group_resource.go @@ -0,0 +1,69 @@ +package policy + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" + managementGroupValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/managementgroup/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" +) + +var _ sdk.ResourceWithUpdate = ManagementGroupAssignmentResource{} + +type ManagementGroupAssignmentResource struct { + base assignmentBaseResource +} + +func (r ManagementGroupAssignmentResource) Arguments() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "management_group_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: managementGroupValidate.ManagementGroupID, + }, + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringIsNotWhiteSpace, + validation.StringLenBetween(3, 24), + // The policy assignment name length must not exceed '24' characters. + ), + }, + } + return r.base.arguments(schema) +} + +func (r ManagementGroupAssignmentResource) Attributes() map[string]*pluginsdk.Schema { + return r.base.attributes() +} + +func (r ManagementGroupAssignmentResource) Create() sdk.ResourceFunc { + return r.base.createFunc(r.ResourceType(), "management_group_id") +} + +func (r ManagementGroupAssignmentResource) Delete() sdk.ResourceFunc { + return r.base.deleteFunc() +} + +func (r ManagementGroupAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.ManagementGroupAssignmentID +} + +func (r ManagementGroupAssignmentResource) ModelObject() interface{} { + return nil +} + +func (r ManagementGroupAssignmentResource) Read() sdk.ResourceFunc { + return r.base.readFunc("management_group_id") +} + +func (r ManagementGroupAssignmentResource) ResourceType() string { + return "azurerm_management_group_policy_assignment" +} + +func (r ManagementGroupAssignmentResource) Update() sdk.ResourceFunc { + return r.base.updateFunc() +} diff --git a/azurerm/internal/services/policy/assignment_management_group_resource_test.go b/azurerm/internal/services/policy/assignment_management_group_resource_test.go new file mode 100644 index 000000000000..174f8aa46625 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_management_group_resource_test.go @@ -0,0 +1,387 @@ +package policy_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type ManagementGroupAssignmentTestResource struct{} + +func TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccManagementGroupPolicyAssignment_basicWithBuiltInPolicySet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccManagementGroupPolicyAssignment_basicWithCustomPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + data.RequiresImportErrorStep(r.withCustomPolicyRequiresImport), + }) +} + +func TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r ManagementGroupAssignmentTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PolicyAssignmentID(state.ID) + if err != nil { + return nil, err + } + + assignment, err := client.Policy.AssignmentsClient.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(assignment.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return utils.Bool(true), nil +} + +func (r ManagementGroupAssignmentTestResource) withBuiltInPolicyBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ %[3]q ] + } + }) +} +`, template, data.RandomString, data.Locations.Primary) +} + +func (r ManagementGroupAssignmentTestResource) withBuiltInPolicyUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ %[3]q, %[4]q ] + } + }) +} +`, template, data.RandomString, data.Locations.Primary, data.Locations.Secondary) +} + +func (r ManagementGroupAssignmentTestResource) withBuiltInPolicySetBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = %[3]q + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomString, data.Locations.Primary) +} + +func (r ManagementGroupAssignmentTestResource) withBuiltInPolicySetUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = %[3]q + + identity { + type = "SystemAssigned" + } + + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomString, data.Locations.Primary) +} + +func (r ManagementGroupAssignmentTestResource) withCustomPolicyBasic(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = azurerm_policy_definition.test.id +} +`, template, data.RandomString) +} + +func (r ManagementGroupAssignmentTestResource) withCustomPolicyComplete(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + // NOTE: we could include parameters here but it's tested extensively elsewhere + // NOTE: intentionally avoiding NotScopes for Management Groups since it's awkward to test, but + // this is covered in the other resources + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = azurerm_policy_definition.test.id + description = "This is a policy assignment from an acceptance test" + display_name = "AccTest Policy %[2]s" + enforce = false + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomString) +} + +func (r ManagementGroupAssignmentTestResource) withCustomPolicyRequiresImport(data acceptance.TestData) string { + template := r.withCustomPolicyBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_management_group_policy_assignment" "import" { + name = azurerm_management_group_policy_assignment.test.name + management_group_id = azurerm_management_group_policy_assignment.test.management_group_id + policy_definition_id = azurerm_management_group_policy_assignment.test.policy_definition_id +} +`, template) +} + +func (r ManagementGroupAssignmentTestResource) withCustomPolicyUpdated(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_management_group_policy_assignment" "test" { + name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.id + policy_definition_id = azurerm_policy_definition.test.id + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomString) +} + +func (r ManagementGroupAssignmentTestResource) templateWithCustomPolicy(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_policy_definition" "test" { + name = "acctestpol-%[2]s" + policy_type = "Custom" + mode = "All" + display_name = "acctestpol-%[2]s" + management_group_id = azurerm_management_group.test.group_id + + policy_rule = < Date: Wed, 23 Jun 2021 17:12:23 +0200 Subject: [PATCH 18/30] New Resource: `azurerm_resource_policy_assignment` ``` $ TF_ACC=1 envchain azurerm go test -v ./azurerm/internal/services/policy -run=TestAccResourcePolicyAssignment_ -timeout=60m === RUN TestAccResourcePolicyAssignment_basicWithBuiltInPolicy === PAUSE TestAccResourcePolicyAssignment_basicWithBuiltInPolicy === RUN TestAccResourcePolicyAssignment_basicWithBuiltInPolicySet === PAUSE TestAccResourcePolicyAssignment_basicWithBuiltInPolicySet === RUN TestAccResourcePolicyAssignment_basicWithCustomPolicy === PAUSE TestAccResourcePolicyAssignment_basicWithCustomPolicy === RUN TestAccResourcePolicyAssignment_basicWithCustomPolicyRequiresImport === PAUSE TestAccResourcePolicyAssignment_basicWithCustomPolicyRequiresImport === RUN TestAccResourcePolicyAssignment_basicWithCustomPolicyComplete === PAUSE TestAccResourcePolicyAssignment_basicWithCustomPolicyComplete === CONT TestAccResourcePolicyAssignment_basicWithBuiltInPolicy === CONT TestAccResourcePolicyAssignment_basicWithCustomPolicyRequiresImport === CONT TestAccResourcePolicyAssignment_basicWithCustomPolicy === CONT TestAccResourcePolicyAssignment_basicWithCustomPolicyComplete --- PASS: TestAccResourcePolicyAssignment_basicWithCustomPolicyRequiresImport (356.44s) === CONT TestAccResourcePolicyAssignment_basicWithBuiltInPolicySet --- PASS: TestAccResourcePolicyAssignment_basicWithCustomPolicy (487.33s) --- PASS: TestAccResourcePolicyAssignment_basicWithCustomPolicyComplete (539.72s) --- PASS: TestAccResourcePolicyAssignment_basicWithBuiltInPolicy (575.55s) --- PASS: TestAccResourcePolicyAssignment_basicWithBuiltInPolicySet (404.72s) PASS ok github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy 761.216s ``` --- .../services/policy/assignment_resource.go | 65 +++ .../policy/assignment_resource_test.go | 395 ++++++++++++++++++ .../policy/policy_definition_resource.go | 1 + .../internal/services/policy/registration.go | 1 + .../policy/validate/resource_assignment_id.go | 23 + azurerm/internal/tf/validation/pluginsdk.go | 17 + 6 files changed, 502 insertions(+) create mode 100644 azurerm/internal/services/policy/assignment_resource.go create mode 100644 azurerm/internal/services/policy/assignment_resource_test.go create mode 100644 azurerm/internal/services/policy/validate/resource_assignment_id.go diff --git a/azurerm/internal/services/policy/assignment_resource.go b/azurerm/internal/services/policy/assignment_resource.go new file mode 100644 index 000000000000..38c9145f0c6b --- /dev/null +++ b/azurerm/internal/services/policy/assignment_resource.go @@ -0,0 +1,65 @@ +package policy + +import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" +) + +var _ sdk.ResourceWithUpdate = ResourceAssignmentResource{} + +type ResourceAssignmentResource struct { + base assignmentBaseResource +} + +func (r ResourceAssignmentResource) Arguments() map[string]*pluginsdk.Schema { + schema := map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotWhiteSpace, + }, + "resource_id": { + //TODO: tests for this + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ResourceAssignmentId(), + }, + } + return r.base.arguments(schema) +} + +func (r ResourceAssignmentResource) Attributes() map[string]*pluginsdk.Schema { + return r.base.attributes() +} + +func (r ResourceAssignmentResource) Create() sdk.ResourceFunc { + return r.base.createFunc(r.ResourceType(), "resource_id") +} + +func (r ResourceAssignmentResource) Delete() sdk.ResourceFunc { + return r.base.deleteFunc() +} + +func (r ResourceAssignmentResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validate.ResourceAssignmentId() +} + +func (r ResourceAssignmentResource) ModelObject() interface{} { + return nil +} + +func (r ResourceAssignmentResource) Read() sdk.ResourceFunc { + return r.base.readFunc("resource_id") +} + +func (r ResourceAssignmentResource) ResourceType() string { + return "azurerm_resource_policy_assignment" +} + +func (r ResourceAssignmentResource) Update() sdk.ResourceFunc { + return r.base.updateFunc() +} diff --git a/azurerm/internal/services/policy/assignment_resource_test.go b/azurerm/internal/services/policy/assignment_resource_test.go new file mode 100644 index 000000000000..00464fc0a7b4 --- /dev/null +++ b/azurerm/internal/services/policy/assignment_resource_test.go @@ -0,0 +1,395 @@ +package policy_test + +import ( + "context" + "fmt" + "testing" + + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type ResourceAssignmentTestResource struct{} + +func TestAccResourcePolicyAssignment_basicWithBuiltInPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_policy_assignment", "test") + r := ResourceAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourcePolicyAssignment_basicWithBuiltInPolicySet(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_policy_assignment", "test") + r := ResourceAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withBuiltInPolicySetBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourcePolicyAssignment_basicWithCustomPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_policy_assignment", "test") + r := ResourceAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyUpdated(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccResourcePolicyAssignment_basicWithCustomPolicyRequiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_policy_assignment", "test") + r := ResourceAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + data.RequiresImportErrorStep(r.withCustomPolicyRequiresImport), + }) +} + +func TestAccResourcePolicyAssignment_basicWithCustomPolicyComplete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_resource_policy_assignment", "test") + r := ResourceAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyBasic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withCustomPolicyComplete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r ResourceAssignmentTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.PolicyAssignmentID(state.ID) + if err != nil { + return nil, err + } + + assignment, err := client.Policy.AssignmentsClient.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(assignment.Response) { + return utils.Bool(false), nil + } + + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return utils.Bool(true), nil +} + +func (r ResourceAssignmentTestResource) withBuiltInPolicyBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ azurerm_resource_group.test.location ] + } + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) withBuiltInPolicyUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_definition" "test" { + display_name = "Allowed locations" +} + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = data.azurerm_policy_definition.test.id + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [ azurerm_resource_group.test.location, %[3]q ] + } + }) +} +`, template, data.RandomInteger, data.Locations.Secondary) +} + +func (r ResourceAssignmentTestResource) withBuiltInPolicySetBasic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = azurerm_resource_group.test.location + + identity { + type = "SystemAssigned" + } +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) withBuiltInPolicySetUpdated(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_policy_set_definition" "test" { + display_name = "Audit machines with insecure password security settings" +} + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = data.azurerm_policy_set_definition.test.id + location = azurerm_resource_group.test.location + + identity { + type = "SystemAssigned" + } + + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) withCustomPolicyBasic(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = azurerm_policy_definition.test.id +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) withCustomPolicyComplete(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + // NOTE: we could include parameters here but it's tested extensively elsewhere + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = azurerm_policy_definition.test.id + description = "This is a policy assignment from an acceptance test" + display_name = "AccTest Policy %[2]d" + enforce = false + not_scopes = [ + format("%%s/subnets/subnet1", azurerm_virtual_network.test.id) + ] + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) withCustomPolicyRequiresImport(data acceptance.TestData) string { + template := r.withCustomPolicyBasic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_resource_policy_assignment" "import" { + name = azurerm_resource_policy_assignment.test.name + resource_id = azurerm_resource_policy_assignment.test.resource_id + policy_definition_id = azurerm_resource_policy_assignment.test.policy_definition_id +} +`, template) +} + +func (r ResourceAssignmentTestResource) withCustomPolicyUpdated(data acceptance.TestData) string { + template := r.templateWithCustomPolicy(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +resource "azurerm_resource_policy_assignment" "test" { + name = "acctestpa-%[2]d" + resource_id = azurerm_virtual_network.test.id + policy_definition_id = azurerm_policy_definition.test.id + metadata = jsonencode({ + "category": "Testing" + }) +} +`, template, data.RandomInteger) +} + +func (r ResourceAssignmentTestResource) templateWithCustomPolicy(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%[1]s + +resource "azurerm_policy_definition" "test" { + name = "acctestpol-%[2]d" + policy_type = "Custom" + mode = "All" + display_name = "acctestpol-%[2]d" + + policy_rule = < Date: Wed, 23 Jun 2021 17:27:11 +0200 Subject: [PATCH 19/30] r/policy_assignment: reverting the changes to the identity block --- .../internal/services/policy/assignment.go | 58 +++++++++ .../policy/assignment_base_resource.go | 29 ++++- .../policy/policy_assignment_resource.go | 121 +++++++----------- 3 files changed, 132 insertions(+), 76 deletions(-) create mode 100644 azurerm/internal/services/policy/assignment.go diff --git a/azurerm/internal/services/policy/assignment.go b/azurerm/internal/services/policy/assignment.go new file mode 100644 index 000000000000..6048cf77b7ce --- /dev/null +++ b/azurerm/internal/services/policy/assignment.go @@ -0,0 +1,58 @@ +package policy + +import ( + "context" + "fmt" + "strconv" + "time" + + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func convertEnforcementMode(mode bool) policy.EnforcementMode { + if mode { + return policy.Default + } else { + return policy.DoNotEnforce + } +} + +func waitForPolicyAssignmentToStabilize(ctx context.Context, client *policy.AssignmentsClient, id parse.PolicyAssignmentId, shouldExist bool) error { + deadline, ok := ctx.Deadline() + if !ok { + return fmt.Errorf("context was missing a deadline") + } + stateConf := &pluginsdk.StateChangeConf{ + Pending: []string{"404"}, + Target: []string{"200"}, + Refresh: func() (interface{}, string, error) { + resp, err := client.Get(ctx, id.Scope, id.Name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return resp, strconv.Itoa(resp.StatusCode), nil + } + + return nil, strconv.Itoa(resp.StatusCode), fmt.Errorf("polling for %s: %+v", id, err) + } + + return resp, strconv.Itoa(resp.StatusCode), nil + }, + MinTimeout: 10 * time.Second, + ContinuousTargetOccurence: 20, + PollInterval: 5 * time.Second, + Timeout: time.Until(deadline), + } + if !shouldExist { + stateConf.Pending = []string{"200"} + stateConf.Target = []string{"404"} + } + + if _, err := stateConf.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} diff --git a/azurerm/internal/services/policy/assignment_base_resource.go b/azurerm/internal/services/policy/assignment_base_resource.go index af8b7c8d76d8..526d4feb5a39 100644 --- a/azurerm/internal/services/policy/assignment_base_resource.go +++ b/azurerm/internal/services/policy/assignment_base_resource.go @@ -66,7 +66,7 @@ func (br assignmentBaseResource) createFunc(resourceName, scopeFieldName string) if assignment.Location == nil { return fmt.Errorf("`location` must be set when `identity` is assigned") } - identity, err := expandAzureRmPolicyIdentity(v.([]interface{})) + identity, err := br.expandIdentity(v.([]interface{})) if err != nil { return fmt.Errorf("expanding `identity`: %+v", err) } @@ -160,7 +160,7 @@ func (br assignmentBaseResource) readFunc(scopeFieldName string) sdk.ResourceFun metadata.ResourceData.Set(scopeFieldName, id.Scope) metadata.ResourceData.Set("location", location.NormalizeNilable(resp.Location)) - if err := metadata.ResourceData.Set("identity", flattenAzureRmPolicyIdentity(resp.Identity)); err != nil { + if err := metadata.ResourceData.Set("identity", br.flattenIdentity(resp.Identity)); err != nil { return fmt.Errorf("setting `identity`: %+v", err) } @@ -254,7 +254,7 @@ func (br assignmentBaseResource) updateFunc() sdk.ResourceFunc { return fmt.Errorf("`location` must be set when `identity` is assigned") } identityRaw := metadata.ResourceData.Get("identity").([]interface{}) - identity, err := expandAzureRmPolicyIdentity(identityRaw) + identity, err := br.expandIdentity(identityRaw) if err != nil { return fmt.Errorf("expanding `identity`: %+v", err) } @@ -368,3 +368,26 @@ func (br assignmentBaseResource) arguments(fields map[string]*pluginsdk.Schema) func (br assignmentBaseResource) attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{} } + +func (br assignmentBaseResource) expandIdentity(input []interface{}) (*policy.Identity, error) { + expanded, err := policyAssignmentIdentity{}.Expand(input) + if err != nil { + return nil, err + } + + return &policy.Identity{ + Type: policy.ResourceIdentityType(expanded.Type), + }, nil +} + +func (br assignmentBaseResource) flattenIdentity(input *policy.Identity) []interface{} { + var config *identity.ExpandedConfig + if input != nil { + config = &identity.ExpandedConfig{ + Type: string(input.Type), + PrincipalId: input.PrincipalID, + TenantId: input.TenantID, + } + } + return policyAssignmentIdentity{}.Flatten(config) +} diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 1deb45f20356..79efc2e608c2 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -1,17 +1,14 @@ package policy import ( - "context" "fmt" "log" - "strconv" "time" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "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/identity" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/location" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/policy/validate" @@ -21,8 +18,6 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) -// TODO: revert the identity behaviour back - func resourceArmPolicyAssignment() *pluginsdk.Resource { return &pluginsdk.Resource{ Create: resourceArmPolicyAssignmentCreate, @@ -77,7 +72,34 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { "location": azure.SchemaLocationOptional(), - "identity": policyAssignmentIdentity{}.Schema(), + //lintignore:XS003 + "identity": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "type": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(policy.None), + string(policy.SystemAssigned), + }, false), + }, + "principal_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tenant_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, "parameters": { Type: pluginsdk.TypeString, @@ -145,11 +167,7 @@ func resourceArmPolicyAssignmentCreate(d *pluginsdk.ResourceData, meta interface if assignment.Location == nil { return fmt.Errorf("`location` must be set when `identity` is assigned") } - identity, err := expandAzureRmPolicyIdentity(v.([]interface{})) - if err != nil { - return fmt.Errorf("expanding `identity`: %+v", err) - } - assignment.Identity = identity + assignment.Identity = expandAzureRmPolicyIdentity(v.([]interface{})) } if v := d.Get("parameters").(string); v != "" { @@ -236,11 +254,7 @@ func resourceArmPolicyAssignmentUpdate(d *pluginsdk.ResourceData, meta interface return fmt.Errorf("`location` must be set when `identity` is assigned") } identityRaw := d.Get("identity").([]interface{}) - identity, err := expandAzureRmPolicyIdentity(identityRaw) - if err != nil { - return fmt.Errorf("expanding `identity`: %+v", err) - } - update.Identity = identity + update.Identity = expandAzureRmPolicyIdentity(identityRaw) } if d.HasChange("metadata") { @@ -356,64 +370,33 @@ func resourceArmPolicyAssignmentDelete(d *pluginsdk.ResourceData, meta interface return nil } -func waitForPolicyAssignmentToStabilize(ctx context.Context, client *policy.AssignmentsClient, id parse.PolicyAssignmentId, shouldExist bool) error { - deadline, ok := ctx.Deadline() - if !ok { - return fmt.Errorf("context was missing a deadline") +func expandAzureRmPolicyIdentity(input []interface{}) *policy.Identity { + if len(input) == 0 || input[0] == nil { + return nil } - stateConf := &pluginsdk.StateChangeConf{ - Pending: []string{"404"}, - Target: []string{"200"}, - Refresh: func() (interface{}, string, error) { - resp, err := client.Get(ctx, id.Scope, id.Name) - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - return resp, strconv.Itoa(resp.StatusCode), nil - } - - return nil, strconv.Itoa(resp.StatusCode), fmt.Errorf("polling for %s: %+v", id, err) - } + identity := input[0].(map[string]interface{}) - return resp, strconv.Itoa(resp.StatusCode), nil - }, - MinTimeout: 10 * time.Second, - ContinuousTargetOccurence: 10, - PollInterval: 5 * time.Second, - Timeout: time.Until(deadline), - } - if !shouldExist { - stateConf.Pending = []string{"200"} - stateConf.Target = []string{"404"} - } - - if _, err := stateConf.WaitForStateContext(ctx); err != nil { - return err + return &policy.Identity{ + Type: policy.ResourceIdentityType(identity["type"].(string)), } - - return nil } -func expandAzureRmPolicyIdentity(input []interface{}) (*policy.Identity, error) { - expanded, err := policyAssignmentIdentity{}.Expand(input) - if err != nil { - return nil, err +func flattenAzureRmPolicyIdentity(identity *policy.Identity) []interface{} { + if identity == nil { + return make([]interface{}, 0) } - return &policy.Identity{ - Type: policy.ResourceIdentityType(expanded.Type), - }, nil -} + result := make(map[string]interface{}) + result["type"] = string(identity.Type) + if identity.PrincipalID != nil { + result["principal_id"] = *identity.PrincipalID + } -func flattenAzureRmPolicyIdentity(input *policy.Identity) []interface{} { - var config *identity.ExpandedConfig - if input != nil { - config = &identity.ExpandedConfig{ - Type: string(input.Type), - PrincipalId: input.PrincipalID, - TenantId: input.TenantID, - } + if identity.TenantID != nil { + result["tenant_id"] = *identity.TenantID } - return policyAssignmentIdentity{}.Flatten(config) + + return []interface{}{result} } func expandAzureRmPolicyNotScopes(input []interface{}) *[]string { @@ -425,11 +408,3 @@ func expandAzureRmPolicyNotScopes(input []interface{}) *[]string { return ¬ScopesRes } - -func convertEnforcementMode(mode bool) policy.EnforcementMode { - if mode { - return policy.Default - } else { - return policy.DoNotEnforce - } -} From d1df2d817466b539bd60f30c4061841ba2b423d3 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 23 Jun 2021 17:43:22 +0200 Subject: [PATCH 20/30] r/policy_assignment: refactoring --- .../policy/assignment_base_resource.go | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/azurerm/internal/services/policy/assignment_base_resource.go b/azurerm/internal/services/policy/assignment_base_resource.go index 526d4feb5a39..536fd3c097f4 100644 --- a/azurerm/internal/services/policy/assignment_base_resource.go +++ b/azurerm/internal/services/policy/assignment_base_resource.go @@ -171,17 +171,14 @@ func (br assignmentBaseResource) readFunc(scopeFieldName string) sdk.ResourceFun metadata.ResourceData.Set("not_scopes", props.NotScopes) metadata.ResourceData.Set("policy_definition_id", props.PolicyDefinitionID) - flattenedMetaData, err := br.flattenMetaData(props.Metadata) - if err != nil { - return fmt.Errorf("serializing `metadata`: %+v", err) - } - metadata.ResourceData.Set("metadata", *flattenedMetaData) + flattenedMetaData := flattenJSON(props.Metadata) + metadata.ResourceData.Set("metadata", flattenedMetaData) - json, err := flattenParameterValuesValueToString(props.Parameters) + flattenedParameters, err := flattenParameterValuesValueToString(props.Parameters) if err != nil { return fmt.Errorf("serializing JSON from `parameters`: %+v", err) } - metadata.ResourceData.Set("parameters", json) + metadata.ResourceData.Set("parameters", flattenedParameters) } return nil @@ -190,21 +187,6 @@ func (br assignmentBaseResource) readFunc(scopeFieldName string) sdk.ResourceFun } } -func (br assignmentBaseResource) flattenMetaData(input interface{}) (*string, error) { - // TODO: fixme - out := flattenJSON(input) - return &out, nil -} - -func (br assignmentBaseResource) flattenParameterValues(input map[string]*policy.ParameterValuesValue) (*string, error) { - // TODO: fixme - out, err := flattenParameterValuesValueToString(input) - if err != nil { - return nil, err - } - return &out, nil -} - func (br assignmentBaseResource) updateFunc() sdk.ResourceFunc { return sdk.ResourceFunc{ Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { From e1a465c5cf9a2eaff427b2908de4c67392222528 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 11:47:49 +0200 Subject: [PATCH 21/30] r/management_group_policy_assignment: adding a complex policy definition --- ...signment_management_group_resource_test.go | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/azurerm/internal/services/policy/assignment_management_group_resource_test.go b/azurerm/internal/services/policy/assignment_management_group_resource_test.go index 174f8aa46625..e31dbc04917a 100644 --- a/azurerm/internal/services/policy/assignment_management_group_resource_test.go +++ b/azurerm/internal/services/policy/assignment_management_group_resource_test.go @@ -147,6 +147,35 @@ func TestAccManagementGroupPolicyAssignment_basicWithCustomPolicyComplete(t *tes }) } +func TestAccManagementGroupPolicyAssignment_basicComplexWithCustomPolicy(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_management_group_policy_assignment", "test") + r := ManagementGroupAssignmentTestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withComplexCustomPolicyAndParameters(data, "Acceptance"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withComplexCustomPolicyAndParameters(data, "Testing"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withComplexCustomPolicyAndParameters(data, "Acceptance Testing"), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r ManagementGroupAssignmentTestResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.PolicyAssignmentID(state.ID) if err != nil { @@ -329,6 +358,120 @@ resource "azurerm_management_group_policy_assignment" "import" { `, template) } +func (r ManagementGroupAssignmentTestResource) withComplexCustomPolicyAndParameters(data acceptance.TestData, metadataValue string) string { + template := r.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_policy_definition" "test" { + name = "acctestpol-%[2]s" + policy_type = "Custom" + mode = "All" + display_name = "acctestpol-%[2]s" + description = "Description for %[2]s" + management_group_id = azurerm_management_group.test.group_id + metadata = < Date: Thu, 24 Jun 2021 11:55:34 +0200 Subject: [PATCH 22/30] sdk: removing the unused PackagePath method This was added in anticipation of other developments but hasn't been used yet for now it's prudent to remove this until we need it --- azurerm/internal/services/eventhub/registration.go | 5 ----- azurerm/internal/services/loadbalancer/registration.go | 5 ----- azurerm/internal/services/resource/registration.go | 5 ----- azurerm/internal/services/web/registration.go | 5 ----- 4 files changed, 20 deletions(-) diff --git a/azurerm/internal/services/eventhub/registration.go b/azurerm/internal/services/eventhub/registration.go index 925ae6354c15..c3b6b53f5ce6 100644 --- a/azurerm/internal/services/eventhub/registration.go +++ b/azurerm/internal/services/eventhub/registration.go @@ -44,11 +44,6 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { } } -// PackagePath is the relative path to this package -func (r Registration) PackagePath() string { - return "TODO" -} - // DataSources returns a list of Data Sources supported by this Service func (r Registration) DataSources() []sdk.DataSource { return []sdk.DataSource{} diff --git a/azurerm/internal/services/loadbalancer/registration.go b/azurerm/internal/services/loadbalancer/registration.go index 98862829f576..51b7a883879a 100644 --- a/azurerm/internal/services/loadbalancer/registration.go +++ b/azurerm/internal/services/loadbalancer/registration.go @@ -49,11 +49,6 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { } } -// PackagePath is the relative path to this package -func (r Registration) PackagePath() string { - return "TODO: do we need this?" -} - // Resources returns a list of Resources supported by this Service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ diff --git a/azurerm/internal/services/resource/registration.go b/azurerm/internal/services/resource/registration.go index 0af28a060f63..ac3962eda181 100644 --- a/azurerm/internal/services/resource/registration.go +++ b/azurerm/internal/services/resource/registration.go @@ -46,11 +46,6 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { } } -// PackagePath is the relative path to this package -func (r Registration) PackagePath() string { - return "TODO: do we need this?" -} - // DataSources returns a list of Data Sources supported by this Service func (r Registration) DataSources() []sdk.DataSource { return []sdk.DataSource{} diff --git a/azurerm/internal/services/web/registration.go b/azurerm/internal/services/web/registration.go index 96139032d09a..30e15a8a308f 100644 --- a/azurerm/internal/services/web/registration.go +++ b/azurerm/internal/services/web/registration.go @@ -55,11 +55,6 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { } } -// PackagePath is the relative path to this package -func (r Registration) PackagePath() string { - return "TODO: do we need this?" -} - func (r Registration) DataSources() []sdk.DataSource { return []sdk.DataSource{ AppServiceEnvironmentV3DataSource{}, From 1c1214d95bcf4fd0b589ec89bcd69713d6b26a8f Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 12:07:31 +0200 Subject: [PATCH 23/30] r/policy_assignment: deprecating in favour of the new resources --- .../services/policy/policy_assignment_resource.go | 14 ++++++++++++++ website/docs/r/policy_assignment.html.markdown | 2 ++ 2 files changed, 16 insertions(+) diff --git a/azurerm/internal/services/policy/policy_assignment_resource.go b/azurerm/internal/services/policy/policy_assignment_resource.go index 79efc2e608c2..3a9b2290de89 100644 --- a/azurerm/internal/services/policy/policy_assignment_resource.go +++ b/azurerm/internal/services/policy/policy_assignment_resource.go @@ -3,6 +3,7 @@ package policy import ( "fmt" "log" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-09-01/policy" @@ -30,6 +31,19 @@ func resourceArmPolicyAssignment() *pluginsdk.Resource { return err }), + DeprecationMessage: func() string { + msg := `The 'azurerm_policy_assignment' resource is deprecated in favour of the: + +- 'azurerm_management_group_policy_assignment' +- 'azurerm_resource_policy_assignment' +- 'azurerm_resource_group_policy_assignment' +- 'azurerm_subscription_policy_assignment' + +resources and will be removed in version 3.0 of the Azure Provider. +` + return strings.ReplaceAll(msg, "'", "`") + }(), + Timeouts: &pluginsdk.ResourceTimeout{ Create: pluginsdk.DefaultTimeout(30 * time.Minute), Read: pluginsdk.DefaultTimeout(5 * time.Minute), diff --git a/website/docs/r/policy_assignment.html.markdown b/website/docs/r/policy_assignment.html.markdown index fa9436ff9a92..46f6a3b2b2d8 100644 --- a/website/docs/r/policy_assignment.html.markdown +++ b/website/docs/r/policy_assignment.html.markdown @@ -10,6 +10,8 @@ description: |- Configures the specified Policy Definition at the specified Scope. Also, Policy Set Definitions are supported. +!> **Note:** The `azurerm_policy_assignment` resource has been deprecated in favour of the `azurerm_management_group_policy_assignment`, `azurerm_resource_policy_assignment`, `azurerm_resource_group_policy_assignment` and `azurerm_subscription_policy_assignment` resources and will be removed in v3.0 of the Azure Provider. + ## Example Usage ```hcl From 3cc35eb307c3e4dfc858ef757336f6ff7eb7902c Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 12:08:26 +0200 Subject: [PATCH 24/30] r/policy_assignment: removing support in 3.0 mode --- azurerm/internal/services/policy/registration.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/azurerm/internal/services/policy/registration.go b/azurerm/internal/services/policy/registration.go index 9b7f9f70379d..56dd18ac4c27 100644 --- a/azurerm/internal/services/policy/registration.go +++ b/azurerm/internal/services/policy/registration.go @@ -1,6 +1,7 @@ package policy import ( + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/sdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" ) @@ -45,11 +46,16 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { - return map[string]*pluginsdk.Resource{ - "azurerm_policy_assignment": resourceArmPolicyAssignment(), + resources := map[string]*pluginsdk.Resource{ "azurerm_policy_definition": resourceArmPolicyDefinition(), "azurerm_policy_set_definition": resourceArmPolicySetDefinition(), "azurerm_policy_remediation": resourceArmPolicyRemediation(), "azurerm_virtual_machine_configuration_policy_assignment": resourceVirtualMachineConfigurationPolicyAssignment(), } + + if !features.ThreePointOh() { + resources["azurerm_policy_assignment"] = resourceArmPolicyAssignment() + } + + return resources } From a3ed1dbe5b465f559b8c5858d3d4f028a28d6601 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 12:43:50 +0200 Subject: [PATCH 25/30] r/management_group_policy_assignment: adding documentation --- ...ment_group_policy_assignment.html.markdown | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 website/docs/r/management_group_policy_assignment.html.markdown diff --git a/website/docs/r/management_group_policy_assignment.html.markdown b/website/docs/r/management_group_policy_assignment.html.markdown new file mode 100644 index 000000000000..82569f0901d1 --- /dev/null +++ b/website/docs/r/management_group_policy_assignment.html.markdown @@ -0,0 +1,113 @@ +--- +subcategory: "Policy" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_management_group_policy_assignment" +description: |- + Manages a Policy Assignment to a Management Group. +--- + +# azurerm_management_group_policy_assignment + +Manages a Policy Assignment to a Management Group. + +## Example Usage + +```hcl +resource "azurerm_management_group" "example" { + display_name = "Some Management Group" +} + +resource "azurerm_policy_definition" "example" { + name = "only-deploy-in-westeurope" + policy_type = "Custom" + mode = "All" + management_group_id = azurerm_management_group.example.group_id + + policy_rule = < **Note:** The `location` field must also be specified when `identity` is specified. + +* `location` - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created. + +* `metadata` - (Optional) A JSON mapping of any Metadata for this Policy. + +* `not_scopes` - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management Group which are excluded from this Policy. + +* `parameters` - (Optional) A JSON mapping of any Parameters for this Policy. Changing this forces a new Management Group Policy Assignment to be created. + +--- + +A `identity` block supports the following: + +* `type` - (Optional) The Type of Managed Identity which should be added to this Policy Definition. The only possible value is `SystemAssigned`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Management Group Policy Assignment. + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID of the Policy Assignment for this Management Group. + +* `tenant_id` - The Tenant ID of the Policy Assignment for this Management Group. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Policy Assignment for this Management Group. +* `read` - (Defaults to 5 minutes) Used when retrieving the Policy Assignment for this Management Group. +* `update` - (Defaults to 30 minutes) Used when updating the Policy Assignment for this Management Group. +* `delete` - (Defaults to 30 minutes) Used when deleting the Policy Assignment for this Management Group. + +## Import + +Management Group Policy Assignments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_management_group_policy_assignment.example /providers/Microsoft.Management/managementGroups/group1/providers/Microsoft.Authorization/policyAssignments/assignment1 +``` \ No newline at end of file From 0df62ef09e027173c274a1fbf59d84b54b4238b2 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 13:00:39 +0200 Subject: [PATCH 26/30] r/resource_policy_assignment: adding docs --- .../resource_policy_assignment.html.markdown | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 website/docs/r/resource_policy_assignment.html.markdown diff --git a/website/docs/r/resource_policy_assignment.html.markdown b/website/docs/r/resource_policy_assignment.html.markdown new file mode 100644 index 000000000000..cf782aef62fb --- /dev/null +++ b/website/docs/r/resource_policy_assignment.html.markdown @@ -0,0 +1,117 @@ +--- +subcategory: "Policy" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_resource_policy_assignment" +description: |- + Manages a Policy Assignment to a Resource. +--- + +# azurerm_resource_policy_assignment + +Manages a Policy Assignment to a Resource. + +## Example Usage + +```hcl +data "azurerm_virtual_network" "example" { + name = "production" + resource_group_name = "networking" +} + +resource "azurerm_policy_definition" "example" { + name = "only-deploy-in-westeurope" + policy_type = "Custom" + mode = "All" + + policy_rule = < To create a Policy Assignment at a Management Group use the `azurerm_management_group_policy_assignment` resource, for a Resource Group use the `azurerm_resource_group_policy_assignment` and for a Subscription use the `azurerm_subscription_policy_assignment` resource. + +--- + +* `description` - (Optional) A description which should be used for this Policy Assignment. + +* `display_name` - (Optional) The Display Name for this Policy Assignment. + +* `enforce` - (Optional) Specifies if this Policy should be enforced or not? + +* `identity` - (Optional) A `identity` block as defined below. + +-> **Note:** The `location` field must also be specified when `identity` is specified. + +* `location` - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created. + +* `metadata` - (Optional) A JSON mapping of any Metadata for this Policy. + +* `not_scopes` - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management Group which are excluded from this Policy. + +* `parameters` - (Optional) A JSON mapping of any Parameters for this Policy. Changing this forces a new Management Group Policy Assignment to be created. + +--- + +A `identity` block supports the following: + +* `type` - (Optional) The Type of Managed Identity which should be added to this Policy Definition. The only possible value is `SystemAssigned`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Resource Policy Assignment. + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID of the Policy Assignment for this Resource. + +* `tenant_id` - The Tenant ID of the Policy Assignment for this Resource. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Policy Assignment for this Resource. +* `read` - (Defaults to 5 minutes) Used when retrieving the Policy Assignment for this Resource. +* `update` - (Defaults to 30 minutes) Used when updating the Policy Assignment for this Resource. +* `delete` - (Defaults to 30 minutes) Used when deleting the Policy Assignment for this Resource. + +## Import + +Resource Policy Assignments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_resource_policy_assignment.example "{resource}/providers/Microsoft.Authorization/policyAssignments/assignment1" +``` + +where `{resource}` is a Resource ID in the form `/subscriptions/00000000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Network/virtualNetworks/network1`. \ No newline at end of file From 55f0a5b1606466bd4597d9cbafb775b3d284378f Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 13:43:10 +0200 Subject: [PATCH 27/30] r/resource_group_policy_assignment: adding docs --- ...urce_group_policy_assignment.html.markdown | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 website/docs/r/resource_group_policy_assignment.html.markdown diff --git a/website/docs/r/resource_group_policy_assignment.html.markdown b/website/docs/r/resource_group_policy_assignment.html.markdown new file mode 100644 index 000000000000..33596db3141f --- /dev/null +++ b/website/docs/r/resource_group_policy_assignment.html.markdown @@ -0,0 +1,113 @@ +--- +subcategory: "Policy" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_resource_group_policy_assignment" +description: |- + Manages a Resource Group Policy Assignment. +--- + +# azurerm_resource_group_policy_assignment + +Manages a Resource Group Policy Assignment. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_policy_definition" "example" { + name = "only-deploy-in-westeurope" + policy_type = "Custom" + mode = "All" + + policy_rule = < **Note:** The `location` field must also be specified when `identity` is specified. + +* `location` - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created. + +* `metadata` - (Optional) A JSON mapping of any Metadata for this Policy. + +* `not_scopes` - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management Group which are excluded from this Policy. + +* `parameters` - (Optional) A JSON mapping of any Parameters for this Policy. Changing this forces a new Management Group Policy Assignment to be created. + +--- + +A `identity` block supports the following: + +* `type` - (Optional) The Type of Managed Identity which should be added to this Policy Definition. The only possible value is `SystemAssigned`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Resource Group Policy Assignment. + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID of the Policy Assignment for this Resource Group. + +* `tenant_id` - The Tenant ID of the Policy Assignment for this Resource Group. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Policy Assignment for this Resource Group. +* `read` - (Defaults to 5 minutes) Used when retrieving the Policy Assignment for this Resource Group. +* `update` - (Defaults to 30 minutes) Used when updating the Policy Assignment for this Resource Group. +* `delete` - (Defaults to 30 minutes) Used when deleting the Policy Assignment for this Resource Group. + +## Import + +Resource Group Policy Assignments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_resource_group_policy_assignment.example /subscriptions/00000000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.Authorization/policyAssignments/assignment1 +``` \ No newline at end of file From 5b0dd7e5525dc390af25e9fd3439f1a258b08768 Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Thu, 24 Jun 2021 13:48:58 +0200 Subject: [PATCH 28/30] r/subscription_policy_assignment: adding docs --- ...bscription_policy_assignment.html.markdown | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 website/docs/r/subscription_policy_assignment.html.markdown diff --git a/website/docs/r/subscription_policy_assignment.html.markdown b/website/docs/r/subscription_policy_assignment.html.markdown new file mode 100644 index 000000000000..9fa18aeac036 --- /dev/null +++ b/website/docs/r/subscription_policy_assignment.html.markdown @@ -0,0 +1,110 @@ +--- +subcategory: "Policy" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_subscription_policy_assignment" +description: |- + Manages a Subscription Policy Assignment. +--- + +# azurerm_subscription_policy_assignment + +Manages a Subscription Policy Assignment. + +## Example Usage + +```hcl +data "azurerm_subscription" "current" {} + +resource "azurerm_policy_definition" "example" { + name = "only-deploy-in-westeurope" + policy_type = "Custom" + mode = "All" + + policy_rule = < **Note:** The `location` field must also be specified when `identity` is specified. + +* `location` - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created. + +* `metadata` - (Optional) A JSON mapping of any Metadata for this Policy. + +* `not_scopes` - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management Group which are excluded from this Policy. + +* `parameters` - (Optional) A JSON mapping of any Parameters for this Policy. Changing this forces a new Management Group Policy Assignment to be created. + +--- + +A `identity` block supports the following: + +* `type` - (Optional) The Type of Managed Identity which should be added to this Policy Definition. The only possible value is `SystemAssigned`. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Subscription Policy Assignment. + +--- + +The `identity` block exports the following: + +* `principal_id` - The Principal ID of the Policy Assignment for this Subscription. + +* `tenant_id` - The Tenant ID of the Policy Assignment for this Subscription. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Policy Assignment for this Subscription. +* `read` - (Defaults to 5 minutes) Used when retrieving the Policy Assignment for this Subscription. +* `update` - (Defaults to 30 minutes) Used when updating the Policy Assignment for this Subscription. +* `delete` - (Defaults to 30 minutes) Used when deleting the Policy Assignment for this Subscription. + +## Import + +Subscription Policy Assignments can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_subscription_policy_assignment.example /subscriptions/00000000-0000-0000-000000000000/providers/Microsoft.Authorization/policyAssignments/assignment1 +``` \ No newline at end of file From ab50b68fe07f7cd2dc58f0582634250d7991dcff Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Wed, 30 Jun 2021 18:12:13 +0200 Subject: [PATCH 29/30] linting --- ...signment_management_group_resource_test.go | 12 +++++------ ...assignment_resource_group_resource_test.go | 20 +++++++++---------- .../policy/assignment_resource_test.go | 20 +++++++++---------- .../assignment_subscription_resource_test.go | 12 +++++------ ...ment_group_policy_assignment.html.markdown | 2 +- ...urce_group_policy_assignment.html.markdown | 8 ++++---- .../resource_policy_assignment.html.markdown | 8 ++++---- ...bscription_policy_assignment.html.markdown | 8 ++++---- 8 files changed, 45 insertions(+), 45 deletions(-) diff --git a/azurerm/internal/services/policy/assignment_management_group_resource_test.go b/azurerm/internal/services/policy/assignment_management_group_resource_test.go index e31dbc04917a..e30ac2322cce 100644 --- a/azurerm/internal/services/policy/assignment_management_group_resource_test.go +++ b/azurerm/internal/services/policy/assignment_management_group_resource_test.go @@ -296,7 +296,7 @@ resource "azurerm_management_group_policy_assignment" "test" { } metadata = jsonencode({ - "category": "Testing" + "category" : "Testing" }) } `, template, data.RandomString, data.Locations.Primary) @@ -338,8 +338,8 @@ resource "azurerm_management_group_policy_assignment" "test" { description = "This is a policy assignment from an acceptance test" display_name = "AccTest Policy %[2]s" enforce = false - metadata = jsonencode({ - "category": "Testing" + metadata = jsonencode({ + "category" : "Testing" }) } `, template, data.RandomString) @@ -373,7 +373,7 @@ resource "azurerm_policy_definition" "test" { mode = "All" display_name = "acctestpol-%[2]s" description = "Description for %[2]s" - management_group_id = azurerm_management_group.test.group_id + management_group_id = azurerm_management_group.test.group_id metadata = < Date: Wed, 30 Jun 2021 12:00:20 -0700 Subject: [PATCH 30/30] lint --- .../services/policy/assignment_base_resource.go | 4 +--- .../assignment_management_group_resource_test.go | 12 ++++++------ .../assignment_resource_group_resource_test.go | 6 +++--- .../services/policy/assignment_resource_test.go | 6 +++--- .../policy/assignment_subscription_resource_test.go | 12 ++++++------ .../r/api_management_api_operation_tag.html.markdown | 2 +- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/azurerm/internal/services/policy/assignment_base_resource.go b/azurerm/internal/services/policy/assignment_base_resource.go index 536fd3c097f4..8475a3477065 100644 --- a/azurerm/internal/services/policy/assignment_base_resource.go +++ b/azurerm/internal/services/policy/assignment_base_resource.go @@ -25,9 +25,7 @@ import ( type policyAssignmentIdentity = identity.SystemAssigned -type assignmentBaseResource struct { - resourceName string -} +type assignmentBaseResource struct{} func (br assignmentBaseResource) createFunc(resourceName, scopeFieldName string) sdk.ResourceFunc { return sdk.ResourceFunc{ diff --git a/azurerm/internal/services/policy/assignment_management_group_resource_test.go b/azurerm/internal/services/policy/assignment_management_group_resource_test.go index e30ac2322cce..a9adba8d065d 100644 --- a/azurerm/internal/services/policy/assignment_management_group_resource_test.go +++ b/azurerm/internal/services/policy/assignment_management_group_resource_test.go @@ -211,9 +211,9 @@ resource "azurerm_management_group_policy_assignment" "test" { name = "acctestpol-%[2]s" management_group_id = azurerm_management_group.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ %[3]q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = ["%[3]s"] } }) } @@ -237,9 +237,9 @@ resource "azurerm_management_group_policy_assignment" "test" { name = "acctestpol-%[2]s" management_group_id = azurerm_management_group.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ %[3]q, %[4]q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = ["%[3]s", "%[4]s"] } }) } diff --git a/azurerm/internal/services/policy/assignment_resource_group_resource_test.go b/azurerm/internal/services/policy/assignment_resource_group_resource_test.go index 9a583fe14c95..b2b713d0efc8 100644 --- a/azurerm/internal/services/policy/assignment_resource_group_resource_test.go +++ b/azurerm/internal/services/policy/assignment_resource_group_resource_test.go @@ -208,9 +208,9 @@ resource "azurerm_resource_group_policy_assignment" "test" { name = "acctestpa-%[2]d" resource_group_id = azurerm_resource_group.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ azurerm_resource_group.test.location, %[3]q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [azurerm_resource_group.test.location, "%[3]s"] } }) } diff --git a/azurerm/internal/services/policy/assignment_resource_test.go b/azurerm/internal/services/policy/assignment_resource_test.go index eaa64eb7190a..317793bb3f37 100644 --- a/azurerm/internal/services/policy/assignment_resource_test.go +++ b/azurerm/internal/services/policy/assignment_resource_test.go @@ -208,9 +208,9 @@ resource "azurerm_resource_policy_assignment" "test" { name = "acctestpa-%[2]d" resource_id = azurerm_virtual_network.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ azurerm_resource_group.test.location, %[3]q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = [azurerm_resource_group.test.location, "%[3]s"] } }) } diff --git a/azurerm/internal/services/policy/assignment_subscription_resource_test.go b/azurerm/internal/services/policy/assignment_subscription_resource_test.go index e160ac203596..1dcc1ea3310d 100644 --- a/azurerm/internal/services/policy/assignment_subscription_resource_test.go +++ b/azurerm/internal/services/policy/assignment_subscription_resource_test.go @@ -182,9 +182,9 @@ resource "azurerm_subscription_policy_assignment" "test" { name = "acctestpa-%[2]d" subscription_id = data.azurerm_subscription.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ %q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = ["%s"] } }) } @@ -208,9 +208,9 @@ resource "azurerm_subscription_policy_assignment" "test" { name = "acctestpa-%[2]d" subscription_id = data.azurerm_subscription.test.id policy_definition_id = data.azurerm_policy_definition.test.id - parameters = jsonencode({ - "listOfAllowedLocations" = { - "value" = [ %[3]q, %[4]q ] + parameters = jsonencode({ + "listOfAllowedLocations" = { + "value" = ["%[3]s", "%[4]s"] } }) } diff --git a/website/docs/r/api_management_api_operation_tag.html.markdown b/website/docs/r/api_management_api_operation_tag.html.markdown index 1164c7a2e95c..432cc4777ac9 100644 --- a/website/docs/r/api_management_api_operation_tag.html.markdown +++ b/website/docs/r/api_management_api_operation_tag.html.markdown @@ -74,4 +74,4 @@ API Management API Operation Tags can be imported using the `resource id`, e.g. ```shell terraform import azurerm_api_management_api_operation_tag.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.ApiManagement/service/service1/apis/api1/operations/operation1/tags/tag1 -``` \ No newline at end of file +```