diff --git a/github/resource_github_organization_ruleset.go b/github/resource_github_organization_ruleset.go index eea93ffc0a..c410148f61 100644 --- a/github/resource_github_organization_ruleset.go +++ b/github/resource_github_organization_ruleset.go @@ -241,6 +241,28 @@ func resourceGithubOrganizationRuleset() *schema.Resource { }, }, }, + "copilot_code_review": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Automatically request Copilot code review for new pull requests if the author has access to Copilot code review and their premium requests quota has not reached the limit.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "review_on_push": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Copilot automatically reviews each new push to the pull request. Defaults to `false`.", + }, + "review_draft_pull_requests": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Copilot automatically reviews draft pull requests before they are marked as ready for review. Defaults to `false`.", + }, + }, + }, + }, "required_status_checks": { Type: schema.TypeList, MaxItems: 1, diff --git a/github/resource_github_organization_ruleset_test.go b/github/resource_github_organization_ruleset_test.go index 774315e6db..cc8e056e81 100644 --- a/github/resource_github_organization_ruleset_test.go +++ b/github/resource_github_organization_ruleset_test.go @@ -103,6 +103,11 @@ resource "github_organization_ruleset" "test" { require_last_push_approval = true } + copilot_code_review { + review_on_push = true + review_draft_pull_requests = false + } + required_status_checks { required_check { @@ -166,6 +171,8 @@ resource "github_organization_ruleset" "test" { resource.TestCheckResourceAttr("github_organization_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.alerts_threshold", "errors"), resource.TestCheckResourceAttr("github_organization_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.security_alerts_threshold", "high_or_higher"), resource.TestCheckResourceAttr("github_organization_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.tool", "CodeQL"), + resource.TestCheckResourceAttr("github_organization_ruleset.test", "rules.0.copilot_code_review.0.review_on_push", "true"), + resource.TestCheckResourceAttr("github_organization_ruleset.test", "rules.0.copilot_code_review.0.review_draft_pull_requests", "false"), ), }, }, diff --git a/github/resource_github_repository_ruleset.go b/github/resource_github_repository_ruleset.go index ebb5400a75..24df346145 100644 --- a/github/resource_github_repository_ruleset.go +++ b/github/resource_github_repository_ruleset.go @@ -594,6 +594,28 @@ func resourceGithubRepositoryRuleset() *schema.Resource { }, }, }, + "copilot_code_review": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Automatically request Copilot code review for new pull requests if the author has access to Copilot code review and their premium requests quota has not reached the limit.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "review_on_push": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Copilot automatically reviews each new push to the pull request. Defaults to `false`.", + }, + "review_draft_pull_requests": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Copilot automatically reviews draft pull requests before they are marked as ready for review. Defaults to `false`.", + }, + }, + }, + }, }, }, }, diff --git a/github/resource_github_repository_ruleset_test.go b/github/resource_github_repository_ruleset_test.go index 54161a0af0..9dc58d599a 100644 --- a/github/resource_github_repository_ruleset_test.go +++ b/github/resource_github_repository_ruleset_test.go @@ -73,6 +73,11 @@ resource "github_repository_ruleset" "test" { min_entries_to_merge_wait_minutes = 60 } + copilot_code_review { + review_on_push = true + review_draft_pull_requests = false + } + required_deployments { required_deployment_environments = [github_repository_environment.example.environment] } @@ -128,6 +133,8 @@ resource "github_repository_ruleset" "test" { resource.TestCheckResourceAttr("github_repository_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.alerts_threshold", "errors"), resource.TestCheckResourceAttr("github_repository_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.security_alerts_threshold", "high_or_higher"), resource.TestCheckResourceAttr("github_repository_ruleset.test", "rules.0.required_code_scanning.0.required_code_scanning_tool.0.tool", "CodeQL"), + resource.TestCheckResourceAttr("github_repository_ruleset.test", "rules.0.copilot_code_review.0.review_on_push", "true"), + resource.TestCheckResourceAttr("github_repository_ruleset.test", "rules.0.copilot_code_review.0.review_draft_pull_requests", "false"), ), }, }, diff --git a/github/util_rules.go b/github/util_rules.go index 734801598f..016bba72c2 100644 --- a/github/util_rules.go +++ b/github/util_rules.go @@ -518,6 +518,16 @@ func expandRules(input []any, org bool) *github.RepositoryRulesetRules { rulesetRules.FileExtensionRestriction = params } + // Copilot code review rule + if v, ok := rulesMap["copilot_code_review"].([]any); ok && len(v) != 0 { + copilotCodeReviewMap := v[0].(map[string]any) + params := &github.CopilotCodeReviewRuleParameters{ + ReviewOnPush: copilotCodeReviewMap["review_on_push"].(bool), + ReviewDraftPullRequests: copilotCodeReviewMap["review_draft_pull_requests"].(bool), + } + rulesetRules.CopilotCodeReview = params + } + return rulesetRules } @@ -734,6 +744,16 @@ func flattenRules(rules *github.RepositoryRulesetRules, org bool) []any { rulesMap["file_extension_restriction"] = fileExtensionRestrictionSlice } + // Copilot code review rule + if rules.CopilotCodeReview != nil { + copilotCodeReviewSlice := make([]map[string]any, 0) + copilotCodeReviewSlice = append(copilotCodeReviewSlice, map[string]any{ + "review_on_push": rules.CopilotCodeReview.ReviewOnPush, + "review_draft_pull_requests": rules.CopilotCodeReview.ReviewDraftPullRequests, + }) + rulesMap["copilot_code_review"] = copilotCodeReviewSlice + } + return []any{rulesMap} } diff --git a/github/util_rules_test.go b/github/util_rules_test.go index 9c09fa03d4..2298fed112 100644 --- a/github/util_rules_test.go +++ b/github/util_rules_test.go @@ -418,3 +418,58 @@ func TestCompletePushRulesetSupport(t *testing.T) { t.Errorf("Expected 3 restricted file extensions, got %d", len(restrictedExts)) } } + +func TestCopilotCodeReviewRoundTrip(t *testing.T) { + // Test that copilot_code_review rule survives expand -> flatten round trip + rulesMap := map[string]any{ + "copilot_code_review": []any{ + map[string]any{ + "review_on_push": true, + "review_draft_pull_requests": false, + }, + }, + } + + input := []any{rulesMap} + + // Expand to GitHub API format + expandedRules := expandRules(input, false) + + if expandedRules == nil { + t.Fatal("Expected expandedRules to not be nil") + } + + if expandedRules.CopilotCodeReview == nil { + t.Fatal("Expected CopilotCodeReview rule to be set") + } + + if expandedRules.CopilotCodeReview.ReviewOnPush != true { + t.Errorf("Expected ReviewOnPush to be true, got %v", expandedRules.CopilotCodeReview.ReviewOnPush) + } + + if expandedRules.CopilotCodeReview.ReviewDraftPullRequests != false { + t.Errorf("Expected ReviewDraftPullRequests to be false, got %v", expandedRules.CopilotCodeReview.ReviewDraftPullRequests) + } + + // Flatten back to terraform format + flattenedResult := flattenRules(expandedRules, false) + + if len(flattenedResult) != 1 { + t.Fatalf("Expected 1 flattened result, got %d", len(flattenedResult)) + } + + flattenedRulesMap := flattenedResult[0].(map[string]any) + copilotRules := flattenedRulesMap["copilot_code_review"].([]map[string]any) + + if len(copilotRules) != 1 { + t.Fatalf("Expected 1 copilot_code_review rule after round trip, got %d", len(copilotRules)) + } + + if copilotRules[0]["review_on_push"] != true { + t.Errorf("Expected review_on_push to be true, got %v", copilotRules[0]["review_on_push"]) + } + + if copilotRules[0]["review_draft_pull_requests"] != false { + t.Errorf("Expected review_draft_pull_requests to be false, got %v", copilotRules[0]["review_draft_pull_requests"]) + } +} diff --git a/website/docs/r/organization_ruleset.html.markdown b/website/docs/r/organization_ruleset.html.markdown index e04118f678..b55e0e0f2c 100644 --- a/website/docs/r/organization_ruleset.html.markdown +++ b/website/docs/r/organization_ruleset.html.markdown @@ -136,6 +136,8 @@ The `rules` block supports the following: * `pull_request` - (Optional) (Block List, Max: 1) Require all commits be made to a non-target branch and submitted via a pull request before they can be merged. (see [below for nested schema](#rules.pull_request)) +* `copilot_code_review` - (Optional) (Block List, Max: 1) Automatically request Copilot code review for new pull requests if the author has access to Copilot code review and their premium requests quota has not reached the limit. (see [below for nested schema](#rulescopilot_code_review)) + * `required_linear_history` - (Optional) (Boolean) Prevent merge commits from being pushed to matching branches. * `required_signatures` - (Optional) (Boolean) Commits pushed to matching branches must have verified signatures. @@ -210,6 +212,12 @@ The `rules` block supports the following: * `required_review_thread_resolution` - (Optional) (Boolean) All conversations on code must be resolved before a pull request can be merged. Defaults to `false`. +#### rules.copilot_code_review #### + +* `review_on_push` - (Optional) (Boolean) Copilot automatically reviews each new push to the pull request. Defaults to `false`. + +* `review_draft_pull_requests` - (Optional) (Boolean) Copilot automatically reviews draft pull requests before they are marked as ready for review. Defaults to `false`. + #### rules.required_status_checks #### * `required_check` - (Required) (Block Set, Min: 1) Status checks that are required. Several can be defined. (see [below for nested schema](#rules.required_status_checks.required_check)) diff --git a/website/docs/r/repository_ruleset.html.markdown b/website/docs/r/repository_ruleset.html.markdown index 61c5c733cb..138555a03f 100644 --- a/website/docs/r/repository_ruleset.html.markdown +++ b/website/docs/r/repository_ruleset.html.markdown @@ -124,6 +124,8 @@ The `rules` block supports the following: * `pull_request` - (Optional) (Block List, Max: 1) Require all commits be made to a non-target branch and submitted via a pull request before they can be merged. (see [below for nested schema](#rulespull_request)) +* `copilot_code_review` - (Optional) (Block List, Max: 1) Automatically request Copilot code review for new pull requests if the author has access to Copilot code review and their premium requests quota has not reached the limit. (see [below for nested schema](#rulescopilot_code_review)) + * `required_deployments` - (Optional) (Block List, Max: 1) Choose which environments must be successfully deployed to before branches can be merged into a branch that matches this rule. (see [below for nested schema](#rulesrequired_deployments)) * `required_linear_history` - (Optional) (Boolean) Prevent merge commits from being pushed to matching branches. @@ -215,6 +217,12 @@ The `rules` block supports the following: * `required_review_thread_resolution` - (Optional) (Boolean) All conversations on code must be resolved before a pull request can be merged. Defaults to `false`. +#### rules.copilot_code_review #### + +* `review_on_push` - (Optional) (Boolean) Copilot automatically reviews each new push to the pull request. Defaults to `false`. + +* `review_draft_pull_requests` - (Optional) (Boolean) Copilot automatically reviews draft pull requests before they are marked as ready for review. Defaults to `false`. + #### rules.required_deployments #### * `required_deployment_environments` - (Required) (List of String) The environments that must be successfully deployed to before branches can be merged.