Skip to content

feat: add coderd_organization_group_sync resource #248

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

Merged
merged 3 commits into from
Aug 19, 2025
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
4 changes: 3 additions & 1 deletion docs/resources/organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ resource "coderd_organization" "blueberry" {

- `description` (String)
- `display_name` (String) Display name of the organization. Defaults to name.
- `group_sync` (Block, Optional) Group sync settings to sync groups from an IdP. (see [below for nested schema](#nestedblock--group_sync))
- `group_sync` (Block, Optional, Deprecated) Group sync settings to sync groups from an IdP.

~> **Deprecated** This block is deprecated. Use the `coderd_organization_group_sync` resource instead. (see [below for nested schema](#nestedblock--group_sync))
- `icon` (String)
- `org_sync_idp_groups` (Set of String) Claims from the IdP provider that will give users access to this organization.
- `role_sync` (Block, Optional) Role sync settings to sync organization roles from an IdP. (see [below for nested schema](#nestedblock--role_sync))
Expand Down
68 changes: 68 additions & 0 deletions docs/resources/organization_group_sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "coderd_organization_group_sync Resource - terraform-provider-coderd"
subcategory: ""
description: |-
Group sync settings for an organization on the Coder deployment.
Multiple instances of this resource for a single organization will conflict.
~> Warning
This resource is only compatible with Coder version 2.16.0 https://github.com/coder/coder/releases/tag/v2.16.0 and later.
---

# coderd_organization_group_sync (Resource)

Group sync settings for an organization on the Coder deployment.
Multiple instances of this resource for a single organization will conflict.

~> **Warning**
This resource is only compatible with Coder version [2.16.0](https://github.com/coder/coder/releases/tag/v2.16.0) and later.

## Example Usage

```terraform
resource "coderd_organization_group_sync" "test" {
organization_id = coderd_organization.test.id
field = "groups"
regex_filter = "test_.*|admin_.*"
auto_create_missing = false

mapping = {
"test_developers" = [coderd_group.test.id]
"admin_users" = [coderd_group.admins.id]
"mixed_group" = [coderd_group.test.id, coderd_group.admins.id]
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `field` (String) The claim field that specifies what groups a user should be in.
- `mapping` (Map of List of String) A map from OIDC group name to Coder group ID.
- `organization_id` (String) The ID of the organization to configure group sync for.

### Optional

- `auto_create_missing` (Boolean) Controls whether groups will be created if they are missing. Defaults to false.
- `regex_filter` (String) A regular expression that will be used to filter the groups returned by the OIDC provider. Any group not matched will be ignored.

## Import

Import is supported using the following syntax:

The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:

```shell
# The ID supplied must be an organization UUID
$ terraform import coderd_organization_group_sync.main_group_sync <org-id>
```
Alternatively, in Terraform v1.5.0 and later, an [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used:

```terraform
import {
to = coderd_organization_group_sync.main_group_sync
id = "<org-id>"
}
```
10 changes: 10 additions & 0 deletions examples/resources/coderd_organization_group_sync/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The ID supplied must be an organization UUID
$ terraform import coderd_organization_group_sync.main_group_sync <org-id>
```
Alternatively, in Terraform v1.5.0 and later, an [`import` block](https://developer.hashicorp.com/terraform/language/import) can be used:

```terraform
import {
to = coderd_organization_group_sync.main_group_sync
id = "<org-id>"
}
12 changes: 12 additions & 0 deletions examples/resources/coderd_organization_group_sync/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "coderd_organization_group_sync" "test" {
organization_id = coderd_organization.test.id
field = "groups"
regex_filter = "test_.*|admin_.*"
auto_create_missing = false

mapping = {
"test_developers" = [coderd_group.test.id]
"admin_users" = [coderd_group.admins.id]
"mixed_group" = [coderd_group.test.id, coderd_group.admins.id]
}
}
39 changes: 39 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,45 @@ func TestIntegration(t *testing.T) {
assert.Equal(t, group.QuotaAllowance, 100)
},
},
{
name: "org-group-sync-test",
preF: func(t testing.TB, c *codersdk.Client) {},
assertF: func(t testing.TB, c *codersdk.Client) {
org, err := c.OrganizationByName(ctx, "test-org-group-sync")
assert.NoError(t, err)
assert.Equal(t, "test-org-group-sync", org.Name)
assert.Equal(t, "Test Organization for Group Sync", org.DisplayName)

testGroup, err := c.GroupByOrgAndName(ctx, org.ID, "test-group")
assert.NoError(t, err)
assert.Equal(t, "test-group", testGroup.Name)
assert.Equal(t, "Test Group", testGroup.DisplayName)
assert.Equal(t, 50, testGroup.QuotaAllowance)

adminGroup, err := c.GroupByOrgAndName(ctx, org.ID, "admin-group")
assert.NoError(t, err)
assert.Equal(t, "admin-group", adminGroup.Name)
assert.Equal(t, "Admin Group", adminGroup.DisplayName)
assert.Equal(t, 100, adminGroup.QuotaAllowance)

// Verify group sync settings
groupSync, err := c.GroupIDPSyncSettings(ctx, org.ID.String())
assert.NoError(t, err)
assert.Equal(t, "groups", groupSync.Field)
assert.NotNil(t, groupSync.RegexFilter)
assert.Equal(t, "test_.*|admin_.*", groupSync.RegexFilter.String())
assert.False(t, groupSync.AutoCreateMissing)

assert.Contains(t, groupSync.Mapping, "test_developers")
assert.Contains(t, groupSync.Mapping, "admin_users")
assert.Contains(t, groupSync.Mapping, "mixed_group")

assert.Contains(t, groupSync.Mapping["test_developers"], testGroup.ID)
assert.Contains(t, groupSync.Mapping["admin_users"], adminGroup.ID)
assert.Contains(t, groupSync.Mapping["mixed_group"], testGroup.ID)
assert.Contains(t, groupSync.Mapping["mixed_group"], adminGroup.ID)
},
},
{
name: "template-test",
preF: func(t testing.TB, c *codersdk.Client) {},
Expand Down
45 changes: 45 additions & 0 deletions integration/org-group-sync-test/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
terraform {
required_providers {
coderd = {
source = "coder/coderd"
version = ">=0.0.0"
}
}
}

resource "coderd_organization" "test" {
name = "test-org-group-sync"
display_name = "Test Organization for Group Sync"
description = "Organization created for testing group sync functionality"
}

resource "coderd_group" "test" {
organization_id = coderd_organization.test.id
name = "test-group"
display_name = "Test Group"
quota_allowance = 50
}

resource "coderd_group" "admins" {
organization_id = coderd_organization.test.id
name = "admin-group"
display_name = "Admin Group"
quota_allowance = 100
}

resource "coderd_organization_group_sync" "test" {
organization_id = coderd_organization.test.id
field = "groups"
regex_filter = "test_.*|admin_.*"
auto_create_missing = false

mapping = {
"test_developers" = [coderd_group.test.id]
"admin_users" = [coderd_group.admins.id]
"mixed_group" = [coderd_group.test.id, coderd_group.admins.id]
}
}

data "coderd_organization" "test_data" {
id = coderd_organization.test.id
}
2 changes: 1 addition & 1 deletion internal/codersdkvalidator/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func checkRegexp(it string) error {
_, err := regexp.Compile("")
_, err := regexp.Compile(it)
return err
Copy link
Preview

Copilot AI Aug 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was previously validating an empty string instead of the actual input. The fix correctly validates the input parameter it.

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks copilot, wouldn't have figured that out on my own

}

Expand Down
Loading
Loading