From df3ad1071de950bda7aa1b5d5d802c4b310eb4ba Mon Sep 17 00:00:00 2001 From: stricklerxc <29963548+stricklerxc@users.noreply.github.com> Date: Fri, 11 Jul 2025 22:51:49 +0000 Subject: [PATCH 1/3] Fix required_code_scanning missing from terraform state --- github/respository_rules_utils.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/github/respository_rules_utils.go b/github/respository_rules_utils.go index 44ccb27de1..fe304a21f4 100644 --- a/github/respository_rules_utils.go +++ b/github/respository_rules_utils.go @@ -542,9 +542,31 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { rule["min_entries_to_merge"] = params.MinEntriesToMerge rule["min_entries_to_merge_wait_minutes"] = params.MinEntriesToMergeWaitMinutes rulesMap[v.Type] = []map[string]interface{}{rule} + + case "required_code_scanning": + var params github.RequiredCodeScanningRuleParameters + + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + + requiredCodeScanningToolSlice := make([]map[string]interface{}, 0) + for _, tool := range params.RequiredCodeScanningTools { + requiredCodeScanningToolSlice = append(requiredCodeScanningToolSlice, map[string]interface{}{ + "alerts_threshold": tool.AlertsThreshold, + "security_alerts_threshold": tool.SecurityAlertsThreshold, + "tool": tool.Tool, + }) + } + + rule := make(map[string]interface{}) + rule["required_code_scanning_tool"] = requiredCodeScanningToolSlice + rulesMap[v.Type] = []map[string]interface{}{rule} } - } + } return []interface{}{rulesMap} } From 765b0cc6245a606999c2161919b072775f444b9e Mon Sep 17 00:00:00 2001 From: Nick Floyd Date: Wed, 26 Nov 2025 14:31:36 -0600 Subject: [PATCH 2/3] fix: resolve build errors in respository_rules_utils.go --- github/respository_rules_utils.go | 333 ++++++++++++++++++++++-------- 1 file changed, 246 insertions(+), 87 deletions(-) diff --git a/github/respository_rules_utils.go b/github/respository_rules_utils.go index fe304a21f4..9cc89108eb 100644 --- a/github/respository_rules_utils.go +++ b/github/respository_rules_utils.go @@ -28,23 +28,38 @@ func resourceGithubRulesetObject(d *schema.ResourceData, org string) *github.Rul Source: source, SourceType: &sourceType, Enforcement: d.Get("enforcement").(string), - BypassActors: expandBypassActors(d.Get("bypass_actors").([]interface{})), - Conditions: expandConditions(d.Get("conditions").([]interface{}), isOrgLevel), - Rules: expandRules(d.Get("rules").([]interface{}), isOrgLevel), + BypassActors: expandBypassActors(d.Get("bypass_actors").([]any)), + Conditions: expandConditions(d.Get("conditions").([]any), isOrgLevel), + Rules: expandRules(d.Get("rules").([]any), isOrgLevel), } } -func expandBypassActors(input []interface{}) []*github.BypassActor { +func expandBypassActors(input []any) []*github.BypassActor { if len(input) == 0 { - return nil + // IMPORTANT: + // Always return an empty slice ([]), not nil. + // If this function returns nil, go-github serializes the field as `"bypass_actors": null`, + // which causes GitHub API to reject the request with: + // 422 "Invalid property /bypass_actors: data cannot be null." + // + // According to the GitHub REST API specification: + // - The "bypass_actors" field must be an array (even if empty). + // - Sending `null` is invalid; sending `[]` explicitly clears the list. + // Reference: + // https://docs.github.com/en/rest/repos/rules#get-a-repository-ruleset + return []*github.BypassActor{} } bypassActors := make([]*github.BypassActor, 0) for _, v := range input { - inputMap := v.(map[string]interface{}) + inputMap := v.(map[string]any) actor := &github.BypassActor{} if v, ok := inputMap["actor_id"].(int); ok { - actor.ActorID = github.Int64(int64(v)) + if v == 0 { + actor.ActorID = nil + } else { + actor.ActorID = github.Int64(int64(v)) + } } if v, ok := inputMap["actor_type"].(string); ok { @@ -59,14 +74,14 @@ func expandBypassActors(input []interface{}) []*github.BypassActor { return bypassActors } -func flattenBypassActors(bypassActors []*github.BypassActor) []interface{} { +func flattenBypassActors(bypassActors []*github.BypassActor) []any { if bypassActors == nil { - return []interface{}{} + return []any{} } - actorsSlice := make([]interface{}, 0) + actorsSlice := make([]any, 0) for _, v := range bypassActors { - actorMap := make(map[string]interface{}) + actorMap := make(map[string]any) actorMap["actor_id"] = v.GetActorID() actorMap["actor_type"] = v.GetActorType() @@ -78,26 +93,26 @@ func flattenBypassActors(bypassActors []*github.BypassActor) []interface{} { return actorsSlice } -func expandConditions(input []interface{}, org bool) *github.RulesetConditions { +func expandConditions(input []any, org bool) *github.RulesetConditions { if len(input) == 0 || input[0] == nil { return nil } rulesetConditions := &github.RulesetConditions{} - inputConditions := input[0].(map[string]interface{}) + inputConditions := input[0].(map[string]any) // ref_name is available for both repo and org rulesets - if v, ok := inputConditions["ref_name"].([]interface{}); ok && v != nil && len(v) != 0 { - inputRefName := v[0].(map[string]interface{}) + if v, ok := inputConditions["ref_name"].([]any); ok && v != nil && len(v) != 0 { + inputRefName := v[0].(map[string]any) include := make([]string, 0) exclude := make([]string, 0) - for _, v := range inputRefName["include"].([]interface{}) { + for _, v := range inputRefName["include"].([]any) { if v != nil { include = append(include, v.(string)) } } - for _, v := range inputRefName["exclude"].([]interface{}) { + for _, v := range inputRefName["exclude"].([]any) { if v != nil { exclude = append(exclude, v.(string)) } @@ -112,18 +127,18 @@ func expandConditions(input []interface{}, org bool) *github.RulesetConditions { // org-only fields if org { // repository_name and repository_id - if v, ok := inputConditions["repository_name"].([]interface{}); ok && v != nil && len(v) != 0 { - inputRepositoryName := v[0].(map[string]interface{}) + if v, ok := inputConditions["repository_name"].([]any); ok && v != nil && len(v) != 0 { + inputRepositoryName := v[0].(map[string]any) include := make([]string, 0) exclude := make([]string, 0) - for _, v := range inputRepositoryName["include"].([]interface{}) { + for _, v := range inputRepositoryName["include"].([]any) { if v != nil { include = append(include, v.(string)) } } - for _, v := range inputRepositoryName["exclude"].([]interface{}) { + for _, v := range inputRepositoryName["exclude"].([]any) { if v != nil { exclude = append(exclude, v.(string)) } @@ -136,7 +151,7 @@ func expandConditions(input []interface{}, org bool) *github.RulesetConditions { Exclude: exclude, Protected: &protected, } - } else if v, ok := inputConditions["repository_id"].([]interface{}); ok && v != nil && len(v) != 0 { + } else if v, ok := inputConditions["repository_id"].([]any); ok && v != nil && len(v) != 0 { repositoryIDs := make([]int64, 0) for _, v := range v { @@ -152,15 +167,15 @@ func expandConditions(input []interface{}, org bool) *github.RulesetConditions { return rulesetConditions } -func flattenConditions(conditions *github.RulesetConditions, org bool) []interface{} { +func flattenConditions(conditions *github.RulesetConditions, org bool) []any { if conditions == nil || conditions.RefName == nil { - return []interface{}{} + return []any{} } - conditionsMap := make(map[string]interface{}) - refNameSlice := make([]map[string]interface{}, 0) + conditionsMap := make(map[string]any) + refNameSlice := make([]map[string]any, 0) - refNameSlice = append(refNameSlice, map[string]interface{}{ + refNameSlice = append(refNameSlice, map[string]any{ "include": conditions.RefName.Include, "exclude": conditions.RefName.Exclude, }) @@ -169,7 +184,7 @@ func flattenConditions(conditions *github.RulesetConditions, org bool) []interfa // org-only fields if org { - repositoryNameSlice := make([]map[string]interface{}, 0) + repositoryNameSlice := make([]map[string]any, 0) if conditions.RepositoryName != nil { var protected bool @@ -178,7 +193,7 @@ func flattenConditions(conditions *github.RulesetConditions, org bool) []interfa protected = *conditions.RepositoryName.Protected } - repositoryNameSlice = append(repositoryNameSlice, map[string]interface{}{ + repositoryNameSlice = append(repositoryNameSlice, map[string]any{ "include": conditions.RepositoryName.Include, "exclude": conditions.RepositoryName.Exclude, "protected": protected, @@ -191,15 +206,15 @@ func flattenConditions(conditions *github.RulesetConditions, org bool) []interfa } } - return []interface{}{conditionsMap} + return []any{conditionsMap} } -func expandRules(input []interface{}, org bool) []*github.RepositoryRule { +func expandRules(input []any, org bool) []*github.RepositoryRule { if len(input) == 0 || input[0] == nil { return nil } - rulesMap := input[0].(map[string]interface{}) + rulesMap := input[0].(map[string]any) rulesSlice := make([]*github.RepositoryRule, 0) // First we expand rules without parameters @@ -235,16 +250,16 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { // Required deployments rule if !org { - if v, ok := rulesMap["required_deployments"].([]interface{}); ok && len(v) != 0 { - requiredDeploymentsMap := make(map[string]interface{}) + if v, ok := rulesMap["required_deployments"].([]any); ok && len(v) != 0 { + requiredDeploymentsMap := make(map[string]any) // If the rule's block is present but has an empty environments list if v[0] == nil { - requiredDeploymentsMap["required_deployment_environments"] = make([]interface{}, 0) + requiredDeploymentsMap["required_deployment_environments"] = make([]any, 0) } else { - requiredDeploymentsMap = v[0].(map[string]interface{}) + requiredDeploymentsMap = v[0].(map[string]any) } envs := make([]string, 0) - for _, v := range requiredDeploymentsMap["required_deployment_environments"].([]interface{}) { + for _, v := range requiredDeploymentsMap["required_deployment_environments"].([]any) { envs = append(envs, v.(string)) } @@ -258,8 +273,8 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { // Pattern parameter rules for _, k := range []string{"commit_message_pattern", "commit_author_email_pattern", "committer_email_pattern", "branch_name_pattern", "tag_name_pattern"} { - if v, ok := rulesMap[k].([]interface{}); ok && len(v) != 0 { - patternParametersMap := v[0].(map[string]interface{}) + if v, ok := rulesMap[k].([]any); ok && len(v) != 0 { + patternParametersMap := v[0].(map[string]any) name := patternParametersMap["name"].(string) negate := patternParametersMap["negate"].(bool) @@ -287,8 +302,8 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { } // Pull request rule - if v, ok := rulesMap["pull_request"].([]interface{}); ok && len(v) != 0 { - pullRequestMap := v[0].(map[string]interface{}) + if v, ok := rulesMap["pull_request"].([]any); ok && len(v) != 0 { + pullRequestMap := v[0].(map[string]any) params := &github.PullRequestRuleParameters{ DismissStaleReviewsOnPush: pullRequestMap["dismiss_stale_reviews_on_push"].(bool), RequireCodeOwnerReview: pullRequestMap["require_code_owner_review"].(bool), @@ -301,8 +316,8 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { } // Merge queue rule - if v, ok := rulesMap["merge_queue"].([]interface{}); ok && len(v) != 0 { - mergeQueueMap := v[0].(map[string]interface{}) + if v, ok := rulesMap["merge_queue"].([]any); ok && len(v) != 0 { + mergeQueueMap := v[0].(map[string]any) params := &github.MergeQueueRuleParameters{ CheckResponseTimeoutMinutes: mergeQueueMap["check_response_timeout_minutes"].(int), GroupingStrategy: mergeQueueMap["grouping_strategy"].(string), @@ -317,15 +332,15 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { } // Required status checks rule - if v, ok := rulesMap["required_status_checks"].([]interface{}); ok && len(v) != 0 { - requiredStatusMap := v[0].(map[string]interface{}) + if v, ok := rulesMap["required_status_checks"].([]any); ok && len(v) != 0 { + requiredStatusMap := v[0].(map[string]any) requiredStatusChecks := make([]github.RuleRequiredStatusChecks, 0) if requiredStatusChecksInput, ok := requiredStatusMap["required_check"]; ok { requiredStatusChecksSet := requiredStatusChecksInput.(*schema.Set) for _, checkMap := range requiredStatusChecksSet.List() { - check := checkMap.(map[string]interface{}) + check := checkMap.(map[string]any) integrationID := github.Int64(int64(check["integration_id"].(int))) params := github.RuleRequiredStatusChecks{ @@ -350,15 +365,15 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { } // Required workflows to pass before merging rule - if v, ok := rulesMap["required_workflows"].([]interface{}); ok && len(v) != 0 { - requiredWorkflowsMap := v[0].(map[string]interface{}) + if v, ok := rulesMap["required_workflows"].([]any); ok && len(v) != 0 { + requiredWorkflowsMap := v[0].(map[string]any) requiredWorkflows := make([]*github.RuleRequiredWorkflow, 0) if requiredWorkflowsInput, ok := requiredWorkflowsMap["required_workflow"]; ok { requiredWorkflowsSet := requiredWorkflowsInput.(*schema.Set) for _, workflowMap := range requiredWorkflowsSet.List() { - workflow := workflowMap.(map[string]interface{}) + workflow := workflowMap.(map[string]any) // Get all parameters repositoryID := github.Int64(int64(workflow["repository_id"].(int))) @@ -375,21 +390,22 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { } params := &github.RequiredWorkflowsRuleParameters{ - RequiredWorkflows: requiredWorkflows, + DoNotEnforceOnCreate: requiredWorkflowsMap["do_not_enforce_on_create"].(bool), + RequiredWorkflows: requiredWorkflows, } rulesSlice = append(rulesSlice, github.NewRequiredWorkflowsRule(params)) } // Required code scanning to pass before merging rule - if v, ok := rulesMap["required_code_scanning"].([]interface{}); ok && len(v) != 0 { - requiredCodeScanningMap := v[0].(map[string]interface{}) + if v, ok := rulesMap["required_code_scanning"].([]any); ok && len(v) != 0 { + requiredCodeScanningMap := v[0].(map[string]any) requiredCodeScanningTools := make([]*github.RuleRequiredCodeScanningTool, 0) if requiredCodeScanningInput, ok := requiredCodeScanningMap["required_code_scanning_tool"]; ok { requiredCodeScanningSet := requiredCodeScanningInput.(*schema.Set) for _, codeScanningMap := range requiredCodeScanningSet.List() { - codeScanningTool := codeScanningMap.(map[string]interface{}) + codeScanningTool := codeScanningMap.(map[string]any) // Get all parameters alertsThreshold := github.String(codeScanningTool["alerts_threshold"].(string)) @@ -412,15 +428,69 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule { rulesSlice = append(rulesSlice, github.NewRequiredCodeScanningRule(params)) } + // file_path_restriction rule + if v, ok := rulesMap["file_path_restriction"].([]any); ok && len(v) != 0 { + filePathRestrictionMap := v[0].(map[string]any) + restrictedFilePaths := make([]string, 0) + for _, path := range filePathRestrictionMap["restricted_file_paths"].([]any) { + restrictedFilePaths = append(restrictedFilePaths, path.(string)) + } + params := &github.RuleFileParameters{ + RestrictedFilePaths: &restrictedFilePaths, + } + rulesSlice = append(rulesSlice, github.NewFilePathRestrictionRule(params)) + } + + // max_file_size rule + if v, ok := rulesMap["max_file_size"].([]any); ok && len(v) != 0 { + maxFileSizeMap := v[0].(map[string]any) + maxFileSize := int64(maxFileSizeMap["max_file_size"].(int)) + params := &github.RuleMaxFileSizeParameters{ + MaxFileSize: maxFileSize, + } + rulesSlice = append(rulesSlice, github.NewMaxFileSizeRule(params)) + + } + + // max_file_path_length rule + if v, ok := rulesMap["max_file_path_length"].([]any); ok && len(v) != 0 { + maxFilePathLengthMap := v[0].(map[string]any) + maxFilePathLength := maxFilePathLengthMap["max_file_path_length"].(int) + params := &github.RuleMaxFilePathLengthParameters{ + MaxFilePathLength: maxFilePathLength, + } + rulesSlice = append(rulesSlice, github.NewMaxFilePathLengthRule(params)) + + } + + // file_extension_restriction rule + if v, ok := rulesMap["file_extension_restriction"].([]any); ok && len(v) != 0 { + fileExtensionRestrictionMap := v[0].(map[string]any) + restrictedFileExtensions := make([]string, 0) + + // Handle schema.TypeSet + extensionSet := fileExtensionRestrictionMap["restricted_file_extensions"].(*schema.Set) + for _, extension := range extensionSet.List() { + restrictedFileExtensions = append(restrictedFileExtensions, extension.(string)) + } + + if len(restrictedFileExtensions) > 0 { + params := &github.RuleFileExtensionRestrictionParameters{ + RestrictedFileExtensions: restrictedFileExtensions, + } + rulesSlice = append(rulesSlice, github.NewFileExtensionRestrictionRule(params)) + } + } + return rulesSlice } -func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { +func flattenRules(rules []*github.RepositoryRule, org bool) []any { if len(rules) == 0 || rules == nil { - return []interface{}{} + return []any{} } - rulesMap := make(map[string]interface{}) + rulesMap := make(map[string]any) for _, v := range rules { switch v.Type { case "creation", "deletion", "required_linear_history", "required_signatures", "non_fast_forward": @@ -458,12 +528,12 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { negate = *params.Negate } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["name"] = name rule["negate"] = negate rule["operator"] = params.Operator rule["pattern"] = params.Pattern - rulesMap[v.Type] = []map[string]interface{}{rule} + rulesMap[v.Type] = []map[string]any{rule} case "required_deployments": if !org { @@ -475,9 +545,9 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { v.Type, v.Parameters) } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["required_deployment_environments"] = params.RequiredDeploymentEnvironments - rulesMap[v.Type] = []map[string]interface{}{rule} + rulesMap[v.Type] = []map[string]any{rule} } case "pull_request": @@ -489,13 +559,13 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { v.Type, v.Parameters) } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["dismiss_stale_reviews_on_push"] = params.DismissStaleReviewsOnPush rule["require_code_owner_review"] = params.RequireCodeOwnerReview rule["require_last_push_approval"] = params.RequireLastPushApproval rule["required_approving_review_count"] = params.RequiredApprovingReviewCount rule["required_review_thread_resolution"] = params.RequiredReviewThreadResolution - rulesMap[v.Type] = []map[string]interface{}{rule} + rulesMap[v.Type] = []map[string]any{rule} case "required_status_checks": var params github.RequiredStatusChecksRuleParameters @@ -506,23 +576,68 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { v.Type, v.Parameters) } - requiredStatusChecksSlice := make([]map[string]interface{}, 0) + requiredStatusChecksSlice := make([]map[string]any, 0) for _, check := range params.RequiredStatusChecks { integrationID := int64(0) if check.IntegrationID != nil { integrationID = *check.IntegrationID } - requiredStatusChecksSlice = append(requiredStatusChecksSlice, map[string]interface{}{ + requiredStatusChecksSlice = append(requiredStatusChecksSlice, map[string]any{ "context": check.Context, "integration_id": integrationID, }) } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["required_check"] = requiredStatusChecksSlice rule["strict_required_status_checks_policy"] = params.StrictRequiredStatusChecksPolicy rule["do_not_enforce_on_create"] = params.DoNotEnforceOnCreate - rulesMap[v.Type] = []map[string]interface{}{rule} + rulesMap[v.Type] = []map[string]any{rule} + + case "workflows": + var params github.RequiredWorkflowsRuleParameters + + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + + requiredWorkflowsSlice := make([]map[string]any, 0) + for _, check := range params.RequiredWorkflows { + requiredWorkflowsSlice = append(requiredWorkflowsSlice, map[string]any{ + "repository_id": check.RepositoryID, + "path": check.Path, + "ref": check.Ref, + }) + } + + rule := make(map[string]any) + rule["do_not_enforce_on_create"] = params.DoNotEnforceOnCreate + rule["required_workflow"] = requiredWorkflowsSlice + rulesMap["required_workflows"] = []map[string]any{rule} + + case "code_scanning": + var params github.RequiredCodeScanningRuleParameters + + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + + requiredCodeScanningSlice := make([]map[string]any, 0) + for _, check := range params.RequiredCodeScanningTools { + requiredCodeScanningSlice = append(requiredCodeScanningSlice, map[string]any{ + "alerts_threshold": check.AlertsThreshold, + "security_alerts_threshold": check.SecurityAlertsThreshold, + "tool": check.Tool, + }) + } + + rule := make(map[string]any) + rule["required_code_scanning_tool"] = requiredCodeScanningSlice + rulesMap["required_code_scanning"] = []map[string]any{rule} case "merge_queue": var params github.MergeQueueRuleParameters @@ -533,7 +648,7 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { v.Type, v.Parameters) } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["check_response_timeout_minutes"] = params.CheckResponseTimeoutMinutes rule["grouping_strategy"] = params.GroupingStrategy rule["max_entries_to_build"] = params.MaxEntriesToBuild @@ -541,51 +656,95 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} { rule["merge_method"] = params.MergeMethod rule["min_entries_to_merge"] = params.MinEntriesToMerge rule["min_entries_to_merge_wait_minutes"] = params.MinEntriesToMergeWaitMinutes - rulesMap[v.Type] = []map[string]interface{}{rule} + rulesMap[v.Type] = []map[string]any{rule} case "required_code_scanning": var params github.RequiredCodeScanningRuleParameters - err := json.Unmarshal(*v.Parameters, ¶ms) - if err != nil { - log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", - v.Type, v.Parameters) - } - - requiredCodeScanningToolSlice := make([]map[string]interface{}, 0) + requiredCodeScanningToolSlice := make([]map[string]any, 0) for _, tool := range params.RequiredCodeScanningTools { - requiredCodeScanningToolSlice = append(requiredCodeScanningToolSlice, map[string]interface{}{ + requiredCodeScanningToolSlice = append(requiredCodeScanningToolSlice, map[string]any{ "alerts_threshold": tool.AlertsThreshold, "security_alerts_threshold": tool.SecurityAlertsThreshold, "tool": tool.Tool, }) } - rule := make(map[string]interface{}) + rule := make(map[string]any) rule["required_code_scanning_tool"] = requiredCodeScanningToolSlice - rulesMap[v.Type] = []map[string]interface{}{rule} - } + rulesMap[v.Type] = []map[string]any{rule} + + case "file_path_restriction": + var params github.RuleFileParameters + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + rule := make(map[string]any) + rule["restricted_file_paths"] = params.GetRestrictedFilePaths() + rulesMap[v.Type] = []map[string]any{rule} + case "max_file_size": + var params github.RuleMaxFileSizeParameters + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + rule := make(map[string]any) + rule["max_file_size"] = params.MaxFileSize + rulesMap[v.Type] = []map[string]any{rule} + + case "max_file_path_length": + var params github.RuleMaxFilePathLengthParameters + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + rule := make(map[string]any) + rule["max_file_path_length"] = params.MaxFilePathLength + rulesMap[v.Type] = []map[string]any{rule} + + case "file_extension_restriction": + var params github.RuleFileExtensionRestrictionParameters + err := json.Unmarshal(*v.Parameters, ¶ms) + if err != nil { + log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v", + v.Type, v.Parameters) + } + rule := make(map[string]any) + rule["restricted_file_extensions"] = params.RestrictedFileExtensions + rulesMap[v.Type] = []map[string]any{rule} + + default: + // Handle unknown rule types (like Copilot code review, etc.) gracefully + // Log the unknown rule type but don't cause Terraform to fail or see a diff + 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) + // Note: We intentionally don't add this to rulesMap to avoid causing diffs for rules that aren't managed by Terraform + } } - return []interface{}{rulesMap} + + return []any{rulesMap} } -func bypassActorsDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { +func bypassActorsDiffSuppressFunc(k, o, n string, d *schema.ResourceData) bool { // If the length has changed, no need to suppress if k == "bypass_actors.#" { - return old == new + return o == n } // Get change to bypass actors - o, n := d.GetChange("bypass_actors") - oldBypassActors := o.([]interface{}) - newBypassActors := n.([]interface{}) + oba, nba := d.GetChange("bypass_actors") + oldBypassActors := oba.([]any) + newBypassActors := nba.([]any) sort.SliceStable(oldBypassActors, func(i, j int) bool { - return oldBypassActors[i].(map[string]interface{})["actor_id"].(int) > oldBypassActors[j].(map[string]interface{})["actor_id"].(int) + return oldBypassActors[i].(map[string]any)["actor_id"].(int) > oldBypassActors[j].(map[string]any)["actor_id"].(int) }) sort.SliceStable(newBypassActors, func(i, j int) bool { - return newBypassActors[i].(map[string]interface{})["actor_id"].(int) > newBypassActors[j].(map[string]interface{})["actor_id"].(int) + return newBypassActors[i].(map[string]any)["actor_id"].(int) > newBypassActors[j].(map[string]any)["actor_id"].(int) }) return reflect.DeepEqual(oldBypassActors, newBypassActors) From 5ee5522317e26b89e4ad7b74d2321831add0c029 Mon Sep 17 00:00:00 2001 From: Nick Floyd Date: Wed, 26 Nov 2025 14:41:44 -0600 Subject: [PATCH 3/3] adds docs --- website/docs/r/organization_ruleset.html.markdown | 8 ++++++++ website/docs/r/repository_ruleset.html.markdown | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/website/docs/r/organization_ruleset.html.markdown b/website/docs/r/organization_ruleset.html.markdown index 8a114739dd..e04118f678 100644 --- a/website/docs/r/organization_ruleset.html.markdown +++ b/website/docs/r/organization_ruleset.html.markdown @@ -54,6 +54,14 @@ resource "github_organization_ruleset" "example" { ref = "main" } } + + required_code_scanning { + required_code_scanning_tool { + alerts_threshold = "errors" + security_alerts_threshold = "high_or_higher" + tool = "CodeQL" + } + } } } diff --git a/website/docs/r/repository_ruleset.html.markdown b/website/docs/r/repository_ruleset.html.markdown index 27c95547b0..277582d58e 100644 --- a/website/docs/r/repository_ruleset.html.markdown +++ b/website/docs/r/repository_ruleset.html.markdown @@ -49,7 +49,13 @@ resource "github_repository_ruleset" "example" { required_deployment_environments = ["test"] } - + required_code_scanning { + required_code_scanning_tool { + alerts_threshold = "errors" + security_alerts_threshold = "high_or_higher" + tool = "CodeQL" + } + } } }