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 support for Dependency Graph Snapshots endpoint #2856

Merged
merged 7 commits into from May 5, 2024
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
113 changes: 113 additions & 0 deletions github/dependency_graph_snapshots.go
@@ -0,0 +1,113 @@
// Copyright 2023 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"context"
"fmt"
)

// DependencyGraphSnapshotResolvedDependency represents a resolved dependency in a dependency graph snapshot.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotResolvedDependency struct {
gmlewis marked this conversation as resolved.
Show resolved Hide resolved
PackageURL *string `json:"package_url,omitempty"`
// Represents whether the dependency is requested directly by the manifest or is a dependency of another dependency.
// Can have the following values:
// - "direct": indicates that the dependency is requested directly by the manifest.
// - "indirect": indicates that the dependency is a dependency of another dependency.
Relationship *string `json:"relationship,omitempty"`
// Represents whether the dependency is required for the primary build artifact or is only used for development.
// Can have the following values:
// - "runtime": indicates that the dependency is required for the primary build artifact.
// - "development": indicates that the dependency is only used for development.
Scope *string `json:"scope,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
}

// DependencyGraphSnapshotJob represents the job that created the snapshot.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotJob struct {
Correlator *string `json:"correlator,omitempty"`
ID *string `json:"id,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
}

// DependencyGraphSnapshotDetector represents a description of the detector used.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotDetector struct {
Name *string `json:"name,omitempty"`
Version *string `json:"version,omitempty"`
URL *string `json:"url,omitempty"`
}

// DependencyGraphSnapshotManifestFile represents the file declaring the repository's dependencies.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotManifestFile struct {
SourceLocation *string `json:"source_location,omitempty"`
}

// DependencyGraphSnapshotManifest represents a collection of related dependencies declared in a file or representing a logical group of dependencies.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotManifest struct {
Name *string `json:"name,omitempty"`
File *DependencyGraphSnapshotManifestFile `json:"file,omitempty"`
Resolved map[string]*DependencyGraphSnapshotResolvedDependency `json:"resolved,omitempty"`
}

// DependencyGraphSnapshot represent a snapshot of a repository's dependencies.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshot struct {
Version int `json:"version"`
Sha *string `json:"sha,omitempty"`
Ref *string `json:"ref,omitempty"`
Job *DependencyGraphSnapshotJob `json:"job,omitempty"`
Detector *DependencyGraphSnapshotDetector `json:"detector,omitempty"`
Scanned *Timestamp `json:"scanned,omitempty"`
Manifests map[string]*DependencyGraphSnapshotManifest `json:"manifests,omitempty"`
}

