Skip to content

Commit

Permalink
Merge pull request #974 from JonasBak/main
Browse files Browse the repository at this point in the history
Add azuread_directory_role_eligibility_schedule_request resource
  • Loading branch information
manicminer authored Sep 22, 2023
2 parents b1f6332 + 14acd9b commit 9c66730
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 14 deletions.
59 changes: 59 additions & 0 deletions docs/resources/directory_role_eligibility_schedule_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
subcategory: "Directory Roles"
---

# Resource: azuread_directory_role_eligibility_schedule_request

Manages a single directory role eligibility schedule request within Azure Active Directory.

## API Permissions

The following API permissions are required in order to use this resource.

The calling principal requires one of the following application roles: `RoleEligibilitySchedule.ReadWrite.Directory` or `RoleManagement.ReadWrite.Directory`.

The calling principal requires one of the following directory roles: `Privileged Role Administrator` or `Global Administrator`.

## Example Usage

```terraform
data "azuread_user" "example" {
user_principal_name = "[email protected]"
}
resource "azuread_directory_role" "example" {
display_name = "Application Administrator"
}
resource "azuread_directory_role_eligibility_schedule_request" "example" {
role_definition_id = azuread_directory_role.example.template_id
principal_id = azuread_user.example.object_id
directory_scope_id = "/"
justification = "Example"
}
```

~> Note the use of the `template_id` attribute when referencing built-in roles.

## Argument Reference

The following arguments are supported:

* `directory_scope_id` - (Required) Identifier of the directory object representing the scope of the role eligibility. Changing this forces a new resource to be created.
* `justification` - (Required) Justification for why the principal is granted the role eligibility. Changing this forces a new resource to be created.
* `principal_id` - (Required) The object ID of the principal to granted the role eligibility. Changing this forces a new resource to be created.
* `role_definition_id` - (Required) The template ID (in the case of built-in roles) or object ID (in the case of custom roles) of the directory role you want to assign. Changing this forces a new resource to be created.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

*No additional attributes are exported*

## Import

Directory role eligibility schedule requests can be imported using the ID of the assignment, e.g.

```shell
terraform import azuread_directory_role_eligibility_schedule_request.example 822ec710-4c9f-4f71-a27a-451759cc7522
```
25 changes: 15 additions & 10 deletions internal/services/directoryroles/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
)

type Client struct {
DirectoryObjectsClient *msgraph.DirectoryObjectsClient
DirectoryRolesClient *msgraph.DirectoryRolesClient
DirectoryRoleTemplatesClient *msgraph.DirectoryRoleTemplatesClient
RoleAssignmentsClient *msgraph.RoleAssignmentsClient
RoleDefinitionsClient *msgraph.RoleDefinitionsClient
DirectoryObjectsClient *msgraph.DirectoryObjectsClient
DirectoryRolesClient *msgraph.DirectoryRolesClient
DirectoryRoleTemplatesClient *msgraph.DirectoryRoleTemplatesClient
RoleAssignmentsClient *msgraph.RoleAssignmentsClient
RoleDefinitionsClient *msgraph.RoleDefinitionsClient
RoleEligibilityScheduleRequestClient *msgraph.RoleEligibilityScheduleRequestClient
}

func NewClient(o *common.ClientOptions) *Client {
Expand All @@ -32,11 +33,15 @@ func NewClient(o *common.ClientOptions) *Client {
roleDefinitionsClient := msgraph.NewRoleDefinitionsClient()
o.ConfigureClient(&roleDefinitionsClient.BaseClient)

roleEligibilityScheduleRequestClient := msgraph.NewRoleEligibilityScheduleRequestClient()
o.ConfigureClient(&roleEligibilityScheduleRequestClient.BaseClient)

return &Client{
DirectoryObjectsClient: directoryObjectsClient,
DirectoryRolesClient: directoryRolesClient,
DirectoryRoleTemplatesClient: directoryRoleTemplatesClient,
RoleAssignmentsClient: roleAssignmentsClient,
RoleDefinitionsClient: roleDefinitionsClient,
DirectoryObjectsClient: directoryObjectsClient,
DirectoryRolesClient: directoryRolesClient,
DirectoryRoleTemplatesClient: directoryRoleTemplatesClient,
RoleAssignmentsClient: roleAssignmentsClient,
RoleDefinitionsClient: roleDefinitionsClient,
RoleEligibilityScheduleRequestClient: roleEligibilityScheduleRequestClient,
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package directoryroles

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"time"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/helpers"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/hashicorp/terraform-provider-azuread/internal/validate"
"github.com/manicminer/hamilton/msgraph"
)

func directoryRoleEligibilityScheduleRequestResource() *schema.Resource {
return &schema.Resource{
CreateContext: directoryRoleEligibilityScheduleRequestResourceCreate,
ReadContext: directoryRoleEligibilityScheduleRequestResourceRead,
DeleteContext: directoryRoleEligibilityScheduleRequestResourceDelete,

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

Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
if _, err := uuid.ParseUUID(id); err != nil {
return fmt.Errorf("specified ID (%q) is not valid: %s", id, err)
}
return nil
}),

Schema: map[string]*schema.Schema{
"role_definition_id": {
Description: "The object ID of the directory role for this role eligibility schedule request",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.UUID,
},

"principal_id": {
Description: "The object ID of the member principal",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.UUID,
},

"directory_scope_id": {
Description: "Identifier of the directory object representing the scope of the role eligibility schedule request",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},

"justification": {
Description: "Justification for why the role is assigned",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},
},
}
}

func directoryRoleEligibilityScheduleRequestResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient

roleDefinitionId := d.Get("role_definition_id").(string)
principalId := d.Get("principal_id").(string)
justification := d.Get("justification").(string)
directoryScopeId := d.Get("directory_scope_id").(string)

