Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions github/code-scanning.go
Original file line number Diff line number Diff line change
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
}

// Analysis represents an individual GitHub Code Scanning Analysis on a single repository.
//
// GitHub API docs: https://docs.github.com/en/rest/reference/code-scanning#list-code-scanning-analyses-for-a-repository
type Analysis struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be merged with the existing SarifAnalysis struct?
If not, can we give it a more specific name?

Copy link
Contributor Author

@yogur yogur Nov 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can't because SarifAnalysis is used for POST request body params, while Analysis is used to unmarshal json response of listing code scanning analysis instances.

I thought about renaming to to ScanningAnalysis or CodeScanningAnalysis but I felt that was redundant with the service name. CodeScanningService. What are your thoughts on this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that this name is global throughout the package github namespace. So even though it is used within the CodeScanningService, it is really github.Analysis which is pretty general, so if you don't mind switching to ScanningAnalysis, I think that would be preferable. Thank you!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes you're right. I updated it. Thanks for the review!

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) ([]*Analysis, *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 []*Analysis
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) (*Analysis, *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(Analysis)
resp, err := s.client.Do(ctx, req, analysis)
if err != nil {
return nil, resp, err
}

return analysis, resp, nil
}
200 changes: 200 additions & 0 deletions github/code-scanning_test.go
Original file line number Diff line number Diff line change
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 Analysis",
"guid": null,
"version": "1.2.0"
},
"deletable": true,
"warning": ""
}
]`)
})

opts := &AnalysesListOptions{SarifID: "8981cd8e-b078-4ac3-a3be-1dad7dbd0b582", Ref: "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 := []*Analysis{
{
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 Analysis"),
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 := &Analysis{
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
})
}
Loading