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 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..17b6507869 --- /dev/null +++ b/github/data_source_github_enterprise_scim_group.go @@ -0,0 +1,69 @@ +package github + +import ( + "context" + + "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: "Retrieves 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(buildTwoPartID(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..fbf9c8e024 --- /dev/null +++ b/github/data_source_github_enterprise_scim_group_test.go @@ -0,0 +1,36 @@ +package github + +import ( + "fmt" + "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) { + 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), + 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 new file mode 100644 index 0000000000..4ac7f9a3fb --- /dev/null +++ b/github/data_source_github_enterprise_scim_groups.go @@ -0,0 +1,179 @@ +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: "Retrieves 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, + ValidateDiagFunc: validation.ToDiagFunc(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 _, group := range groups { + flat = append(flat, flattenEnterpriseSCIMGroup(group)) + } + + 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 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..c201504223 --- /dev/null +++ b/github/data_source_github_enterprise_scim_groups_test.go @@ -0,0 +1,32 @@ +package github + +import ( + "fmt" + "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) { + 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), + 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 new file mode 100644 index 0000000000..f99850bc3a --- /dev/null +++ b/github/data_source_github_enterprise_scim_user.go @@ -0,0 +1,77 @@ +package github + +import ( + "context" + + "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: "Retrieves 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(buildTwoPartID(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..9324090878 --- /dev/null +++ b/github/data_source_github_enterprise_scim_user_test.go @@ -0,0 +1,38 @@ +package github + +import ( + "fmt" + "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) { + 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), + 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 new file mode 100644 index 0000000000..26989cc690 --- /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: "Retrieves 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, + ValidateDiagFunc: validation.ToDiagFunc(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 _, user := range users { + flat = append(flat, flattenEnterpriseSCIMUser(user)) + } + + 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..a3b86ad704 --- /dev/null +++ b/github/data_source_github_enterprise_scim_users_test.go @@ -0,0 +1,32 @@ +package github + +import ( + "fmt" + "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) { + 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), + 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/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(), }, } diff --git a/github/util_enterprise_scim.go b/github/util_enterprise_scim.go new file mode 100644 index 0000000000..87567ad4e9 --- /dev/null +++ b/github/util_enterprise_scim.go @@ -0,0 +1,250 @@ +package github + +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. +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 _, member := range members { + item := map[string]any{ + "value": member.Value, + } + if member.Ref != nil { + item["ref"] = *member.Ref + } + if member.Display != nil { + item["display_name"] = *member.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 _, email := range emails { + out = append(out, map[string]any{ + "value": email.Value, + "type": email.Type, + "primary": email.Primary, + }) + } + return out +} + +func flattenEnterpriseSCIMUserRoles(roles []*gh.SCIMEnterpriseUserRole) []any { + out := make([]any, 0, len(roles)) + for _, role := range roles { + item := map[string]any{ + "value": role.Value, + } + if role.Display != nil { + item["display"] = *role.Display + } + if role.Type != nil { + item["type"] = *role.Type + } + if role.Primary != nil { + item["primary"] = *role.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 +} + +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.", + }, + } +} diff --git a/github/util_enterprise_scim_test.go b/github/util_enterprise_scim_test.go new file mode 100644 index 0000000000..ecd051cd1a --- /dev/null +++ b/github/util_enterprise_scim_test.go @@ -0,0 +1,376 @@ +package github + +import ( + "testing" + "time" + + gh "github.com/google/go-github/v84/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/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/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`. 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`. 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`. 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`. 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. --- 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