Skip to content

Commit

Permalink
"azurerm_role_assignment" supports property "delegated_managed_identi…
Browse files Browse the repository at this point in the history
…ty_resource_id" (#11848)

* "azurerm_role_assignment" supports property "delegated_managed_identity_resource_id"

* update

* update

Co-authored-by: kt <[email protected]>
  • Loading branch information
njuCZ and katbyte authored Jun 2, 2021
1 parent 802edbf commit 359ce78
Show file tree
Hide file tree
Showing 5 changed files with 213 additions and 18 deletions.
25 changes: 23 additions & 2 deletions azurerm/internal/services/authorization/parse/role_assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ type RoleAssignmentId struct {
ResourceGroup string
ManagementGroup string
Name string
TenantId string
}

func NewRoleAssignmentID(subscriptionId, resourceGroup, managementGroup, name string) (*RoleAssignmentId, error) {
func NewRoleAssignmentID(subscriptionId, resourceGroup, managementGroup, name, tenantId string) (*RoleAssignmentId, error) {
if subscriptionId == "" && resourceGroup == "" && managementGroup == "" {
return nil, fmt.Errorf("one of subscriptionId, resourceGroup, or managementGroup must be provided")
}
Expand All @@ -36,10 +37,13 @@ func NewRoleAssignmentID(subscriptionId, resourceGroup, managementGroup, name st
ResourceGroup: resourceGroup,
ManagementGroup: managementGroup,
Name: name,
TenantId: tenantId,
}, nil
}

func (id RoleAssignmentId) ID() string {
// in general case, the id format does not change
// for cross tenant scenario, add the tenantId info
func (id RoleAssignmentId) AzureResourceID() string {
if id.ManagementGroup != "" {
fmtString := "/providers/Microsoft.Management/managementGroups/%s/providers/Microsoft.Authorization/roleAssignments/%s"
return fmt.Sprintf(fmtString, id.ManagementGroup, id.Name)
Expand All @@ -54,13 +58,30 @@ func (id RoleAssignmentId) ID() string {
return fmt.Sprintf(fmtString, id.SubscriptionID, id.Name)
}

func (id RoleAssignmentId) ID() string {
return ConstructRoleAssignmentId(id.AzureResourceID(), id.TenantId)
}

func ConstructRoleAssignmentId(azureResourceId, tenantId string) string {
if tenantId == "" {
return azureResourceId
}
return fmt.Sprintf("%s|%s", azureResourceId, tenantId)
}

func RoleAssignmentID(input string) (*RoleAssignmentId, error) {
if len(input) == 0 {
return nil, fmt.Errorf("Role Assignment ID is empty string")
}

roleAssignmentId := RoleAssignmentId{}

parts := strings.Split(input, "|")
if len(parts) == 2 {
roleAssignmentId.TenantId = parts[1]
input = parts[0]
}

switch {
case strings.HasPrefix(input, "/subscriptions/"):
id, err := azure.ParseAzureResourceID(input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,66 @@ func TestRoleAssignmentIDFormatter(t *testing.T) {
ResourceGroup string
ManagementGroup string
Name string
TenantId string
Expected string
}{
{
SubscriptionId: "",
ResourceGroup: "",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "group1",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
Expected: "/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "group1",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
Expected: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "",
ResourceGroup: "",
ManagementGroup: "12345678-1234-9876-4563-123456789012",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "",
Expected: "/providers/Microsoft.Management/managementGroups/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "",
ResourceGroup: "",
ManagementGroup: "12345678-1234-9876-4563-123456789012",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "34567812-3456-7653-6742-345678901234",
Expected: "/providers/Microsoft.Management/managementGroups/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121|34567812-3456-7653-6742-345678901234",
},
}
for _, v := range testData {
t.Logf("testing %+v", v)
actual, err := NewRoleAssignmentID(v.SubscriptionId, v.ResourceGroup, v.ManagementGroup, v.Name)
actual, err := NewRoleAssignmentID(v.SubscriptionId, v.ResourceGroup, v.ManagementGroup, v.Name, v.TenantId)
if err != nil {
if v.Expected == "" {
continue
Expand Down Expand Up @@ -151,6 +166,16 @@ func TestRoleAssignmentID(t *testing.T) {
Name: "23456781-2349-8764-5631-234567890121",
},
},
{
Input: "/providers/Microsoft.Management/managementGroups/managementGroup1/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121|34567812-3456-7653-6742-345678901234",
Expected: &RoleAssignmentId{
SubscriptionID: "",
ResourceGroup: "",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
TenantId: "34567812-3456-7653-6742-345678901234",
},
},
}

for _, v := range testData {
Expand Down
80 changes: 65 additions & 15 deletions azurerm/internal/services/authorization/role_assignment_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import (
"time"

"github.com/Azure/azure-sdk-for-go/services/preview/authorization/mgmt/2020-04-01-preview/authorization"
"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2019-11-01/subscriptions"
"github.com/hashicorp/go-uuid"
"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/services/authorization/parse"
billingValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/billing/validate"
managementGroupValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/managementgroup/validate"
resourceValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/resource/validate"
Expand Down Expand Up @@ -96,6 +98,13 @@ func resourceArmRoleAssignment() *pluginsdk.Resource {
Computed: true,
},

"delegated_managed_identity_resource_id": {
Type: pluginsdk.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: azure.ValidateResourceID,
},

"description": {
Type: pluginsdk.TypeString,
Optional: true,
Expand Down Expand Up @@ -128,6 +137,8 @@ func resourceArmRoleAssignment() *pluginsdk.Resource {
func resourceArmRoleAssignmentCreate(d *pluginsdk.ResourceData, meta interface{}) error {
roleAssignmentsClient := meta.(*clients.Client).Authorization.RoleAssignmentsClient
roleDefinitionsClient := meta.(*clients.Client).Authorization.RoleDefinitionsClient
subscriptionClient := meta.(*clients.Client).Subscription.Client
subscriptionId := meta.(*clients.Client).Account.SubscriptionId
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
defer cancel()

Expand Down Expand Up @@ -163,7 +174,17 @@ func resourceArmRoleAssignmentCreate(d *pluginsdk.ResourceData, meta interface{}
name = uuid
}

existing, err := roleAssignmentsClient.Get(ctx, scope, name, "")
tenantId := ""
delegatedManagedIdentityResourceID := d.Get("delegated_managed_identity_resource_id").(string)
if len(delegatedManagedIdentityResourceID) > 0 {
var err error
tenantId, err = getTenantIdBySubscriptionId(ctx, subscriptionClient, subscriptionId)
if err != nil {
return err
}
}

existing, err := roleAssignmentsClient.Get(ctx, scope, name, tenantId)
if err != nil {
if !utils.ResponseWasNotFound(existing.Response) {
return fmt.Errorf("Error checking for presence of existing Role Assignment ID for %q (Scope %q): %+v", name, scope, err)
Expand All @@ -182,6 +203,10 @@ func resourceArmRoleAssignmentCreate(d *pluginsdk.ResourceData, meta interface{}
},
}

if len(delegatedManagedIdentityResourceID) > 0 {
properties.RoleAssignmentProperties.DelegatedManagedIdentityResourceID = utils.String(delegatedManagedIdentityResourceID)
}

condition := d.Get("condition").(string)
conditionVersion := d.Get("condition_version").(string)

Expand All @@ -197,19 +222,19 @@ func resourceArmRoleAssignmentCreate(d *pluginsdk.ResourceData, meta interface{}
properties.RoleAssignmentProperties.PrincipalType = authorization.ServicePrincipal
}

if err := pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), retryRoleAssignmentsClient(d, scope, name, properties, meta)); err != nil {
if err := pluginsdk.Retry(d.Timeout(pluginsdk.TimeoutCreate), retryRoleAssignmentsClient(d, scope, name, properties, meta, tenantId)); err != nil {
return err
}

read, err := roleAssignmentsClient.Get(ctx, scope, name, "")
read, err := roleAssignmentsClient.Get(ctx, scope, name, tenantId)
if err != nil {
return err
}
if read.ID == nil {
return fmt.Errorf("Cannot read Role Assignment ID for %q (Scope %q)", name, scope)
}

d.SetId(*read.ID)
d.SetId(parse.ConstructRoleAssignmentId(*read.ID, tenantId))
return resourceArmRoleAssignmentRead(d, meta)
}

Expand All @@ -219,7 +244,11 @@ func resourceArmRoleAssignmentRead(d *pluginsdk.ResourceData, meta interface{})
ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d)
defer cancel()

resp, err := client.GetByID(ctx, d.Id(), "")
id, err := parse.RoleAssignmentID(d.Id())
if err != nil {
return err
}
resp, err := client.GetByID(ctx, id.AzureResourceID(), id.TenantId)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
log.Printf("[DEBUG] Role Assignment ID %q was not found - removing from state", d.Id())
Expand All @@ -237,6 +266,7 @@ func resourceArmRoleAssignmentRead(d *pluginsdk.ResourceData, meta interface{})
d.Set("role_definition_id", props.RoleDefinitionID)
d.Set("principal_id", props.PrincipalID)
d.Set("principal_type", props.PrincipalType)
d.Set("delegated_managed_identity_resource_id", props.DelegatedManagedIdentityResourceID)
d.Set("description", props.Description)
d.Set("condition", props.Condition)
d.Set("condition_version", props.ConditionVersion)
Expand Down Expand Up @@ -267,7 +297,7 @@ func resourceArmRoleAssignmentDelete(d *pluginsdk.ResourceData, meta interface{}
return err
}

resp, err := client.Delete(ctx, id.scope, id.name, "")
resp, err := client.Delete(ctx, id.scope, id.name, id.tenantId)
if err != nil {
if !utils.ResponseWasNotFound(resp.Response) {
return err
Expand All @@ -277,7 +307,7 @@ func resourceArmRoleAssignmentDelete(d *pluginsdk.ResourceData, meta interface{}
return nil
}

func retryRoleAssignmentsClient(d *pluginsdk.ResourceData, scope string, name string, properties authorization.RoleAssignmentCreateParameters, meta interface{}) func() *pluginsdk.RetryError {
func retryRoleAssignmentsClient(d *pluginsdk.ResourceData, scope string, name string, properties authorization.RoleAssignmentCreateParameters, meta interface{}, tenantId string) func() *pluginsdk.RetryError {
return func() *pluginsdk.RetryError {
roleAssignmentsClient := meta.(*clients.Client).Authorization.RoleAssignmentsClient
ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d)
Expand Down Expand Up @@ -306,7 +336,7 @@ func retryRoleAssignmentsClient(d *pluginsdk.ResourceData, scope string, name st
Target: []string{
"ready",
},
Refresh: roleAssignmentCreateStateRefreshFunc(ctx, roleAssignmentsClient, *resp.ID),
Refresh: roleAssignmentCreateStateRefreshFunc(ctx, roleAssignmentsClient, *resp.ID, tenantId),
MinTimeout: 5 * time.Second,
ContinuousTargetOccurence: 5,
Timeout: d.Timeout(pluginsdk.TimeoutCreate),
Expand All @@ -321,27 +351,36 @@ func retryRoleAssignmentsClient(d *pluginsdk.ResourceData, scope string, name st
}

type roleAssignmentId struct {
scope string
name string
scope string
name string
tenantId string
}

func parseRoleAssignmentId(input string) (*roleAssignmentId, error) {
segments := strings.Split(input, "/providers/Microsoft.Authorization/roleAssignments/")
tenantId := ""
segments := strings.Split(input, "|")
if len(segments) == 2 {
tenantId = segments[1]
input = segments[0]
}

segments = strings.Split(input, "/providers/Microsoft.Authorization/roleAssignments/")
if len(segments) != 2 {
return nil, fmt.Errorf("Expected Role Assignment ID to be in the format `{scope}/providers/Microsoft.Authorization/roleAssignments/{name}` but got %q", input)
}

// /{scope}/providers/Microsoft.Authorization/roleAssignments/{roleAssignmentName}
id := roleAssignmentId{
scope: strings.TrimPrefix(segments[0], "/"),
name: segments[1],
scope: strings.TrimPrefix(segments[0], "/"),
name: segments[1],
tenantId: tenantId,
}
return &id, nil
}

func roleAssignmentCreateStateRefreshFunc(ctx context.Context, client *authorization.RoleAssignmentsClient, roleID string) pluginsdk.StateRefreshFunc {
func roleAssignmentCreateStateRefreshFunc(ctx context.Context, client *authorization.RoleAssignmentsClient, roleID string, tenantId string) pluginsdk.StateRefreshFunc {
return func() (interface{}, string, error) {
resp, err := client.GetByID(ctx, roleID, "")
resp, err := client.GetByID(ctx, roleID, tenantId)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
return resp, "pending", nil
Expand All @@ -351,3 +390,14 @@ func roleAssignmentCreateStateRefreshFunc(ctx context.Context, client *authoriza
return resp, "ready", nil
}
}

func getTenantIdBySubscriptionId(ctx context.Context, client *subscriptions.Client, subscriptionId string) (string, error) {
resp, err := client.Get(ctx, subscriptionId)
if err != nil {
return "", fmt.Errorf("get tenant Id by Subscription %s: %+v", subscriptionId, err)
}
if resp.TenantID == nil {
return "", fmt.Errorf("tenant Id is nil by Subscription %s: %+v", subscriptionId, resp)
}
return *resp.TenantID, nil
}
Loading

0 comments on commit 359ce78

Please sign in to comment.