diff --git a/developer/terraform-best-practices.mdx b/developer/terraform-best-practices.mdx index d4d9ccb9..2e36bd08 100644 --- a/developer/terraform-best-practices.mdx +++ b/developer/terraform-best-practices.mdx @@ -97,6 +97,10 @@ Use [conductorone_custom_app_entitlement](https://registry.terraform.io/provider This resource also supports the `match_baton_id` field, which links the Terraform resource to an external ID (such as an Okta group ID). When the connector syncs, C1 merges the two rather than creating a duplicate. + +`match_baton_id` only works with the Okta, GCP, and GWS connectors. + + ```hcl data "conductorone_app" "okta_app" { display_name = "Okta v2" @@ -216,10 +220,86 @@ resource "conductorone_app_entitlement" "test_entitlement_update_multistep" { - **Terraform import is not required.** Reference the entitlement by ID using a data source lookup. - **JSON encoding is required** for `multi_step` provisioning and `account_provision` config blocks. -- **Entitlement owners** are managed with a separate resource, [conductorone_app_entitlement_owner](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/app_entitlement_owner). Owners are always a list of C1 users — the resource accepts `user_ids` only, and setting the list replaces any existing owners for that entitlement. See the example below. -- **App-level owners** are managed with [conductorone_app_owner](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/resources/app_owner), which takes an `app_id` and a `user_ids` list. Setting `user_ids` replaces any existing owners for the app, and changing either field forces the resource to be replaced. +- **Entitlement and app owners** are managed with separate resources, not fields on `conductorone_app_entitlement`. See [Managing app, entitlement, and connector ownership](#managing-app-entitlement-and-connector-ownership) — use the v2 owner resources there for new configurations. + + +`duration_grant` and `duration_unset` are mutually exclusive. Set `duration_grant` to a duration string in seconds (for example, `"3600s"` for one hour) to cap how long a grant lasts. Set `duration_unset = {}` for no maximum duration. Never set both. + + +--- + +## Managing app, entitlement, and connector ownership + +C1 has two generations of Terraform resources for assigning owners. The **v1** resources (`conductorone_app_owner`, `conductorone_app_entitlement_owner`, `conductorone_app_resource_owner`) each accept a `user_ids` list and only support C1 users as owners. The **v2** resources assign exactly one owner — either a C1 user or another entitlement — per resource instance, and add support for entitlement-type owners that v1 does not have. -### Example: manage entitlement owners + +**Use the v2 resources for new configurations.** They support entitlement-type owners, which v1 does not, and are the actively developed API. The v1 resources remain supported for existing configurations — see [Legacy: v1 owner resources](#legacy-v1-owner-resources) below — but shouldn't be your starting point for anything new. + + +### v1 vs v2 at a glance + +| Resource | Assigns owner(s) to | Owner type | API version | +|---|---|---|---| +| `conductorone_app_owner` | An app | List of C1 users (`user_ids`) | v1 | +| `conductorone_app_entitlement_owner` | An entitlement | List of C1 users (`user_ids`) | v1 | +| `conductorone_app_resource_owner` | An app resource | List of C1 users (`user_ids`) | v1 | +| `conductorone_app_owner_user` | An app | A single C1 user (`user_ref_id`) | v2 | +| `conductorone_app_owner_entitlement` | An app | A single entitlement | v2 | +| `conductorone_app_entitlement_owner_user` | An entitlement | A single C1 user (`user_ref_id`) | v2 | +| `conductorone_app_entitlement_owner_entitlement` | An entitlement | A single entitlement | v2 | +| `conductorone_connector_owner_user` | A connector | A single C1 user (`user_ref_id`) | v2 | +| `conductorone_connector_owner_entitlement` | A connector | A single entitlement | v2 | + + +`conductorone_app_resource_owner` does not yet have a usable v2 equivalent. Continue using the v1 `conductorone_app_resource_owner` resource for app resource ownership. + + +### Example: assign an entitlement owner (user) + +Use `conductorone_app_entitlement_owner_user` to assign a single C1 user as the owner of an entitlement. + +```hcl +resource "conductorone_app_entitlement_owner_user" "access_profile_owner" { + app_id = "" # The app that contains the entitlement being owned + entitlement_id = "" # The entitlement being assigned an owner + role_slug = "primary" # The owner role slug — see "Keep in mind" below + user_ref_id = "" # The C1 user who will own this entitlement +} +``` + +### Example: assign an entitlement owner (entitlement) + +Use `conductorone_app_entitlement_owner_entitlement` when ownership should be held by another entitlement rather than a specific user — for example, so ownership follows whoever holds a role or group membership. + +```hcl +resource "conductorone_app_entitlement_owner_entitlement" "access_profile_owner" { + app_id = "" # The app that contains the entitlement being owned + entitlement_id = "" # The entitlement being assigned an owner + role_slug = "primary" # The owner role slug — see "Keep in mind" below + app_entitlement_ref_app_id = "" # App ID of the entitlement that will act as owner + app_entitlement_ref_id = "" # ID of the entitlement that will act as owner +} +``` + +The two resources differ only in how the owner is identified: + +- **`_user`** — the owner is a single C1 user, identified by `user_ref_id`. +- **`_entitlement`** — the owner is another entitlement, identified by both `app_entitlement_ref_app_id` and `app_entitlement_ref_id`. Both fields are required because an entitlement's ID alone isn't unique across apps. + +The app-level (`conductorone_app_owner_user` / `conductorone_app_owner_entitlement`) and connector-level (`conductorone_connector_owner_user` / `conductorone_connector_owner_entitlement`) resources follow the same `app_id`/`role_slug` plus `user_ref_id`-or-`app_entitlement_ref_*` pattern. The connector resources additionally require a `connector_id` identifying the owned connector. + +### Keep in mind + +- **`role_slug` is required on every v2 owner resource.** Existing configurations set it to `"primary"`. Confirm this is the value your tenant expects — the field itself is defined as a plain required string in the provider schema, with no enforced default or allowed-values list. +- **v2 owner resources have no in-place update.** Every field, including `role_slug`, forces a destroy-and-recreate if changed. Plan for resource replacement, not in-place updates, when rotating an owner. +- **v2 owner resources are singular, not lists.** One resource instance represents exactly one owner. To assign multiple owners of the same type, declare multiple resource instances (for example, with `for_each`) rather than a single resource with a list. +- **Only v2 supports entitlement-type owners.** The v1 resources (`conductorone_app_owner`, `conductorone_app_entitlement_owner`, `conductorone_app_resource_owner`) accept C1 users only. + +### Legacy: v1 owner resources + +The v1 resources remain supported for existing configurations. Prefer the v2 resources above for anything new — see the recommendation at the top of this section. + +### Example: manage entitlement owners (v1) Use this pattern to set the owners of an entitlement, replacing any owners currently set. @@ -234,7 +314,7 @@ resource "conductorone_app_entitlement_owner" "role_owners" { } ``` -### Example: manage app owners +### Example: manage app owners (v1) Use the same pattern with `conductorone_app_owner` to set the owners of an app, replacing any owners currently set. @@ -251,10 +331,6 @@ resource "conductorone_app_owner" "aws_owners" { When looking up users, the [conductorone_user](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/data-sources/user) data source returns a single user matching your search criteria, while [conductorone_users](https://registry.terraform.io/providers/ConductorOne/conductorone/latest/docs/data-sources/users) returns a paginated `list` of matching users. Use `conductorone_user` when you need one user's ID (as in the examples above) and `conductorone_users` when you need to iterate over multiple users. - -`duration_grant` and `duration_unset` are mutually exclusive. Set `duration_grant` to a duration string in seconds (for example, `"3600s"` for one hour) to cap how long a grant lasts. Set `duration_unset = {}` for no maximum duration. Never set both. - - --- ## Creating access profiles