Skip to content

Commit fa8d3eb

Browse files
deigaclaude
andcommitted
feat(ruleset): add required_reviewers support for pull_request rules
Add support for the required_reviewers rule parameter in pull_request rules for both organization and repository rulesets. This allows requiring specific team reviewers to approve changes based on file patterns. Implementation includes: - requiredReviewersSchema() shared schema definition - expandRequiredReviewers() for Terraform->API conversion - flattenRequiredReviewers() for API->Terraform conversion - Integration into expandRules() and flattenRules() - Schema integration in both org and repo ruleset resources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent c1019c3 commit fa8d3eb

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

github/resource_github_organization_ruleset.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
248248
Default: false,
249249
Description: "All conversations on code must be resolved before a pull request can be merged. Defaults to `false`.",
250250
},
251+
"required_reviewers": requiredReviewersSchema(),
251252
},
252253
},
253254
},

github/resource_github_repository_ruleset.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ func resourceGithubRepositoryRuleset() *schema.Resource {
236236
Default: false,
237237
Description: "All conversations on code must be resolved before a pull request can be merged. Defaults to `false`.",
238238
},
239+
"required_reviewers": requiredReviewersSchema(),
239240
},
240241
},
241242
},

github/util_rules.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,59 @@ import (
99
"github.com/google/go-github/v81/github"
1010
"github.com/hashicorp/terraform-plugin-log/tflog"
1111
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1213
)
1314

1415
// This is a workaround for the SDK not setting the default value for the allowed_merge_methods field.
1516
var defaultPullRequestMergeMethods = []github.PullRequestMergeMethod{github.PullRequestMergeMethodMerge, github.PullRequestMergeMethodRebase, github.PullRequestMergeMethodSquash}
1617