// DependencyGraphSnapshotCreationData represents the dependency snapshot's creation result.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
type DependencyGraphSnapshotCreationData struct {
ID int64 `json:"id"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
Message *string `json:"message,omitempty"`
// Represents the snapshot creation result.
// Can have the following values:
// - "SUCCESS": indicates that the snapshot was successfully created and the repository's dependencies were updated.
// - "ACCEPTED": indicates that the snapshot was successfully created, but the repository's dependencies were not updated.
// - "INVALID": indicates that the snapshot was malformed.
Result *string `json:"result,omitempty"`
}

// CreateSnapshot creates a new snapshot of a repository's dependencies.
//
// GitHub API docs: https://docs.github.com/rest/dependency-graph/dependency-submission#create-a-snapshot-of-dependencies-for-a-repository
//
//meta:operation POST /repos/{owner}/{repo}/dependency-graph/snapshots
func (s *DependencyGraphService) CreateSnapshot(ctx context.Context, owner, repo string, dependencyGraphSnapshot *DependencyGraphSnapshot) (*DependencyGraphSnapshotCreationData, *Response, error) {
url := fmt.Sprintf("repos/%v/%v/dependency-graph/snapshots", owner, repo)

req, err := s.client.NewRequest("POST", url, dependencyGraphSnapshot)
if err != nil {
return nil, nil, err
}

var snapshotCreationData *DependencyGraphSnapshotCreationData
resp, err := s.client.Do(ctx, req, &snapshotCreationData)
if err != nil {
return nil, resp, err
}

return snapshotCreationData, resp, nil
}
94 changes: 94 additions & 0 deletions github/dependency_graph_snapshots_test.go
@@ -0,0 +1,94 @@
// Copyright 2023 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"context"
"fmt"
"net/http"
"testing"
"time"

"github.com/google/go-cmp/cmp"
)

func TestDependencyGraphService_CreateSnapshot(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()

mux.HandleFunc("/repos/o/r/dependency-graph/snapshots", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "POST")
testBody(t, r, `{"version":0,"sha":"ce587453ced02b1526dfb4cb910479d431683101","ref":"refs/heads/main","job":{"correlator":"yourworkflowname_youractionname","id":"yourrunid","html_url":"https://example.com"},"detector":{"name":"octo-detector","version":"0.0.1","url":"https://github.com/octo-org/octo-repo"},"scanned":"2022-06-14T20:25:00Z","manifests":{"package-lock.json":{"name":"package-lock.json","file":{"source_location":"src/package-lock.json"},"resolved":{"@actions/core":{"package_url":"pkg:/npm/%40actions/core@1.1.9","relationship":"direct","scope":"runtime","dependencies":["@actions/http-client"]},"@actions/http-client":{"package_url":"pkg:/npm/%40actions/http-client@1.0.7","relationship":"indirect","scope":"runtime","dependencies":["tunnel"]},"tunnel":{"package_url":"pkg:/npm/tunnel@0.0.6","relationship":"indirect","scope":"runtime"}}}}}`+"\n")
fmt.Fprint(w, `{"id":12345,"created_at":"2022-06-14T20:25:01Z","message":"Dependency results for the repo have been successfully updated.","result":"SUCCESS"}`)
})

ctx := context.Background()
snapshot := &DependencyGraphSnapshot{
Version: 0,
Sha: String("ce587453ced02b1526dfb4cb910479d431683101"),
Ref: String("refs/heads/main"),
Job: &DependencyGraphSnapshotJob{
Correlator: String("yourworkflowname_youractionname"),
ID: String("yourrunid"),
HTMLURL: String("https://example.com"),
},
Detector: &DependencyGraphSnapshotDetector{
Name: String("octo-detector"),
Version: String("0.0.1"),
URL: String("https://github.com/octo-org/octo-repo"),
},
Scanned: &Timestamp{time.Date(2022, time.June, 14, 20, 25, 00, 0, time.UTC)},
Manifests: map[string]*DependencyGraphSnapshotManifest{
"package-lock.json": {
Name: String("package-lock.json"),
File: &DependencyGraphSnapshotManifestFile{SourceLocation: String("src/package-lock.json")},
Resolved: map[string]*DependencyGraphSnapshotResolvedDependency{
"@actions/core": {
PackageURL: String("pkg:/npm/%40actions/core@1.1.9"),
Relationship: String("direct"),
Scope: String("runtime"),
Dependencies: []string{"@actions/http-client"},
},
"@actions/http-client": {
PackageURL: String("pkg:/npm/%40actions/http-client@1.0.7"),
Relationship: String("indirect"),
Scope: String("runtime"),
Dependencies: []string{"tunnel"},
},
"tunnel": {
PackageURL: String("pkg:/npm/tunnel@0.0.6"),
Relationship: String("indirect"),
Scope: String("runtime"),
},
},
},
},
}

snapshotCreationData, _, err := client.DependencyGraph.CreateSnapshot(ctx, "o", "r", snapshot)
if err != nil {
t.Errorf("DependencyGraph.CreateSnapshot returned error: %v", err)
}

want := &DependencyGraphSnapshotCreationData{
ID: 12345,
CreatedAt: &Timestamp{time.Date(2022, time.June, 14, 20, 25, 01, 0, time.UTC)},
Message: String("Dependency results for the repo have been successfully updated."),
Result: String("SUCCESS"),
}
if !cmp.Equal(snapshotCreationData, want) {
t.Errorf("DependencyGraph.CreateSnapshot returned %+v, want %+v", snapshotCreationData, want)
}

const methodName = "CreateSnapshot"
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.DependencyGraph.CreateSnapshot(ctx, "o", "r", snapshot)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}
160 changes: 160 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.