Skip to content

Commit 0ff0a4c

Browse files
committed
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
1 parent 5d9d8a1 commit 0ff0a4c

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
@@ -244,6 +244,7 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
244244
Default: false,
245245
Description: "All conversations on code must be resolved before a pull request can be merged. Defaults to `false`.",
246246
},
247+
"required_reviewers": requiredReviewersSchema(),
247248
},
248249
},
249250
},

github/resource_github_repository_ruleset.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ func resourceGithubRepositoryRuleset() *schema.Resource {
231231
Default: false,
232232
Description: "All conversations on code must be resolved before a pull request can be merged. Defaults to `false`.",
233233
},
234+
"required_reviewers": requiredReviewersSchema(),
234235
},
235236
},
236237
},

github/util_rules.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ 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
type Target string
@@ -22,6 +23,53 @@ const (
2223
// This is a workaround for the SDK not setting the default value for the allowed_merge_methods field.
2324
var defaultPullRequestMergeMethods = []github.PullRequestMergeMethod{github.PullRequestMergeMethodMerge, github.PullRequestMergeMethodRebase, github.PullRequestMergeMethodSquash}
2425

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

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

@@ -342,6 +456,12 @@ func expandRules(input []any, org bool) *github.RepositoryRulesetRules {
342456
RequiredReviewThreadResolution: pullRequestMap["required_review_thread_resolution"].(bool),
343457
AllowedMergeMethods: toPullRequestMergeMethods(allowedMergeMethods),
344458
}
459+
460+
// Add required reviewers if provided
461+
if reqReviewers, ok := pullRequestMap["required_reviewers"].([]any); ok && len(reqReviewers) != 0 {
462+
params.RequiredReviewers = expandRequiredReviewers(reqReviewers)
463+
}
464+
345465
rulesetRules.PullRequest = params
346466
}
347467

@@ -592,6 +712,7 @@ func flattenRules(rules *github.RepositoryRulesetRules, org bool) []any {
592712
"required_approving_review_count": rules.PullRequest.RequiredApprovingReviewCount,
593713
"required_review_thread_resolution": rules.PullRequest.RequiredReviewThreadResolution,
594714
"allowed_merge_methods": rules.PullRequest.AllowedMergeMethods,
715+
"required_reviewers": flattenRequiredReviewers(rules.PullRequest.RequiredReviewers),
595716
})
596717
log.Printf("[DEBUG] Flattened Pull Request rules slice: %#v", pullRequestSlice)
597718
rulesMap["pull_request"] = pullRequestSlice

0 commit comments

Comments
 (0)