Skip to content

Repository files navigation

Temporal Cloud Group Terraform module

CI Apply Tests

Terraform module which creates a Temporal Cloud user group, together with its account and namespace permissions and its membership.

When to use this module

This is the module that grants access. Whatever else your account uses, something has to say which namespaces a group can reach and at what level, and that is what lives here.

If your account has SAML and SCIM, this is the module you want — not user. Those two features cover authentication and provisioning, and stop short of permissions:

Concern Owned by
Authentication — signing in SAML SSO
Which people exist, and their group membership SCIM, from your identity provider
What a group is allowed to do this module
Machine access for workers and CI service-account — never a group

Temporal Cloud's SCIM documentation is explicit that roles are assigned to a group after it syncs. So SCIM delivers a group with the right people in it and stops; account_access and namespace_accesses remain yours to set, and granting them to groups rather than to individuals is what makes a synced user's access follow from their membership.

Two ways to use it, depending on where the group comes from:

  • The group is provisioned by SCIM, or otherwise already exists. Set create_group = false and pass group_id plus an empty name, enable create_group_access, and leave create_group_members off so the identity provider stays the only writer of membership. See Groups provisioned by SCIM.
  • You own the group outright. Let the module create it and manage membership here too. Appropriate when there is no SCIM integration — both SAML and SCIM are paid features — or for a group that has no equivalent in the directory.

Because temporalcloud_group_access owns a group's entire access map rather than individual entries, exactly one configuration may manage a given group's access. Two module calls pointing at the same group_id will overwrite each other on every apply.

Requirements

The temporalcloud provider authenticates with an API key, read from the TEMPORAL_CLOUD_API_KEY environment variable:

export TEMPORAL_CLOUD_API_KEY="<your-api-key>"

The provider authenticates when it initialises, so a key is needed even for a terraform plan that would create nothing. Keep the key out of version control — an untracked .env file rather than a committed .tfvars.

Usage

A group with account-level access

The group's role on the account is the coarsest control: it applies everywhere and needs no namespace to point at.

module "group" {
  source  = "terraform-temporalcloud-modules/group/temporalcloud"
  version = "~> 2.0"

  name = "platform-engineering"

  create_group_access = true
  account_access      = "developer"
}

Namespace permissions

namespace_accesses grants per-namespace permissions. It is a set, and it replaces the group's whole namespace access map on every apply, so it has to list every namespace the group should reach.

module "group" {
  source  = "terraform-temporalcloud-modules/group/temporalcloud"
  version = "~> 2.0"

  name = "support"

  create_group_access = true

  # `none` gives no account-wide role, so the group reaches exactly the two
  # namespaces below and nothing else.
  account_access = "none"

  namespace_accesses = [
    {
      namespace_id = module.orders.namespace_id
      permission   = "read"
    },
    {
      namespace_id = module.payments.namespace_id
      permission   = "write"
    },
  ]
}

Membership

Membership is set by user ID, not email address. The temporalcloud_users data source lists the account's existing users; temporalcloud_user invites new ones and belongs to a different module in this family.

data "temporalcloud_users" "all" {}

module "group" {
  source  = "terraform-temporalcloud-modules/group/temporalcloud"
  version = "~> 2.0"

  name = "platform-engineering"

  create_group_members = true

  users = [
    for u in data.temporalcloud_users.all.users : u.id
    if endswith(u.email, "@example.com")
  ]
}

Groups provisioned by SCIM

If your account uses SCIM, the identity provider — Entra ID, Okta and so on — creates groups in Temporal Cloud and keeps their membership in sync. Temporal Cloud assigns both to the integration: a SCIM group cannot be created or deleted except through it, and its membership is managed through it. Terraform must not own either.

  • temporalcloud_group always creates a new group; it cannot adopt an existing one. Since a SCIM group cannot be created any other way, a SCIM group reaches this module through group_id with create_group = false.
  • Leave create_group_members off. temporalcloud_group_members replaces a group's whole member list on every apply, and that list belongs to the identity provider.

Roles and namespace permissions are assigned in Temporal Cloud once a group has synced, rather than carried across from the identity provider, so they are the part of a SCIM group Terraform should own. Set create_group = false, resolve the group's Temporal Cloud ID from its identity provider ID with the temporalcloud_scim_group data source, and leave create_group_members off:

