Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bring resources (+data source) for role def & assignment #159

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/services/authorization/client/client.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package client

import (
"github.com/Azure/azure-sdk-for-go/profiles/2020-09-01/authorization/mgmt/authorization"
"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"
"github.com/hashicorp/terraform-provider-azurestack/internal/common"
)

type Client struct {
RoleAssignmentsClient *authorization.RoleAssignmentsClient
RoleDefinitionsClient *authorization.RoleDefinitionsClient
ServicePrincipalsClient *graphrbac.ServicePrincipalsClient
}

Expand Down
90 changes: 90 additions & 0 deletions internal/services/authorization/parse/role_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package parse

import (
"fmt"
"strings"

"github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids"
)

type RoleAssignmentId struct {
SubscriptionID string
ResourceGroup string
ManagementGroup string
Name string
}

func NewRoleAssignmentID(subscriptionId, resourceGroup, managementGroup, name string) (*RoleAssignmentId, error) {
if subscriptionId == "" && resourceGroup == "" && managementGroup == "" {
return nil, fmt.Errorf("one of subscriptionId, resourceGroup, or managementGroup must be provided")
}

if managementGroup != "" {
if subscriptionId != "" || resourceGroup != "" {
return nil, fmt.Errorf("cannot provide subscriptionId or resourceGroup when managementGroup is provided")
}
}

if resourceGroup != "" {
if subscriptionId == "" {
return nil, fmt.Errorf("subscriptionId must not be empty when resourceGroup is provided")
}
}

return &RoleAssignmentId{
SubscriptionID: subscriptionId,
ResourceGroup: resourceGroup,
ManagementGroup: managementGroup,
Name: name,
}, nil
}

func (id RoleAssignmentId) ID() string {
if id.ManagementGroup != "" {
fmtString := "/providers/Microsoft.Management/managementGroups/%s/providers/Microsoft.Authorization/roleAssignments/%s"
return fmt.Sprintf(fmtString, id.ManagementGroup, id.Name)
}

if id.ResourceGroup != "" {
fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Authorization/roleAssignments/%s"
return fmt.Sprintf(fmtString, id.SubscriptionID, id.ResourceGroup, id.Name)
}

fmtString := "/subscriptions/%s/providers/Microsoft.Authorization/roleAssignments/%s"
return fmt.Sprintf(fmtString, id.SubscriptionID, id.Name)
}

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

roleAssignmentId := RoleAssignmentId{}

switch {
case strings.HasPrefix(input, "/subscriptions/"):
id, err := resourceids.ParseAzureResourceID(input)
if err != nil {
return nil, fmt.Errorf("could not parse %q as Azure resource ID", input)
}
roleAssignmentId.SubscriptionID = id.SubscriptionID
roleAssignmentId.ResourceGroup = id.ResourceGroup
if roleAssignmentId.Name, err = id.PopSegment("roleAssignments"); err != nil {
return nil, err
}
case strings.HasPrefix(input, "/providers/Microsoft.Management/"):
idParts := strings.Split(input, "/providers/Microsoft.Authorization/roleAssignments/")
if len(idParts) != 2 {
return nil, fmt.Errorf("could not parse Role Assignment ID %q for Management Group", input)
}
if idParts[1] == "" {
return nil, fmt.Errorf("ID was missing a value for the roleAssignments element")
}
roleAssignmentId.Name = idParts[1]
roleAssignmentId.ManagementGroup = strings.TrimPrefix(idParts[0], "/providers/Microsoft.Management/managementGroups/")
default:
return nil, fmt.Errorf("could not parse Role Assignment ID %q", input)
}

return &roleAssignmentId, nil
}
190 changes: 190 additions & 0 deletions internal/services/authorization/parse/role_assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package parse

import (
"testing"
)

type Formatter interface {
ID() string
}

var _ Formatter = RoleAssignmentId{}

