diff --git a/github/code-scanning.go b/github/code-scanning.go index 4508c3390d..9616f3a26d 100644 --- a/github/code-scanning.go +++ b/github/code-scanning.go @@ -119,6 +119,38 @@ type AlertListOptions struct { ListOptions } +// AnalysesListOptions specifies optional parameters to the CodeScanningService.ListAnalysesForRepo method. +type AnalysesListOptions struct { + // Return code scanning analyses belonging to the same SARIF upload. + SarifID *string `url:"sarif_id,omitempty"` + + // Return code scanning analyses for a specific branch reference. The ref can be formatted as refs/heads/ or simply . + Ref *string `url:"ref,omitempty"` + + ListOptions +} + +// ScanningAnalysis represents an individual GitHub Code Scanning ScanningAnalysis on a single repository. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-analyses-for-a-repository +type ScanningAnalysis struct { + ID *int64 `json:"id,omitempty"` + Ref *string `json:"ref,omitempty"` + CommitSHA *string `json:"commit_sha,omitempty"` + AnalysisKey *string `json:"analysis_key,omitempty"` + Environment *string `json:"environment,omitempty"` + Error *string `json:"error,omitempty"` + Category *string `json:"category,omitempty"` + CreatedAt *Timestamp `json:"created_at,omitempty"` + ResultsCount *int `json:"results_count,omitempty"` + RulesCount *int `json:"rules_count,omitempty"` + URL *string `json:"url,omitempty"` + SarifID *string `json:"sarif_id,omitempty"` + Tool *Tool `json:"tool,omitempty"` + Deletable *bool `json:"deletable,omitempty"` + Warning *string `json:"warning,omitempty"` +} + // SarifAnalysis specifies the results of a code scanning job. // // GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#upload-an-analysis-as-sarif-data @@ -215,3 +247,56 @@ func (s *CodeScanningService) UploadSarif(ctx context.Context, owner, repo strin return sarifID, resp, nil } + +// ListAnalysesForRepo lists code scanning analyses for a repository. +// +// Lists the details of all code scanning analyses for a repository, starting with the most recent. +// You must use an access token with the security_events scope to use this endpoint. +// GitHub Apps must have the security_events read permission to use this endpoint. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-analyses-for-a-repository +func (s *CodeScanningService) ListAnalysesForRepo(ctx context.Context, owner, repo string, opts *AnalysesListOptions) ([]*ScanningAnalysis, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/code-scanning/analyses", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var analyses []*ScanningAnalysis + resp, err := s.client.Do(ctx, req, &analyses) + if err != nil { + return nil, resp, err + } + + return analyses, resp, nil +} + +// GetAnalysis gets a single code scanning analysis for a repository. +// +// You must use an access token with the security_events scope to use this endpoint. +// GitHub Apps must have the security_events read permission to use this endpoint. +// +// The security analysis_id is the ID of the analysis, as returned from the ListAnalysesForRepo operation. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#get-a-code-scanning-analysis-for-a-repository +func (s *CodeScanningService) GetAnalysis(ctx context.Context, owner, repo string, id int64) (*ScanningAnalysis, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/code-scanning/analyses/%v", owner, repo, id) + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + analysis := new(ScanningAnalysis) + resp, err := s.client.Do(ctx, req, analysis) + if err != nil { + return nil, resp, err + } + + return analysis, resp, nil +} diff --git a/github/code-scanning_test.go b/github/code-scanning_test.go index e96d0a0d14..0be58153f0 100644 --- a/github/code-scanning_test.go +++ b/github/code-scanning_test.go @@ -16,7 +16,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestActionsService_Alert_ID(t *testing.T) { +func TestCodeScanningService_Alert_ID(t *testing.T) { // Test: nil Alert ID == 0 var a *Alert id := a.ID() @@ -54,7 +54,7 @@ func TestActionsService_Alert_ID(t *testing.T) { } } -func TestActionsService_UploadSarif(t *testing.T) { +func TestCodeScanningService_UploadSarif(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -89,7 +89,7 @@ func TestActionsService_UploadSarif(t *testing.T) { }) } -func TestActionsService_ListAlertsForRepo(t *testing.T) { +func TestCodeScanningService_ListAlertsForRepo(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -283,7 +283,7 @@ func TestActionsService_ListAlertsForRepo(t *testing.T) { }) } -func TestActionsService_GetAlert(t *testing.T) { +func TestCodeScanningService_GetAlert(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -530,3 +530,203 @@ func TestMessage_Marshal(t *testing.T) { testJSONMarshal(t, u, want) } + +func TestCodeScanningService_ListAnalysesForRepo(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/code-scanning/analyses", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + testFormValues(t, r, values{"sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582", "ref": "heads/master"}) + fmt.Fprint(w, `[ + { + "ref": "refs/heads/main", + "commit_sha": "d99612c3e1f2970085cfbaeadf8f010ef69bad83", + "analysis_key": ".github/workflows/codeql-analysis.yml:analyze", + "environment": "{\"language\":\"python\"}", + "error": "", + "category": ".github/workflows/codeql-analysis.yml:analyze/language:python", + "created_at": "2020-08-27T15:05:21Z", + "results_count": 17, + "rules_count": 49, + "id": 201, + "url": "https://api.github.com/repos/o/r/code-scanning/analyses/201", + "sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582", + "tool": { + "name": "CodeQL", + "guid": null, + "version": "2.4.0" + }, + "deletable": true, + "warning": "" + }, + { + "ref": "refs/heads/my-branch", + "commit_sha": "c8cff6510d4d084fb1b4aa13b64b97ca12b07321", + "analysis_key": ".github/workflows/shiftleft.yml:build", + "environment": "{}", + "error": "", + "category": ".github/workflows/shiftleft.yml:build/", + "created_at": "2020-08-27T15:05:21Z", + "results_count": 17, + "rules_count": 32, + "id": 200, + "url": "https://api.github.com/repos/o/r/code-scanning/analyses/200", + "sarif_id": "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582", + "tool": { + "name": "Python Security ScanningAnalysis", + "guid": null, + "version": "1.2.0" + }, + "deletable": true, + "warning": "" + } + ]`) + }) + + opts := &AnalysesListOptions{SarifID: String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"), Ref: String("heads/master")} + ctx := context.Background() + analyses, _, err := client.CodeScanning.ListAnalysesForRepo(ctx, "o", "r", opts) + if err != nil { + t.Errorf("CodeScanning.ListAnalysesForRepo returned error: %v", err) + } + + date := &Timestamp{time.Date(2020, time.August, 27, 15, 05, 21, 0, time.UTC)} + want := []*ScanningAnalysis{ + { + ID: Int64(201), + Ref: String("refs/heads/main"), + CommitSHA: String("d99612c3e1f2970085cfbaeadf8f010ef69bad83"), + AnalysisKey: String(".github/workflows/codeql-analysis.yml:analyze"), + Environment: String("{\"language\":\"python\"}"), + Error: String(""), + Category: String(".github/workflows/codeql-analysis.yml:analyze/language:python"), + CreatedAt: date, + ResultsCount: Int(17), + RulesCount: Int(49), + URL: String("https://api.github.com/repos/o/r/code-scanning/analyses/201"), + SarifID: String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"), + Tool: &Tool{ + Name: String("CodeQL"), + GUID: nil, + Version: String("2.4.0"), + }, + Deletable: Bool(true), + Warning: String(""), + }, + { + ID: Int64(200), + Ref: String("refs/heads/my-branch"), + CommitSHA: String("c8cff6510d4d084fb1b4aa13b64b97ca12b07321"), + AnalysisKey: String(".github/workflows/shiftleft.yml:build"), + Environment: String("{}"), + Error: String(""), + Category: String(".github/workflows/shiftleft.yml:build/"), + CreatedAt: date, + ResultsCount: Int(17), + RulesCount: Int(32), + URL: String("https://api.github.com/repos/o/r/code-scanning/analyses/200"), + SarifID: String("8981cd8e-b078-4ac3-a3be-1dad7dbd0b582"), + Tool: &Tool{ + Name: String("Python Security ScanningAnalysis"), + GUID: nil, + Version: String("1.2.0"), + }, + Deletable: Bool(true), + Warning: String(""), + }, + } + if !cmp.Equal(analyses, want) { + t.Errorf("CodeScanning.ListAnalysesForRepo returned %+v, want %+v", analyses, want) + } + + const methodName = "ListAnalysesForRepo" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.CodeScanning.ListAnalysesForRepo(ctx, "\n", "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.CodeScanning.ListAnalysesForRepo(ctx, "o", "r", opts) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestCodeScanningService_GetAnalysis(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/repos/o/r/code-scanning/analyses/3602840", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + fmt.Fprint(w, `{ + "ref": "refs/heads/main", + "commit_sha": "c18c69115654ff0166991962832dc2bd7756e655", + "analysis_key": ".github/workflows/codeql-analysis.yml:analyze", + "environment": "{\"language\":\"javascript\"}", + "error": "", + "category": ".github/workflows/codeql-analysis.yml:analyze/language:javascript", + "created_at": "2021-01-13T11:55:49Z", + "results_count": 3, + "rules_count": 67, + "id": 3602840, + "url": "https://api.github.com/repos/o/r/code-scanning/analyses/201", + "sarif_id": "47177e22-5596-11eb-80a1-c1e54ef945c6", + "tool": { + "name": "CodeQL", + "guid": null, + "version": "2.4.0" + }, + "deletable": true, + "warning": "" + }`) + }) + + ctx := context.Background() + analysis, _, err := client.CodeScanning.GetAnalysis(ctx, "o", "r", 3602840) + if err != nil { + t.Errorf("CodeScanning.GetAnalysis returned error: %v", err) + } + + date := &Timestamp{time.Date(2021, time.January, 13, 11, 55, 49, 0, time.UTC)} + want := &ScanningAnalysis{ + ID: Int64(3602840), + Ref: String("refs/heads/main"), + CommitSHA: String("c18c69115654ff0166991962832dc2bd7756e655"), + AnalysisKey: String(".github/workflows/codeql-analysis.yml:analyze"), + Environment: String("{\"language\":\"javascript\"}"), + Error: String(""), + Category: String(".github/workflows/codeql-analysis.yml:analyze/language:javascript"), + CreatedAt: date, + ResultsCount: Int(3), + RulesCount: Int(67), + URL: String("https://api.github.com/repos/o/r/code-scanning/analyses/201"), + SarifID: String("47177e22-5596-11eb-80a1-c1e54ef945c6"), + Tool: &Tool{ + Name: String("CodeQL"), + GUID: nil, + Version: String("2.4.0"), + }, + Deletable: Bool(true), + Warning: String(""), + } + if !cmp.Equal(analysis, want) { + t.Errorf("CodeScanning.GetAnalysis returned %+v, want %+v", analysis, want) + } + + const methodName = "GetAnalysis" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.CodeScanning.GetAnalysis(ctx, "\n", "\n", -123) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.CodeScanning.GetAnalysis(ctx, "o", "r", 3602840) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index 7bd870167e..4c4dc892c0 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -284,6 +284,22 @@ func (a *Alert) GetURL() string { return *a.URL } +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (a *AnalysesListOptions) GetRef() string { + if a == nil || a.Ref == nil { + return "" + } + return *a.Ref +} + +// GetSarifID returns the SarifID field if it's non-nil, zero value otherwise. +func (a *AnalysesListOptions) GetSarifID() string { + if a == nil || a.SarifID == nil { + return "" + } + return *a.SarifID +} + // GetVerifiablePasswordAuthentication returns the VerifiablePasswordAuthentication field if it's non-nil, zero value otherwise. func (a *APIMeta) GetVerifiablePasswordAuthentication() bool { if a == nil || a.VerifiablePasswordAuthentication == nil { @@ -15212,6 +15228,126 @@ func (s *SarifID) GetURL() string { return *s.URL } +// GetAnalysisKey returns the AnalysisKey field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetAnalysisKey() string { + if s == nil || s.AnalysisKey == nil { + return "" + } + return *s.AnalysisKey +} + +// GetCategory returns the Category field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetCategory() string { + if s == nil || s.Category == nil { + return "" + } + return *s.Category +} + +// GetCommitSHA returns the CommitSHA field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetCommitSHA() string { + if s == nil || s.CommitSHA == nil { + return "" + } + return *s.CommitSHA +} + +// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetCreatedAt() Timestamp { + if s == nil || s.CreatedAt == nil { + return Timestamp{} + } + return *s.CreatedAt +} + +// GetDeletable returns the Deletable field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetDeletable() bool { + if s == nil || s.Deletable == nil { + return false + } + return *s.Deletable +} + +// GetEnvironment returns the Environment field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetEnvironment() string { + if s == nil || s.Environment == nil { + return "" + } + return *s.Environment +} + +// GetError returns the Error field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetError() string { + if s == nil || s.Error == nil { + return "" + } + return *s.Error +} + +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetID() int64 { + if s == nil || s.ID == nil { + return 0 + } + return *s.ID +} + +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetRef() string { + if s == nil || s.Ref == nil { + return "" + } + return *s.Ref +} + +// GetResultsCount returns the ResultsCount field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetResultsCount() int { + if s == nil || s.ResultsCount == nil { + return 0 + } + return *s.ResultsCount +} + +// GetRulesCount returns the RulesCount field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetRulesCount() int { + if s == nil || s.RulesCount == nil { + return 0 + } + return *s.RulesCount +} + +// GetSarifID returns the SarifID field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetSarifID() string { + if s == nil || s.SarifID == nil { + return "" + } + return *s.SarifID +} + +// GetTool returns the Tool field. +func (s *ScanningAnalysis) GetTool() *Tool { + if s == nil { + return nil + } + return s.Tool +} + +// GetURL returns the URL field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetURL() string { + if s == nil || s.URL == nil { + return "" + } + return *s.URL +} + +// GetWarning returns the Warning field if it's non-nil, zero value otherwise. +func (s *ScanningAnalysis) GetWarning() string { + if s == nil || s.Warning == nil { + return "" + } + return *s.Warning +} + // GetActive returns the Active field if it's non-nil, zero value otherwise. func (s *SCIMUserAttributes) GetActive() bool { if s == nil || s.Active == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index bbdff0ad3f..e3174035dd 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -308,6 +308,26 @@ func TestAlert_GetURL(tt *testing.T) { a.GetURL() } +func TestAnalysesListOptions_GetRef(tt *testing.T) { + var zeroValue string + a := &AnalysesListOptions{Ref: &zeroValue} + a.GetRef() + a = &AnalysesListOptions{} + a.GetRef() + a = nil + a.GetRef() +} + +func TestAnalysesListOptions_GetSarifID(tt *testing.T) { + var zeroValue string + a := &AnalysesListOptions{SarifID: &zeroValue} + a.GetSarifID() + a = &AnalysesListOptions{} + a.GetSarifID() + a = nil + a.GetSarifID() +} + func TestAPIMeta_GetVerifiablePasswordAuthentication(tt *testing.T) { var zeroValue bool a := &APIMeta{VerifiablePasswordAuthentication: &zeroValue} @@ -17789,6 +17809,153 @@ func TestSarifID_GetURL(tt *testing.T) { s.GetURL() } +func TestScanningAnalysis_GetAnalysisKey(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{AnalysisKey: &zeroValue} + s.GetAnalysisKey() + s = &ScanningAnalysis{} + s.GetAnalysisKey() + s = nil + s.GetAnalysisKey() +} + +func TestScanningAnalysis_GetCategory(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{Category: &zeroValue} + s.GetCategory() + s = &ScanningAnalysis{} + s.GetCategory() + s = nil + s.GetCategory() +} + +func TestScanningAnalysis_GetCommitSHA(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{CommitSHA: &zeroValue} + s.GetCommitSHA() + s = &ScanningAnalysis{} + s.GetCommitSHA() + s = nil + s.GetCommitSHA() +} + +func TestScanningAnalysis_GetCreatedAt(tt *testing.T) { + var zeroValue Timestamp + s := &ScanningAnalysis{CreatedAt: &zeroValue} + s.GetCreatedAt() + s = &ScanningAnalysis{} + s.GetCreatedAt() + s = nil + s.GetCreatedAt() +} + +func TestScanningAnalysis_GetDeletable(tt *testing.T) { + var zeroValue bool + s := &ScanningAnalysis{Deletable: &zeroValue} + s.GetDeletable() + s = &ScanningAnalysis{} + s.GetDeletable() + s = nil + s.GetDeletable() +} + +func TestScanningAnalysis_GetEnvironment(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{Environment: &zeroValue} + s.GetEnvironment() + s = &ScanningAnalysis{} + s.GetEnvironment() + s = nil + s.GetEnvironment() +} + +func TestScanningAnalysis_GetError(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{Error: &zeroValue} + s.GetError() + s = &ScanningAnalysis{} + s.GetError() + s = nil + s.GetError() +} + +func TestScanningAnalysis_GetID(tt *testing.T) { + var zeroValue int64 + s := &ScanningAnalysis{ID: &zeroValue} + s.GetID() + s = &ScanningAnalysis{} + s.GetID() + s = nil + s.GetID() +} + +func TestScanningAnalysis_GetRef(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{Ref: &zeroValue} + s.GetRef() + s = &ScanningAnalysis{} + s.GetRef() + s = nil + s.GetRef() +} + +func TestScanningAnalysis_GetResultsCount(tt *testing.T) { + var zeroValue int + s := &ScanningAnalysis{ResultsCount: &zeroValue} + s.GetResultsCount() + s = &ScanningAnalysis{} + s.GetResultsCount() + s = nil + s.GetResultsCount() +} + +func TestScanningAnalysis_GetRulesCount(tt *testing.T) { + var zeroValue int + s := &ScanningAnalysis{RulesCount: &zeroValue} + s.GetRulesCount() + s = &ScanningAnalysis{} + s.GetRulesCount() + s = nil + s.GetRulesCount() +} + +func TestScanningAnalysis_GetSarifID(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{SarifID: &zeroValue} + s.GetSarifID() + s = &ScanningAnalysis{} + s.GetSarifID() + s = nil + s.GetSarifID() +} + +func TestScanningAnalysis_GetTool(tt *testing.T) { + s := &ScanningAnalysis{} + s.GetTool() + s = nil + s.GetTool() +} + +func TestScanningAnalysis_GetURL(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{URL: &zeroValue} + s.GetURL() + s = &ScanningAnalysis{} + s.GetURL() + s = nil + s.GetURL() +} + +func TestScanningAnalysis_GetWarning(tt *testing.T) { + var zeroValue string + s := &ScanningAnalysis{Warning: &zeroValue} + s.GetWarning() + s = &ScanningAnalysis{} + s.GetWarning() + s = nil + s.GetWarning() +} + func TestSCIMUserAttributes_GetActive(tt *testing.T) { var zeroValue bool s := &SCIMUserAttributes{Active: &zeroValue}