Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bfd140c
initial pass at fetching bundles with sas urls
malancas Nov 6, 2024
7160f7e
Merge branch 'trunk' into fetch-artifact-attestation-bundles-with-sas…
malancas Dec 13, 2024
5a6a796
fetch bundles with sas url
malancas Dec 16, 2024
e51b4ef
remove unused method
malancas Dec 16, 2024
6b95175
add httpClient field to LiveClient struct
malancas Dec 16, 2024
45121f6
go mod tidy
malancas Dec 16, 2024
8f5d710
var naming
malancas Dec 16, 2024
fb020f2
update error messages
malancas Dec 16, 2024
e4431a3
add mock http client
malancas Dec 16, 2024
ab4912f
fix failing tests
malancas Dec 16, 2024
706314b
Merge branch 'trunk' into fetch-artifact-attestation-bundles-with-sas…
malancas Jan 6, 2025
9051da3
provide additional logging and fallback
malancas Jan 6, 2025
fc984c9
Merge branch 'fetch-artifact-attestation-bundles-with-sas-url' of git…
malancas Jan 6, 2025
311f2b2
return fetch attestations err directly
malancas Jan 6, 2025
070b67e
fetch bundles in parallel
malancas Jan 6, 2025
e03a36e
add tests for bundle url fetch and fallback
malancas Jan 6, 2025
b1af4b0
Merge branch 'trunk' into fetch-artifact-attestation-bundles-with-sas…
malancas Jan 6, 2025
0202ca8
add test case for bundle url fetch failure
malancas Jan 6, 2025
fb4fc7e
Merge branch 'fetch-artifact-attestation-bundles-with-sas-url' of git…
malancas Jan 6, 2025
6986511
add mutex for test field
malancas Jan 6, 2025
9ecd90c
setup testing struct for test cases
malancas Jan 7, 2025
e34e188
add http client test constructors
malancas Jan 7, 2025
ecf55c6
use mock to assert number of http calls
malancas Jan 7, 2025
9d88ca8
simplify mock http client
malancas Jan 7, 2025
7838e91
more mock http client cleanup
malancas Jan 7, 2025
3c0280c
undo name change for now
malancas Jan 7, 2025
37e0969
remove spaces
malancas Jan 7, 2025
0a602fa
undo other name change
malancas Jan 7, 2025
258c69c
undo more name chanages
malancas Jan 7, 2025
f46cccb
comment
malancas Jan 7, 2025
d4f3fbc
Merge branch 'trunk' into fetch-artifact-attestation-bundles-with-sas…
malancas Jan 7, 2025
42cb254
remove old comment
malancas Jan 7, 2025
51a74ae
Update pkg/cmd/attestation/api/client.go
malancas Jan 7, 2025
8d89dd9
Update pkg/cmd/attestation/api/client.go
malancas Jan 7, 2025
33d0002
update tests to use new function name
malancas Jan 7, 2025
8ad877b
add check for invalid attestation
malancas Jan 8, 2025
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/distribution/reference v0.5.0
github.com/gabriel-vasile/mimetype v1.4.7
github.com/gdamore/tcell/v2 v2.5.4
github.com/golang/snappy v0.0.4
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.20.2
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/certificate-transparency-go v1.2.1 h1:4iW/NwzqOqYEEoCBEFP+jPbBXbLqMpq3CifMyOnDUME=
github.com/google/certificate-transparency-go v1.2.1/go.mod h1:bvn/ytAccv+I6+DGkqpvSsEdiVGramgaSC6RD3tEmeE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/attestation/api/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func newErrNoAttestations(name, digest string) ErrNoAttestations {
}

type Attestation struct {
Bundle *bundle.Bundle `json:"bundle"`
Bundle *bundle.Bundle `json:"bundle"`
BundleURL string `json:"bundle_url"`
}

type AttestationsResponse struct {
Expand Down
128 changes: 114 additions & 14 deletions pkg/cmd/attestation/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import (
"github.com/cenkalti/backoff/v4"
"github.com/cli/cli/v2/api"
ioconfig "github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/golang/snappy"
v1 "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/sigstore-go/pkg/bundle"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/encoding/protojson"
)

const (
Expand All @@ -19,28 +24,39 @@ const (
maxLimitForFetch = 100
)

type apiClient interface {
// Allow injecting backoff interval in tests.
var getAttestationRetryInterval = time.Millisecond * 200

// githubApiClient makes REST calls to the GitHub API
type githubApiClient interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the client that implements this interface should only be used for making requests against the GitHub API, I'm renaming this interface to more explicitly reference the GitHub API.

REST(hostname, method, p string, body io.Reader, data interface{}) error
RESTWithNext(hostname, method, p string, body io.Reader, data interface{}) (string, error)
}

// httpClient makes HTTP calls to all non-GitHub API endpoints
type httpClient interface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the bundle URL we use to fetch the bundle is not part of the GitHub API, we need to use regular HTTP client.

Get(url string) (*http.Response, error)
}

type Client interface {
GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error)
GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error)
GetTrustDomain() (string, error)
}

type LiveClient struct {
api apiClient
host string
logger *ioconfig.Handler
githubAPI githubApiClient
httpClient httpClient
host string
logger *ioconfig.Handler
}