func TestRoleAssignmentIDFormatter(t *testing.T) {
testData := []struct {
SubscriptionId string
ResourceGroup string
ManagementGroup string
Name string
Expected string
}{
{
SubscriptionId: "",
ResourceGroup: "",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "group1",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
},
{
SubscriptionId: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
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",
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",
Expected: "/providers/Microsoft.Management/managementGroups/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
},
}
for _, v := range testData {
t.Logf("testing %+v", v)
actual, err := NewRoleAssignmentID(v.SubscriptionId, v.ResourceGroup, v.ManagementGroup, v.Name)
if err != nil {
if v.Expected == "" {
continue
}
}
actualId := actual.ID()
if actualId != v.Expected {
t.Fatalf("expected %q, got %q", v.Expected, actualId)
}
}
}

func TestRoleAssignmentID(t *testing.T) {
testData := []struct {
Input string
Error bool
Expected *RoleAssignmentId
}{
{
// empty
Input: "",
Error: true,
},

{
// missing SubscriptionId
Input: "/",
Error: true,
},

{
// just subscription
Input: "/subscriptions/12345678-1234-9876-4563-123456789012/",
Error: true,
},

{
// missing ResourceGroup value
Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/",
Error: true,
},

{
// missing Management Group value
Input: "/providers/Microsoft.Management/managementGroups/",
Error: true,
},

{
// missing Role Assignment value at Subscription Scope
Input: "/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/",
Error: true,
},

{
// missing Role Assignment value at Management Group scope
Input: "/providers/Microsoft.Management/managementGroups/managementGroup1/providers/Microsoft.Authorization/roleAssignments/",
Error: true,
},

{
// valid at subscription scope
Input: "/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
Expected: &RoleAssignmentId{
SubscriptionID: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
},
},

{
// valid at resource group scope
Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/group1/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
Expected: &RoleAssignmentId{
SubscriptionID: "12345678-1234-9876-4563-123456789012",
ResourceGroup: "group1",
ManagementGroup: "",
Name: "23456781-2349-8764-5631-234567890121",
},
},

{
// valid at management group scope
Input: "/providers/Microsoft.Management/managementGroups/managementGroup1/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121",
Expected: &RoleAssignmentId{
SubscriptionID: "",
ResourceGroup: "",
ManagementGroup: "managementGroup1",
Name: "23456781-2349-8764-5631-234567890121",
},
},
}

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

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

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

if v.Error {
t.Fatal("Expect an error but didn't get one")
}

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

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

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

if actual.ManagementGroup != v.Expected.ManagementGroup {
t.Fatalf("Expected %q but got %q for Role Assignment Name", v.Expected.ManagementGroup, actual.ManagementGroup)
}
}
}
40 changes: 40 additions & 0 deletions internal/services/authorization/parse/role_definition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package parse

import (
"fmt"
"strings"
)

type RoleDefinitionID struct {
ResourceID string
Scope string
RoleID string
}

// RoleDefinitionId is a pseudo ID for storing Scope parameter as this it not retrievable from API
// It is formed of the Azure Resource ID for the Role and the Scope it is created against
func RoleDefinitionId(input string) (*RoleDefinitionID, error) {
parts := strings.Split(input, "|")
if len(parts) != 2 {
return nil, fmt.Errorf("could not parse Role Definition ID, invalid format %q", input)
}

idParts := strings.Split(parts[0], "roleDefinitions/")

if !strings.HasPrefix(parts[1], "/subscriptions/") && !strings.HasPrefix(parts[1], "/providers/Microsoft.Management/managementGroups/") {
return nil, fmt.Errorf("failed to parse scope from Role Definition ID %q", input)
}

roleDefinitionID := RoleDefinitionID{
ResourceID: parts[0],
Scope: parts[1],
}

if len(idParts) < 1 {
return nil, fmt.Errorf("failed to parse Role Definition ID from resource ID %q", input)
} else {
roleDefinitionID.RoleID = idParts[1]
}

return &roleDefinitionID, nil
}
8 changes: 6 additions & 2 deletions internal/services/authorization/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ func (r Registration) WebsiteCategories() []string {
// SupportedDataSources returns the supported Data Sources supported by this Service
func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{
"azurestack_client_config": clientConfigDataSource(),
"azurestack_client_config": clientConfigDataSource(),
"azurestack_role_definition": dataSourceArmRoleDefinition(),
}
}

// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{}
return map[string]*pluginsdk.Resource{
"azurestack_role_assignment": resourceArmRoleAssignment(),
"azurestack_role_definition": resourceArmRoleDefinition(),
}
}
Loading