mirror of
https://github.com/google/go-github.git
synced 2026-10-28 16:34:37 +08:00
247 lines
9.1 KiB
Go
247 lines
9.1 KiB
Go
// 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"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// DependencyGraphService handles communication with the dependency graph
|
|
// related methods of the GitHub API.
|
|
//
|
|
// GitHub API docs: https://docs.github.com/rest/dependency-graph?apiVersion=2022-11-28
|
|
type DependencyGraphService service
|
|
|
|
// SBOM represents a software bill of materials, which describes the
|
|
// packages/libraries that a repository depends on.
|
|
type SBOM struct {
|
|
SBOM *SBOMInfo `json:"sbom,omitempty"`
|
|
}
|
|
|
|
// CreationInfo represents when the SBOM was created and who created it.
|
|
type CreationInfo struct {
|
|
Created *Timestamp `json:"created,omitempty"`
|
|
Creators []string `json:"creators,omitempty"`
|
|
}
|
|
|
|
// RepoDependencies represents the dependencies of a repo.
|
|
type RepoDependencies struct {
|
|
SPDXID *string `json:"SPDXID,omitempty"`
|
|
// Package name
|
|
Name *string `json:"name,omitempty"`
|
|
VersionInfo *string `json:"versionInfo,omitempty"`
|
|
DownloadLocation *string `json:"downloadLocation,omitempty"`
|
|
FilesAnalyzed *bool `json:"filesAnalyzed,omitempty"`
|
|
LicenseConcluded *string `json:"licenseConcluded,omitempty"`
|
|
LicenseDeclared *string `json:"licenseDeclared,omitempty"`
|
|
ExternalRefs []*PackageExternalRef `json:"externalRefs,omitempty"`
|
|
}
|
|
|
|
// PackageExternalRef allows an Package to reference an external sources of additional information,
|
|
// like asset identifiers, or downloadable content that are relevant to the package,
|
|
// Example for identifiers (e.g., PURL/SWID/CPE) for a package in the SBOM.
|
|
// https://spdx.github.io/spdx-spec/v2.3/package-information/#721-external-reference-field
|
|
type PackageExternalRef struct {
|
|
// ReferenceCategory specifies the external reference categories such
|
|
// SECURITY", "PACKAGE-MANAGER", "PERSISTENT-ID", or "OTHER"
|
|
// Example: "PACKAGE-MANAGER"
|
|
ReferenceCategory string `json:"referenceCategory"`
|
|
|
|
// ReferenceType specifies the type of external reference.
|
|
// For PACKAGE-MANAGER, it could be "purl"; other types include "cpe22Type", "swid", etc.
|
|
ReferenceType string `json:"referenceType"`
|
|
|
|
// ReferenceLocator is the actual unique identifier or URI for the external reference.
|
|
// Example: "pkg:golang/github.com/spf13/cobra@1.8.1"
|
|
ReferenceLocator string `json:"referenceLocator"`
|
|
}
|
|
|
|
// SBOMRelationship provides information about the relationship between two SPDX elements.
|
|
// Element could be packages or files in the SBOM.
|
|
// For example, to represent a relationship between two different Files, between a Package and a File,
|
|
// between two Packages, or between one SPDXDocument and another SPDXDocument.
|
|
// https://spdx.github.io/spdx-spec/v2.3/relationships-between-SPDX-elements/
|
|
type SBOMRelationship struct {
|
|
// SPDXElementID is the identifier of the SPDX element that has a relationship.
|
|
// Example: "SPDXRef-github-interlynk-io-sbomqs-main-f43c98"
|
|
SPDXElementID string `json:"spdxElementId"`
|
|
|
|
// RelatedSPDXElement is the identifier of the related SPDX element.
|
|
// Example: "SPDXRef-golang-github.comspf13-cobra-1.8.1-75c946"
|
|
RelatedSPDXElement string `json:"relatedSpdxElement"`
|
|
|
|
// RelationshipType describes the type of relationship between the two elements.
|
|
// Such as "DEPENDS_ON", "DESCRIBES", "CONTAINS", etc., as defined by SPDX 2.3.
|
|
// Example: "DEPENDS_ON", "CONTAINS", "DESCRIBES", etc.
|
|
RelationshipType string `json:"relationshipType"`
|
|
}
|
|
|
|
// SBOMInfo represents a software bill of materials (SBOM) using SPDX.
|
|
// SPDX is an open standard for SBOMs that
|
|
// identifies and catalogs components, licenses, copyrights, security
|
|
// references, and other metadata relating to software.
|
|
type SBOMInfo struct {
|
|
SPDXID *string `json:"SPDXID,omitempty"`
|
|
SPDXVersion *string `json:"spdxVersion,omitempty"`
|
|
CreationInfo *CreationInfo `json:"creationInfo,omitempty"`
|
|
|
|
// Repo name
|
|
Name *string `json:"name,omitempty"`
|
|
DataLicense *string `json:"dataLicense,omitempty"`
|
|
DocumentDescribes []string `json:"documentDescribes,omitempty"`
|
|
DocumentNamespace *string `json:"documentNamespace,omitempty"`
|
|
|
|
// List of packages dependencies
|
|
Packages []*RepoDependencies `json:"packages,omitempty"`
|
|
|
|
// List of relationships between packages
|
|
Relationships []*SBOMRelationship `json:"relationships,omitempty"`
|
|
}
|
|
|
|
func (s SBOM) String() string {
|
|
return Stringify(s)
|
|
}
|
|
|
|
// GetSBOM fetches the software bill of materials for a repository.
|
|
//
|
|
// Deprecated: GitHub is retiring this endpoint on 11-13-2026 in favor of the
|
|
// asynchronous workflow. Use GenerateSBOM followed by FetchSBOM.
|
|
//
|
|
// GitHub API docs: https://docs.github.com/rest/dependency-graph/sboms?apiVersion=2022-11-28#export-a-software-bill-of-materials-sbom-for-a-repository
|
|
//
|
|
//meta:operation GET /repos/{owner}/{repo}/dependency-graph/sbom
|
|
func (s *DependencyGraphService) GetSBOM(ctx context.Context, owner, repo string) (*SBOM, *Response, error) {
|
|
u := fmt.Sprintf("repos/%v/%v/dependency-graph/sbom", owner, repo)
|
|
|
|
req, err := s.client.NewRequest(ctx, "GET", u, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var sbom *SBOM
|
|
resp, err := s.client.Do(req, &sbom)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
|
|
return sbom, resp, nil
|
|
}
|
|
|
|
// SBOMGeneration represents the response to a request to generate
|
|
// a software bill of materials for a repository.
|
|
type SBOMGeneration struct {
|
|
// SBOMURL is the URL the generated SBOM can be fetched from once it's ready.
|
|
SBOMURL *string `json:"sbom_url,omitempty"`
|
|
}
|
|
|
|
// GenerateSBOM requests the generation of a software bill of materials for a repository.
|
|
//
|
|
// Generation is asynchronous. Pass the UUID from the returned
|
|
// SBOMGeneration to FetchSBOM to retrieve the report once GitHub has built it.
|
|
//
|
|
// GitHub API docs: https://docs.github.com/rest/dependency-graph/sboms?apiVersion=2022-11-28#request-generation-of-a-software-bill-of-materials-sbom-for-a-repository
|
|
//
|
|
//meta:operation GET /repos/{owner}/{repo}/dependency-graph/sbom/generate-report
|
|
func (s *DependencyGraphService) GenerateSBOM(ctx context.Context, owner, repo string) (*SBOMGeneration, *Response, error) {
|
|
u := fmt.Sprintf("repos/%v/%v/dependency-graph/sbom/generate-report", owner, repo)
|
|
|
|
req, err := s.client.NewRequest(ctx, "GET", u, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var generation *SBOMGeneration
|
|
resp, err := s.client.Do(req, &generation)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
|
|
return generation, resp, nil
|
|
}
|
|
|
|
// FetchSBOM downloads a software bill of materials or returns a redirect URL.
|
|
//
|
|
// sbomUUID is the final path segment of the SBOMURL returned by GenerateSBOM.
|
|
//
|
|
// If followRedirectsClient is nil, FetchSBOM returns the download URL in
|
|
// redirectURL and a nil sbom. Otherwise, it downloads the report and returns
|
|
// sbom with an empty redirectURL.
|
|
//
|
|
// Use http.DefaultClient or another client that does not add authentication
|
|
// headers when fetching the pre-signed download URL.
|
|
//
|
|
// While GitHub is generating the SBOM, FetchSBOM returns an *AcceptedError
|
|
// and status code 202. The request can be repeated later.
|
|
//
|
|
// GitHub API docs: https://docs.github.com/rest/dependency-graph/sboms?apiVersion=2022-11-28#fetch-a-software-bill-of-materials-sbom-for-a-repository
|
|
//
|
|
//meta:operation GET /repos/{owner}/{repo}/dependency-graph/sbom/fetch-report/{sbom_uuid}
|
|
func (s *DependencyGraphService) FetchSBOM(ctx context.Context, owner, repo, sbomUUID string, followRedirectsClient *http.Client) (sbom *SBOM, redirectURL string, resp *Response, err error) {
|
|
u := fmt.Sprintf("repos/%v/%v/dependency-graph/sbom/fetch-report/%v", owner, repo, sbomUUID)
|
|
|
|
req, err := s.client.NewRequest(ctx, "GET", u, nil)
|
|
if err != nil {
|
|
return nil, "", nil, err
|
|
}
|
|
|
|
loc, resp, err := s.client.bareDoUntilFound(req, 10)
|
|
if err != nil {
|
|
return nil, "", resp, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if loc == nil {
|
|
return nil, "", resp, fmt.Errorf("expected redirect, got status %v", resp.Status)
|
|
}
|
|
|
|
if followRedirectsClient == nil {
|
|
return nil, loc.String(), resp, nil
|
|
}
|
|
|
|
sbom, err = s.fetchSBOMFromURL(ctx, followRedirectsClient, loc.String())
|
|
if err != nil {
|
|
return nil, "", resp, err
|
|
}
|
|
|
|
return sbom, "", resp, nil
|
|
}
|
|
|
|
// fetchSBOMFromURL downloads and decodes an SPDX report from a temporary download
|
|
// URL.
|
|
//
|
|
// The request must not carry s.client's credentials: the URL is pre-signed and
|
|
// its host rejects authenticated requests.
|
|
func (s *DependencyGraphService) fetchSBOMFromURL(ctx context.Context, followRedirectsClient *http.Client, url string) (*SBOM, error) {
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := followRedirectsClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// CheckResponse substitutes resp.Body with a re-readable copy on error responses,
|
|
// so capture the network body first: it is the one that must be closed.
|
|
origBody := resp.Body
|
|
defer origBody.Close()
|
|
|
|
if err := CheckResponse(resp); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var info *SBOMInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SBOM{SBOM: info}, nil
|
|
}
|