now := time.Now()
properties := msgraph.UnifiedRoleEligibilityScheduleRequest{
Action: utils.String(msgraph.UnifiedRoleScheduleRequestActionAdminAssign),
RoleDefinitionId: &roleDefinitionId,
PrincipalId: &principalId,
Justification: &justification,
DirectoryScopeId: &directoryScopeId,
ScheduleInfo: &msgraph.RequestSchedule{
StartDateTime: &now,
Expiration: &msgraph.ExpirationPattern{
Type: utils.String(msgraph.ExpirationPatternTypeNoExpiration),
},
},
}

roleEligibilityScheduleRequest, status, err := client.Create(ctx, properties)
if err != nil {
return tf.ErrorDiagF(err, "Eligibility schedule request for role %q to principal %q, received %d with error: %+v", roleDefinitionId, principalId, status, err)
}
if roleEligibilityScheduleRequest == nil || roleEligibilityScheduleRequest.ID == nil {
return tf.ErrorDiagF(errors.New("returned role roleEligibilityScheduleRequest ID was nil"), "API Error")
}

d.SetId(*roleEligibilityScheduleRequest.ID)

if err := helpers.WaitForUpdate(ctx, func(ctx context.Context) (*bool, error) {
defer func() { client.BaseClient.DisableRetries = false }()
client.BaseClient.DisableRetries = true

resr, status, err := client.Get(ctx, *roleEligibilityScheduleRequest.ID, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return utils.Bool(false), nil
}
return nil, err
}
return utils.Bool(resr != nil), nil
}); err != nil {
return tf.ErrorDiagF(err, "Waiting for role eligibility schedule request for %q to be created for directory role %q", principalId, roleDefinitionId)
}

return directoryRoleEligibilityScheduleRequestResourceRead(ctx, d, meta)
}

func directoryRoleEligibilityScheduleRequestResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient

id := d.Id()
roleEligibilityScheduleRequest, status, err := client.Get(ctx, id, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] roleEligibilityScheduleRequest with ID %q was not found - removing from state", id)
d.SetId("")
return nil
}
return tf.ErrorDiagF(err, "Retrieving roleEligibilityScheduleRequest %q", id)
}

tf.Set(d, "role_definition_id", roleEligibilityScheduleRequest.RoleDefinitionId)
tf.Set(d, "principal_id", roleEligibilityScheduleRequest.PrincipalId)
tf.Set(d, "justification", roleEligibilityScheduleRequest.Justification)
tf.Set(d, "directory_scope_id", roleEligibilityScheduleRequest.DirectoryScopeId)

return nil
}

func directoryRoleEligibilityScheduleRequestResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).DirectoryRoles.RoleEligibilityScheduleRequestClient

id := d.Id()
roleEligibilityScheduleRequest, _, err := client.Get(ctx, id, odata.Query{})
if err != nil {
return tf.ErrorDiagF(err, "Retrieving roleEligibilityScheduleRequest %q", id)
}

roleEligibilityScheduleRequest.Action = utils.String(msgraph.UnifiedRoleScheduleRequestActionAdminRemove)

if _, _, err := client.Create(ctx, *roleEligibilityScheduleRequest); err != nil {
return tf.ErrorDiagF(err, "Deleting role eligibility schedule request %q: %+v", d.Id(), err)
}
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package directoryroles_test

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
)

type RoleEligibilityScheduleRequestResource struct{}

func TestAccRoleEligibilityScheduleRequest_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_directory_role_eligibility_schedule_request", "test")
r := RoleEligibilityScheduleRequestResource{}

data.ResourceTestIgnoreDangling(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
})
}

func (r RoleEligibilityScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
client := clients.DirectoryRoles.RoleEligibilityScheduleRequestClient

resr, status, err := client.Get(ctx, state.ID, odata.Query{})
if err != nil {
fmt.Printf("%s, %v\n", err.Error(), status)
if status == http.StatusNotFound {
return nil, fmt.Errorf("Role Eligibility Schedule Request with ID %q does not exist", state.ID)
}
return nil, fmt.Errorf("failed to retrieve Role Eligibility Schedule Request with object ID %q: %+v", state.ID, err)
}

return utils.Bool(resr.ID != nil && *resr.ID == state.ID), nil
}

func (r RoleEligibilityScheduleRequestResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azuread" {}
data "azuread_domains" "test" {
only_initial = true
}
resource "azuread_user" "test" {
user_principal_name = "acctestManager.%[1]d@${data.azuread_domains.test.domains.0.domain_name}"
display_name = "acctestManager-%[1]d"
password = "%[2]s"
}
resource "azuread_directory_role" "test" {
display_name = "Application Administrator"
}
resource "azuread_directory_role_eligibility_schedule_request" "test" {
role_definition_id = azuread_directory_role.test.template_id
principal_id = azuread_user.test.object_id
directory_scope_id = "/"
justification = "abc"
}
`, data.RandomInteger, data.RandomPassword)
}
9 changes: 5 additions & 4 deletions internal/services/directoryroles/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource {
// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azuread_custom_directory_role": customDirectoryRoleResource(),
"azuread_directory_role": directoryRoleResource(),
"azuread_directory_role_assignment": directoryRoleAssignmentResource(),
"azuread_directory_role_member": directoryRoleMemberResource(),
"azuread_custom_directory_role": customDirectoryRoleResource(),
"azuread_directory_role": directoryRoleResource(),
"azuread_directory_role_assignment": directoryRoleAssignmentResource(),
"azuread_directory_role_member": directoryRoleMemberResource(),
"azuread_directory_role_eligibility_schedule_request": directoryRoleEligibilityScheduleRequestResource(),
}
}

0 comments on commit 9c66730

Please sign in to comment.