diff --git a/go.mod b/go.mod index cda0b62..79fca9d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/carabiner-dev/attestation v0.2.1 github.com/carabiner-dev/collector v0.3.5 github.com/carabiner-dev/signer v0.4.5 - github.com/carabiner-dev/vcslocator v0.4.3 + github.com/carabiner-dev/vcslocator v0.4.4 github.com/fatih/color v1.19.0 github.com/go-git/go-billy/v5 v5.9.0 github.com/go-git/go-git/v5 v5.19.1 diff --git a/go.sum b/go.sum index 0b1b5da..eff030e 100644 --- a/go.sum +++ b/go.sum @@ -112,8 +112,8 @@ github.com/carabiner-dev/sbomfs v0.1.0 h1:gEsmn85hod7JTLs2dDr5C1x4Af7FUEhI0lbTur github.com/carabiner-dev/sbomfs v0.1.0/go.mod h1:UyPyTSNx9JOLZVgTmM9WXdmgVqDWXCYwr1LK1Ts+7H0= github.com/carabiner-dev/signer v0.4.5 h1:H3XHHqorZw7wvLysbGCc+FM90nSdzFlODj+mIGMsYJc= github.com/carabiner-dev/signer v0.4.5/go.mod h1:B/53ToJAIgwM+KuDwj52+HwnlA5p8Rmz2OXQdy9x+xs= -github.com/carabiner-dev/vcslocator v0.4.3 h1:rXKwVT8N4hS85GEJQGd0ZgOjTcALuCslsqZyg4g6qxA= -github.com/carabiner-dev/vcslocator v0.4.3/go.mod h1:o0v3BV06HMg20GaJctA8feu0W/aan+XhHq0i+wr6Jq0= +github.com/carabiner-dev/vcslocator v0.4.4 h1:5uzb2yKfslMHY9RkkpUW28jLx2iVX93Al/GjSvG/2Ok= +github.com/carabiner-dev/vcslocator v0.4.4/go.mod h1:qfYEs44nf9Fm/kiN120rTgruJn7PoHQyLXWQ9aO+SwE= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= diff --git a/pkg/attest/attester.go b/pkg/attest/attester.go index 8fef6a8..2e0a42a 100644 --- a/pkg/attest/attester.go +++ b/pkg/attest/attester.go @@ -8,8 +8,10 @@ import ( "errors" "fmt" "slices" + "sync" "time" + "github.com/carabiner-dev/collector" intoto "github.com/in-toto/attestation/go/v1" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/proto" @@ -46,6 +48,14 @@ type Attester struct { backend models.VcsBackend Options AttesterOptions authenticator *auth.Authenticator + + // collectorMtx guards the memoized collector agents. + collectorMtx sync.Mutex + // collectors memoizes one collector agent per repository (keyed by its + // HTTP URL). Reusing the agent shares its attestation cache across the + // multiple reads done for a revision (e.g. provenance and VSA, which query + // the same subject), so the underlying git-notes data is fetched once. + collectors map[string]*collector.Agent } type optFn func(*Attester) error diff --git a/pkg/attest/provenance.go b/pkg/attest/provenance.go index 2ffefb5..f17ca4e 100644 --- a/pkg/attest/provenance.go +++ b/pkg/attest/provenance.go @@ -80,6 +80,19 @@ func GetTagProvPred(statement *intoto.Statement) (*provenance.TagProvenancePred, } func (a *Attester) getCollector(branch *models.Branch) (*collector.Agent, error) { + // Reuse a single agent per repository so its attestation cache is shared + // across the several reads we do for a revision (provenance, VSA, parent + // commit). Without this, each read builds a fresh agent with an empty cache + // and re-fetches the same git-notes data. + key := branch.Repository.GetHttpURL() + + a.collectorMtx.Lock() + defer a.collectorMtx.Unlock() + + if cached, ok := a.collectors[key]; ok { + return cached, nil + } + if err := collector.LoadDefaultRepositoryTypes(); err != nil { return nil, err } @@ -130,6 +143,11 @@ func (a *Attester) getCollector(branch *models.Branch) (*collector.Agent, error) } } + if a.collectors == nil { + a.collectors = map[string]*collector.Agent{} + } + a.collectors[key] = agent + return agent, nil }