18+
// requiredReviewersSchema returns the schema definition for required_reviewers block.
19+
// This is shared between organization and repository rulesets.
20+
func requiredReviewersSchema() *schema.Schema {
21+
return &schema.Schema{
22+
Type: schema.TypeList,
23+
Optional: true,
24+
Description: "Require specific reviewers to approve pull requests targeting matching branches. Note: This feature is in beta and subject to change.",
25+
Elem: &schema.Resource{
26+
Schema: map[string]*schema.Schema{
27+
"reviewer": {
28+
Type: schema.TypeList,
29+
Required: true,
30+
MaxItems: 1,
31+
Description: "The reviewer that must review matching files.",
32+
Elem: &schema.Resource{
33+
Schema: map[string]*schema.Schema{
34+
"id": {
35+
Type: schema.TypeInt,
36+
Required: true,
37+
Description: "The ID of the reviewer that must review.",
38+
},
39+
"type": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
ValidateDiagFunc: toDiagFunc(validation.StringInSlice([]string{"Team"}, false), "type"),
43+
Description: "The type of reviewer. Currently only `Team` is supported.",
44+
},
45+
},
46+
},
47+
},
48+
"file_patterns": {
49+
Type: schema.TypeList,
50+
Required: true,
51+
MinItems: 1,
52+
Description: "File patterns (fnmatch syntax) that this reviewer must approve.",
53+
Elem: &schema.Schema{Type: schema.TypeString},
54+
},
55+
"minimum_approvals": {
56+
Type: schema.TypeInt,
57+
Required: true,
58+
Description: "Minimum number of approvals required from this reviewer. Set to 0 to make approval optional.",
59+
},
60+
},
61+
},
62+
}
63+
}
64+
1765
// Helper function to safely convert interface{} to int, handling both int and float64.
1866
func toInt(v any) int {
1967
switch val := v.(type) {
@@ -57,6 +105,72 @@ func toPullRequestMergeMethods(input any) []github.PullRequestMergeMethod {
57105
return mergeMethods
58106
}
59107

108+
// expandRequiredReviewers converts Terraform schema data to go-github RequiredReviewers.
109+
func expandRequiredReviewers(input []any) []*github.RulesetRequiredReviewer {
110+
if len(input) == 0 {
111+
return nil
112+
}
113+
114+
reviewers := make([]*github.RulesetRequiredReviewer, 0, len(input))
115+
for _, item := range input {
116+
reviewerMap := item.(map[string]any)
117+
118+
var reviewer *github.RulesetReviewer
119+
if rv, ok := reviewerMap["reviewer"].([]any); ok && len(rv) != 0 {
120+
reviewerData := rv[0].(map[string]any)
121+
reviewerType := github.RulesetReviewerType(reviewerData["type"].(string))
122+
reviewer = &github.RulesetReviewer{
123+
ID: github.Ptr(int64(reviewerData["id"].(int))),
124+
Type: &reviewerType,
125+
}
126+
}
127+
128+
filePatterns := make([]string, 0)
129+
if fp, ok := reviewerMap["file_patterns"].([]any); ok {
130+
for _, p := range fp {
131+
filePatterns = append(filePatterns, p.(string))
132+
}
133+
}
134+
135+
reviewers = append(reviewers, &github.RulesetRequiredReviewer{
136+
MinimumApprovals: github.Ptr(reviewerMap["minimum_approvals"].(int)),
137+
FilePatterns: filePatterns,
138+
Reviewer: reviewer,
139+
})
140+
}
141+
return reviewers
142+
}
143+
144+
// flattenRequiredReviewers converts go-github RequiredReviewers to Terraform schema data.
145+
func flattenRequiredReviewers(reviewers []*github.RulesetRequiredReviewer) []map[string]any {
146+
if len(reviewers) == 0 {
147+
return nil
148+
}
149+
150+
reviewersList := make([]map[string]any, 0, len(reviewers))
151+
for _, rr := range reviewers {
152+
reviewerMap := map[string]any{
153+
"file_patterns": rr.FilePatterns,
154+
"minimum_approvals": 0,
155+
}
156+
if rr.MinimumApprovals != nil {
157+
reviewerMap["minimum_approvals"] = *rr.MinimumApprovals
158+
}
159+
if rr.Reviewer != nil {
160+
reviewerData := map[string]any{}
161+
if rr.Reviewer.ID != nil {
162+
reviewerData["id"] = int(*rr.Reviewer.ID)
163+
}
164+
if rr.Reviewer.Type != nil {
165+
reviewerData["type"] = string(*rr.Reviewer.Type)
166+
}
167+
reviewerMap["reviewer"] = []map[string]any{reviewerData}
168+
}
169+
reviewersList = append(reviewersList, reviewerMap)
170+
}
171+
return reviewersList
172+
}
173+
60174
func resourceGithubRulesetObject(d *schema.ResourceData, org string) github.RepositoryRuleset {
61175
isOrgLevel := len(org) > 0
62176

@@ -334,6 +448,12 @@ func expandRules(input []any, org bool) *github.RepositoryRulesetRules {
334448
RequiredReviewThreadResolution: pullRequestMap["required_review_thread_resolution"].(bool),
335449
AllowedMergeMethods: toPullRequestMergeMethods(allowedMergeMethods),
336450
}
451+
452+
// Add required reviewers if provided
453+
if reqReviewers, ok := pullRequestMap["required_reviewers"].([]any); ok && len(reqReviewers) != 0 {
454+
params.RequiredReviewers = expandRequiredReviewers(reqReviewers)
455+
}
456+
337457
rulesetRules.PullRequest = params
338458
}
339459

@@ -574,6 +694,7 @@ func flattenRules(rules *github.RepositoryRulesetRules, org bool) []any {
574694
"required_approving_review_count": rules.PullRequest.RequiredApprovingReviewCount,
575695
"required_review_thread_resolution": rules.PullRequest.RequiredReviewThreadResolution,
576696
"allowed_merge_methods": rules.PullRequest.AllowedMergeMethods,
697+
"required_reviewers": flattenRequiredReviewers(rules.PullRequest.RequiredReviewers),
577698
})
578699
log.Printf("[DEBUG] Flattened Pull Request rules slice: %#v", pullRequestSlice)
579700
rulesMap["pull_request"] = pullRequestSlice

0 commit comments

Comments
 (0)