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

fixed #1031 adds provision on demand #1032

Merged
merged 3 commits into from
May 9, 2024
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
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
Loading