Skip to content

Commit 7e9ce49

Browse files
committed
feat: add customization validation for enterprise ruleset configuration
1 parent 1eff493 commit 7e9ce49

2 files changed

Lines changed: 122 additions & 0 deletions

File tree

github/resource_github_enterprise_ruleset.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ func resourceGithubEnterpriseRuleset() *schema.Resource {
2222
UpdateContext: resourceGithubEnterpriseRulesetUpdate,
2323
DeleteContext: resourceGithubEnterpriseRulesetDelete,
2424

25+
CustomizeDiff: resourceGithubEnterpriseRulesetCustomizeDiff,
26+
2527
Schema: map[string]*schema.Schema{
2628
"enterprise_slug": {
2729
Type: schema.TypeString,
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
)
9+
10+
// Repository target rules (enterprise only)
11+
var repositoryTargetRules = []string{
12+
"repository_creation",
13+
"repository_deletion",
14+
"repository_transfer",
15+
"repository_name",
16+
"repository_visibility",
17+
}
18+
19+
// resourceGithubEnterpriseRulesetCustomizeDiff validates enterprise ruleset configuration
20+
func resourceGithubEnterpriseRulesetCustomizeDiff(_ context.Context, d *schema.ResourceDiff, _ interface{}) error {
21+
target := d.Get("target").(string)
22+
23+
// Validate conditions
24+
if err := validateEnterpriseConditions(d, target); err != nil {
25+
return err
26+
}
27+
28+
// Validate rules
29+
if err := validateEnterpriseRules(d, target); err != nil {
30+
return err
31+
}
32+
33+
return nil
34+
}
35+
36+
// validateEnterpriseConditions validates conditions based on target type
37+
func validateEnterpriseConditions(d *schema.ResourceDiff, target string) error {
38+
conditions := d.Get("conditions").([]interface{})
39+
if len(conditions) == 0 {
40+
return nil
41+
}
42+
43+
conditionsMap := conditions[0].(map[string]interface{})
44+
refName := conditionsMap["ref_name"].([]interface{})
45+
hasRefName := len(refName) > 0
46+
47+
switch target {
48+
case "branch", "tag":
49+
if !hasRefName {
50+
return fmt.Errorf("'ref_name' condition is required when target is '%s'", target)
51+
}
52+
case "push", "repository":
53+
if hasRefName {
54+
return fmt.Errorf("'ref_name' condition must not be set when target is '%s'", target)
55+
}
56+
}
57+
58+
return nil
59+
}
60+
61+
// validateEnterpriseRules validates rules based on target type
62+
func validateEnterpriseRules(d *schema.ResourceDiff, target string) error {
63+
rules := d.Get("rules").([]interface{})
64+
if len(rules) == 0 {
65+
return nil
66+
}
67+
68+
rulesMap := rules[0].(map[string]interface{})
69+
70+
// Repository rules only valid for repository target
71+
if target != "repository" {
72+
for _, rule := range repositoryTargetRules {
73+
if isRuleSet(rulesMap, rule) {
74+
return fmt.Errorf("rule '%s' is only valid for target 'repository', not '%s'", rule, target)
75+
}
76+
}
77+
}
78+
79+
// Push rules only valid for push target
80+
pushRules := []string{"file_path_restriction", "max_file_size", "max_file_path_length", "file_extension_restriction"}
81+
if target != "push" {
82+
for _, rule := range pushRules {
83+
if isRuleSet(rulesMap, rule) {
84+
return fmt.Errorf("rule '%s' is only valid for target 'push', not '%s'", rule, target)
85+
}
86+
}
87+
}
88+
89+
// Branch/tag rules not valid for push or repository targets
90+
if target == "push" || target == "repository" {
91+
branchTagRules := []string{
92+
"creation", "deletion", "update", "required_linear_history", "required_signatures",
93+
"pull_request", "required_status_checks", "non_fast_forward", "commit_message_pattern",
94+
"commit_author_email_pattern", "committer_email_pattern", "branch_name_pattern",
95+
"tag_name_pattern", "required_workflows", "required_code_scanning", "copilot_code_review",
96+
}
97+
for _, rule := range branchTagRules {
98+
if isRuleSet(rulesMap, rule) {
99+
return fmt.Errorf("rule '%s' is only valid for target 'branch' or 'tag', not '%s'", rule, target)
100+
}
101+
}
102+
}
103+
104+
return nil
105+
}
106+
107+
// isRuleSet checks if a rule is set in the rules map
108+
func isRuleSet(rules map[string]interface{}, ruleName string) bool {
109+
if val, ok := rules[ruleName]; ok {
110+
switch v := val.(type) {
111+
case bool:
112+
return v
113+
case []interface{}:
114+
return len(v) > 0
115+
default:
116+
return val != nil
117+
}
118+
}
119+
return false
120+
}

0 commit comments

Comments
 (0)