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

Support for Administrative Units and their members #672

Merged
merged 3 commits into from
Nov 25, 2021
Merged
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
1 change: 1 addition & 0 deletions .teamcity/components/project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.Project
const val providerName = "azuread"

var services = mapOf(
"administrativeunits" to "Administrative Units",
"applications" to "Applications",
"approleassignments" to "App Role Assignments",
"conditionalaccess" to "Conditional Access",
Expand Down
50 changes: 50 additions & 0 deletions docs/data-sources/administrative_unit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
subcategory: "Administrative Units"
---

# Data Source: azuread_administrative_unit

Gets information about an adminisrative unit in Azure Active Directory.

## API Permissions

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

When authenticated with a service principal, this data source requires one of the following application roles: `AdministrativeUnit.Read.All` or `Directory.Read.All`

When authenticated with a user principal, this data source does not require any additional roles.

## Example Usage (by Group Display Name)

*Look up by display name*
```terraform
data "azuread_administrative_unit" "example" {
display_name = "Example-AU"
}
```

*Look up by object ID*
```terraform
data "azuread_administrative_unit" "example" {
object_id = "00000000-0000-0000-0000-000000000000"
}
```

## Argument Reference

The following arguments are supported:

* `display_name` - (Optional) Specifies the display name of the administrative unit.
* `object_id` - (Optional) Specifies the object ID of the administrative unit.

~> One of `display_name` or `object_id` must be specified.

## Attributes Reference

The following attributes are exported:

* `description` - The description of the administrative unit.
* `display_name` - The display name of the administrative unit.
* `members` - A list of object IDs of members who are present in this administrative unit.
* `object_id` - The object ID of the administrative unit.
* `visibility` - Whether the administrative unit _and_ its members are hidden or publicly viewable in the directory. One of: `Hiddenmembership` or `Public`.
51 changes: 51 additions & 0 deletions docs/resources/administrative_unit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
subcategory: "Administrative Units"
---

# Resource: azuread_administrative_unit

Manages an Administrative Unit within Azure Active Directory.

## API Permissions

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

When authenticated with a service principal, this resource requires one of the following application roles: `AdministrativeUnit.ReadWrite.All` or `Directory.ReadWrite.All`

When authenticated with a user principal, this resource requires one of the following directory roles: `Privileged Role Administrator` or `Global Administrator`

## Example Usage

```terraform
resource "azuread_administrative_unit" "example" {
display_name = "Example-AU"
description = "Just an example"
visibility = "Public"
}
```

## Argument Reference

The following arguments are supported:

* `description` - (Optional) The description of the administrative unit.
* `display_name` - (Required) The display name of the administrative unit.
* `members` - (Optional) A set of object IDs of members who should be present in this administrative unit. Supported object types are Users or Groups.

!> **Warning** Do not use the `members` property at the same time as the [azuread_administrative_unit_member](https://registry.terraform.io/providers/hashicorp/azuread/latest/docs/resources/administrative_unit_member) resource for the same administrative unit. Doing so will cause a conflict and administrative unit members will be removed.

* `visibility` - (Optional) Whether the administrative unit _and_ its members are hidden or publicly viewable in the directory. Must be one of: `Hiddenmembership` or `Public`. Defaults to `Public`.
manicminer marked this conversation as resolved.
Show resolved Hide resolved

## Attributes Reference

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

* `object_id` - The object ID of the administrative unit.

## Import

Administrative units can be imported using their object ID, e.g.

```shell
terraform import azuread_administrative_unit.example 00000000-0000-0000-0000-000000000000
```
58 changes: 58 additions & 0 deletions docs/resources/administrative_unit_member.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
subcategory: "Administrative Units"
---

# Resource: azuread_administrative_unit_member

Manages a single administrative unit membership within Azure Active Directory.

~> **Warning** Do not use this resource at the same time as the `members` property of the `azuread_administrative_unit` resource for the same administrative unit. Doing so will cause a conflict and administrative unit members will be removed.

## API Permissions

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

When authenticated with a service principal, this resource requires one of the following application roles: `AdministrativeUnit.ReadWrite.All` or `Directory.ReadWrite.All`

When authenticated with a user principal, this resource 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_administrative_unit" "example" {
display_name = "Example-AU"
}

resource "azuread_administrative_unit_member" "example" {
administrative_unit_object_id = azuread_administrative_unit.example.id
member_object_id = data.azuread_user.example.id
}
```

## Argument Reference

The following arguments are supported:

* `administrative_unit_object_id` - (Required) The object ID of the administrative unit you want to add the member to. Changing this forces a new resource to be created.
* `member_object_id` - (Required) The object ID of the user or group you want to add as a member of the administrative unit. 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

Administrative unit members can be imported using the object ID of the administrative unit and the object ID of the member, e.g.

```shell
terraform import azuread_administrative_unit_member.test 00000000-0000-0000-0000-000000000000/member/11111111-1111-1111-1111-111111111111
```

-> This ID format is unique to Terraform and is composed of the Administrative Unit Object ID and the target Member Object ID in the format `{AdministrativeUnitObjectID}/member/{MemberObjectID}`.
21 changes: 12 additions & 9 deletions internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/manicminer/hamilton/environments"

"github.com/hashicorp/terraform-provider-azuread/internal/common"
administrativeunits "github.com/hashicorp/terraform-provider-azuread/internal/services/administrativeunits/client"
applications "github.com/hashicorp/terraform-provider-azuread/internal/services/applications/client"
approleassignments "github.com/hashicorp/terraform-provider-azuread/internal/services/approleassignments/client"
conditionalaccess "github.com/hashicorp/terraform-provider-azuread/internal/services/conditionalaccess/client"
Expand All @@ -32,20 +33,22 @@ type Client struct {

StopContext context.Context

Applications *applications.Client
AppRoleAssignments *approleassignments.Client
ConditionalAccess *conditionalaccess.Client
DirectoryRoles *directoryroles.Client
Domains *domains.Client
Groups *groups.Client
Invitations *invitations.Client
ServicePrincipals *serviceprincipals.Client
Users *users.Client
AdministrativeUnits *administrativeunits.Client
Applications *applications.Client
AppRoleAssignments *approleassignments.Client
ConditionalAccess *conditionalaccess.Client
DirectoryRoles *directoryroles.Client
Domains *domains.Client
Groups *groups.Client
Invitations *invitations.Client
ServicePrincipals *serviceprincipals.Client
Users *users.Client
}

func (client *Client) build(ctx context.Context, o *common.ClientOptions) error {
client.StopContext = ctx

client.AdministrativeUnits = administrativeunits.NewClient(o)
client.Applications = applications.NewClient(o)
client.AppRoleAssignments = approleassignments.NewClient(o)
client.Domains = domains.NewClient(o)
Expand Down
2 changes: 2 additions & 0 deletions internal/provider/services.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"github.com/hashicorp/terraform-provider-azuread/internal/services/administrativeunits"
"github.com/hashicorp/terraform-provider-azuread/internal/services/applications"
"github.com/hashicorp/terraform-provider-azuread/internal/services/approleassignments"
"github.com/hashicorp/terraform-provider-azuread/internal/services/conditionalaccess"
Expand All @@ -14,6 +15,7 @@ import (

func SupportedServices() []ServiceRegistration {
return []ServiceRegistration{
administrativeunits.Registration{},
applications.Registration{},
approleassignments.Registration{},
conditionalaccess.Registration{},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package administrativeunits

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

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"

"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/validate"
)

func administrativeUnitDataSource() *schema.Resource {
return &schema.Resource{
ReadContext: administrativeUnitDataSourceRead,

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),
},

Schema: map[string]*schema.Schema{
"object_id": {
Description: "The object ID of the administrative unit",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},

"display_name": {
Description: "The display name for the administrative unit",
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},

"description": {
Description: "The description for the administrative unit",
Type: schema.TypeString,
Computed: true,
},

"members": {
Description: "A list of object IDs of members who are be present in this administrative unit.",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"visibility": {
Description: "Whether the administrative unit and its members are hidden or publicly viewable in the directory",
Type: schema.TypeString,
Computed: true,
},
},
}
}

func administrativeUnitDataSourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).AdministrativeUnits.AdministrativeUnitsClient

var administrativeUnit msgraph.AdministrativeUnit
var displayName, objectId string

if v, ok := d.GetOk("display_name"); ok {
displayName = v.(string)
}
if v, ok := d.GetOk("object_id"); ok {
objectId = v.(string)
}

if displayName != "" {
filter := fmt.Sprintf("displayName eq '%s'", displayName)
administrativeUnits, _, err := client.List(ctx, odata.Query{Filter: filter})
if err != nil || administrativeUnits == nil {
return tf.ErrorDiagPathF(err, "display_name", "No administrative unit found matching specified filter (%s)", filter)
}

count := len(*administrativeUnits)
if count > 1 {
return tf.ErrorDiagPathF(err, "display_name", "More than one administrative unit found matching specified filter (%s)", filter)
} else if count == 0 {
return tf.ErrorDiagPathF(err, "display_name", "No administrative unit found matching specified filter (%s)", filter)
}

administrativeUnit = (*administrativeUnits)[0]
} else if objectId != "" {
au, status, err := client.Get(ctx, objectId, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(nil, "object_id", "No administrative unit found with object ID: %q", objectId)
}
return tf.ErrorDiagF(err, "Retrieving administrative unit with object ID: %q", d.Id())
}

administrativeUnit = *au
}

if administrativeUnit.ID == nil {
return tf.ErrorDiagF(fmt.Errorf("API returned administrative unit with nil object ID"), "Bad API response")
}

d.SetId(*administrativeUnit.ID)

tf.Set(d, "description", administrativeUnit.Description)
tf.Set(d, "display_name", administrativeUnit.DisplayName)
tf.Set(d, "object_id", administrativeUnit.ID)
tf.Set(d, "visibility", administrativeUnit.Visibility)

members, _, err := client.ListMembers(ctx, *administrativeUnit.ID)
if err != nil {
return tf.ErrorDiagPathF(err, "members", "Could not retrieve members for administrative unit with object ID %q", d.Id())
}
tf.Set(d, "members", members)

return nil
}
Loading