Skip to content

Commit

Permalink
Merge pull request #1032 from iwarapter/feat/provision_on_demand
Browse files Browse the repository at this point in the history
fixed #1031 adds provision on demand
  • Loading branch information
manicminer authored May 9, 2024
2 parents f27c468 + faa7b04 commit 5c41f28
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 2 deletions.
110 changes: 110 additions & 0 deletions docs/resources/synchronization_job_provision_on_demand.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
subcategory: "Synchronization"
---

# Resource: azuread_synchronization_job_provision_on_demand

Manages synchronization job on demand provisioning associated with a service principal (enterprise application) 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: `Synchronization.ReadWrite.All`

## Example Usage

*Basic example*

```terraform
data "azuread_client_config" "current" {}
resource "azuread_group" "example" {
display_name = "example"
owners = [data.azuread_client_config.current.object_id]
security_enabled = true
}
data "azuread_application_template" "example" {
display_name = "Azure Databricks SCIM Provisioning Connector"
}
resource "azuread_application" "example" {
display_name = "example"
template_id = data.azuread_application_template.example.template_id
feature_tags {
enterprise = true
gallery = true
}
}
resource "azuread_service_principal" "example" {
client_id = azuread_application.example.client_id
use_existing = true
}
resource "azuread_synchronization_secret" "example" {
service_principal_id = azuread_service_principal.example.id
credential {
key = "BaseAddress"
value = "https://adb-example.azuredatabricks.net/api/2.0/preview/scim"
}
credential {
key = "SecretToken"
value = "some-token"
}
}
resource "azuread_synchronization_job" "example" {
service_principal_id = azuread_service_principal.example.id
template_id = "dataBricks"
enabled = true
}
resource "azuread_synchronization_job_provision_on_demand" "example" {
service_principal_id = azuread_service_principal.example.id
synchronization_job_id = azuread_synchronization_job.example.id
parameter {
# see specific synchronization schema for rule id https://learn.microsoft.com/en-us/graph/api/synchronization-synchronizationschema-get?view=graph-rest-beta
rule_id = ""
subject {
object_id = azuread_group.example.object_id
object_type_name = "Group"
}
}
}
```

## Argument Reference

The following arguments are supported:


- `synchronization_job_id` (Required) Identifier of the synchronization template this job is based on.
- `parameter` (Required) One or more `parameter` blocks as documented below.
- `service_principal_id` (Required) The object ID of the service principal for the synchronization job.
- `triggers` (Optional) Map of arbitrary keys and values that, when changed, will trigger a re-invocation. To force a re-invocation without changing these keys/values, use the [`terraform taint` command](https://www.terraform.io/docs/commands/taint.html).

---

`parameter` block supports the following:

* `rule_id` (Required) The identifier of the synchronization rule to be applied. This rule ID is defined in the schema for a given synchronization job or template.
* `subject` (Required) One or more `subject` blocks as documented below.

---

`subject` block supports the following:

* `object_id` (String) The identifier of an object to which a synchronization job is to be applied. Can be one of the following: (1) An onPremisesDistinguishedName for synchronization from Active Directory to Azure AD. (2) The user ID for synchronization from Azure AD to a third-party. (3) The Worker ID of the Workday worker for synchronization from Workday to either Active Directory or Azure AD.
* `object_type_name` (String) The type of the object to which a synchronization job is to be applied. Can be one of the following: `user` for synchronizing between Active Directory and Azure AD, `User` for synchronizing a user between Azure AD and a third-party application, `Worker` for synchronization a user between Workday and either Active Directory or Azure AD, `Group` for synchronizing a group between Azure AD and a third-party application.

## Attributes Reference

No additional attributes are exported.

## Import

This resource does not support importing.
5 changes: 3 additions & 2 deletions internal/services/synchronization/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource {
// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*pluginsdk.Resource {
return map[string]*pluginsdk.Resource{
"azuread_synchronization_job": synchronizationJobResource(),
"azuread_synchronization_secret": synchronizationSecretResource(),
"azuread_synchronization_job": synchronizationJobResource(),
"azuread_synchronization_job_provision_on_demand": synchronizationJobProvisionOnDemandResource(),
"azuread_synchronization_secret": synchronizationSecretResource(),
}
}

Expand Down
35 changes: 35 additions & 0 deletions internal/services/synchronization/synchronization.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,41 @@ func expandSynchronizationSecretKeyStringValuePair(in []interface{}) *[]msgraph.
return &result
}

func expandSynchronizationJobApplicationParameters(in []interface{}) *[]msgraph.SynchronizationJobApplicationParameters {
result := make([]msgraph.SynchronizationJobApplicationParameters, 0)

for _, raw := range in {
if raw == nil {
continue
}
item := raw.(map[string]interface{})

result = append(result, msgraph.SynchronizationJobApplicationParameters{
Subjects: expandSynchronizationJobSubject(item["subject"].([]interface{})),
RuleId: pointer.To(item["rule_id"].(string)),
})
}

return &result
}