func NewLiveClient(hc *http.Client, host string, l *ioconfig.Handler) *LiveClient {
return &LiveClient{
api: api.NewClientFromHTTP(hc),
host: strings.TrimSuffix(host, "/"),
logger: l,
githubAPI: api.NewClientFromHTTP(hc),
host: strings.TrimSuffix(host, "/"),
httpClient: hc,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We use the same http.Client provided by the Factory object when the subcommand is invoked to make requests with bundle URLs.

logger: l,
}
}

Expand All @@ -52,7 +68,17 @@ func (c *LiveClient) BuildRepoAndDigestURL(repo, digest string) string {
// GetByRepoAndDigest fetches the attestation by repo and digest
func (c *LiveClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) {
url := c.BuildRepoAndDigestURL(repo, digest)
return c.getAttestations(url, repo, digest, limit)
attestations, err := c.getAttestations(url, repo, digest, limit)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The getAttestations method currently returns the api.Attestation type even though we really only care about the *bundle.Bundle type returned as a field within the Attestation type. I think we should consider updating this method to return *bundle.Bundle instead in a follow up PR.

if err != nil {
return nil, err
}

bundles, err := c.fetchBundlesByURL(attestations)
if err != nil {
return nil, fmt.Errorf("failed to fetch bundle with URL: %w", err)
}

return bundles, nil
}

func (c *LiveClient) BuildOwnerAndDigestURL(owner, digest string) string {
Expand All @@ -63,7 +89,21 @@ func (c *LiveClient) BuildOwnerAndDigestURL(owner, digest string) string {
// GetByOwnerAndDigest fetches attestation by owner and digest
func (c *LiveClient) GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error) {
url := c.BuildOwnerAndDigestURL(owner, digest)
return c.getAttestations(url, owner, digest, limit)
attestations, err := c.getAttestations(url, owner, digest, limit)
if err != nil {
return nil, err
}

if len(attestations) == 0 {
return nil, newErrNoAttestations(owner, digest)
}

bundles, err := c.fetchBundlesByURL(attestations)
if err != nil {
return nil, fmt.Errorf("failed to fetch bundle with URL: %w", err)
}

return bundles, nil
}

// GetTrustDomain returns the current trust domain. If the default is used
Expand All @@ -72,9 +112,6 @@ func (c *LiveClient) GetTrustDomain() (string, error) {
return c.getTrustDomain(MetaPath)
}

// Allow injecting backoff interval in tests.
var getAttestationRetryInterval = time.Millisecond * 200
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved this to the top of the file alongside other variables and consts.


func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*Attestation, error) {
c.logger.VerbosePrintf("Fetching attestations for artifact digest %s\n\n", digest)

Expand All @@ -97,7 +134,7 @@ func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*At
// if no attestation or less than limit, then keep fetching
for url != "" && len(attestations) < limit {
err := backoff.Retry(func() error {
newURL, restErr := c.api.RESTWithNext(c.host, http.MethodGet, url, nil, &resp)
newURL, restErr := c.githubAPI.RESTWithNext(c.host, http.MethodGet, url, nil, &resp)

if restErr != nil {
if shouldRetry(restErr) {
Expand Down Expand Up @@ -130,6 +167,69 @@ func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*At
return attestations, nil
}

func (c *LiveClient) fetchBundleFromAttestations(attestations []*Attestation) ([]*Attestation, error) {
fetched := make([]*Attestation, len(attestations))
g := errgroup.Group{}
for i, a := range attestations {
g.Go(func() error {
b, err := c.GetBundle(url)
if err != nil {
return fmt.Errorf("failed to fetch bundle with URL: %w", err)
}
fetched[i] = &Attestation{
Bundle: b,
}
return nil
})
}

if err := g.Wait(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

For my own edification: if any of these go funcs errors out, then we bail on everything.

return nil, err
}

return fetched, nil
}

func (c *LiveClient) fetchBundleByURL(a *Attestation) (*bundle.Bundle, error) {
// for now, we fallback to the bundle field if the bundle URL is empty
if a.BundleURL == "" {
c.logger.VerbosePrintf("Bundle URL is empty. Falling back to bundle field\n\n")
return a.Bundle, nil
}

c.logger.VerbosePrintf("Fetching attestation bundle with bundle URL\n\n")

resp, err := c.httpClient.Get(a.BundleURL)
if err != nil {
return nil, err
}

defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read blob storage response body: %w", err)
}

var out []byte
decompressed, err := snappy.Decode(out, body)
if err != nil {
return nil, fmt.Errorf("failed to decompress with snappy: %w", err)
}

var pbBundle v1.Bundle
if err = protojson.Unmarshal(decompressed, &pbBundle); err != nil {
return nil, fmt.Errorf("failed to unmarshal to bundle: %w", err)
}

c.logger.VerbosePrintf("Successfully fetched bundle\n\n")

return bundle.NewBundle(&pbBundle)
}

func shouldRetry(err error) bool {
var httpError api.HTTPError
if errors.As(err, &httpError) {
Expand All @@ -146,7 +246,7 @@ func (c *LiveClient) getTrustDomain(url string) (string, error) {

bo := backoff.NewConstantBackOff(getAttestationRetryInterval)
err := backoff.Retry(func() error {
restErr := c.api.REST(c.host, http.MethodGet, url, nil, &resp)
restErr := c.githubAPI.REST(c.host, http.MethodGet, url, nil, &resp)
if restErr != nil {
if shouldRetry(restErr) {
return restErr
Expand Down
Loading