From 72c31545ed8fe05d6cf84ca9613c2a218f06e1b0 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 22 Feb 2026 21:31:21 +0200 Subject: [PATCH 01/13] Update docs Signed-off-by: Timo Sand --- website/docs/r/team_settings.html.markdown | 28 ++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/website/docs/r/team_settings.html.markdown b/website/docs/r/team_settings.html.markdown index 318062904e..11dd23cdfb 100644 --- a/website/docs/r/team_settings.html.markdown +++ b/website/docs/r/team_settings.html.markdown @@ -11,7 +11,7 @@ This resource manages the team settings (in particular the request review delega Creating this resource will alter the team Code Review settings. -The team must both belong to the same organization configured in the provider on GitHub. +The team must both belong to the same organization configured in the provider on GitHub. ~> **Note**: This resource relies on the v4 GraphQl GitHub API. If this API is not available, or the Stone Crop schema preview is not available, then this resource will not work as intended. @@ -38,26 +38,34 @@ resource "github_team_settings" "code_review_settings" { The following arguments are supported: -* `team_id` - (Required) The GitHub team id or the GitHub team slug -* `review_request_delegation` - (Optional) The settings for delegating code reviews to individuals on behalf of the team. If this block is present, even without any fields, then review request delegation will be enabled for the team. See [GitHub Review Request Delegation](#github-review-request-delegation-configuration) below for details. See [GitHub's documentation](https://docs.github.com/en/organizations/organizing-members-into-teams/managing-code-review-settings-for-your-team#configuring-team-notifications) for more configuration details. +- `team_id` - (Required) The GitHub team id or the GitHub team slug +- `review_request_delegation` - (Optional) The settings for delegating code reviews to individuals on behalf of the team. If this block is present, even without any fields, then review request delegation will be enabled for the team. See [GitHub Review Request Delegation](#github-review-request-delegation-configuration) below for details. See [GitHub's documentation](https://docs.github.com/en/organizations/organizing-members-into-teams/managing-code-review-settings-for-your-team#configuring-team-notifications) for more configuration details. ### GitHub Review Request Delegation Configuration The following arguments are supported: -* `algorithm` - (Optional) The algorithm to use when assigning pull requests to team members. Supported values are `ROUND_ROBIN` and `LOAD_BALANCE`. Default value is `ROUND_ROBIN` -* `member_count` - (Optional) The number of team members to assign to a pull request -* `notify` - (Optional) whether to notify the entire team when at least one member is also assigned to the pull request +- `algorithm` - (Optional) The algorithm to use when assigning pull requests to team members. Supported values are `ROUND_ROBIN` and `LOAD_BALANCE`. Default value is `ROUND_ROBIN` +- `member_count` - (Optional) The number of team members to assign to a pull request +- `notify` - (Optional) whether to notify the entire team when at least one member is also assigned to the pull request +## Attributes Reference + +The following additional attributes are exported: + +- `team_slug` - The slug of the Team. +- `team_uid` - The unique node ID of the Team on GitHub. Corresponds to the ID of the `github_team_settings` resource. ## Import GitHub Teams can be imported using the GitHub team ID, or the team slug e.g. +```text +terraform import github_team_settings.code_review_settings 1234567 ``` -$ terraform import github_team.code_review_settings 1234567 -``` + or, + +```text +terraform import github_team_settings.code_review_settings SomeTeam ``` -$ terraform import github_team_settings.code_review_settings SomeTeam -``` \ No newline at end of file From fd3365733d9db50b23e79d4a9bbddbfa4ac252e5 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 10:11:10 +0200 Subject: [PATCH 02/13] Update tests and add missing coverage Signed-off-by: Timo Sand --- github/resource_github_team_settings_test.go | 220 +++++++++++++++++-- 1 file changed, 202 insertions(+), 18 deletions(-) diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index fb3276b198..1548a8e893 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -1,6 +1,7 @@ package github import ( + "context" "fmt" "regexp" "strings" @@ -8,6 +9,10 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "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/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/shurcooL/githubv4" ) @@ -29,20 +34,27 @@ func TestAccGithubTeamSettings(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, Steps: []resource.TestStep{ { Config: config, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("github_team_settings.test", "team_id"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("team_id"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("team_slug"), knownvalue.NotNull()), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("team_uid"), knownvalue.NotNull()), + }, }, { Config: strings.Replace(config, `github_team.test.id`, `github_team.test.slug`, 1), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("github_team_settings.test", "team_id"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("team_id"), knownvalue.NotNull()), + }, }, }, }) @@ -71,30 +83,39 @@ func TestAccGithubTeamSettings(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, Steps: []resource.TestStep{ { Config: fmt.Sprintf(config, teamName, testAlgorithm, 1, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_team_settings.test", "review_request_delegation.0.algorithm", string(testAlgorithm)), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact(string(testAlgorithm))), + }, }, { Config: fmt.Sprintf(config, teamName, githubv4.TeamReviewAssignmentAlgorithmLoadBalance, 1, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_team_settings.test", "review_request_delegation.0.algorithm", string(githubv4.TeamReviewAssignmentAlgorithmLoadBalance)), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact(string(githubv4.TeamReviewAssignmentAlgorithmLoadBalance))), + }, }, { Config: fmt.Sprintf(config, teamName, testAlgorithm, 3, true), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_team_settings.test", "review_request_delegation.0.member_count", "3"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(3)), + }, }, { Config: fmt.Sprintf(config, teamName, testAlgorithm, 3, false), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("github_team_settings.test", "review_request_delegation.0.notify", "false"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(false)), + }, }, }, }) @@ -122,6 +143,7 @@ func TestAccGithubTeamSettings(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, Steps: []resource.TestStep{ { Config: config, @@ -130,4 +152,166 @@ func TestAccGithubTeamSettings(t *testing.T) { }, }) }) + + t.Run("manages removing review request delegation", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + configWithDelegation := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + review_request_delegation { + algorithm = "ROUND_ROBIN" + member_count = 1 + notify = true + } + } + `, teamName) + + configWithoutDelegation := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: configWithDelegation, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + }, + }, + { + Config: configWithoutDelegation, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation"), + knownvalue.ListExact([]knownvalue.Check{})), + }, + }, + { + Config: configWithDelegation, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + }, + }, + }, + }) + }) + + t.Run("creates_with_empty_review_request_delegation_block_without_error", func(t *testing.T) { + t.Skip("TODO this isn't working as expected") + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + review_request_delegation {} + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), knownvalue.StringExact(string(githubv4.TeamReviewAssignmentAlgorithmRoundRobin))), + statecheck.ExpectKnownValue("github_team_settings.test", tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), knownvalue.Bool(false)), + }, + }, + }, + }) + }) + t.Run("validates_member_count_greater_than_0", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + review_request_delegation { + member_count = 0 + } + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`expected member_count to be at least (1), got 0`), + }, + }, + }) + }) +} + +func testAccCheckGithubTeamSettingsDestroy(s *terraform.State) error { + meta, err := getTestMeta() + if err != nil { + return err + } + graphql := meta.v4client + orgName := meta.name + + for _, rs := range s.RootModule().Resources { + if rs.Type != "github_team_settings" { + continue + } + + teamSlug := rs.Primary.Attributes["team_slug"] + if teamSlug == "" { + continue + } + + query := queryTeamSettings{} + variables := map[string]any{ + "slug": githubv4.String(teamSlug), + "login": githubv4.String(orgName), + } + + err := graphql.Query(context.Background(), &query, variables) + if err != nil { + // Team itself may have been destroyed, which is fine + continue + } + + if query.Organization.Team.ReviewRequestDelegation { + return fmt.Errorf("team %s still has review request delegation enabled", teamSlug) + } + } + return nil } From e9fd5be3a4ee5bca55a67527250b789792e0abc3 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 11:58:29 +0200 Subject: [PATCH 03/13] Add more tests to cover `Import` and a few more `Update` cases Signed-off-by: Timo Sand --- github/resource_github_team_settings_test.go | 260 ++++++++++++++++++- 1 file changed, 259 insertions(+), 1 deletion(-) diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index 1548a8e893..86f511eee7 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/hashicorp/terraform-plugin-testing/compare" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" @@ -272,7 +273,264 @@ func TestAccGithubTeamSettings(t *testing.T) { Steps: []resource.TestStep{ { Config: config, - ExpectError: regexp.MustCompile(`expected member_count to be at least (1), got 0`), + ExpectError: regexp.MustCompile(`expected member_count to be at least \(1\), got 0`), + }, + }, + }) + }) + + t.Run("imports_team_settings_without_delegation_using_team_id", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs("github_team_settings.test", tfjsonpath.New("team_id"), "github_team.test", tfjsonpath.New("id"), compare.ValuesSame()), + }, + }, + { + ResourceName: "github_team_settings.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources["github_team.test"] + if !ok { + return "", fmt.Errorf("not found: github_team.test") + } + return rs.Primary.ID, nil + }, + }, + }, + }) + }) + t.Run("imports_team_settings_without_delegation_using_team_slug", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = github_team.test.slug + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs("github_team_settings.test", tfjsonpath.New("team_id"), "github_team.test", tfjsonpath.New("slug"), compare.ValuesSame()), + }, + }, + { + ResourceName: "github_team_settings.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources["github_team.test"] + if !ok { + return "", fmt.Errorf("not found: github_team.test") + } + return rs.Primary.Attributes["slug"], nil + }, + }, + }, + }) + }) + + t.Run("imports team settings with delegation", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = github_team.test.id + review_request_delegation { + algorithm = "ROUND_ROBIN" + member_count = 2 + notify = true + } + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(2)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(true)), + }, + }, + { + ResourceName: "github_team_settings.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources["github_team.test"] + if !ok { + return "", fmt.Errorf("not found: github_team.test") + } + return rs.Primary.Attributes["id"], nil + }, + }, + }, + }) + }) + + t.Run("manages adding review request delegation to existing team settings", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + configWithoutDelegation := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + } + `, teamName) + + configWithDelegation := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + review_request_delegation { + algorithm = "LOAD_BALANCE" + member_count = 2 + notify = true + } + } + `, teamName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: configWithoutDelegation, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation"), + knownvalue.ListExact([]knownvalue.Check{})), + }, + }, + { + Config: configWithDelegation, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("LOAD_BALANCE")), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(2)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(true)), + }, + }, + }, + }) + }) + + t.Run("manages updating only notify field", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := ` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + review_request_delegation { + algorithm = "ROUND_ROBIN" + member_count = 1 + notify = %t + } + } + ` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(config, teamName, true), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(1)), + }, + }, + { + Config: fmt.Sprintf(config, teamName, false), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(false)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(1)), + }, }, }, }) From f64a3fb605bf1a080077ed1527bdc8a4812993bd Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:03:18 +0200 Subject: [PATCH 04/13] Refactor `Import` to be Context-aware Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 505b45053e..e2a71c1590 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -17,7 +17,7 @@ func resourceGithubTeamSettings() *schema.Resource { Update: resourceGithubTeamSettingsUpdate, Delete: resourceGithubTeamSettingsDelete, Importer: &schema.ResourceImporter{ - State: resourceGithubTeamSettingsImport, + StateContext: resourceGithubTeamSettingsImport, }, Schema: map[string]*schema.Schema{ "team_id": { @@ -186,8 +186,8 @@ func resourceGithubTeamSettingsDelete(d *schema.ResourceData, meta any) error { return graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) } -func resourceGithubTeamSettingsImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - nodeId, slug, err := resolveTeamIDs(d.Id(), meta.(*Owner), context.Background()) +func resourceGithubTeamSettingsImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + nodeId, slug, err := resolveTeamIDs(d.Id(), meta.(*Owner), ctx) if err != nil { return nil, err } @@ -201,7 +201,7 @@ func resourceGithubTeamSettingsImport(d *schema.ResourceData, meta any) ([]*sche if err = d.Set("team_uid", nodeId); err != nil { return nil, err } - return []*schema.ResourceData{d}, resourceGithubTeamSettingsRead(d, meta) + return []*schema.ResourceData{d}, nil } func resolveTeamIDs(idOrSlug string, meta *Owner, ctx context.Context) (nodeId, slug string, err error) { From d961c7cc2dcbdcca8a57e3590277a33ff6790656 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:17:38 +0200 Subject: [PATCH 05/13] Refactor to use Context-aware functions Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 77 ++++++++++++++----------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index e2a71c1590..235fdf9ecb 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -5,6 +5,7 @@ import ( "errors" "strconv" + "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" "github.com/shurcooL/githubv4" @@ -12,10 +13,10 @@ import ( func resourceGithubTeamSettings() *schema.Resource { return &schema.Resource{ - Create: resourceGithubTeamSettingsCreate, - Read: resourceGithubTeamSettingsRead, - Update: resourceGithubTeamSettingsUpdate, - Delete: resourceGithubTeamSettingsDelete, + CreateContext: resourceGithubTeamSettingsCreate, + ReadContext: resourceGithubTeamSettingsRead, + UpdateContext: resourceGithubTeamSettingsUpdate, + DeleteContext: resourceGithubTeamSettingsDelete, Importer: &schema.ResourceImporter{ StateContext: resourceGithubTeamSettingsImport, }, @@ -72,36 +73,32 @@ func resourceGithubTeamSettings() *schema.Resource { } } -func resourceGithubTeamSettingsCreate(d *schema.ResourceData, meta any) error { - err := checkOrganization(meta) - if err != nil { - return err +func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) } + teamIDString := d.Get("team_id").(string) + // Given a string that is either a team id or team slug, return the // get the basic details of the team including node_id and slug - ctx := context.Background() - - teamIDString, _ := d.Get("team_id").(string) - nodeId, slug, err := resolveTeamIDs(teamIDString, meta.(*Owner), ctx) if err != nil { - return err + return diag.FromErr(err) } d.SetId(nodeId) if err = d.Set("team_slug", slug); err != nil { - return err + return diag.FromErr(err) } if err = d.Set("team_uid", nodeId); err != nil { - return err + return diag.FromErr(err) } - return resourceGithubTeamSettingsUpdate(d, meta) + return resourceGithubTeamSettingsUpdate(ctx, d, meta) } -func resourceGithubTeamSettingsRead(d *schema.ResourceData, meta any) error { - err := checkOrganization(meta) - if err != nil { - return err +func resourceGithubTeamSettingsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + if err := checkOrganization(meta); err != nil { + return diag.FromErr(err) } graphql := meta.(*Owner).v4client @@ -115,9 +112,9 @@ func resourceGithubTeamSettingsRead(d *schema.ResourceData, meta any) error { "login": githubv4.String(orgName), } - e := graphql.Query(meta.(*Owner).StopContext, &query, variables) - if e != nil { - return e + err := graphql.Query(ctx, &query, variables) + if err != nil { + return diag.FromErr(err) } if query.Organization.Team.ReviewRequestDelegation { @@ -126,21 +123,19 @@ func resourceGithubTeamSettingsRead(d *schema.ResourceData, meta any) error { reviewRequestDelegation["member_count"] = query.Organization.Team.ReviewRequestDelegationCount reviewRequestDelegation["notify"] = query.Organization.Team.ReviewRequestDelegationNotifyAll if err = d.Set("review_request_delegation", []any{reviewRequestDelegation}); err != nil { - return err + return diag.FromErr(err) } } else { if err = d.Set("review_request_delegation", []any{}); err != nil { - return err + return diag.FromErr(err) } } return nil } -func resourceGithubTeamSettingsUpdate(d *schema.ResourceData, meta any) error { +func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { if d.HasChange("review_request_delegation") || d.IsNewResource() { - - ctx := context.WithValue(context.Background(), ctxId, d.Id()) graphql := meta.(*Owner).v4client if setting := d.Get("review_request_delegation").([]any); len(setting) == 0 { var mutation struct { @@ -149,7 +144,11 @@ func resourceGithubTeamSettingsUpdate(d *schema.ResourceData, meta any) error { } `graphql:"updateTeamReviewAssignment(input:$input)"` } - return graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + if err != nil { + return diag.FromErr(err) + } + return nil } else { settings := d.Get("review_request_delegation").([]any)[0].(map[string]any) @@ -160,21 +159,25 @@ func resourceGithubTeamSettingsUpdate(d *schema.ResourceData, meta any) error { } teamReviewAlgorithm := githubv4.TeamReviewAssignmentAlgorithm(settings["algorithm"].(string)) - return graphql.Mutate(ctx, &mutation, githubv4.UpdateTeamReviewAssignmentInput{ + updateTeamReviewAssignmentInput := githubv4.UpdateTeamReviewAssignmentInput{ ID: d.Id(), Enabled: githubv4.Boolean(true), Algorithm: &teamReviewAlgorithm, TeamMemberCount: new(githubv4.Int(settings["member_count"].(int))), NotifyTeam: new(githubv4.Boolean(settings["notify"].(bool))), - }, nil) + } + err := graphql.Mutate(ctx, &mutation, updateTeamReviewAssignmentInput, nil) + if err != nil { + return diag.FromErr(err) + } + return nil } } - return resourceGithubTeamSettingsRead(d, meta) + return resourceGithubTeamSettingsRead(ctx, d, meta) } -func resourceGithubTeamSettingsDelete(d *schema.ResourceData, meta any) error { - ctx := context.WithValue(context.Background(), ctxId, d.Id()) +func resourceGithubTeamSettingsDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { graphql := meta.(*Owner).v4client var mutation struct { @@ -183,7 +186,11 @@ func resourceGithubTeamSettingsDelete(d *schema.ResourceData, meta any) error { } `graphql:"updateTeamReviewAssignment(input:$input)"` } - return graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + if err != nil { + return diag.FromErr(err) + } + return nil } func resourceGithubTeamSettingsImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { From ed80f2b3f65df56e0081ed05c212573fd91b07d6 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:23:41 +0200 Subject: [PATCH 06/13] Use `resource.ParallelTest` to make testing quicker Signed-off-by: Timo Sand --- github/resource_github_team_settings_test.go | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index 86f511eee7..901ea865ab 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -32,7 +32,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -81,7 +81,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -141,7 +141,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -185,7 +185,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -234,7 +234,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -266,7 +266,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -294,7 +294,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -335,7 +335,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -382,7 +382,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -448,7 +448,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } `, teamName) - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, @@ -499,7 +499,7 @@ func TestAccGithubTeamSettings(t *testing.T) { } ` - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { skipUnlessHasOrgs(t) }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckGithubTeamSettingsDestroy, From 6db1fc26de15509f7831393148d324b67656f759 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:24:00 +0200 Subject: [PATCH 07/13] Remove unused call to `Read` Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 235fdf9ecb..64308d05a6 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -174,7 +174,7 @@ func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceDat } } - return resourceGithubTeamSettingsRead(ctx, d, meta) + return nil } func resourceGithubTeamSettingsDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { From 5a705474aa03434f771a1816bb7fb3a0fb016a77 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:39:17 +0200 Subject: [PATCH 08/13] Uncouple `Create` and `Update` Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 89 +++++++++++++++++++------ 1 file changed, 67 insertions(+), 22 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 64308d05a6..a455066099 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -5,6 +5,7 @@ import ( "errors" "strconv" + "github.com/hashicorp/terraform-plugin-log/tflog" "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" @@ -73,19 +74,28 @@ func resourceGithubTeamSettings() *schema.Resource { } } -func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - if err := checkOrganization(meta); err != nil { +func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + meta := m.(*Owner) + if err := checkOrganization(m); err != nil { return diag.FromErr(err) } + graphql := meta.v4client teamIDString := d.Get("team_id").(string) + tflog.Debug(ctx, "Resolving team_id to Team node_id and slug", map[string]any{ + "team_id": teamIDString, + }) // Given a string that is either a team id or team slug, return the // get the basic details of the team including node_id and slug - nodeId, slug, err := resolveTeamIDs(teamIDString, meta.(*Owner), ctx) + nodeId, slug, err := resolveTeamIDs(teamIDString, meta, ctx) if err != nil { return diag.FromErr(err) } + tflog.Trace(ctx, "Resolved team_id to Team node_id and slug", map[string]any{ + "node_id": nodeId, + "slug": slug, + }) d.SetId(nodeId) if err = d.Set("team_slug", slug); err != nil { return diag.FromErr(err) @@ -93,7 +103,42 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat if err = d.Set("team_uid", nodeId); err != nil { return diag.FromErr(err) } - return resourceGithubTeamSettingsUpdate(ctx, d, meta) + + reviewRequestDelegation := d.Get("review_request_delegation").([]any) + + var mutation struct { + UpdateTeamReviewAssignment struct { + ClientMutationId githubv4.ID `graphql:"clientMutationId"` + } `graphql:"updateTeamReviewAssignment(input:$input)"` + } + + if len(reviewRequestDelegation) == 0 { + tflog.Debug(ctx, "No review request delegation settings provided, disabling review request delegation", map[string]any{ + "team_id": d.Id(), + "team_slug": slug, + }) + + err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + if err != nil { + return diag.FromErr(err) + } + } else { + settings := reviewRequestDelegation[0].(map[string]any) + + teamReviewAlgorithm := githubv4.TeamReviewAssignmentAlgorithm(settings["algorithm"].(string)) + updateTeamReviewAssignmentInput := githubv4.UpdateTeamReviewAssignmentInput{ + ID: d.Id(), + Enabled: githubv4.Boolean(true), + Algorithm: &teamReviewAlgorithm, + TeamMemberCount: githubv4.NewInt(githubv4.Int(settings["member_count"].(int))), + NotifyTeam: githubv4.NewBoolean(githubv4.Boolean(settings["notify"].(bool))), + } + err := graphql.Mutate(ctx, &mutation, updateTeamReviewAssignmentInput, nil) + if err != nil { + return diag.FromErr(err) + } + } + return nil } func resourceGithubTeamSettingsRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { @@ -134,29 +179,30 @@ func resourceGithubTeamSettingsRead(ctx context.Context, d *schema.ResourceData, return nil } -func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - if d.HasChange("review_request_delegation") || d.IsNewResource() { - graphql := meta.(*Owner).v4client - if setting := d.Get("review_request_delegation").([]any); len(setting) == 0 { - var mutation struct { - UpdateTeamReviewAssignment struct { - ClientMutationId githubv4.ID `graphql:"clientMutationId"` - } `graphql:"updateTeamReviewAssignment(input:$input)"` - } +func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + if d.HasChange("review_request_delegation") { + meta := m.(*Owner) + graphql := meta.v4client + reviewRequestDelegation := d.Get("review_request_delegation").([]any) + + var mutation struct { + UpdateTeamReviewAssignment struct { + ClientMutationId githubv4.ID `graphql:"clientMutationId"` + } `graphql:"updateTeamReviewAssignment(input:$input)"` + } + + if len(reviewRequestDelegation) == 0 { + tflog.Debug(ctx, "No review request delegation settings provided, disabling review request delegation", map[string]any{ + "team_id": d.Id(), + "team_slug": d.Get("team_slug").(string), + }) err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) if err != nil { return diag.FromErr(err) } - return nil } else { - settings := d.Get("review_request_delegation").([]any)[0].(map[string]any) - - var mutation struct { - UpdateTeamReviewAssignment struct { - ClientMutationId githubv4.ID `graphql:"clientMutationId"` - } `graphql:"updateTeamReviewAssignment(input:$input)"` - } + settings := reviewRequestDelegation[0].(map[string]any) teamReviewAlgorithm := githubv4.TeamReviewAssignmentAlgorithm(settings["algorithm"].(string)) updateTeamReviewAssignmentInput := githubv4.UpdateTeamReviewAssignmentInput{ @@ -170,7 +216,6 @@ func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceDat if err != nil { return diag.FromErr(err) } - return nil } } From fdf38118c5ffc3257dff44e862d06e1dbc30c0fc Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 12:49:33 +0200 Subject: [PATCH 09/13] Fix resource to be able to set empty `review_request_delegation` block Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 21 ++++++++++++++++---- github/resource_github_team_settings_test.go | 1 - 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index a455066099..396b8813c8 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -53,10 +53,10 @@ func resourceGithubTeamSettings() *schema.Resource { ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{string(githubv4.TeamReviewAssignmentAlgorithmRoundRobin), string(githubv4.TeamReviewAssignmentAlgorithmLoadBalance)}, false)), }, "member_count": { - Type: schema.TypeInt, - Optional: true, - RequiredWith: []string{"review_request_delegation"}, - Description: "The number of team members to assign to a pull request.", + Type: schema.TypeInt, + Optional: true, + Default: 1, + Description: "The number of team members to assign to a pull request.", ValidateDiagFunc: validation.ToDiagFunc(validation.All( validation.IntAtLeast(1), )), @@ -112,6 +112,13 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat } `graphql:"updateTeamReviewAssignment(input:$input)"` } + tflog.Debug(ctx, "Review request delegation settings", map[string]any{ + "team_id": d.Id(), + "team_slug": slug, + "review_request_delegation": reviewRequestDelegation, + "length_of_settings": len(reviewRequestDelegation), + }) + if len(reviewRequestDelegation) == 0 { tflog.Debug(ctx, "No review request delegation settings provided, disabling review request delegation", map[string]any{ "team_id": d.Id(), @@ -123,6 +130,11 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat return diag.FromErr(err) } } else { + tflog.Debug(ctx, "Review request delegation settings provided, setting according to provided configuration", map[string]any{ + "team_id": d.Id(), + "team_slug": slug, + "review_request_delegation": reviewRequestDelegation, + }) settings := reviewRequestDelegation[0].(map[string]any) teamReviewAlgorithm := githubv4.TeamReviewAssignmentAlgorithm(settings["algorithm"].(string)) @@ -133,6 +145,7 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat TeamMemberCount: githubv4.NewInt(githubv4.Int(settings["member_count"].(int))), NotifyTeam: githubv4.NewBoolean(githubv4.Boolean(settings["notify"].(bool))), } + err := graphql.Mutate(ctx, &mutation, updateTeamReviewAssignmentInput, nil) if err != nil { return diag.FromErr(err) diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index 901ea865ab..3db6337f0d 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -219,7 +219,6 @@ func TestAccGithubTeamSettings(t *testing.T) { }) t.Run("creates_with_empty_review_request_delegation_block_without_error", func(t *testing.T) { - t.Skip("TODO this isn't working as expected") randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) config := fmt.Sprintf(` From 2376026a26d12b397a64af6e291f034e0f189b43 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 16:14:43 +0200 Subject: [PATCH 10/13] Add top-level `notify` to enable setting it without `review_request_delegation` Mark nested `notify` as deprecated Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 83 ++++++++-- github/resource_github_team_settings_test.go | 161 ++++++++++++++++++- website/docs/r/team_settings.html.markdown | 28 +++- 3 files changed, 252 insertions(+), 20 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 396b8813c8..08baf22cda 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -38,6 +38,13 @@ func resourceGithubTeamSettings() *schema.Resource { Computed: true, Description: "The unique ID of the Team on GitHub. Corresponds to the ID of the 'github_team_settings' resource.", }, + "notify": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether to notify the entire team when at least one member is also assigned to the pull request.", + ConflictsWith: []string{"review_request_delegation.0.notify"}, + }, "review_request_delegation": { Type: schema.TypeList, MaxItems: 1, @@ -62,10 +69,12 @@ func resourceGithubTeamSettings() *schema.Resource { )), }, "notify": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: "whether to notify the entire team when at least one member is also assigned to the pull request.", + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "whether to notify the entire team when at least one member is also assigned to the pull request.", + Deprecated: "Use the top-level notify attribute instead.", + ConflictsWith: []string{"notify"}, }, }, }, @@ -119,13 +128,19 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat "length_of_settings": len(reviewRequestDelegation), }) + notify := resolveNotify(ctx, d) + if len(reviewRequestDelegation) == 0 { tflog.Debug(ctx, "No review request delegation settings provided, disabling review request delegation", map[string]any{ "team_id": d.Id(), "team_slug": slug, + "notify": notify, }) - err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + input := defaultTeamReviewAssignmentSettings(d.Id()) + input.NotifyTeam = new(githubv4.Boolean(notify)) + + err := graphql.Mutate(ctx, &mutation, input, nil) if err != nil { return diag.FromErr(err) } @@ -134,6 +149,7 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat "team_id": d.Id(), "team_slug": slug, "review_request_delegation": reviewRequestDelegation, + "notify": notify, }) settings := reviewRequestDelegation[0].(map[string]any) @@ -142,8 +158,8 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat ID: d.Id(), Enabled: githubv4.Boolean(true), Algorithm: &teamReviewAlgorithm, - TeamMemberCount: githubv4.NewInt(githubv4.Int(settings["member_count"].(int))), - NotifyTeam: githubv4.NewBoolean(githubv4.Boolean(settings["notify"].(bool))), + TeamMemberCount: new(githubv4.Int(settings["member_count"].(int))), + NotifyTeam: new(githubv4.Boolean(notify)), } err := graphql.Mutate(ctx, &mutation, updateTeamReviewAssignmentInput, nil) @@ -175,11 +191,29 @@ func resourceGithubTeamSettingsRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } + notifyValue := query.Organization.Team.ReviewRequestDelegationNotifyAll + + // Set notify in the location matching the user's config: top-level or + // deprecated nested field inside review_request_delegation. + _, usesDeprecatedNotify := d.GetOk("review_request_delegation.0.notify") + tflog.Debug(ctx, "Uses deprecated notify", map[string]any{ + "uses_deprecated_notify": usesDeprecatedNotify, + "notify_value": notifyValue, + }) + + if !usesDeprecatedNotify { + if err = d.Set("notify", notifyValue); err != nil { + return diag.FromErr(err) + } + } + if query.Organization.Team.ReviewRequestDelegation { reviewRequestDelegation := make(map[string]any) reviewRequestDelegation["algorithm"] = query.Organization.Team.ReviewRequestDelegationAlgorithm reviewRequestDelegation["member_count"] = query.Organization.Team.ReviewRequestDelegationCount - reviewRequestDelegation["notify"] = query.Organization.Team.ReviewRequestDelegationNotifyAll + if usesDeprecatedNotify { + reviewRequestDelegation["notify"] = notifyValue + } if err = d.Set("review_request_delegation", []any{reviewRequestDelegation}); err != nil { return diag.FromErr(err) } @@ -193,10 +227,11 @@ func resourceGithubTeamSettingsRead(ctx context.Context, d *schema.ResourceData, } func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { - if d.HasChange("review_request_delegation") { + if d.HasChange("review_request_delegation") || d.HasChange("notify") { meta := m.(*Owner) graphql := meta.v4client reviewRequestDelegation := d.Get("review_request_delegation").([]any) + notify := resolveNotify(ctx, d) var mutation struct { UpdateTeamReviewAssignment struct { @@ -208,9 +243,13 @@ func resourceGithubTeamSettingsUpdate(ctx context.Context, d *schema.ResourceDat tflog.Debug(ctx, "No review request delegation settings provided, disabling review request delegation", map[string]any{ "team_id": d.Id(), "team_slug": d.Get("team_slug").(string), + "notify": notify, }) - err := graphql.Mutate(ctx, &mutation, defaultTeamReviewAssignmentSettings(d.Id()), nil) + input := defaultTeamReviewAssignmentSettings(d.Id()) + input.NotifyTeam = new(githubv4.Boolean(notify)) + + err := graphql.Mutate(ctx, &mutation, input, nil) if err != nil { return diag.FromErr(err) } @@ -299,6 +338,30 @@ func resolveTeamIDs(idOrSlug string, meta *Owner, ctx context.Context) (nodeId, } } +// resolveNotify returns the notify value from the top-level attribute or the +// deprecated nested attribute inside review_request_delegation. The top-level +// attribute takes precedence. Since ConflictsWith prevents both from being set, +// only one source can be active at a time. +func resolveNotify(ctx context.Context, d *schema.ResourceData) bool { + // Check if top-level notify is explicitly configured. + if v, ok := d.GetOk("notify"); ok { + tflog.Debug(ctx, "Top-level notify is explicitly configured", map[string]any{ + "notify": v, + }) + return v.(bool) + } + + // Fall back to deprecated nested field + if v, ok := d.GetOk("review_request_delegation.0.notify"); ok { + tflog.Debug(ctx, "Deprecated nested notify is explicitly configured", map[string]any{ + "notify": v, + }) + return v.(bool) + } + + return false +} + func defaultTeamReviewAssignmentSettings(id string) githubv4.UpdateTeamReviewAssignmentInput { roundRobinAlgo := githubv4.TeamReviewAssignmentAlgorithmRoundRobin return githubv4.UpdateTeamReviewAssignmentInput{ diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index 3db6337f0d..268d89e993 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" @@ -373,10 +374,10 @@ func TestAccGithubTeamSettings(t *testing.T) { resource "github_team_settings" "test" { team_id = github_team.test.id + notify = true review_request_delegation { algorithm = "ROUND_ROBIN" member_count = 2 - notify = true } } `, teamName) @@ -397,13 +398,22 @@ func TestAccGithubTeamSettings(t *testing.T) { knownvalue.Int64Exact(2)), statecheck.ExpectKnownValue("github_team_settings.test", tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), + knownvalue.Bool(false)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), knownvalue.Bool(true)), }, }, { - ResourceName: "github_team_settings.test", - ImportState: true, - ImportStateVerify: true, + ResourceName: "github_team_settings.test", + ImportStateKind: resource.ImportBlockWithID, + ImportState: true, + ImportPlanChecks: resource.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue("github_team_settings.test", tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("notify"), knownvalue.Bool(false)), + plancheck.ExpectKnownValue("github_team_settings.test", tfjsonpath.New("notify"), knownvalue.Bool(true)), + }, + }, ImportStateIdFunc: func(s *terraform.State) (string, error) { rs, ok := s.RootModule().Resources["github_team.test"] if !ok { @@ -478,6 +488,149 @@ func TestAccGithubTeamSettings(t *testing.T) { }) }) + t.Run("manages top-level notify without delegation", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := ` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + notify = %t + } + ` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(config, teamName, true), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), + knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation"), + knownvalue.ListExact([]knownvalue.Check{})), + }, + }, + { + Config: fmt.Sprintf(config, teamName, false), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), + knownvalue.Bool(false)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation"), + knownvalue.ListExact([]knownvalue.Check{})), + }, + }, + }, + }) + }) + + t.Run("manages top-level notify with delegation", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := ` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = "${github_team.test.id}" + notify = %t + review_request_delegation { + algorithm = "ROUND_ROBIN" + member_count = 2 + } + } + ` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(config, teamName, true), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), + knownvalue.Bool(true)), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("algorithm"), + knownvalue.StringExact("ROUND_ROBIN")), + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("review_request_delegation").AtSliceIndex(0).AtMapKey("member_count"), + knownvalue.Int64Exact(2)), + }, + }, + { + Config: fmt.Sprintf(config, teamName, false), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), + knownvalue.Bool(false)), + }, + }, + }, + }) + }) + + t.Run("imports team settings with top-level notify", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) + + config := fmt.Sprintf(` + resource "github_team" "test" { + name = "%s" + description = "generated by terraform provider automated testing" + } + + resource "github_team_settings" "test" { + team_id = github_team.test.id + notify = true + } + `, teamName) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { skipUnlessHasOrgs(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckGithubTeamSettingsDestroy, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_team_settings.test", + tfjsonpath.New("notify"), + knownvalue.Bool(true)), + }, + }, + { + ResourceName: "github_team_settings.test", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources["github_team.test"] + if !ok { + return "", fmt.Errorf("not found: github_team.test") + } + return rs.Primary.Attributes["id"], nil + }, + }, + }, + }) + }) + t.Run("manages updating only notify field", func(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) teamName := fmt.Sprintf("%steam-settings-%s", testResourcePrefix, randomID) diff --git a/website/docs/r/team_settings.html.markdown b/website/docs/r/team_settings.html.markdown index 11dd23cdfb..674b410fcc 100644 --- a/website/docs/r/team_settings.html.markdown +++ b/website/docs/r/team_settings.html.markdown @@ -17,19 +17,34 @@ The team must both belong to the same organization configured in the provider on ## Example Usage +### Notify without delegation + +```hcl +resource "github_team" "some_team" { + name = "SomeTeam" + description = "Some cool team" +} + +resource "github_team_settings" "code_review_settings" { + team_id = github_team.some_team.id + notify = true +} +``` + +### Notify with delegation + ```hcl -# Add a repository to the team resource "github_team" "some_team" { name = "SomeTeam" description = "Some cool team" } resource "github_team_settings" "code_review_settings" { - team_id = github_team.some_team.id + team_id = github_team.some_team.id + notify = true review_request_delegation { - algorithm = "ROUND_ROBIN" - member_count = 1 - notify = true + algorithm = "ROUND_ROBIN" + member_count = 1 } } ``` @@ -39,6 +54,7 @@ resource "github_team_settings" "code_review_settings" { The following arguments are supported: - `team_id` - (Required) The GitHub team id or the GitHub team slug +- `notify` - (Optional) Whether to notify the entire team when at least one member is also assigned to the pull request. Can be set independently of `review_request_delegation`. Default value is `false`. - `review_request_delegation` - (Optional) The settings for delegating code reviews to individuals on behalf of the team. If this block is present, even without any fields, then review request delegation will be enabled for the team. See [GitHub Review Request Delegation](#github-review-request-delegation-configuration) below for details. See [GitHub's documentation](https://docs.github.com/en/organizations/organizing-members-into-teams/managing-code-review-settings-for-your-team#configuring-team-notifications) for more configuration details. ### GitHub Review Request Delegation Configuration @@ -47,7 +63,7 @@ The following arguments are supported: - `algorithm` - (Optional) The algorithm to use when assigning pull requests to team members. Supported values are `ROUND_ROBIN` and `LOAD_BALANCE`. Default value is `ROUND_ROBIN` - `member_count` - (Optional) The number of team members to assign to a pull request -- `notify` - (Optional) whether to notify the entire team when at least one member is also assigned to the pull request +- `notify` - (Optional, **Deprecated**: Use the top-level `notify` attribute instead.) Whether to notify the entire team when at least one member is also assigned to the pull request. Conflicts with the top-level `notify` attribute. ## Attributes Reference From a9e42e15ef7ba9893c94533d1486914309cb9568 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 23 Feb 2026 16:23:22 +0200 Subject: [PATCH 11/13] Update docs Signed-off-by: Timo Sand --- website/docs/r/team_settings.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/team_settings.html.markdown b/website/docs/r/team_settings.html.markdown index 674b410fcc..c09d8ad051 100644 --- a/website/docs/r/team_settings.html.markdown +++ b/website/docs/r/team_settings.html.markdown @@ -62,7 +62,7 @@ The following arguments are supported: The following arguments are supported: - `algorithm` - (Optional) The algorithm to use when assigning pull requests to team members. Supported values are `ROUND_ROBIN` and `LOAD_BALANCE`. Default value is `ROUND_ROBIN` -- `member_count` - (Optional) The number of team members to assign to a pull request +- `member_count` - (Optional) The number of team members to assign to a pull request. Default value is `1`. - `notify` - (Optional, **Deprecated**: Use the top-level `notify` attribute instead.) Whether to notify the entire team when at least one member is also assigned to the pull request. Conflicts with the top-level `notify` attribute. ## Attributes Reference From dc6f431628e63280ba8e9774f2f161a6947de131 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 24 Feb 2026 22:02:20 +0200 Subject: [PATCH 12/13] Consolidate `getTeamSlugContext` and `resolveTeamIDs` to use shared logic with `getTeam` Signed-off-by: Timo Sand --- github/resource_github_team_settings.go | 38 +++++-------------------- github/util_team.go | 25 ++++++++++++++++ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 08baf22cda..0a6272acc0 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -2,8 +2,6 @@ package github import ( "context" - "errors" - "strconv" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -97,7 +95,7 @@ func resourceGithubTeamSettingsCreate(ctx context.Context, d *schema.ResourceDat }) // Given a string that is either a team id or team slug, return the // get the basic details of the team including node_id and slug - nodeId, slug, err := resolveTeamIDs(teamIDString, meta, ctx) + nodeId, slug, err := resolveTeamIDs(ctx, meta, teamIDString) if err != nil { return diag.FromErr(err) } @@ -291,7 +289,7 @@ func resourceGithubTeamSettingsDelete(ctx context.Context, d *schema.ResourceDat } func resourceGithubTeamSettingsImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - nodeId, slug, err := resolveTeamIDs(d.Id(), meta.(*Owner), ctx) + nodeId, slug, err := resolveTeamIDs(ctx, meta.(*Owner), d.Id()) if err != nil { return nil, err } @@ -308,34 +306,12 @@ func resourceGithubTeamSettingsImport(ctx context.Context, d *schema.ResourceDat return []*schema.ResourceData{d}, nil } -func resolveTeamIDs(idOrSlug string, meta *Owner, ctx context.Context) (nodeId, slug string, err error) { - client := meta.v3client - orgName := meta.name - orgId := meta.id - - teamId, parseIntErr := strconv.ParseInt(idOrSlug, 10, 64) - if parseIntErr != nil { - // The given id not an integer, assume it is a team slug - team, _, slugErr := client.Teams.GetTeamBySlug(ctx, orgName, idOrSlug) - if slugErr != nil { - return "", "", errors.New(parseIntErr.Error() + slugErr.Error()) - } - return team.GetNodeID(), team.GetSlug(), nil - } else { - // The given id is an integer, assume it is a team id - team, _, teamIdErr := client.Teams.GetTeamByID(ctx, orgId, teamId) - if teamIdErr != nil { - // There isn't a team with the given ID, assume it is a teamslug - team, _, slugErr := client.Teams.GetTeamBySlug(ctx, orgName, idOrSlug) - if slugErr != nil { - return "", "", errors.New(teamIdErr.Error() + slugErr.Error()) - } - - return team.GetNodeID(), team.GetSlug(), nil - } - - return team.GetNodeID(), team.GetSlug(), nil +func resolveTeamIDs(ctx context.Context, meta *Owner, idOrSlug string) (nodeId, slug string, err error) { + team, err := getTeam(ctx, meta, idOrSlug) + if err != nil { + return "", "", err } + return team.GetNodeID(), team.GetSlug(), nil } // resolveNotify returns the notify value from the top-level attribute or the diff --git a/github/util_team.go b/github/util_team.go index 8f674054d3..e79c2d2eea 100644 --- a/github/util_team.go +++ b/github/util_team.go @@ -2,6 +2,7 @@ package github import ( "context" + "fmt" "strconv" "github.com/google/go-github/v84/github" @@ -150,3 +151,27 @@ func lookupTeamID(ctx context.Context, client *github.Client, orgName, slug stri } return team.GetID(), nil } + +// Given a string that is either a team id or team slug, return the +// team object it is referring to. +func getTeam(ctx context.Context, meta *Owner, idOrSlug string) (*github.Team, error) { + client := meta.v3client + orgName := meta.name + orgId := meta.id + + if id, ok := parseTeamID(idOrSlug); ok { + // The given id is an integer, assume it is the team ID. + team, _, err := client.Teams.GetTeamByID(ctx, orgId, id) + if err != nil { + return nil, fmt.Errorf("failed to get team by ID (%d): %w", id, err) + } + return team, nil + } + + // The given id not an integer, assume it is a team slug + team, _, err := client.Teams.GetTeamBySlug(ctx, orgName, idOrSlug) + if err != nil { + return nil, fmt.Errorf("failed to get team by slug (%s): %w", idOrSlug, err) + } + return team, nil +} From 28b45a83fcbf6b8eb999338e6a42612a8518c0d7 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Fri, 27 Mar 2026 19:33:48 +0200 Subject: [PATCH 13/13] Remove named returns --- github/resource_github_team_settings.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/resource_github_team_settings.go b/github/resource_github_team_settings.go index 0a6272acc0..6da6491223 100644 --- a/github/resource_github_team_settings.go +++ b/github/resource_github_team_settings.go @@ -306,7 +306,7 @@ func resourceGithubTeamSettingsImport(ctx context.Context, d *schema.ResourceDat return []*schema.ResourceData{d}, nil } -func resolveTeamIDs(ctx context.Context, meta *Owner, idOrSlug string) (nodeId, slug string, err error) { +func resolveTeamIDs(ctx context.Context, meta *Owner, idOrSlug string) (string, string, error) { team, err := getTeam(ctx, meta, idOrSlug) if err != nil { return "", "", err