Skip to content

Commit

Permalink
Add azuread_directory_role_eligibility_schedule_request resource
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasBak committed May 12, 2023
1 parent 2c1c00e commit ed918f8
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 15 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,5 @@ require (
)

go 1.19

replace github.com/manicminer/hamilton => ../hamilton
25 changes: 15 additions & 10 deletions internal/services/directoryroles/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,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 @@ -29,11 +30,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,177 @@
package directoryroles

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

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"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 id == "" {
return errors.New("id was empty")
}
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)

deadline, ok := ctx.Deadline()
if !ok {
return tf.ErrorDiagF(errors.New("context has no deadline"), "Waiting for directory role %q eligibility schedule request to principal %q to take effect", roleDefinitionId, principalId)
}
timeout := time.Until(deadline)
_, err = (&resource.StateChangeConf{
Pending: []string{"Waiting"},
Target: []string{"Done"},
Timeout: timeout,
MinTimeout: 1 * time.Second,
ContinuousTargetOccurence: 3,
Refresh: func() (interface{}, string, error) {
_, status, err := client.Get(ctx, *roleEligibilityScheduleRequest.ID, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return "stub", "Waiting", nil
}
return nil, "Error", fmt.Errorf("retrieving role eligibility schedule request")
}
return "stub", "Done", nil
},
}).WaitForStateContext(ctx)
if err != nil {
return tf.ErrorDiagF(err, "Waiting for role eligibility schedule request for %q to reflect in 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 @@ -28,9 +28,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(),
}
}
28 changes: 28 additions & 0 deletions vendor/github.com/manicminer/hamilton/msgraph/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ed918f8

Please sign in to comment.