From 134d7599a96958297c3f9e55319498382cc9286f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:29:40 +0100 Subject: [PATCH 01/15] feat(enterprise-scim): add utility functions for SCIM data sources Add util_enterprise_scim.go with helper functions for flattening SCIM user and group data from the GitHub API. --- github/util_enterprise_scim.go | 224 +++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 github/util_enterprise_scim.go diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go new file mode 100644 index 0000000000..562d564251 --- /dev/null +++ b/github/util_enterprise_scim.go @@ -0,0 +1,224 @@ +package github + +import ( + "context" + + gh "github.com/google/go-github/v81/github" +) + +// enterpriseSCIMListAllGroups fetches all SCIM groups for an enterprise with automatic pagination. +func enterpriseSCIMListAllGroups(ctx context.Context, client *gh.Client, enterprise, filter string, count int) ([]*gh.SCIMEnterpriseGroupAttributes, *gh.SCIMEnterpriseGroups, error) { + startIndex := 1 + all := make([]*gh.SCIMEnterpriseGroupAttributes, 0) + var firstResp *gh.SCIMEnterpriseGroups + + for { + opts := &gh.ListProvisionedSCIMGroupsEnterpriseOptions{ + StartIndex: gh.Ptr(startIndex), + Count: gh.Ptr(count), + } + if filter != "" { + opts.Filter = gh.Ptr(filter) + } + + page, _, err := client.Enterprise.ListProvisionedSCIMGroups(ctx, enterprise, opts) + if err != nil { + return nil, nil, err + } + + if firstResp == nil { + firstResp = page + } + + all = append(all, page.Resources...) + + if len(page.Resources) == 0 { + break + } + if page.TotalResults != nil && len(all) >= *page.TotalResults { + break + } + + startIndex += len(page.Resources) + } + + if firstResp == nil { + firstResp = &gh.SCIMEnterpriseGroups{ + Schemas: []string{gh.SCIMSchemasURINamespacesListResponse}, + TotalResults: gh.Ptr(len(all)), + StartIndex: gh.Ptr(1), + ItemsPerPage: gh.Ptr(count), + } + } + + return all, firstResp, nil +} + +// enterpriseSCIMListAllUsers fetches all SCIM users for an enterprise with automatic pagination. +func enterpriseSCIMListAllUsers(ctx context.Context, client *gh.Client, enterprise, filter string, count int) ([]*gh.SCIMEnterpriseUserAttributes, *gh.SCIMEnterpriseUsers, error) { + startIndex := 1 + all := make([]*gh.SCIMEnterpriseUserAttributes, 0) + var firstResp *gh.SCIMEnterpriseUsers + + for { + opts := &gh.ListProvisionedSCIMUsersEnterpriseOptions{ + StartIndex: gh.Ptr(startIndex), + Count: gh.Ptr(count), + } + if filter != "" { + opts.Filter = gh.Ptr(filter) + } + + page, _, err := client.Enterprise.ListProvisionedSCIMUsers(ctx, enterprise, opts) + if err != nil { + return nil, nil, err + } + + if firstResp == nil { + firstResp = page + } + + all = append(all, page.Resources...) + + if len(page.Resources) == 0 { + break + } + if page.TotalResults != nil && len(all) >= *page.TotalResults { + break + } + + startIndex += len(page.Resources) + } + + if firstResp == nil { + firstResp = &gh.SCIMEnterpriseUsers{ + Schemas: []string{gh.SCIMSchemasURINamespacesListResponse}, + TotalResults: gh.Ptr(len(all)), + StartIndex: gh.Ptr(1), + ItemsPerPage: gh.Ptr(count), + } + } + + return all, firstResp, nil +} + +func flattenEnterpriseSCIMMeta(meta *gh.SCIMEnterpriseMeta) []any { + if meta == nil { + return nil + } + m := map[string]any{ + "resource_type": meta.ResourceType, + } + if meta.Created != nil { + m["created"] = meta.Created.String() + } + if meta.LastModified != nil { + m["last_modified"] = meta.LastModified.String() + } + if meta.Location != nil { + m["location"] = *meta.Location + } + return []any{m} +} + +func flattenEnterpriseSCIMGroupMembers(members []*gh.SCIMEnterpriseDisplayReference) []any { + out := make([]any, 0, len(members)) + for _, m := range members { + item := map[string]any{ + "value": m.Value, + } + if m.Ref != nil { + item["ref"] = *m.Ref + } + if m.Display != nil { + item["display_name"] = *m.Display + } + out = append(out, item) + } + return out +} + +func flattenEnterpriseSCIMGroup(group *gh.SCIMEnterpriseGroupAttributes) map[string]any { + m := map[string]any{ + "schemas": group.Schemas, + "members": flattenEnterpriseSCIMGroupMembers(group.Members), + "meta": flattenEnterpriseSCIMMeta(group.Meta), + } + if group.ID != nil { + m["id"] = *group.ID + } + if group.ExternalID != nil { + m["external_id"] = *group.ExternalID + } + if group.DisplayName != nil { + m["display_name"] = *group.DisplayName + } + return m +} + +func flattenEnterpriseSCIMUserName(name *gh.SCIMEnterpriseUserName) []any { + if name == nil { + return nil + } + m := map[string]any{ + "family_name": name.FamilyName, + "given_name": name.GivenName, + } + if name.Formatted != nil { + m["formatted"] = *name.Formatted + } + if name.MiddleName != nil { + m["middle_name"] = *name.MiddleName + } + return []any{m} +} + +func flattenEnterpriseSCIMUserEmails(emails []*gh.SCIMEnterpriseUserEmail) []any { + out := make([]any, 0, len(emails)) + for _, e := range emails { + out = append(out, map[string]any{ + "value": e.Value, + "type": e.Type, + "primary": e.Primary, + }) + } + return out +} + +func flattenEnterpriseSCIMUserRoles(roles []*gh.SCIMEnterpriseUserRole) []any { + out := make([]any, 0, len(roles)) + for _, r := range roles { + item := map[string]any{ + "value": r.Value, + } + if r.Display != nil { + item["display"] = *r.Display + } + if r.Type != nil { + item["type"] = *r.Type + } + if r.Primary != nil { + item["primary"] = *r.Primary + } + out = append(out, item) + } + return out +} + +func flattenEnterpriseSCIMUser(user *gh.SCIMEnterpriseUserAttributes) map[string]any { + m := map[string]any{ + "schemas": user.Schemas, + "user_name": user.UserName, + "display_name": user.DisplayName, + "external_id": user.ExternalID, + "active": user.Active, + "name": flattenEnterpriseSCIMUserName(user.Name), + "emails": flattenEnterpriseSCIMUserEmails(user.Emails), + "roles": flattenEnterpriseSCIMUserRoles(user.Roles), + "meta": flattenEnterpriseSCIMMeta(user.Meta), + } + if user.ID != nil { + m["id"] = *user.ID + } + return m +} From d410b1e45db2b623c6cbd0f257a13695148fddb1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:29:55 +0100 Subject: [PATCH 02/15] feat(enterprise-scim): add github_enterprise_scim_group data source Retrieve information about a single SCIM group by ID. Includes: - Data source implementation - Acceptance tests - Documentation --- ...ata_source_github_enterprise_scim_group.go | 70 +++++++++++++++++++ ...ource_github_enterprise_scim_group_test.go | 34 +++++++++ .../d/enterprise_scim_group.html.markdown | 28 ++++++++ 3 files changed, 132 insertions(+) create mode 100644 github/data_source_github_enterprise_scim_group.go create mode 100644 github/data_source_github_enterprise_scim_group_test.go create mode 100644 website/docs/d/enterprise_scim_group.html.markdown diff --git a/github/data_source_github_enterprise_scim_group.go b/github/data_source_github_enterprise_scim_group.go new file mode 100644 index 0000000000..f2ef893269 --- /dev/null +++ b/github/data_source_github_enterprise_scim_group.go @@ -0,0 +1,70 @@ +package github + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubEnterpriseSCIMGroup() *schema.Resource { + s := enterpriseSCIMGroupSchema() + s["enterprise"] = &schema.Schema{ + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + } + s["scim_group_id"] = &schema.Schema{ + Description: "The SCIM group ID.", + Type: schema.TypeString, + Required: true, + } + + return &schema.Resource{ + Description: "Lookup SCIM provisioning information for a single GitHub enterprise group.", + ReadContext: dataSourceGithubEnterpriseSCIMGroupRead, + Schema: s, + } +} + +func dataSourceGithubEnterpriseSCIMGroupRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise := d.Get("enterprise").(string) + scimGroupID := d.Get("scim_group_id").(string) + + group, _, err := client.Enterprise.GetProvisionedSCIMGroup(ctx, enterprise, scimGroupID, nil) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(fmt.Sprintf("%s/%s", enterprise, scimGroupID)) + + if err := d.Set("schemas", group.Schemas); err != nil { + return diag.FromErr(err) + } + if group.ID != nil { + if err := d.Set("id", *group.ID); err != nil { + return diag.FromErr(err) + } + } + if group.ExternalID != nil { + if err := d.Set("external_id", *group.ExternalID); err != nil { + return diag.FromErr(err) + } + } + if group.DisplayName != nil { + if err := d.Set("display_name", *group.DisplayName); err != nil { + return diag.FromErr(err) + } + } + if err := d.Set("members", flattenEnterpriseSCIMGroupMembers(group.Members)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("meta", flattenEnterpriseSCIMMeta(group.Meta)); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/data_source_github_enterprise_scim_group_test.go b/github/data_source_github_enterprise_scim_group_test.go new file mode 100644 index 0000000000..a113e6f6ac --- /dev/null +++ b/github/data_source_github_enterprise_scim_group_test.go @@ -0,0 +1,34 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubEnterpriseSCIMGroupDataSource(t *testing.T) { + t.Run("reads group without error", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: fmt.Sprintf(` + data "github_enterprise_scim_groups" "all" { + enterprise = "%s" + } + + data "github_enterprise_scim_group" "test" { + enterprise = "%[1]s" + scim_group_id = data.github_enterprise_scim_groups.all.resources[0].id + } + `, testAccConf.enterpriseSlug), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "id"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "display_name"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "schemas.#"), + ), + }}, + }) + }) +} diff --git a/website/docs/d/enterprise_scim_group.html.markdown b/website/docs/d/enterprise_scim_group.html.markdown new file mode 100644 index 0000000000..9c94d7aa2c --- /dev/null +++ b/website/docs/d/enterprise_scim_group.html.markdown @@ -0,0 +1,28 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_scim_group" +description: |- + Get SCIM provisioning information for a GitHub enterprise group. +--- + +# github_enterprise_scim_group + +Use this data source to retrieve SCIM provisioning information for a single enterprise group. + +## Example Usage + +```hcl +data "github_enterprise_scim_group" "example" { + enterprise = "example-co" + scim_group_id = "123456" +} +``` + +## Argument Reference + +* `enterprise` - (Required) The enterprise slug. +* `scim_group_id` - (Required) The SCIM group ID. + +## Attributes Reference + +* `schemas`, `id`, `external_id`, `display_name`, `members`, `meta`. From 415e83678a3f0deba0ad482cf908452305620c53 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:30:06 +0100 Subject: [PATCH 03/15] feat(enterprise-scim): add github_enterprise_scim_groups data source List all SCIM groups in an enterprise with optional filtering. Includes: - Data source implementation - Acceptance tests - Documentation --- ...ta_source_github_enterprise_scim_groups.go | 204 ++++++++++++++++++ ...urce_github_enterprise_scim_groups_test.go | 30 +++ .../d/enterprise_scim_groups.html.markdown | 49 +++++ 3 files changed, 283 insertions(+) create mode 100644 github/data_source_github_enterprise_scim_groups.go create mode 100644 github/data_source_github_enterprise_scim_groups_test.go create mode 100644 website/docs/d/enterprise_scim_groups.html.markdown diff --git a/github/data_source_github_enterprise_scim_groups.go b/github/data_source_github_enterprise_scim_groups.go new file mode 100644 index 0000000000..b66c2b201f --- /dev/null +++ b/github/data_source_github_enterprise_scim_groups.go @@ -0,0 +1,204 @@ +package github + +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataSourceGithubEnterpriseSCIMGroups() *schema.Resource { + return &schema.Resource{ + Description: "Lookup SCIM groups provisioned for a GitHub enterprise.", + ReadContext: dataSourceGithubEnterpriseSCIMGroupsRead, + + Schema: map[string]*schema.Schema{ + "enterprise": { + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + }, + "filter": { + Description: "Optional SCIM filter. See GitHub SCIM enterprise docs for supported filters.", + Type: schema.TypeString, + Optional: true, + }, + "results_per_page": { + Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateFunc: validation.IntBetween(1, 100), + }, + + "schemas": { + Description: "SCIM response schemas.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "total_results": { + Description: "The total number of results returned by the SCIM endpoint.", + Type: schema.TypeInt, + Computed: true, + }, + "start_index": { + Description: "The startIndex from the first SCIM page.", + Type: schema.TypeInt, + Computed: true, + }, + "items_per_page": { + Description: "The itemsPerPage from the first SCIM page.", + Type: schema.TypeInt, + Computed: true, + }, + "resources": { + Description: "All SCIM groups.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: enterpriseSCIMGroupSchema(), + }, + }, + }, + } +} + +func dataSourceGithubEnterpriseSCIMGroupsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise := d.Get("enterprise").(string) + filter := d.Get("filter").(string) + count := d.Get("results_per_page").(int) + + groups, first, err := enterpriseSCIMListAllGroups(ctx, client, enterprise, filter, count) + if err != nil { + return diag.FromErr(err) + } + + flat := make([]any, 0, len(groups)) + for _, g := range groups { + flat = append(flat, flattenEnterpriseSCIMGroup(g)) + } + + id := fmt.Sprintf("%s/scim-groups", enterprise) + if filter != "" { + id = fmt.Sprintf("%s?filter=%s", id, url.QueryEscape(filter)) + } + + d.SetId(id) + + if err := d.Set("schemas", first.Schemas); err != nil { + return diag.FromErr(err) + } + if first.TotalResults != nil { + if err := d.Set("total_results", *first.TotalResults); err != nil { + return diag.FromErr(err) + } + } + startIndex := 1 + if first.StartIndex != nil && *first.StartIndex > 0 { + startIndex = *first.StartIndex + } + if err := d.Set("start_index", startIndex); err != nil { + return diag.FromErr(err) + } + itemsPerPage := count + if first.ItemsPerPage != nil && *first.ItemsPerPage > 0 { + itemsPerPage = *first.ItemsPerPage + } + if err := d.Set("items_per_page", itemsPerPage); err != nil { + return diag.FromErr(err) + } + if err := d.Set("resources", flat); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func enterpriseSCIMMetaSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "resource_type": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM resource type.", + }, + "created": { + Type: schema.TypeString, + Computed: true, + Description: "The creation timestamp.", + }, + "last_modified": { + Type: schema.TypeString, + Computed: true, + Description: "The lastModified timestamp.", + }, + "location": { + Type: schema.TypeString, + Computed: true, + Description: "The resource location.", + }, + } +} + +func enterpriseSCIMGroupSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "schemas": { + Type: schema.TypeList, + Computed: true, + Description: "SCIM schemas for this group.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM group ID.", + }, + "external_id": { + Type: schema.TypeString, + Computed: true, + Description: "The external ID for the group.", + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM group displayName.", + }, + "members": { + Type: schema.TypeList, + Computed: true, + Description: "Group members.", + Elem: &schema.Resource{Schema: enterpriseSCIMGroupMemberSchema()}, + }, + "meta": { + Type: schema.TypeList, + Computed: true, + Description: "Resource metadata.", + Elem: &schema.Resource{Schema: enterpriseSCIMMetaSchema()}, + }, + } +} + +func enterpriseSCIMGroupMemberSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Computed: true, + Description: "Member identifier.", + }, + "ref": { + Type: schema.TypeString, + Computed: true, + Description: "Member reference URL.", + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: "Member display name.", + }, + } +} diff --git a/github/data_source_github_enterprise_scim_groups_test.go b/github/data_source_github_enterprise_scim_groups_test.go new file mode 100644 index 0000000000..0628cf006c --- /dev/null +++ b/github/data_source_github_enterprise_scim_groups_test.go @@ -0,0 +1,30 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubEnterpriseSCIMGroupsDataSource(t *testing.T) { + t.Run("lists groups without error", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: fmt.Sprintf(` + data "github_enterprise_scim_groups" "test" { + enterprise = "%s" + } + `, testAccConf.enterpriseSlug), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "id"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "total_results"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "schemas.#"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "resources.#"), + ), + }}, + }) + }) +} diff --git a/website/docs/d/enterprise_scim_groups.html.markdown b/website/docs/d/enterprise_scim_groups.html.markdown new file mode 100644 index 0000000000..f8a1ce01df --- /dev/null +++ b/website/docs/d/enterprise_scim_groups.html.markdown @@ -0,0 +1,49 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_scim_groups" +description: |- + Get SCIM groups provisioned for a GitHub enterprise. +--- + +# github_enterprise_scim_groups + +Use this data source to retrieve SCIM groups provisioned for a GitHub enterprise. + +## Example Usage + +```hcl +data "github_enterprise_scim_groups" "example" { + enterprise = "example-co" +} +``` + +## Argument Reference + +* `enterprise` - (Required) The enterprise slug. +* `filter` - (Optional) SCIM filter string. +* `results_per_page` - (Optional) Page size used while auto-fetching all pages (mapped to SCIM `count`). + +### Notes on `filter` + +`filter` is passed to the GitHub SCIM API as-is (server-side filtering). It is **not** a Terraform expression and it does **not** understand provider schema paths. + +GitHub supports **only one** filter expression and only for these attributes on the enterprise `Groups` listing endpoint: + +* `externalId` +* `id` +* `displayName` + +Example: + +```hcl +filter = "displayName eq \"Engineering\"" +``` + +## Attributes Reference + +* `schemas` - SCIM response schemas. +* `total_results` - Total number of SCIM groups. +* `start_index` - Start index from the first page. +* `items_per_page` - Items per page from the first page. +* `resources` - List of SCIM groups. Each entry includes: + * `schemas`, `id`, `external_id`, `display_name`, `members`, `meta`. From 7fcb201def9e9dd0c63d9cd82a85daaf424eaac5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:30:17 +0100 Subject: [PATCH 04/15] feat(enterprise-scim): add github_enterprise_scim_user data source Retrieve information about a single SCIM user by ID. Includes: - Data source implementation - Acceptance tests - Documentation --- ...data_source_github_enterprise_scim_user.go | 78 +++++++++++++++++++ ...source_github_enterprise_scim_user_test.go | 36 +++++++++ .../docs/d/enterprise_scim_user.html.markdown | 28 +++++++ 3 files changed, 142 insertions(+) create mode 100644 github/data_source_github_enterprise_scim_user.go create mode 100644 github/data_source_github_enterprise_scim_user_test.go create mode 100644 website/docs/d/enterprise_scim_user.html.markdown diff --git a/github/data_source_github_enterprise_scim_user.go b/github/data_source_github_enterprise_scim_user.go new file mode 100644 index 0000000000..715e0cf0f6 --- /dev/null +++ b/github/data_source_github_enterprise_scim_user.go @@ -0,0 +1,78 @@ +package github + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubEnterpriseSCIMUser() *schema.Resource { + s := enterpriseSCIMUserSchema() + s["enterprise"] = &schema.Schema{ + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + } + s["scim_user_id"] = &schema.Schema{ + Description: "The SCIM user ID.", + Type: schema.TypeString, + Required: true, + } + + return &schema.Resource{ + Description: "Lookup SCIM provisioning information for a single GitHub enterprise user.", + ReadContext: dataSourceGithubEnterpriseSCIMUserRead, + Schema: s, + } +} + +func dataSourceGithubEnterpriseSCIMUserRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise := d.Get("enterprise").(string) + scimUserID := d.Get("scim_user_id").(string) + + user, _, err := client.Enterprise.GetProvisionedSCIMUser(ctx, enterprise, scimUserID) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(fmt.Sprintf("%s/%s", enterprise, scimUserID)) + + if err := d.Set("schemas", user.Schemas); err != nil { + return diag.FromErr(err) + } + if user.ID != nil { + if err := d.Set("id", *user.ID); err != nil { + return diag.FromErr(err) + } + } + if err := d.Set("external_id", user.ExternalID); err != nil { + return diag.FromErr(err) + } + if err := d.Set("user_name", user.UserName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("display_name", user.DisplayName); err != nil { + return diag.FromErr(err) + } + if err := d.Set("active", user.Active); err != nil { + return diag.FromErr(err) + } + if err := d.Set("name", flattenEnterpriseSCIMUserName(user.Name)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("emails", flattenEnterpriseSCIMUserEmails(user.Emails)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("roles", flattenEnterpriseSCIMUserRoles(user.Roles)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("meta", flattenEnterpriseSCIMMeta(user.Meta)); err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/github/data_source_github_enterprise_scim_user_test.go b/github/data_source_github_enterprise_scim_user_test.go new file mode 100644 index 0000000000..22b2a4e7d8 --- /dev/null +++ b/github/data_source_github_enterprise_scim_user_test.go @@ -0,0 +1,36 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubEnterpriseSCIMUserDataSource(t *testing.T) { + t.Run("reads user without error", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: fmt.Sprintf(` + data "github_enterprise_scim_users" "all" { + enterprise = "%s" + } + + data "github_enterprise_scim_user" "test" { + enterprise = "%[1]s" + scim_user_id = data.github_enterprise_scim_users.all.resources[0].id + } + `, testAccConf.enterpriseSlug), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "id"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "user_name"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "display_name"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "schemas.#"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "emails.#"), + ), + }}, + }) + }) +} diff --git a/website/docs/d/enterprise_scim_user.html.markdown b/website/docs/d/enterprise_scim_user.html.markdown new file mode 100644 index 0000000000..e2760d3ccc --- /dev/null +++ b/website/docs/d/enterprise_scim_user.html.markdown @@ -0,0 +1,28 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_scim_user" +description: |- + Get SCIM provisioning information for a GitHub enterprise user. +--- + +# github_enterprise_scim_user + +Use this data source to retrieve SCIM provisioning information for a single enterprise user. + +## Example Usage + +```hcl +data "github_enterprise_scim_user" "example" { + enterprise = "example-co" + scim_user_id = "123456" +} +``` + +## Argument Reference + +* `enterprise` - (Required) The enterprise slug. +* `scim_user_id` - (Required) The SCIM user ID. + +## Attributes Reference + +* `schemas`, `id`, `external_id`, `user_name`, `display_name`, `active`, `name`, `emails`, `roles`, `meta`. From 473abfd0d78f24c8ac48c67d9c92506375bb0524 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:30:28 +0100 Subject: [PATCH 05/15] feat(enterprise-scim): add github_enterprise_scim_users data source List all SCIM users in an enterprise with optional filtering. Includes: - Data source implementation - Acceptance tests - Documentation --- ...ata_source_github_enterprise_scim_users.go | 251 ++++++++++++++++++ ...ource_github_enterprise_scim_users_test.go | 30 +++ .../d/enterprise_scim_users.html.markdown | 56 ++++ 3 files changed, 337 insertions(+) create mode 100644 github/data_source_github_enterprise_scim_users.go create mode 100644 github/data_source_github_enterprise_scim_users_test.go create mode 100644 website/docs/d/enterprise_scim_users.html.markdown diff --git a/github/data_source_github_enterprise_scim_users.go b/github/data_source_github_enterprise_scim_users.go new file mode 100644 index 0000000000..69b845f79e --- /dev/null +++ b/github/data_source_github_enterprise_scim_users.go @@ -0,0 +1,251 @@ +package github + +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func dataSourceGithubEnterpriseSCIMUsers() *schema.Resource { + return &schema.Resource{ + Description: "Lookup SCIM users provisioned for a GitHub enterprise.", + ReadContext: dataSourceGithubEnterpriseSCIMUsersRead, + + Schema: map[string]*schema.Schema{ + "enterprise": { + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + }, + "filter": { + Description: "Optional SCIM filter. See GitHub SCIM enterprise docs for supported filters.", + Type: schema.TypeString, + Optional: true, + }, + "results_per_page": { + Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateFunc: validation.IntBetween(1, 100), + }, + + "schemas": { + Description: "SCIM response schemas.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "total_results": { + Description: "The total number of results returned by the SCIM endpoint.", + Type: schema.TypeInt, + Computed: true, + }, + "start_index": { + Description: "The startIndex from the first SCIM page.", + Type: schema.TypeInt, + Computed: true, + }, + "items_per_page": { + Description: "The itemsPerPage from the first SCIM page.", + Type: schema.TypeInt, + Computed: true, + }, + "resources": { + Description: "All SCIM users.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: enterpriseSCIMUserSchema(), + }, + }, + }, + } +} + +func dataSourceGithubEnterpriseSCIMUsersRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + + enterprise := d.Get("enterprise").(string) + filter := d.Get("filter").(string) + count := d.Get("results_per_page").(int) + + users, first, err := enterpriseSCIMListAllUsers(ctx, client, enterprise, filter, count) + if err != nil { + return diag.FromErr(err) + } + + flat := make([]any, 0, len(users)) + for _, u := range users { + flat = append(flat, flattenEnterpriseSCIMUser(u)) + } + + id := fmt.Sprintf("%s/scim-users", enterprise) + if filter != "" { + id = fmt.Sprintf("%s?filter=%s", id, url.QueryEscape(filter)) + } + + d.SetId(id) + + if err := d.Set("schemas", first.Schemas); err != nil { + return diag.FromErr(err) + } + if first.TotalResults != nil { + if err := d.Set("total_results", *first.TotalResults); err != nil { + return diag.FromErr(err) + } + } + startIndex := 1 + if first.StartIndex != nil && *first.StartIndex > 0 { + startIndex = *first.StartIndex + } + if err := d.Set("start_index", startIndex); err != nil { + return diag.FromErr(err) + } + itemsPerPage := count + if first.ItemsPerPage != nil && *first.ItemsPerPage > 0 { + itemsPerPage = *first.ItemsPerPage + } + if err := d.Set("items_per_page", itemsPerPage); err != nil { + return diag.FromErr(err) + } + if err := d.Set("resources", flat); err != nil { + return diag.FromErr(err) + } + + return nil +} + +func enterpriseSCIMUserSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "schemas": { + Type: schema.TypeList, + Computed: true, + Description: "SCIM schemas for this user.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM user ID.", + }, + "external_id": { + Type: schema.TypeString, + Computed: true, + Description: "The external ID for the user.", + }, + "user_name": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM userName.", + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM displayName.", + }, + "active": { + Type: schema.TypeBool, + Computed: true, + Description: "Whether the user is active.", + }, + "name": { + Type: schema.TypeList, + Computed: true, + Description: "User name object.", + Elem: &schema.Resource{Schema: enterpriseSCIMUserNameSchema()}, + }, + "emails": { + Type: schema.TypeList, + Computed: true, + Description: "User emails.", + Elem: &schema.Resource{Schema: enterpriseSCIMUserEmailSchema()}, + }, + "roles": { + Type: schema.TypeList, + Computed: true, + Description: "User roles.", + Elem: &schema.Resource{Schema: enterpriseSCIMUserRoleSchema()}, + }, + "meta": { + Type: schema.TypeList, + Computed: true, + Description: "Resource metadata.", + Elem: &schema.Resource{Schema: enterpriseSCIMMetaSchema()}, + }, + } +} + +func enterpriseSCIMUserNameSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "formatted": { + Type: schema.TypeString, + Computed: true, + Description: "Formatted name.", + }, + "family_name": { + Type: schema.TypeString, + Computed: true, + Description: "Family name.", + }, + "given_name": { + Type: schema.TypeString, + Computed: true, + Description: "Given name.", + }, + "middle_name": { + Type: schema.TypeString, + Computed: true, + Description: "Middle name.", + }, + } +} + +func enterpriseSCIMUserEmailSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Computed: true, + Description: "Email address.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Email type.", + }, + "primary": { + Type: schema.TypeBool, + Computed: true, + Description: "Whether this email is primary.", + }, + } +} + +func enterpriseSCIMUserRoleSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Computed: true, + Description: "Role value.", + }, + "display": { + Type: schema.TypeString, + Computed: true, + Description: "Role display.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "Role type.", + }, + "primary": { + Type: schema.TypeBool, + Computed: true, + Description: "Whether this role is primary.", + }, + } +} diff --git a/github/data_source_github_enterprise_scim_users_test.go b/github/data_source_github_enterprise_scim_users_test.go new file mode 100644 index 0000000000..ae420b62d5 --- /dev/null +++ b/github/data_source_github_enterprise_scim_users_test.go @@ -0,0 +1,30 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubEnterpriseSCIMUsersDataSource(t *testing.T) { + t.Run("lists users without error", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: fmt.Sprintf(` + data "github_enterprise_scim_users" "test" { + enterprise = "%s" + } + `, testAccConf.enterpriseSlug), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "id"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "total_results"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "schemas.#"), + resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "resources.#"), + ), + }}, + }) + }) +} diff --git a/website/docs/d/enterprise_scim_users.html.markdown b/website/docs/d/enterprise_scim_users.html.markdown new file mode 100644 index 0000000000..43b816a169 --- /dev/null +++ b/website/docs/d/enterprise_scim_users.html.markdown @@ -0,0 +1,56 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_scim_users" +description: |- + Get SCIM users provisioned for a GitHub enterprise. +--- + +# github_enterprise_scim_users + +Use this data source to retrieve SCIM users provisioned for a GitHub enterprise. + +## Example Usage + +```hcl +data "github_enterprise_scim_users" "example" { + enterprise = "example-co" +} +``` + +## Argument Reference + +* `enterprise` - (Required) The enterprise slug. +* `filter` - (Optional) SCIM filter string. +* `results_per_page` - (Optional) Page size used while auto-fetching all pages (mapped to SCIM `count`). + +### Notes on `filter` + +`filter` is passed to the GitHub SCIM API as-is (server-side filtering). It is **not** a Terraform expression and it does **not** understand provider schema paths such as `name[0].family_name`. + +GitHub supports **only one** filter expression and only for these attributes on the enterprise `Users` listing endpoint: + +* `userName` +* `externalId` +* `id` +* `displayName` + +Examples: + +```hcl +filter = "userName eq \"E012345\"" +``` + +```hcl +filter = "externalId eq \"9138790-10932-109120392-12321\"" +``` + +If you need to filter by other values that only exist in the Terraform schema (for example `name[0].family_name`), retrieve the users and filter locally in Terraform. + +## Attributes Reference + +* `schemas` - SCIM response schemas. +* `total_results` - Total number of SCIM users. +* `start_index` - Start index from the first page. +* `items_per_page` - Items per page from the first page. +* `resources` - List of SCIM users. Each entry includes: + * `schemas`, `id`, `external_id`, `user_name`, `display_name`, `active`, `name`, `emails`, `roles`, `meta`. From 28c8bb78692fd5648bc8923d638fe77053658348 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:30:37 +0100 Subject: [PATCH 06/15] feat(enterprise-scim): register SCIM data sources in provider - Register 4 data sources in provider.go: - github_enterprise_scim_group - github_enterprise_scim_groups - github_enterprise_scim_user - github_enterprise_scim_users --- github/provider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/github/provider.go b/github/provider.go index 2d5d6dc33a..3fa1fbf055 100644 --- a/github/provider.go +++ b/github/provider.go @@ -294,6 +294,10 @@ func Provider() *schema.Provider { "github_user_external_identity": dataSourceGithubUserExternalIdentity(), "github_users": dataSourceGithubUsers(), "github_enterprise": dataSourceGithubEnterprise(), + "github_enterprise_scim_groups": dataSourceGithubEnterpriseSCIMGroups(), + "github_enterprise_scim_group": dataSourceGithubEnterpriseSCIMGroup(), + "github_enterprise_scim_users": dataSourceGithubEnterpriseSCIMUsers(), + "github_enterprise_scim_user": dataSourceGithubEnterpriseSCIMUser(), "github_repository_environment_deployment_policies": dataSourceGithubRepositoryEnvironmentDeploymentPolicies(), }, } From 617f924078a84095201fd44c613ca076cbbe1fde Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:31:08 +0100 Subject: [PATCH 07/15] fix(enterprise-scim): update go-github import to v82 Update import paths from go-github/v81 to go-github/v82 to match the current version in upstream/main. --- github/util_enterprise_scim.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go index 562d564251..0851b02db0 100644 --- a/github/util_enterprise_scim.go +++ b/github/util_enterprise_scim.go @@ -3,7 +3,7 @@ package github import ( "context" - gh "github.com/google/go-github/v81/github" + gh "github.com/google/go-github/v82/github" ) // enterpriseSCIMListAllGroups fetches all SCIM groups for an enterprise with automatic pagination. From 914e632df45f83d1fa277911fb12abf627a2f4fe Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Tue, 17 Feb 2026 14:16:46 +0100 Subject: [PATCH 08/15] test(enterprise-scim): migrate SCIM acceptance tests to terraform-plugin-testing --- github/data_source_github_enterprise_scim_group_test.go | 2 +- github/data_source_github_enterprise_scim_groups_test.go | 2 +- github/data_source_github_enterprise_scim_user_test.go | 2 +- github/data_source_github_enterprise_scim_users_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/github/data_source_github_enterprise_scim_group_test.go b/github/data_source_github_enterprise_scim_group_test.go index a113e6f6ac..0f8db3c58d 100644 --- a/github/data_source_github_enterprise_scim_group_test.go +++ b/github/data_source_github_enterprise_scim_group_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseSCIMGroupDataSource(t *testing.T) { diff --git a/github/data_source_github_enterprise_scim_groups_test.go b/github/data_source_github_enterprise_scim_groups_test.go index 0628cf006c..f81e70fa54 100644 --- a/github/data_source_github_enterprise_scim_groups_test.go +++ b/github/data_source_github_enterprise_scim_groups_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseSCIMGroupsDataSource(t *testing.T) { diff --git a/github/data_source_github_enterprise_scim_user_test.go b/github/data_source_github_enterprise_scim_user_test.go index 22b2a4e7d8..f3b8aa6fe3 100644 --- a/github/data_source_github_enterprise_scim_user_test.go +++ b/github/data_source_github_enterprise_scim_user_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseSCIMUserDataSource(t *testing.T) { diff --git a/github/data_source_github_enterprise_scim_users_test.go b/github/data_source_github_enterprise_scim_users_test.go index ae420b62d5..e452cee1bc 100644 --- a/github/data_source_github_enterprise_scim_users_test.go +++ b/github/data_source_github_enterprise_scim_users_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseSCIMUsersDataSource(t *testing.T) { From 85f07ce925c9564c137109c37dce85f5a1be79f1 Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Wed, 18 Feb 2026 10:50:50 +0100 Subject: [PATCH 09/15] fix(enterprise-scim): address review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename single-char variables (Rule #29): m→member, e→email, r→role, u→user, g→group - Fix descriptions (Rule #2a): 'Lookup' → 'Retrieves' in all 4 data sources - Fix ValidateFunc → ValidateDiagFunc (Rule #23) in users/groups - Migrate tests from Check to ConfigStateChecks (Rule #31) - Add unit tests for all flatten utility functions (Rule #33) - Add sidebar entries for SCIM data sources in github.erb --- ...ata_source_github_enterprise_scim_group.go | 2 +- ...ource_github_enterprise_scim_group_test.go | 12 +- ...ta_source_github_enterprise_scim_groups.go | 16 +- ...urce_github_enterprise_scim_groups_test.go | 14 +- ...data_source_github_enterprise_scim_user.go | 2 +- ...source_github_enterprise_scim_user_test.go | 16 +- ...ata_source_github_enterprise_scim_users.go | 16 +- ...ource_github_enterprise_scim_users_test.go | 14 +- github/util_enterprise_scim.go | 36 +- github/util_enterprise_scim_test.go | 376 ++++++++++++++++++ website/github.erb | 12 + 11 files changed, 456 insertions(+), 60 deletions(-) create mode 100644 github/util_enterprise_scim_test.go diff --git a/github/data_source_github_enterprise_scim_group.go b/github/data_source_github_enterprise_scim_group.go index f2ef893269..43f89c8a26 100644 --- a/github/data_source_github_enterprise_scim_group.go +++ b/github/data_source_github_enterprise_scim_group.go @@ -22,7 +22,7 @@ func dataSourceGithubEnterpriseSCIMGroup() *schema.Resource { } return &schema.Resource{ - Description: "Lookup SCIM provisioning information for a single GitHub enterprise group.", + Description: "Retrieves SCIM provisioning information for a single GitHub enterprise group.", ReadContext: dataSourceGithubEnterpriseSCIMGroupRead, Schema: s, } diff --git a/github/data_source_github_enterprise_scim_group_test.go b/github/data_source_github_enterprise_scim_group_test.go index 0f8db3c58d..fbf9c8e024 100644 --- a/github/data_source_github_enterprise_scim_group_test.go +++ b/github/data_source_github_enterprise_scim_group_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubEnterpriseSCIMGroupDataSource(t *testing.T) { @@ -23,11 +26,10 @@ func TestAccGithubEnterpriseSCIMGroupDataSource(t *testing.T) { scim_group_id = data.github_enterprise_scim_groups.all.resources[0].id } `, testAccConf.enterpriseSlug), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "id"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "display_name"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_group.test", "schemas.#"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("data.github_enterprise_scim_group.test", tfjsonpath.New("display_name"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_group.test", tfjsonpath.New("schemas"), knownvalue.NotNull()), + }, }}, }) }) diff --git a/github/data_source_github_enterprise_scim_groups.go b/github/data_source_github_enterprise_scim_groups.go index b66c2b201f..c9a544fe81 100644 --- a/github/data_source_github_enterprise_scim_groups.go +++ b/github/data_source_github_enterprise_scim_groups.go @@ -12,7 +12,7 @@ import ( func dataSourceGithubEnterpriseSCIMGroups() *schema.Resource { return &schema.Resource{ - Description: "Lookup SCIM groups provisioned for a GitHub enterprise.", + Description: "Retrieves SCIM groups provisioned for a GitHub enterprise.", ReadContext: dataSourceGithubEnterpriseSCIMGroupsRead, Schema: map[string]*schema.Schema{ @@ -27,11 +27,11 @@ func dataSourceGithubEnterpriseSCIMGroups() *schema.Resource { Optional: true, }, "results_per_page": { - Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", - Type: schema.TypeInt, - Optional: true, - Default: 100, - ValidateFunc: validation.IntBetween(1, 100), + Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 100)), }, "schemas": { @@ -80,8 +80,8 @@ func dataSourceGithubEnterpriseSCIMGroupsRead(ctx context.Context, d *schema.Res } flat := make([]any, 0, len(groups)) - for _, g := range groups { - flat = append(flat, flattenEnterpriseSCIMGroup(g)) + for _, group := range groups { + flat = append(flat, flattenEnterpriseSCIMGroup(group)) } id := fmt.Sprintf("%s/scim-groups", enterprise) diff --git a/github/data_source_github_enterprise_scim_groups_test.go b/github/data_source_github_enterprise_scim_groups_test.go index f81e70fa54..c201504223 100644 --- a/github/data_source_github_enterprise_scim_groups_test.go +++ b/github/data_source_github_enterprise_scim_groups_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubEnterpriseSCIMGroupsDataSource(t *testing.T) { @@ -18,12 +21,11 @@ func TestAccGithubEnterpriseSCIMGroupsDataSource(t *testing.T) { enterprise = "%s" } `, testAccConf.enterpriseSlug), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "id"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "total_results"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "schemas.#"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_groups.test", "resources.#"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("data.github_enterprise_scim_groups.test", tfjsonpath.New("total_results"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_groups.test", tfjsonpath.New("schemas"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_groups.test", tfjsonpath.New("resources"), knownvalue.NotNull()), + }, }}, }) }) diff --git a/github/data_source_github_enterprise_scim_user.go b/github/data_source_github_enterprise_scim_user.go index 715e0cf0f6..1568a72937 100644 --- a/github/data_source_github_enterprise_scim_user.go +++ b/github/data_source_github_enterprise_scim_user.go @@ -22,7 +22,7 @@ func dataSourceGithubEnterpriseSCIMUser() *schema.Resource { } return &schema.Resource{ - Description: "Lookup SCIM provisioning information for a single GitHub enterprise user.", + Description: "Retrieves SCIM provisioning information for a single GitHub enterprise user.", ReadContext: dataSourceGithubEnterpriseSCIMUserRead, Schema: s, } diff --git a/github/data_source_github_enterprise_scim_user_test.go b/github/data_source_github_enterprise_scim_user_test.go index f3b8aa6fe3..9324090878 100644 --- a/github/data_source_github_enterprise_scim_user_test.go +++ b/github/data_source_github_enterprise_scim_user_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubEnterpriseSCIMUserDataSource(t *testing.T) { @@ -23,13 +26,12 @@ func TestAccGithubEnterpriseSCIMUserDataSource(t *testing.T) { scim_user_id = data.github_enterprise_scim_users.all.resources[0].id } `, testAccConf.enterpriseSlug), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "id"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "user_name"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "display_name"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "schemas.#"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_user.test", "emails.#"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("data.github_enterprise_scim_user.test", tfjsonpath.New("user_name"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_user.test", tfjsonpath.New("display_name"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_user.test", tfjsonpath.New("schemas"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_user.test", tfjsonpath.New("emails"), knownvalue.NotNull()), + }, }}, }) }) diff --git a/github/data_source_github_enterprise_scim_users.go b/github/data_source_github_enterprise_scim_users.go index 69b845f79e..26989cc690 100644 --- a/github/data_source_github_enterprise_scim_users.go +++ b/github/data_source_github_enterprise_scim_users.go @@ -12,7 +12,7 @@ import ( func dataSourceGithubEnterpriseSCIMUsers() *schema.Resource { return &schema.Resource{ - Description: "Lookup SCIM users provisioned for a GitHub enterprise.", + Description: "Retrieves SCIM users provisioned for a GitHub enterprise.", ReadContext: dataSourceGithubEnterpriseSCIMUsersRead, Schema: map[string]*schema.Schema{ @@ -27,11 +27,11 @@ func dataSourceGithubEnterpriseSCIMUsers() *schema.Resource { Optional: true, }, "results_per_page": { - Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", - Type: schema.TypeInt, - Optional: true, - Default: 100, - ValidateFunc: validation.IntBetween(1, 100), + Description: "Number of results per request (mapped to SCIM 'count'). Used while auto-fetching all pages.", + Type: schema.TypeInt, + Optional: true, + Default: 100, + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 100)), }, "schemas": { @@ -80,8 +80,8 @@ func dataSourceGithubEnterpriseSCIMUsersRead(ctx context.Context, d *schema.Reso } flat := make([]any, 0, len(users)) - for _, u := range users { - flat = append(flat, flattenEnterpriseSCIMUser(u)) + for _, user := range users { + flat = append(flat, flattenEnterpriseSCIMUser(user)) } id := fmt.Sprintf("%s/scim-users", enterprise) diff --git a/github/data_source_github_enterprise_scim_users_test.go b/github/data_source_github_enterprise_scim_users_test.go index e452cee1bc..a3b86ad704 100644 --- a/github/data_source_github_enterprise_scim_users_test.go +++ b/github/data_source_github_enterprise_scim_users_test.go @@ -5,6 +5,9 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubEnterpriseSCIMUsersDataSource(t *testing.T) { @@ -18,12 +21,11 @@ func TestAccGithubEnterpriseSCIMUsersDataSource(t *testing.T) { enterprise = "%s" } `, testAccConf.enterpriseSlug), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "id"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "total_results"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "schemas.#"), - resource.TestCheckResourceAttrSet("data.github_enterprise_scim_users.test", "resources.#"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("data.github_enterprise_scim_users.test", tfjsonpath.New("total_results"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_users.test", tfjsonpath.New("schemas"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("data.github_enterprise_scim_users.test", tfjsonpath.New("resources"), knownvalue.NotNull()), + }, }}, }) }) diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go index 0851b02db0..0d9bfc5127 100644 --- a/github/util_enterprise_scim.go +++ b/github/util_enterprise_scim.go @@ -123,15 +123,15 @@ func flattenEnterpriseSCIMMeta(meta *gh.SCIMEnterpriseMeta) []any { func flattenEnterpriseSCIMGroupMembers(members []*gh.SCIMEnterpriseDisplayReference) []any { out := make([]any, 0, len(members)) - for _, m := range members { + for _, member := range members { item := map[string]any{ - "value": m.Value, + "value": member.Value, } - if m.Ref != nil { - item["ref"] = *m.Ref + if member.Ref != nil { + item["ref"] = *member.Ref } - if m.Display != nil { - item["display_name"] = *m.Display + if member.Display != nil { + item["display_name"] = *member.Display } out = append(out, item) } @@ -175,11 +175,11 @@ func flattenEnterpriseSCIMUserName(name *gh.SCIMEnterpriseUserName) []any { func flattenEnterpriseSCIMUserEmails(emails []*gh.SCIMEnterpriseUserEmail) []any { out := make([]any, 0, len(emails)) - for _, e := range emails { + for _, email := range emails { out = append(out, map[string]any{ - "value": e.Value, - "type": e.Type, - "primary": e.Primary, + "value": email.Value, + "type": email.Type, + "primary": email.Primary, }) } return out @@ -187,18 +187,18 @@ func flattenEnterpriseSCIMUserEmails(emails []*gh.SCIMEnterpriseUserEmail) []any func flattenEnterpriseSCIMUserRoles(roles []*gh.SCIMEnterpriseUserRole) []any { out := make([]any, 0, len(roles)) - for _, r := range roles { + for _, role := range roles { item := map[string]any{ - "value": r.Value, + "value": role.Value, } - if r.Display != nil { - item["display"] = *r.Display + if role.Display != nil { + item["display"] = *role.Display } - if r.Type != nil { - item["type"] = *r.Type + if role.Type != nil { + item["type"] = *role.Type } - if r.Primary != nil { - item["primary"] = *r.Primary + if role.Primary != nil { + item["primary"] = *role.Primary } out = append(out, item) } diff --git a/github/util_enterprise_scim_test.go b/github/util_enterprise_scim_test.go new file mode 100644 index 0000000000..78cc4d9775 --- /dev/null +++ b/github/util_enterprise_scim_test.go @@ -0,0 +1,376 @@ +package github + +import ( + "testing" + "time" + + gh "github.com/google/go-github/v82/github" +) + +func TestFlattenEnterpriseSCIMMeta(t *testing.T) { + t.Run("returns nil for nil input", func(t *testing.T) { + result := flattenEnterpriseSCIMMeta(nil) + if result != nil { + t.Fatalf("expected nil, got %v", result) + } + }) + + t.Run("returns resource_type only when no optional fields", func(t *testing.T) { + meta := &gh.SCIMEnterpriseMeta{ + ResourceType: "User", + } + result := flattenEnterpriseSCIMMeta(meta) + if len(result) != 1 { + t.Fatalf("expected 1 element, got %d", len(result)) + } + m := result[0].(map[string]any) + if m["resource_type"] != "User" { + t.Fatalf("expected resource_type 'User', got %v", m["resource_type"]) + } + if _, ok := m["created"]; ok { + t.Fatal("expected 'created' to be absent") + } + if _, ok := m["last_modified"]; ok { + t.Fatal("expected 'last_modified' to be absent") + } + if _, ok := m["location"]; ok { + t.Fatal("expected 'location' to be absent") + } + }) + + t.Run("returns all fields when fully populated", func(t *testing.T) { + now := time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC) + meta := &gh.SCIMEnterpriseMeta{ + ResourceType: "Group", + Created: &gh.Timestamp{Time: now}, + LastModified: &gh.Timestamp{Time: now.Add(time.Hour)}, + Location: gh.Ptr("https://api.github.com/scim/v2/enterprises/test/Groups/123"), + } + result := flattenEnterpriseSCIMMeta(meta) + if len(result) != 1 { + t.Fatalf("expected 1 element, got %d", len(result)) + } + m := result[0].(map[string]any) + if m["resource_type"] != "Group" { + t.Fatalf("expected resource_type 'Group', got %v", m["resource_type"]) + } + if m["created"] == "" { + t.Fatal("expected 'created' to be non-empty") + } + if m["last_modified"] == "" { + t.Fatal("expected 'last_modified' to be non-empty") + } + if m["location"] != "https://api.github.com/scim/v2/enterprises/test/Groups/123" { + t.Fatalf("expected location URL, got %v", m["location"]) + } + }) +} + +func TestFlattenEnterpriseSCIMGroupMembers(t *testing.T) { + t.Run("returns empty slice for nil input", func(t *testing.T) { + result := flattenEnterpriseSCIMGroupMembers(nil) + if len(result) != 0 { + t.Fatalf("expected empty slice, got %d elements", len(result)) + } + }) + + t.Run("returns members with all fields", func(t *testing.T) { + members := []*gh.SCIMEnterpriseDisplayReference{ + { + Value: "user-1", + Ref: gh.Ptr("https://api.github.com/scim/v2/enterprises/test/Users/user-1"), + Display: gh.Ptr("Alice"), + }, + { + Value: "user-2", + }, + } + result := flattenEnterpriseSCIMGroupMembers(members) + if len(result) != 2 { + t.Fatalf("expected 2 members, got %d", len(result)) + } + + first := result[0].(map[string]any) + if first["value"] != "user-1" { + t.Fatalf("expected value 'user-1', got %v", first["value"]) + } + if first["ref"] != "https://api.github.com/scim/v2/enterprises/test/Users/user-1" { + t.Fatalf("expected ref URL, got %v", first["ref"]) + } + if first["display_name"] != "Alice" { + t.Fatalf("expected display_name 'Alice', got %v", first["display_name"]) + } + + second := result[1].(map[string]any) + if second["value"] != "user-2" { + t.Fatalf("expected value 'user-2', got %v", second["value"]) + } + if _, ok := second["ref"]; ok { + t.Fatal("expected 'ref' to be absent") + } + if _, ok := second["display_name"]; ok { + t.Fatal("expected 'display_name' to be absent") + } + }) +} + +func TestFlattenEnterpriseSCIMGroup(t *testing.T) { + t.Run("returns all fields", func(t *testing.T) { + group := &gh.SCIMEnterpriseGroupAttributes{ + ID: gh.Ptr("group-123"), + ExternalID: gh.Ptr("ext-456"), + DisplayName: gh.Ptr("Engineering"), + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:Group"}, + Members: []*gh.SCIMEnterpriseDisplayReference{ + {Value: "user-1", Display: gh.Ptr("Alice")}, + }, + } + result := flattenEnterpriseSCIMGroup(group) + if result["id"] != "group-123" { + t.Fatalf("expected id 'group-123', got %v", result["id"]) + } + if result["external_id"] != "ext-456" { + t.Fatalf("expected external_id 'ext-456', got %v", result["external_id"]) + } + if result["display_name"] != "Engineering" { + t.Fatalf("expected display_name 'Engineering', got %v", result["display_name"]) + } + members := result["members"].([]any) + if len(members) != 1 { + t.Fatalf("expected 1 member, got %d", len(members)) + } + }) + + t.Run("handles nil optional fields", func(t *testing.T) { + group := &gh.SCIMEnterpriseGroupAttributes{ + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:Group"}, + } + result := flattenEnterpriseSCIMGroup(group) + if _, ok := result["id"]; ok { + t.Fatal("expected 'id' to be absent") + } + if _, ok := result["external_id"]; ok { + t.Fatal("expected 'external_id' to be absent") + } + if _, ok := result["display_name"]; ok { + t.Fatal("expected 'display_name' to be absent") + } + }) +} + +func TestFlattenEnterpriseSCIMUserName(t *testing.T) { + t.Run("returns nil for nil input", func(t *testing.T) { + result := flattenEnterpriseSCIMUserName(nil) + if result != nil { + t.Fatalf("expected nil, got %v", result) + } + }) + + t.Run("returns required fields only", func(t *testing.T) { + name := &gh.SCIMEnterpriseUserName{ + GivenName: "John", + FamilyName: "Doe", + } + result := flattenEnterpriseSCIMUserName(name) + if len(result) != 1 { + t.Fatalf("expected 1 element, got %d", len(result)) + } + m := result[0].(map[string]any) + if m["given_name"] != "John" { + t.Fatalf("expected given_name 'John', got %v", m["given_name"]) + } + if m["family_name"] != "Doe" { + t.Fatalf("expected family_name 'Doe', got %v", m["family_name"]) + } + if _, ok := m["formatted"]; ok { + t.Fatal("expected 'formatted' to be absent") + } + if _, ok := m["middle_name"]; ok { + t.Fatal("expected 'middle_name' to be absent") + } + }) + + t.Run("returns all fields when populated", func(t *testing.T) { + name := &gh.SCIMEnterpriseUserName{ + GivenName: "John", + FamilyName: "Doe", + Formatted: gh.Ptr("John M. Doe"), + MiddleName: gh.Ptr("M."), + } + result := flattenEnterpriseSCIMUserName(name) + if len(result) != 1 { + t.Fatalf("expected 1 element, got %d", len(result)) + } + m := result[0].(map[string]any) + if m["formatted"] != "John M. Doe" { + t.Fatalf("expected formatted 'John M. Doe', got %v", m["formatted"]) + } + if m["middle_name"] != "M." { + t.Fatalf("expected middle_name 'M.', got %v", m["middle_name"]) + } + }) +} + +func TestFlattenEnterpriseSCIMUserEmails(t *testing.T) { + t.Run("returns empty slice for nil input", func(t *testing.T) { + result := flattenEnterpriseSCIMUserEmails(nil) + if len(result) != 0 { + t.Fatalf("expected empty slice, got %d elements", len(result)) + } + }) + + t.Run("returns emails with all fields", func(t *testing.T) { + emails := []*gh.SCIMEnterpriseUserEmail{ + {Value: "alice@example.com", Type: "work", Primary: true}, + {Value: "alice@personal.com", Type: "home", Primary: false}, + } + result := flattenEnterpriseSCIMUserEmails(emails) + if len(result) != 2 { + t.Fatalf("expected 2 emails, got %d", len(result)) + } + + first := result[0].(map[string]any) + if first["value"] != "alice@example.com" { + t.Fatalf("expected value 'alice@example.com', got %v", first["value"]) + } + if first["type"] != "work" { + t.Fatalf("expected type 'work', got %v", first["type"]) + } + if first["primary"] != true { + t.Fatalf("expected primary true, got %v", first["primary"]) + } + + second := result[1].(map[string]any) + if second["primary"] != false { + t.Fatalf("expected primary false, got %v", second["primary"]) + } + }) +} + +func TestFlattenEnterpriseSCIMUserRoles(t *testing.T) { + t.Run("returns empty slice for nil input", func(t *testing.T) { + result := flattenEnterpriseSCIMUserRoles(nil) + if len(result) != 0 { + t.Fatalf("expected empty slice, got %d elements", len(result)) + } + }) + + t.Run("returns roles with all fields", func(t *testing.T) { + roles := []*gh.SCIMEnterpriseUserRole{ + { + Value: "user", + Display: gh.Ptr("User"), + Type: gh.Ptr("default"), + Primary: gh.Ptr(true), + }, + } + result := flattenEnterpriseSCIMUserRoles(roles) + if len(result) != 1 { + t.Fatalf("expected 1 role, got %d", len(result)) + } + role := result[0].(map[string]any) + if role["value"] != "user" { + t.Fatalf("expected value 'user', got %v", role["value"]) + } + if role["display"] != "User" { + t.Fatalf("expected display 'User', got %v", role["display"]) + } + if role["type"] != "default" { + t.Fatalf("expected type 'default', got %v", role["type"]) + } + if role["primary"] != true { + t.Fatalf("expected primary true, got %v", role["primary"]) + } + }) + + t.Run("handles nil optional fields", func(t *testing.T) { + roles := []*gh.SCIMEnterpriseUserRole{ + {Value: "admin"}, + } + result := flattenEnterpriseSCIMUserRoles(roles) + if len(result) != 1 { + t.Fatalf("expected 1 role, got %d", len(result)) + } + role := result[0].(map[string]any) + if _, ok := role["display"]; ok { + t.Fatal("expected 'display' to be absent") + } + if _, ok := role["type"]; ok { + t.Fatal("expected 'type' to be absent") + } + if _, ok := role["primary"]; ok { + t.Fatal("expected 'primary' to be absent") + } + }) +} + +func TestFlattenEnterpriseSCIMUser(t *testing.T) { + t.Run("returns all fields", func(t *testing.T) { + user := &gh.SCIMEnterpriseUserAttributes{ + ID: gh.Ptr("scim-user-123"), + UserName: "alice", + DisplayName: "Alice Wonderland", + ExternalID: "ext-789", + Active: true, + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"}, + Name: &gh.SCIMEnterpriseUserName{ + GivenName: "Alice", + FamilyName: "Wonderland", + }, + Emails: []*gh.SCIMEnterpriseUserEmail{ + {Value: "alice@example.com", Type: "work", Primary: true}, + }, + Roles: []*gh.SCIMEnterpriseUserRole{ + {Value: "user"}, + }, + } + result := flattenEnterpriseSCIMUser(user) + if result["id"] != "scim-user-123" { + t.Fatalf("expected id 'scim-user-123', got %v", result["id"]) + } + if result["user_name"] != "alice" { + t.Fatalf("expected user_name 'alice', got %v", result["user_name"]) + } + if result["display_name"] != "Alice Wonderland" { + t.Fatalf("expected display_name 'Alice Wonderland', got %v", result["display_name"]) + } + if result["external_id"] != "ext-789" { + t.Fatalf("expected external_id 'ext-789', got %v", result["external_id"]) + } + if result["active"] != true { + t.Fatalf("expected active true, got %v", result["active"]) + } + nameSlice := result["name"].([]any) + if len(nameSlice) != 1 { + t.Fatalf("expected 1 name entry, got %d", len(nameSlice)) + } + emailsSlice := result["emails"].([]any) + if len(emailsSlice) != 1 { + t.Fatalf("expected 1 email entry, got %d", len(emailsSlice)) + } + rolesSlice := result["roles"].([]any) + if len(rolesSlice) != 1 { + t.Fatalf("expected 1 role entry, got %d", len(rolesSlice)) + } + }) + + t.Run("handles nil ID", func(t *testing.T) { + user := &gh.SCIMEnterpriseUserAttributes{ + UserName: "bob", + DisplayName: "Bob Builder", + ExternalID: "ext-000", + Active: false, + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"}, + } + result := flattenEnterpriseSCIMUser(user) + if _, ok := result["id"]; ok { + t.Fatal("expected 'id' to be absent") + } + if result["user_name"] != "bob" { + t.Fatalf("expected user_name 'bob', got %v", result["user_name"]) + } + if result["active"] != false { + t.Fatalf("expected active false, got %v", result["active"]) + } + }) +} diff --git a/website/github.erb b/website/github.erb index 997536b42f..ba17cd4d49 100644 --- a/website/github.erb +++ b/website/github.erb @@ -100,6 +100,18 @@
  • github_enterprise
  • +
  • + github_enterprise_scim_group +
  • +
  • + github_enterprise_scim_groups +
  • +
  • + github_enterprise_scim_user +
  • +
  • + github_enterprise_scim_users +
  • github_external_groups
  • From 45a182137cc7f3d6d2999dbd1e0453d1b6ce80ea Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Mon, 23 Feb 2026 18:00:08 +0100 Subject: [PATCH 10/15] fix(enterprise-scim): update go-github import from v82 to v83 --- github/util_enterprise_scim.go | 2 +- github/util_enterprise_scim_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go index 0d9bfc5127..1f49406603 100644 --- a/github/util_enterprise_scim.go +++ b/github/util_enterprise_scim.go @@ -3,7 +3,7 @@ package github import ( "context" - gh "github.com/google/go-github/v82/github" + gh "github.com/google/go-github/v83/github" ) // enterpriseSCIMListAllGroups fetches all SCIM groups for an enterprise with automatic pagination. diff --git a/github/util_enterprise_scim_test.go b/github/util_enterprise_scim_test.go index 78cc4d9775..b3d5ae9550 100644 --- a/github/util_enterprise_scim_test.go +++ b/github/util_enterprise_scim_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - gh "github.com/google/go-github/v82/github" + gh "github.com/google/go-github/v83/github" ) func TestFlattenEnterpriseSCIMMeta(t *testing.T) { From 9c0ad3d2de8f787d1bb23c78e511db3af38cce4b Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Wed, 18 Mar 2026 20:57:32 +0100 Subject: [PATCH 11/15] fix: update go-github import to v84 after rebase --- github/util_enterprise_scim.go | 2 +- github/util_enterprise_scim_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go index 1f49406603..8fe6c3b4db 100644 --- a/github/util_enterprise_scim.go +++ b/github/util_enterprise_scim.go @@ -3,7 +3,7 @@ package github import ( "context" - gh "github.com/google/go-github/v83/github" + gh "github.com/google/go-github/v84/github" ) // enterpriseSCIMListAllGroups fetches all SCIM groups for an enterprise with automatic pagination. diff --git a/github/util_enterprise_scim_test.go b/github/util_enterprise_scim_test.go index b3d5ae9550..ecd051cd1a 100644 --- a/github/util_enterprise_scim_test.go +++ b/github/util_enterprise_scim_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - gh "github.com/google/go-github/v83/github" + gh "github.com/google/go-github/v84/github" ) func TestFlattenEnterpriseSCIMMeta(t *testing.T) { From 2c1658c65e922178112782077527df1bb5fb817d Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Fri, 20 Mar 2026 12:53:27 +0100 Subject: [PATCH 12/15] fix(enterprise-scim): replace "Github" with "GitHub" in page_title (#41) Closes #33 - website/docs/d/enterprise.html.markdown - website/docs/r/enterprise_organization.html.markdown The 4 files listed in the issue were already correct. These 2 additional files had the wrong capitalization in the enterprise-scim branch. --- website/docs/d/enterprise.html.markdown | 2 +- website/docs/r/enterprise_organization.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/d/enterprise.html.markdown b/website/docs/d/enterprise.html.markdown index 251e0ee951..b5f28119fe 100644 --- a/website/docs/d/enterprise.html.markdown +++ b/website/docs/d/enterprise.html.markdown @@ -1,6 +1,6 @@ --- layout: "github" -page_title: "Github: github_enterprise" +page_title: "GitHub: github_enterprise" description: |- Get an enterprise. --- diff --git a/website/docs/r/enterprise_organization.html.markdown b/website/docs/r/enterprise_organization.html.markdown index 94b39e94ee..1236ce8629 100644 --- a/website/docs/r/enterprise_organization.html.markdown +++ b/website/docs/r/enterprise_organization.html.markdown @@ -1,6 +1,6 @@ --- layout: "github" -page_title: "Github: github_enterprise_organization" +page_title: "GitHub: github_enterprise_organization" description: |- Create and manages a GitHub enterprise organization. --- From 0e0c26d37adf9aa1979b9448a799d1d0e88d5a9b Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Mon, 23 Mar 2026 16:49:42 +0100 Subject: [PATCH 13/15] fix(scim): use buildTwoPartID for data source IDs Replace fmt.Sprintf("%s/%s", ...) with buildTwoPartID() in both SCIM data sources, consistent with project convention. Separator changes from '/' to ':'. Remove unused 'fmt' import from both files. Closes #53 --- github/data_source_github_enterprise_scim_group.go | 3 +-- github/data_source_github_enterprise_scim_user.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/github/data_source_github_enterprise_scim_group.go b/github/data_source_github_enterprise_scim_group.go index 43f89c8a26..17b6507869 100644 --- a/github/data_source_github_enterprise_scim_group.go +++ b/github/data_source_github_enterprise_scim_group.go @@ -2,7 +2,6 @@ package github import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -39,7 +38,7 @@ func dataSourceGithubEnterpriseSCIMGroupRead(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - d.SetId(fmt.Sprintf("%s/%s", enterprise, scimGroupID)) + d.SetId(buildTwoPartID(enterprise, scimGroupID)) if err := d.Set("schemas", group.Schemas); err != nil { return diag.FromErr(err) diff --git a/github/data_source_github_enterprise_scim_user.go b/github/data_source_github_enterprise_scim_user.go index 1568a72937..f99850bc3a 100644 --- a/github/data_source_github_enterprise_scim_user.go +++ b/github/data_source_github_enterprise_scim_user.go @@ -2,7 +2,6 @@ package github import ( "context" - "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -39,7 +38,7 @@ func dataSourceGithubEnterpriseSCIMUserRead(ctx context.Context, d *schema.Resou return diag.FromErr(err) } - d.SetId(fmt.Sprintf("%s/%s", enterprise, scimUserID)) + d.SetId(buildTwoPartID(enterprise, scimUserID)) if err := d.Set("schemas", user.Schemas); err != nil { return diag.FromErr(err) From 453fde190c655df1771321d3383e2446c37604e1 Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Mon, 23 Mar 2026 17:16:37 +0100 Subject: [PATCH 14/15] refactor(scim): move enterpriseSCIMMetaSchema() to util_enterprise_scim.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure refactor — no functional change. The shared schema helper was defined in data_source_github_enterprise_scim_groups.go but referenced by both groups and users data sources. Moving it to the shared util file makes the codebase easier to navigate. Closes #54 --- ...ta_source_github_enterprise_scim_groups.go | 25 ------------------ github/util_enterprise_scim.go | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/github/data_source_github_enterprise_scim_groups.go b/github/data_source_github_enterprise_scim_groups.go index c9a544fe81..4ac7f9a3fb 100644 --- a/github/data_source_github_enterprise_scim_groups.go +++ b/github/data_source_github_enterprise_scim_groups.go @@ -120,31 +120,6 @@ func dataSourceGithubEnterpriseSCIMGroupsRead(ctx context.Context, d *schema.Res return nil } -func enterpriseSCIMMetaSchema() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "resource_type": { - Type: schema.TypeString, - Computed: true, - Description: "The SCIM resource type.", - }, - "created": { - Type: schema.TypeString, - Computed: true, - Description: "The creation timestamp.", - }, - "last_modified": { - Type: schema.TypeString, - Computed: true, - Description: "The lastModified timestamp.", - }, - "location": { - Type: schema.TypeString, - Computed: true, - Description: "The resource location.", - }, - } -} - func enterpriseSCIMGroupSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ "schemas": { diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go index 8fe6c3b4db..87567ad4e9 100644 --- a/github/util_enterprise_scim.go +++ b/github/util_enterprise_scim.go @@ -4,6 +4,7 @@ import ( "context" gh "github.com/google/go-github/v84/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) // enterpriseSCIMListAllGroups fetches all SCIM groups for an enterprise with automatic pagination. @@ -222,3 +223,28 @@ func flattenEnterpriseSCIMUser(user *gh.SCIMEnterpriseUserAttributes) map[string } return m } + +func enterpriseSCIMMetaSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "resource_type": { + Type: schema.TypeString, + Computed: true, + Description: "The SCIM resource type.", + }, + "created": { + Type: schema.TypeString, + Computed: true, + Description: "The creation timestamp.", + }, + "last_modified": { + Type: schema.TypeString, + Computed: true, + Description: "The lastModified timestamp.", + }, + "location": { + Type: schema.TypeString, + Computed: true, + Description: "The resource location.", + }, + } +} From 3e8d5a1a19caf95e5de4d642d03bbc1844b153e5 Mon Sep 17 00:00:00 2001 From: "Victor M. Varela" Date: Mon, 23 Mar 2026 20:24:53 +0100 Subject: [PATCH 15/15] chore(lint): exclude modernize newexpr rule for Ptr calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The modernize/newexpr rule incorrectly suggests replacing github.Ptr(x) with new(x), but new() takes a type not an expression — the auto-fix generates invalid Go code. Exclude this specific rule pattern. --- .golangci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 900dce093b..27dc87a4ed 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -47,6 +47,9 @@ linters: - linters: - staticcheck text: "SA1019.*ByID.*deprecated" + - linters: + - modernize + text: "newexpr: call of Ptr" issues: max-issues-per-linter: 0