Repository: google/go-github
Pull request #3256 (2024-09-03 14:53:27)

feat: Add support for code_scanning
Suggestion to add support for code_scanning in Github API.

[Ruleset API docs](https://docs.github.com/en/rest/orgs/rules?apiVersion=2022-11-28#create-an-organization-repository-ruleset)

gmlewis: @ihor-hrytskiv - can you please resolve the conflicts in this PR and push (not force-push) the updates when you get a chance?  Hopefully we will then get a second LGTM+Approval and can proceed to merge.  Thank you!
gmlewis: Thank you, @air-hand !
Updating branch...

Patch:
diff --git a/github/repos_rules.go b/github/repos_rules.go
index b812bd0b..bb8aa121 100644
--- a/github/repos_rules.go
+++ b/github/repos_rules.go
@@ -159,6 +159,18 @@ type RequiredWorkflowsRuleParameters struct {
 	RequiredWorkflows    []*RuleRequiredWorkflow `json:"workflows"`
 }
 
+// RuleRequiredCodeScanningTools represents the RequiredCodeScanningTools for the RequiredCodeScanningParameters object.
+type RuleRequiredCodeScanningTools struct {
+	AlertsThreshold         string `json:"alerts_threshold"`
+	SecurityAlertsThreshold string `json:"security_alerts_threshold"`
+	Tool                    string `json:"tool"`
+}
+
+// RequiredCodeScanningRuleParameters represents the code_scanning rule parameters.
+type RequiredCodeScanningRuleParameters struct {
+	RequiredCodeScanningTools []*RuleRequiredCodeScanningTools `json:"code_scanning_tools"`
+}
+
 // RepositoryRule represents a GitHub Rule.
 type RepositoryRule struct {
 	Type              string           `json:"type"`
@@ -491,6 +503,15 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error {
 		bytes, _ := json.Marshal(params)
 		rawParams := json.RawMessage(bytes)
 
+		r.Parameters = &rawParams
+	case "code_scanning":
+		params := RequiredCodeScanningRuleParameters{}
+		if err := json.Unmarshal(*RepositoryRule.Parameters, &params); err != nil {
+			return err
+		}
+		bytes, _ := json.Marshal(params)
+		rawParams := json.RawMessage(bytes)
+
 		r.Parameters = &rawParams
 	case "max_file_path_length":
 		params := RuleMaxFilePathLengthParameters{}
@@ -705,6 +726,18 @@ func NewRequiredWorkflowsRule(params *RequiredWorkflowsRuleParameters) (rule *Re
 	}
 }
 
+// NewRequiredCodeScanningRule creates a rule to require which tools must provide code scanning results before the reference is updated.
+func NewRequiredCodeScanningRule(params *RequiredCodeScanningRuleParameters) (rule *RepositoryRule) {
+	bytes, _ := json.Marshal(params)
+
+	rawParams := json.RawMessage(bytes)
+
+	return &RepositoryRule{
+		Type:       "code_scanning",
+		Parameters: &rawParams,
+	}
+}
+
 // NewFilePathRestrictionRule creates a rule to restrict file paths from being pushed to.
 func NewFilePathRestrictionRule(params *RuleFileParameters) (rule *RepositoryRule) {
 	bytes, _ := json.Marshal(params)


Test patch:
diff --git a/github/orgs_rules_test.go b/github/orgs_rules_test.go
index 66cf6bd5..353705fe 100644
--- a/github/orgs_rules_test.go
+++ b/github/orgs_rules_test.go
@@ -202,6 +202,18 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 				  "operator": "contains",
 				  "pattern": "github"
 				}
+			  },
+			  {
+			    "type": "code_scanning",
+			    "parameters": {
+				  "code_scanning_tools": [
+				    {
+					  "tool": "CodeQL",
+					  "security_alerts_threshold": "high_or_higher",
+					  "alerts_threshold": "errors"
+				    }
+				  ]
+			    }
 			  }
 			]
 		  }`)
@@ -288,6 +300,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	})
 	if err != nil {
@@ -374,6 +395,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoNames(t *testing.T)
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	}
 	if !cmp.Equal(ruleset, want) {
@@ -524,6 +554,18 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 				  "operator": "contains",
 				  "pattern": "github"
 				}
+			  },
+			  {
+			    "type": "code_scanning",
+			    "parameters": {
+				  "code_scanning_tools": [
+				    {
+					  "tool": "CodeQL",
+					  "security_alerts_threshold": "high_or_higher",
+					  "alerts_threshold": "errors"
+				    }
+				  ]
+			    }
 			  }
 			]
 		  }`)
@@ -617,6 +659,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	})
 	if err != nil {
@@ -710,6 +761,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoProperty(t *testing.
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	}
 	if !cmp.Equal(ruleset, want) {
@@ -852,6 +912,18 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 				  "operator": "contains",
 				  "pattern": "github"
 				}
+			  },
+			  {
+			    "type": "code_scanning",
+			    "parameters": {
+				  "code_scanning_tools": [
+				    {
+					  "tool": "CodeQL",
+					  "security_alerts_threshold": "high_or_higher",
+					  "alerts_threshold": "errors"
+				    }
+				  ]
+			    }
 			  }
 			]
 		  }`)
@@ -936,6 +1008,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	})
 	if err != nil {
@@ -1020,6 +1101,15 @@ func TestOrganizationsService_CreateOrganizationRuleset_RepoIDs(t *testing.T) {
 				Operator: "contains",
 				Pattern:  "github",
 			}),
+			NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
 		},
 	}
 	if !cmp.Equal(ruleset, want) {
diff --git a/github/repos_rules_test.go b/github/repos_rules_test.go
index e7faf7e7..4af486aa 100644
--- a/github/repos_rules_test.go
+++ b/github/repos_rules_test.go
@@ -294,7 +294,7 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) {
 			},
 			wantErr: true,
 		},
-		"Required workflows params": {
+		"Valid Required workflows params": {
 			data: `{"type":"workflows","parameters":{"workflows":[{"path": ".github/workflows/test.yml", "repository_id": 1}]}}`,
 			want: NewRequiredWorkflowsRule(&RequiredWorkflowsRuleParameters{
 				RequiredWorkflows: []*RuleRequiredWorkflow{
@@ -305,6 +305,34 @@ func TestRepositoryRule_UnmarshalJSON(t *testing.T) {
 				},
 			}),
 		},
+		"Invalid Required workflows params": {
+			data: `{"type":"workflows","parameters":{"workflows":[{"path": ".github/workflows/test.yml", "repository_id": "test"}]}}`,
+			want: &RepositoryRule{
+				Type:       "workflows",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
+		"Valid Required code_scanning params": {
+			data: `{"type":"code_scanning","parameters":{"code_scanning_tools":[{"tool": "CodeQL", "security_alerts_threshold": "high_or_higher", "alerts_threshold": "errors"}]}}`,
+			want: NewRequiredCodeScanningRule(&RequiredCodeScanningRuleParameters{
+				RequiredCodeScanningTools: []*RuleRequiredCodeScanningTools{
+					{
+						Tool:                    "CodeQL",
+						SecurityAlertsThreshold: "high_or_higher",
+						AlertsThreshold:         "errors",
+					},
+				},
+			}),
+		},
+		"Invalid Required code_scanning params": {
+			data: `{"type":"code_scanning","parameters":{"code_scanning_tools":[{"tool": 1}]}}`,
+			want: &RepositoryRule{
+				Type:       "code_scanning",
+				Parameters: nil,
+			},
+			wantErr: true,
+		},
 		"Invalid type": {
 			data: `{"type":"unknown"}`,
 			want: &RepositoryRule{