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..3eb319f184 --- /dev/null +++ b/github/data_source_github_enterprise_scim_group.go @@ -0,0 +1,92 @@ +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 { + return &schema.Resource{ + Description: "Lookup SCIM provisioning information for a single GitHub enterprise group.", + ReadContext: dataSourceGithubEnterpriseSCIMGroupRead, + + Schema: map[string]*schema.Schema{ + "enterprise": { + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + }, + "scim_group_id": { + Description: "The SCIM group ID.", + Type: schema.TypeString, + Required: true, + }, + + "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 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)) + + _ = d.Set("schemas", group.Schemas) + if group.ID != nil { + _ = d.Set("id", *group.ID) + } + if group.ExternalID != nil { + _ = d.Set("external_id", *group.ExternalID) + } + if group.DisplayName != nil { + _ = d.Set("display_name", *group.DisplayName) + } + _ = d.Set("members", flattenEnterpriseSCIMGroupMembers(group.Members)) + _ = d.Set("meta", flattenEnterpriseSCIMMeta(group.Meta)) + + 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..0fc9513a7a --- /dev/null +++ b/github/data_source_github_enterprise_scim_group_test.go @@ -0,0 +1,35 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccGithubEnterpriseSCIMGroupDataSource(t *testing.T) { + 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.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: config, + Check: check, + }}, + }) +} 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..69e69d1baf --- /dev/null +++ b/github/data_source_github_enterprise_scim_groups.go @@ -0,0 +1,196 @@ +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) + + _ = d.Set("schemas", first.Schemas) + if first.TotalResults != nil { + _ = d.Set("total_results", *first.TotalResults) + } + if first.StartIndex != nil && *first.StartIndex > 0 { + _ = d.Set("start_index", *first.StartIndex) + } else { + _ = d.Set("start_index", 1) + } + if first.ItemsPerPage != nil && *first.ItemsPerPage > 0 { + _ = d.Set("items_per_page", *first.ItemsPerPage) + } else { + _ = d.Set("items_per_page", count) + } + if err := d.Set("resources", flat); err != nil { + return diag.FromErr(fmt.Errorf("error setting resources: %w", 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..4a82d47689 --- /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) { + 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.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: config, + Check: check, + }}, + }) +} 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..cea982b43b --- /dev/null +++ b/github/data_source_github_enterprise_scim_user.go @@ -0,0 +1,114 @@ +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 { + return &schema.Resource{ + Description: "Lookup SCIM provisioning information for a single GitHub enterprise user.", + ReadContext: dataSourceGithubEnterpriseSCIMUserRead, + + Schema: map[string]*schema.Schema{ + "enterprise": { + Description: "The enterprise slug.", + Type: schema.TypeString, + Required: true, + }, + "scim_user_id": { + Description: "The SCIM user ID.", + Type: schema.TypeString, + Required: true, + }, + + "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 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)) + + _ = d.Set("schemas", user.Schemas) + if user.ID != nil { + _ = d.Set("id", *user.ID) + } + _ = d.Set("external_id", user.ExternalID) + _ = d.Set("user_name", user.UserName) + _ = d.Set("display_name", user.DisplayName) + _ = d.Set("active", user.Active) + _ = d.Set("name", flattenEnterpriseSCIMUserName(user.Name)) + _ = d.Set("emails", flattenEnterpriseSCIMUserEmails(user.Emails)) + _ = d.Set("roles", flattenEnterpriseSCIMUserRoles(user.Roles)) + _ = d.Set("meta", flattenEnterpriseSCIMMeta(user.Meta)) + + 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..691f3f8689 --- /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) { + 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.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: config, + Check: check, + }}, + }) +} 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..0a05fd8d10 --- /dev/null +++ b/github/data_source_github_enterprise_scim_users.go @@ -0,0 +1,243 @@ +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) + + _ = d.Set("schemas", first.Schemas) + if first.TotalResults != nil { + _ = d.Set("total_results", *first.TotalResults) + } + if first.StartIndex != nil && *first.StartIndex > 0 { + _ = d.Set("start_index", *first.StartIndex) + } else { + _ = d.Set("start_index", 1) + } + if first.ItemsPerPage != nil && *first.ItemsPerPage > 0 { + _ = d.Set("items_per_page", *first.ItemsPerPage) + } else { + _ = d.Set("items_per_page", count) + } + if err := d.Set("resources", flat); err != nil { + return diag.FromErr(fmt.Errorf("error setting resources: %w", 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..d505629ce2 --- /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) { + 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.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{{ + Config: config, + Check: check, + }}, + }) +} diff --git a/github/provider.go b/github/provider.go index 480bcba3fb..86f413bf0d 100644 --- a/github/provider.go +++ b/github/provider.go @@ -289,6 +289,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..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 +} 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`.