Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CodeScanningService.ListAnalysesForRepo and CodeScanningService.GetAnalysis #2210

Merged
merged 5 commits into from Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 85 additions & 0 deletions github/code-scanning.go
Expand Up @@ -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/<branch name> or simply <branch name>.
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
Expand Down Expand Up @@ -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
}
208 changes: 204 additions & 4 deletions github/code-scanning_test.go
Expand Up @@ -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()
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
})
}