diff --git a/RESOURCES.md b/RESOURCES.md
index b1b1c3e2b0..4ed6c23bf1 100644
--- a/RESOURCES.md
+++ b/RESOURCES.md
@@ -113,6 +113,7 @@ The overall status of each resource or data source is captured in this document
| `github_rest_api` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
| `github_ssh_keys` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
| `github_team` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+| `github_team_external_groups` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_team_members` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_team_repositories` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_tree` | ⚠️ | ✅ | ❓ | ❓ | ❓ | ❓ |
diff --git a/docs/data-sources/team_external_groups.md b/docs/data-sources/team_external_groups.md
new file mode 100644
index 0000000000..6257ce4a77
--- /dev/null
+++ b/docs/data-sources/team_external_groups.md
@@ -0,0 +1,60 @@
+---
+page_title: "github_team_external_groups (Data Source) - GitHub"
+subcategory: ""
+description: |-
+ Data source to retrieve external groups for a specific GitHub team.
+---
+
+# github_team_external_groups (Data Source)
+
+Data source to retrieve external groups for a specific GitHub team.
+
+## Example Usage
+
+```terraform
+data "github_team_external_groups" "example" {
+ slug = "example"
+}
+```
+
+
+
+## Schema
+
+### Required
+
+- `slug` (String) The slug of the GitHub team.
+
+### Read-Only
+
+- `external_groups` (List of Object) List of external groups connected to the team. (see [below for nested schema](#nestedatt--external_groups))
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `external_groups`
+
+Read-Only:
+
+- `group_id` (Number) The internal ID of the group.
+- `group_name` (String) The display name of the group.
+- `updated_at` (String) The time of the last update for this group.
diff --git a/examples/data-sources/github_team_external_groups/data-source-1.tf b/examples/data-sources/github_team_external_groups/data-source-1.tf
new file mode 100644
index 0000000000..508de945f7
--- /dev/null
+++ b/examples/data-sources/github_team_external_groups/data-source-1.tf
@@ -0,0 +1,3 @@
+data "github_team_external_groups" "example" {
+ slug = "example"
+}
diff --git a/github/acc_helpers_team_test.go b/github/acc_helpers_team_test.go
index 6d80df3aa2..575c03ebd6 100644
--- a/github/acc_helpers_team_test.go
+++ b/github/acc_helpers_team_test.go
@@ -124,3 +124,31 @@ func mustAddRepositoryToTeam(t *testing.T, team *github.Team, repo *github.Repos
t.Fatalf("failed to add team %s to test repository %s: %v", team.GetName(), repo.GetName(), err)
}
}
+
+func mustRemoveAllMembersFromTeam(t *testing.T, team *github.Team) {
+ t.Helper()
+
+ members, _, err := testAccConf.meta.v3client.Teams.ListTeamMembersBySlug(t.Context(), testAccConf.meta.name, team.GetSlug(), &github.TeamListTeamMembersOptions{})
+ if err != nil {
+ t.Fatalf("failed to list members of test team %s: %v", team.GetName(), err)
+ }
+
+ for _, member := range members {
+ if _, err := testAccConf.meta.v3client.Teams.RemoveTeamMembershipBySlug(t.Context(), testAccConf.meta.name, team.GetSlug(), member.GetLogin()); err != nil {
+ t.Fatalf("failed to remove member %s from test team %s: %v", member.GetLogin(), team.GetName(), err)
+ }
+ }
+}
+
+func mustConnectTeamToExternalGroup(t *testing.T, team *github.Team, externalGroupID int64) {
+ t.Helper()
+
+ mustRemoveAllMembersFromTeam(t, team)
+
+ _, _, err := testAccConf.meta.v3client.Teams.UpdateConnectedExternalGroup(t.Context(), testAccConf.meta.name, team.GetSlug(), github.UpdateConnectedExternalGroupRequest{
+ GroupID: externalGroupID,
+ })
+ if err != nil {
+ t.Fatalf("failed to connect team %s to external group %d: %v", team.GetName(), externalGroupID, err)
+ }
+}
diff --git a/github/acc_test.go b/github/acc_test.go
index 4b0729473b..ffed281a05 100644
--- a/github/acc_test.go
+++ b/github/acc_test.go
@@ -145,6 +145,10 @@ func TestMain(m *testing.M) {
testRepositoryVisibility: "public",
}
+ if i, err := strconv.Atoi(os.Getenv("GH_TEST_EXTERNAL_GROUP1_ID")); err == nil {
+ conf.testExternalGroup1ID = i
+ }
+
if conf.authMode != anonymous {
conf.owner = os.Getenv("GITHUB_OWNER")
conf.token = os.Getenv("GITHUB_TOKEN")
diff --git a/github/data_source_github_team_external_groups.go b/github/data_source_github_team_external_groups.go
new file mode 100644
index 0000000000..b745cee413
--- /dev/null
+++ b/github/data_source_github_team_external_groups.go
@@ -0,0 +1,103 @@
+package github
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/google/go-github/v92/github"
+ "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 dataSourceGithubTeamExternalGroups() *schema.Resource {
+ return &schema.Resource{
+ Description: "Data source to retrieve external groups for a specific GitHub team.",
+ ReadContext: dataSourceGithubTeamExternalGroupsRead,
+ Schema: map[string]*schema.Schema{
+ "slug": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The slug of the GitHub team.",
+ ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty),
+ },
+ "external_groups": {
+ Description: "List of external groups connected to the team.",
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "group_id": {
+ Description: "The internal ID of the group.",
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+ "group_name": {
+ Description: "The display name of the group.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "updated_at": {
+ Description: "The time of the last update for this group.",
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dataSourceGithubTeamExternalGroupsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
+ meta, _ := m.(*Owner)
+
+ if ok, diags := checkOrganizationOK(meta); !ok {
+ return diags
+ }
+
+ orgName := meta.name
+ slug, ok := d.Get("slug").(string)
+ if !ok {
+ return diag.Errorf("expected type of %s to be string", d.Get("slug"))
+ }
+
+ externalGroups, _, err := meta.v3client.Teams.ListExternalGroupsForTeamBySlug(ctx, orgName, slug)
+ if err != nil {
+ // Teams with explicit members cannot be externally managed, so they have no external groups.
+ var ghErr *github.ErrorResponse
+ if errors.As(err, &ghErr) && ghErr.Response != nil &&
+ ghErr.Response.StatusCode == http.StatusBadRequest &&
+ strings.Contains(ghErr.Message, "has explicit members") {
+ externalGroups = &github.ExternalGroupList{}
+ } else {
+ return diag.FromErr(err)
+ }
+ }
+
+ groups := make([]map[string]any, 0)
+ for _, group := range externalGroups.Groups {
+ g := map[string]any{
+ "group_id": group.GetGroupID(),
+ "group_name": group.GetGroupName(),
+ "updated_at": group.GetUpdatedAt().Format(time.RFC3339),
+ }
+
+ groups = append(groups, g)
+ }
+
+ id, err := buildID(orgName, slug)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ d.SetId(id)
+
+ if err := d.Set("external_groups", groups); err != nil {
+ return diag.Errorf("error setting external_groups: %v", err)
+ }
+
+ return nil
+}
diff --git a/github/data_source_github_team_external_groups_test.go b/github/data_source_github_team_external_groups_test.go
new file mode 100644
index 0000000000..0e86eef0a4
--- /dev/null
+++ b/github/data_source_github_team_external_groups_test.go
@@ -0,0 +1,128 @@
+package github
+
+import (
+ "fmt"
+ "regexp"
+ "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 TestAccGithubTeamExternalGroupsDataSource(t *testing.T) {
+ t.Parallel()
+
+ skipUnlessEMUEnterprise(t)
+
+ t.Run("queries_empty_slug", func(t *testing.T) {
+ config := `
+ data "github_team_external_groups" "test" {
+ slug = ""
+ }
+ `
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ExpectError: regexp.MustCompile(`expected "slug" to not be an empty string`),
+ },
+ },
+ })
+ })
+
+ t.Run("queries_non_existent_team", func(t *testing.T) {
+ config := `
+ data "github_team_external_groups" "test" {
+ slug = "non-existing-team-slug"
+ }
+ `
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ExpectError: regexp.MustCompile(`Not Found`),
+ },
+ },
+ })
+ })
+
+ t.Run("queries_team_without_external_groups", func(t *testing.T) {
+ team := mustCreateTestTeam(t)
+ config := fmt.Sprintf(`
+ data "github_team_external_groups" "test" {
+ slug = "%s"
+ }
+ `, team.GetSlug())
+
+ mustRemoveAllMembersFromTeam(t, team)
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_team_external_groups.test", tfjsonpath.New("external_groups"), knownvalue.ListSizeExact(0)),
+ },
+ },
+ },
+ })
+ })
+
+ t.Run("queries_team_with_regular_membership", func(t *testing.T) {
+ team := mustCreateTestTeam(t)
+ config := fmt.Sprintf(`
+ data "github_team_external_groups" "test" {
+ slug = "%s"
+ }
+ `, team.GetSlug())
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_team_external_groups.test", tfjsonpath.New("external_groups"), knownvalue.ListSizeExact(0)),
+ },
+ },
+ },
+ })
+ })
+
+ t.Run("queries_team_with_external_groups", func(t *testing.T) {
+ if testAccConf.testExternalGroup1ID == 0 {
+ t.Skip("Skipping as no external groups are configured for the test organization")
+ }
+ team := mustCreateTestTeam(t)
+ groupID := int64(testAccConf.testExternalGroup1ID)
+ mustConnectTeamToExternalGroup(t, team, groupID)
+ config := fmt.Sprintf(`
+ data "github_team_external_groups" "test" {
+ slug = "%s"
+ }
+ `, team.GetSlug())
+
+ resource.Test(t, resource.TestCase{
+ ProviderFactories: providerFactories,
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ ConfigStateChecks: []statecheck.StateCheck{
+ statecheck.ExpectKnownValue("data.github_team_external_groups.test", tfjsonpath.New("external_groups"), knownvalue.ListExact([]knownvalue.Check{
+ knownvalue.MapPartial(map[string]knownvalue.Check{
+ "group_id": knownvalue.Int64Exact(groupID),
+ }),
+ })),
+ },
+ },
+ },
+ })
+ })
+}
diff --git a/github/provider.go b/github/provider.go
index c391d8454c..d5a0d4514c 100644
--- a/github/provider.go
+++ b/github/provider.go
@@ -336,6 +336,7 @@ func NewProvider(version, commit string) func() *schema.Provider {
"github_team": dataSourceGithubTeam(),
"github_team_members": dataSourceGithubTeamMembers(),
"github_team_repositories": dataSourceGithubTeamRepositories(),
+ "github_team_external_groups": dataSourceGithubTeamExternalGroups(),
"github_tree": dataSourceGithubTree(),
"github_user": dataSourceGithubUser(),
"github_user_external_identity": dataSourceGithubUserExternalIdentity(),
diff --git a/templates/data-sources/team_external_groups.md.tmpl b/templates/data-sources/team_external_groups.md.tmpl
new file mode 100644
index 0000000000..1dbebd3e37
--- /dev/null
+++ b/templates/data-sources/team_external_groups.md.tmpl
@@ -0,0 +1,48 @@
+---
+page_title: "{{.Name}} ({{.Type}}) - {{.RenderedProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | trimspace }}
+
+{{ if .HasExamples -}}
+## Example Usage
+
+{{- range .ExampleFiles }}
+
+{{ tffile . }}
+{{- end }}
+{{- end }}
+
+" }}
+{{ $line }}
+{{- end }}
+{{- end }}
+-->
+
+## Schema
+
+### Required
+
+- `slug` (String) The slug of the GitHub team.
+
+### Read-Only
+
+- `external_groups` (List of Object) List of external groups connected to the team. (see [below for nested schema](#nestedatt--external_groups))
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `external_groups`
+
+Read-Only:
+
+- `group_id` (Number) The internal ID of the group.
+- `group_name` (String) The display name of the group.
+- `updated_at` (String) The time of the last update for this group.