Skip to content

Commit 1b1cdcd

Browse files
authored
fix/feature: Adds support for max_file_size, max_file_path_length, file_extension_restriction, and unknown rulesets for repos and orgs (#2821)
* Adds support for max_file_size and file_extension_restriction and default rulesets * Adds test coverage * Adds support for max_file_path_length * Removes unnecessary boxing * fixes cast issue in tests * Adds organizational push rulesets * Updates docs for rulesets
1 parent 7ed1c98 commit 1b1cdcd

7 files changed

+794
-1
lines changed

github/resource_github_organization_ruleset.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,74 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
504504
},
505505
},
506506
},
507+
"file_path_restriction": {
508+
Type: schema.TypeList,
509+
Optional: true,
510+
MaxItems: 1,
511+
Description: "Prevent commits that include changes in specified file paths from being pushed to the commit graph.",
512+
Elem: &schema.Resource{
513+
Schema: map[string]*schema.Schema{
514+
"restricted_file_paths": {
515+
Type: schema.TypeList,
516+
MinItems: 1,
517+
Required: true,
518+
Description: "The file paths that are restricted from being pushed to the commit graph.",
519+
Elem: &schema.Schema{
520+
Type: schema.TypeString,
521+
},
522+
},
523+
},
524+
},
525+
},
526+
"max_file_size": {
527+
Type: schema.TypeList,
528+
Optional: true,
529+
MaxItems: 1,
530+
Description: "Prevent pushes based on file size.",
531+
Elem: &schema.Resource{
532+
Schema: map[string]*schema.Schema{
533+
"max_file_size": {
534+
Type: schema.TypeInt,
535+
Required: true,
536+
Description: "The maximum allowed size of a file in bytes.",
537+
},
538+
},
539+
},
540+
},
541+
"max_file_path_length": {
542+
Type: schema.TypeList,
543+
Optional: true,
544+
MaxItems: 1,
545+
Description: "Prevent pushes based on file path length.",
546+
Elem: &schema.Resource{
547+
Schema: map[string]*schema.Schema{
548+
"max_file_path_length": {
549+
Type: schema.TypeInt,
550+
Required: true,
551+
Description: "The maximum allowed length of a file path.",
552+
},
553+
},
554+
},
555+
},
556+
"file_extension_restriction": {
557+
Type: schema.TypeList,
558+
Optional: true,
559+
MaxItems: 1,
560+
Description: "Prevent pushes based on file extensions.",
561+
Elem: &schema.Resource{
562+
Schema: map[string]*schema.Schema{
563+
"restricted_file_extensions": {
564+
Type: schema.TypeSet,
565+
MinItems: 1,
566+
Required: true,
567+
Description: "The file extensions that are restricted from being pushed to the commit graph.",
568+
Elem: &schema.Schema{
569+
Type: schema.TypeString,
570+
},
571+
},
572+
},
573+
},
574+
},
507575
},
508576
},
509577
},

github/resource_github_organization_ruleset_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,3 +576,100 @@ func TestGithubOrganizationRulesets(t *testing.T) {
576576
})
577577

578578
}
579+
580+
func TestOrganizationPushRulesetSupport(t *testing.T) {
581+
// Test that organization push rulesets support all push-specific rules
582+
// This is a unit test since it only validates the expand/flatten functionality
583+
584+
rulesMap := map[string]interface{}{
585+
"file_path_restriction": []interface{}{
586+
map[string]interface{}{
587+
"restricted_file_paths": []interface{}{"secrets/", "*.key", "private/"},
588+
},
589+
},
590+
"max_file_size": []interface{}{
591+
map[string]interface{}{
592+
"max_file_size": float64(10485760), // 10MB
593+
},
594+
},
595+
"max_file_path_length": []interface{}{
596+
map[string]interface{}{
597+
"max_file_path_length": 250,
598+
},
599+
},
600+
"file_extension_restriction": []interface{}{
601+
map[string]interface{}{
602+
"restricted_file_extensions": []interface{}{".exe", ".bat", ".sh", ".ps1"},
603+
},
604+
},
605+
}
606+
607+
input := []interface{}{rulesMap}
608+
609+
// Test expand functionality (organization rulesets use org=true)
610+
expandedRules := expandRules(input, true)
611+
612+
if len(expandedRules) != 4 {
613+
t.Fatalf("Expected 4 expanded rules for organization push ruleset, got %d", len(expandedRules))
614+
}
615+
616+
// Verify we have all expected push rule types
617+
ruleTypes := make(map[string]bool)
618+
for _, rule := range expandedRules {
619+
ruleTypes[rule.Type] = true
620+
}
621+
622+
expectedPushRules := []string{"file_path_restriction", "max_file_size", "max_file_path_length", "file_extension_restriction"}
623+
for _, expectedType := range expectedPushRules {
624+
if !ruleTypes[expectedType] {
625+
t.Errorf("Expected organization push rule type %s not found in expanded rules", expectedType)
626+
}
627+
}
628+
629+
// Test flatten functionality (organization rulesets use org=true)
630+
flattenedResult := flattenRules(expandedRules, true)
631+
632+
if len(flattenedResult) != 1 {
633+
t.Fatalf("Expected 1 flattened result, got %d", len(flattenedResult))
634+
}
635+
636+
flattenedRulesMap := flattenedResult[0].(map[string]interface{})
637+
638+
// Verify file_path_restriction
639+
filePathRules := flattenedRulesMap["file_path_restriction"].([]map[string]interface{})
640+
if len(filePathRules) != 1 {
641+
t.Fatalf("Expected 1 file_path_restriction rule, got %d", len(filePathRules))
642+
}
643+
restrictedPaths := filePathRules[0]["restricted_file_paths"].([]string)
644+
if len(restrictedPaths) != 3 {
645+
t.Errorf("Expected 3 restricted file paths, got %d", len(restrictedPaths))
646+
}
647+
648+
// Verify max_file_size
649+
maxFileSizeRules := flattenedRulesMap["max_file_size"].([]map[string]interface{})
650+
if len(maxFileSizeRules) != 1 {
651+
t.Fatalf("Expected 1 max_file_size rule, got %d", len(maxFileSizeRules))
652+
}
653+
if maxFileSizeRules[0]["max_file_size"] != int64(10485760) {
654+
t.Errorf("Expected max_file_size to be 10485760, got %v", maxFileSizeRules[0]["max_file_size"])
655+
}
656+
657+
// Verify max_file_path_length
658+
maxFilePathLengthRules := flattenedRulesMap["max_file_path_length"].([]map[string]interface{})
659+
if len(maxFilePathLengthRules) != 1 {
660+
t.Fatalf("Expected 1 max_file_path_length rule, got %d", len(maxFilePathLengthRules))
661+
}
662+
if maxFilePathLengthRules[0]["max_file_path_length"] != 250 {
663+
t.Errorf("Expected max_file_path_length to be 250, got %v", maxFilePathLengthRules[0]["max_file_path_length"])
664+
}
665+
666+
// Verify file_extension_restriction
667+
fileExtRules := flattenedRulesMap["file_extension_restriction"].([]map[string]interface{})
668+
if len(fileExtRules) != 1 {
669+
t.Fatalf("Expected 1 file_extension_restriction rule, got %d", len(fileExtRules))
670+
}
671+
restrictedExts := fileExtRules[0]["restricted_file_extensions"].([]string)
672+
if len(restrictedExts) != 4 {
673+
t.Errorf("Expected 4 restricted file extensions, got %d", len(restrictedExts))
674+
}
675+
}