data "temporalcloud_scim_group" "contractors" {
  # The group's Object ID in the identity provider, not its Temporal Cloud ID.
  idp_id = "00000000-0000-0000-0000-000000000000"
}

module "contractor_access" {
  source  = "terraform-temporalcloud-modules/group/temporalcloud"
  version = "~> 2.0"

  create_group = false
  group_id     = data.temporalcloud_scim_group.contractors.id

  # Required by Terraform, and unused here — the SCIM group keeps its own name.
  name = ""

  create_group_access = true
  account_access      = "none"

  namespace_accesses = [
    {
      namespace_id = module.sandbox.namespace_id
      permission   = "write"
    },
  ]

  # Owned by the identity provider.
  create_group_members = false
}

The same pattern adopts any group created outside Terraform, SCIM or not — supply group_id directly when you already have the Temporal Cloud ID.

Notes

Provider and Temporal Cloud behaviours worth knowing before you plan:

  • account_access and permission are different vocabularies. account_access is owner, admin, developer, read or none. namespace_accesses[*].permission is admin, write or read. write is not an account role, developer is not a namespace permission, and both are matched case-insensitively.
  • owner and admin cannot hold explicit namespace permissions. Those roles already reach every namespace, so combining them with namespace_accesses is rejected — namespace_accesses must be empty when account_access is admin. Pair namespace_accesses with developer, read or none.
  • owner can only be adopted by import. It cannot be created, updated or deleted without Temporal support.
  • Both child resources replace, they do not merge. namespace_accesses owns the group's entire access map and users owns its entire membership, so anything granted or added outside Terraform is removed on the next apply.
  • Empty sets are rejected, for both namespace_accesses and account_access_custom_roles — omit the variable instead. users is the exception: an empty set creates no membership resource rather than sending one.
  • Only one membership resource may exist per group. Calling this module twice against the same group_id with create_group_members = true in both makes each apply overwrite the other's members.

Examples

  • complete — a group with an account role, namespace permissions and membership, with the namespace it points at created alongside
  • scim-managed-group — permissions for a group the identity provider owns, leaving the group and its membership to SCIM

Managing several groups

The wrappers submodule creates many groups from one call, for use with Terragrunt or anywhere a for_each on the module block is awkward:

module "groups" {
  source  = "terraform-temporalcloud-modules/group/temporalcloud//wrappers"
  version = "~> 2.0"

  defaults = {
    create_group_access = true
    account_access      = "read"
  }

  items = {
    platform = { name = "platform-engineering", account_access = "developer" }
    support  = { name = "support" }
  }
}

Which inputs are required

name is required outright — it carries no default, because temporalcloud_group requires it — and the generated Inputs table below says so. Everything else that is required is required only behind one of the three gates, which the table cannot express.

Where a rule is caught differs, and the tables say so:

  • validate — this module's own variable validations. They run under terraform validate and need no credentials.
  • plan — the provider's schema validators. They see a module input only once its value is known, which is at plan rather than validate. The provider authenticates when it initialises, so reaching them needs an API key.
  • apply — nothing catches it beforehand.
  • nothing — no resource and no error. Two inputs behave this way when omitted; they are the ones worth reading twice.

name and group_id

Terraform demands a value for name whether or not a group is created, but it is only meaningful when one is. Which of the two matters follows create_group:

create_group name group_id
true (the default) The name of the new group. An empty string is sent as an empty name and nothing before apply objects. Ignored — access and membership always attach to the group the module created.
false Unused: the adopted group keeps its own name. Pass "". Required. Leave it empty and nothing happens — with no group to attach to, create_group_access and create_group_members both produce no resource, no error, and an empty group_id output.

With create_group_access = true

Input Omitting it
account_access Fails at plan: Attribute account_access value must be one of: ["owner" "admin" "developer" "read" "none"], got: "". The provider requires an account role and infers none. Use none for a group whose permissions come entirely from namespace_accesses.

With create_group_members = true

Input Omitting it
users Nothing happens. The provider requires the attribute and rejects an empty set, so rather than send one the module creates no membership resource — silently. A group whose membership never appears is usually this.

Combinations that are rejected

Combination Caught at Error
account_access of owner or admin with namespace_accesses plan namespace_accesses must be empty when account_access is <role>
[] for namespace_accesses or account_access_custom_roles validate Empty … sets are not accepted by the provider. Omit the variable instead.
A role outside owner, admin, developer, read, none, or a namespace permission outside admin, write, read validate Account access must be one of: … / Namespace access permission must be one of: …