func expandSynchronizationJobSubject(in []interface{}) *[]msgraph.SynchronizationJobSubject {
result := make([]msgraph.SynchronizationJobSubject, 0)
for _, raw := range in {
if raw == nil {
continue
}
item := raw.(map[string]interface{})

result = append(result, msgraph.SynchronizationJobSubject{
ObjectId: pointer.To(item["object_id"].(string)),
ObjectTypeName: pointer.To(item["object_type_name"].(string)),
})
}

return &result
}

func flattenSynchronizationSchedule(in *msgraph.SynchronizationSchedule) []map[string]interface{} {
if in == nil {
return []map[string]interface{}{}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package synchronization

import (
"context"
"errors"
"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/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/tf/validation"
"github.com/manicminer/hamilton/msgraph"
)

func synchronizationJobProvisionOnDemandResource() *schema.Resource {
return &schema.Resource{
CreateContext: synchronizationProvisionOnDemandResourceCreate,
ReadContext: synchronizationProvisionOnDemandResourceRead,
DeleteContext: synchronizationProvisionOnDemandResourceDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(15 * time.Minute),
Read: schema.DefaultTimeout(1 * time.Minute),
Delete: schema.DefaultTimeout(1 * time.Minute),
},
SchemaVersion: 0,

Schema: map[string]*schema.Schema{
"service_principal_id": {
Description: "The object ID of the service principal for which this synchronization job should be provisioned",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID),
},

"synchronization_job_id": {
Description: "The identifier for the synchronization jop.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"parameter": {
Description: "Represents the objects that will be provisioned and the synchronization rules executed. The resource is primarily used for on-demand provisioning.",
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"rule_id": {
Description: "The identifier of the synchronization rule to be applied. This rule ID is defined in the schema for a given synchronization job or template.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"subject": {
Description: "The identifiers of one or more objects to which a synchronizationJob is to be applied.",
Type: schema.TypeList,
Required: true,
ForceNew: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"object_id": {
Description: "The identifier of an object to which a synchronization job is to be applied. Can be one of the following: (1) An onPremisesDistinguishedName for synchronization from Active Directory to Azure AD. (2) The user ID for synchronization from Azure AD to a third-party. (3) The Worker ID of the Workday worker for synchronization from Workday to either Active Directory or Azure AD.",
Type: schema.TypeString,
Required: true,
},

"object_type_name": {
Description: "The type of the object to which a synchronization job is to be applied. Can be one of the following: `user` for synchronizing between Active Directory and Azure AD, `User` for synchronizing a user between Azure AD and a third-party application, `Worker` for synchronization a user between Workday and either Active Directory or Azure AD, `Group` for synchronizing a group between Azure AD and a third-party application.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"Group", "user", "User", "Worker"}, false),
},
},
},
},
},
},
},

"triggers": {
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func synchronizationProvisionOnDemandResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ServicePrincipals.SynchronizationJobClient
spClient := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient
objectId := d.Get("service_principal_id").(string)
jobId := d.Get("synchronization_job_id").(string)

tf.LockByName(servicePrincipalResourceName, objectId)
defer tf.UnlockByName(servicePrincipalResourceName, objectId)

servicePrincipal, status, err := spClient.Get(ctx, objectId, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(nil, "service_principal_id", "Service principal with object ID %q was not found", objectId)
}
return tf.ErrorDiagPathF(err, "service_principal_id", "Retrieving service principal with object ID %q", objectId)
}
if servicePrincipal == nil || servicePrincipal.ID() == nil {
return tf.ErrorDiagF(errors.New("nil service principal or service principal with nil ID was returned"), "API error retrieving service principal with object ID %q", objectId)
}

job, status, err := client.Get(ctx, jobId, objectId)
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(nil, "job_id", "Job with object ID %q was not found for service principle %q", jobId, objectId)
}
return tf.ErrorDiagPathF(err, "job_id", "Retrieving job with object ID %q for service principle %q", jobId, objectId)
}
if job == nil || job.ID == nil {
return tf.ErrorDiagF(errors.New("nil job or job with nil ID was returned"), "API error retrieving job with object ID %q/%s", objectId, jobId)
}

// Create a new synchronization job
synchronizationProvisionOnDemand := &msgraph.SynchronizationJobProvisionOnDemand{
Parameters: expandSynchronizationJobApplicationParameters(d.Get("parameter").([]interface{})),
}

_, err = client.ProvisionOnDemand(ctx, jobId, synchronizationProvisionOnDemand, *servicePrincipal.ID())
if err != nil {
return tf.ErrorDiagF(err, "Creating synchronization job for service principal ID %q", *servicePrincipal.ID())
}

id, _ := uuid.GenerateUUID()
d.SetId(id)

return synchronizationProvisionOnDemandResourceRead(ctx, d, meta)
}

func synchronizationProvisionOnDemandResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}

func synchronizationProvisionOnDemandResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
Loading

0 comments on commit 5c41f28

Please sign in to comment.