github/resource_github_repository_ruleset.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,21 @@ func resourceGithubRepositoryRuleset() *schema.Resource {
544544
},
545545
},
546546
},
547+
"max_file_path_length": {
548+
Type: schema.TypeList,
549+
Optional: true,
550+
MaxItems: 1,
551+
Description: "Prevent pushes based on file path length.",
552+
Elem: &schema.Resource{
553+
Schema: map[string]*schema.Schema{
554+
"max_file_path_length": {
555+
Type: schema.TypeInt,
556+
Required: true,
557+
Description: "The maximum allowed length of a file path.",
558+
},
559+
},
560+
},
561+
},
547562
"file_extension_restriction": {
548563
Type: schema.TypeList,
549564
Optional: true,

github/respository_rules_utils.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,17 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule {
441441

442442
}
443443

444+
// max_file_path_length rule
445+
if v, ok := rulesMap["max_file_path_length"].([]interface{}); ok && len(v) != 0 {
446+
maxFilePathLengthMap := v[0].(map[string]interface{})
447+
maxFilePathLength := maxFilePathLengthMap["max_file_path_length"].(int)
448+
params := &github.RuleMaxFilePathLengthParameters{
449+
MaxFilePathLength: maxFilePathLength,
450+
}
451+
rulesSlice = append(rulesSlice, github.NewMaxFilePathLengthRule(params))
452+
453+
}
454+
444455
// file_extension_restriction rule
445456
if v, ok := rulesMap["file_extension_restriction"].([]interface{}); ok && len(v) != 0 {
446457
fileExtensionRestrictionMap := v[0].(map[string]interface{})
@@ -640,6 +651,45 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} {
640651
rule := make(map[string]interface{})
641652
rule["restricted_file_paths"] = params.GetRestrictedFilePaths()
642653
rulesMap[v.Type] = []map[string]interface{}{rule}
654+
655+
case "max_file_size":
656+
var params github.RuleMaxFileSizeParameters
657+
err := json.Unmarshal(*v.Parameters, &params)
658+
if err != nil {
659+
log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v",
660+
v.Type, v.Parameters)
661+
}
662+
rule := make(map[string]interface{})
663+
rule["max_file_size"] = params.MaxFileSize
664+
rulesMap[v.Type] = []map[string]interface{}{rule}
665+
666+
case "max_file_path_length":
667+
var params github.RuleMaxFilePathLengthParameters
668+
err := json.Unmarshal(*v.Parameters, &params)
669+
if err != nil {
670+
log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v",
671+
v.Type, v.Parameters)
672+
}
673+
rule := make(map[string]interface{})
674+
rule["max_file_path_length"] = params.MaxFilePathLength
675+
rulesMap[v.Type] = []map[string]interface{}{rule}
676+
677+
case "file_extension_restriction":
678+
var params github.RuleFileExtensionRestrictionParameters
679+
err := json.Unmarshal(*v.Parameters, &params)
680+
if err != nil {
681+
log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v",
682+
v.Type, v.Parameters)
683+
}
684+
rule := make(map[string]interface{})
685+
rule["restricted_file_extensions"] = params.RestrictedFileExtensions
686+
rulesMap[v.Type] = []map[string]interface{}{rule}
687+
688+
default:
689+
// Handle unknown rule types (like Copilot code review, etc.) gracefully
690+
// Log the unknown rule type but don't cause Terraform to fail or see a diff
691+
log.Printf("[DEBUG] Ignoring unknown repository rule type: %s. This rule was likely added outside of Terraform (e.g., via GitHub UI) and is not yet supported by the provider.", v.Type)
692+
// Note: We intentionally don't add this to rulesMap to avoid causing diffs for rules that aren't managed by Terraform
643693
}
644694
}
645695

0 commit comments

Comments
 (0)