Inside namespace_accesses

Both keys of every entry are required. namespace_id and permission are required in the provider schema and in this module's object type, which the generated table cannot show: it lists the variable, not the keys inside it.

Requirements

Name Version
terraform >= 1.5.7
temporalcloud >= 1.6.0

Providers

Name Version
temporalcloud >= 1.6.0

Modules

No modules.

Resources

Name Type
temporalcloud_group.this resource
temporalcloud_group_access.this resource
temporalcloud_group_members.this resource

Inputs

Name Description Type Default Required
account_access The group's role on the account: owner, admin, developer, read or none, matched case-insensitively. Required when create_group_access is true — there is no account role the provider will infer, and the empty default is rejected at plan. Use none for a group whose permissions come entirely from namespace_accesses. owner can only be adopted by import — it cannot be created, updated or deleted without Temporal support string "" no
account_access_custom_roles IDs of custom roles granted at account level, in addition to the built-in role in account_access. Optional; the group holds only the role in account_access when unset. Omit rather than passing an empty set set(string) null no
create_group Controls if the group should be created. Set to false and supply group_id to manage the access and membership of a group that already exists, such as one provisioned by SCIM. name is required by Terraform either way, so pass "" for it when adopting an existing group bool true no
create_group_access Controls if the group's access should be managed. Setting it to true makes account_access required. Requires either create_group = true or group_id set to an existing group; with neither, no access resource is created bool false no
create_group_members Controls if the group's membership should be managed. Setting it to true makes a non-empty users required. Requires either create_group = true or group_id set to an existing group; with neither, no membership resource is created. Leave false for SCIM-provisioned groups, whose membership is owned by the identity provider bool false no
group_id The ID of an existing group to attach access and membership to. Required when create_group is false, and ignored otherwise. Leaving it empty with create_group = false leaves nothing to attach to, so the module creates no resources at all and reports no error. For groups created outside Terraform — a SCIM-provisioned group, for example, whose ID comes from the temporalcloud_scim_group data source string "" no
members_timeouts Create and delete timeouts for the group membership, as duration strings such as 30s or 2h45m. Optional; the provider's own defaults apply to whichever is unset
object({
create = optional(string)
delete = optional(string)
})
{} no
name The name of the group to create. Unused when create_group is false, where the adopted group keeps the name it already has — pass "" there, since Terraform requires a value for it regardless string n/a yes
namespace_accesses Per-namespace permissions for the group, as a set of namespace_id and permission pairs. permission is admin, write or read, matched case-insensitively. Optional; without it the group reaches namespaces only through its account_access role. This replaces the group's entire namespace access map, so it must list every namespace the group can reach. Leave unset for groups whose account_access is owner or admin — those roles already reach every namespace and explicit permissions are rejected. Omit rather than passing an empty set
set(object({
namespace_id = string
permission = string
}))
null no
timeouts Create and delete timeouts for the group, as duration strings such as 30s or 2h45m. Optional; the provider's own defaults apply to whichever is unset
object({
create = optional(string)
delete = optional(string)
})
{} no
users IDs of the users that make up the group, as returned by the temporalcloud_users data source or the id of a temporalcloud_user resource. Required, and non-empty, when create_group_members is true: the provider requires the attribute, so rather than send an empty membership the module creates no membership resource at all and reports no error. This replaces the group's entire membership, so users added outside Terraform are removed on the next apply set(string) [] no

Outputs

Name Description
group_account_access The group's role on the account
group_account_access_custom_roles IDs of the custom roles granted to the group at account level. Empty when none are granted
group_id The unique identifier of the group. Echoes group_id when the module manages an existing group rather than creating one
group_members IDs of the users that make up the group
group_name The name of the group. Empty when the module manages a group it did not create
group_namespace_accesses The group's complete namespace access map, as a set of namespace_id and permission pairs. Empty when the group has no namespace access
group_state The current state of the group. Empty when the module manages a group it did not create

Contributing

See CONTRIBUTING.md for the development workflow, how the test layers are arranged, and the Temporal Cloud API behaviours the tests exist to guard against.

License

Apache-2.0 licensed. See LICENSE.

About

Terraform module which creates Temporal Cloud groups with their access and membership

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages