@@ -19,8 +19,10 @@ import (
1919 "bytes"
2020 "context"
2121 "encoding/json"
22+ "errors"
2223 "fmt"
2324 "io"
25+ "sort"
2426 "time"
2527
2628 "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop"
@@ -37,27 +39,21 @@ type Referrer struct {
3739 Kind string
3840 // Wether the item is downloadable from CAS or not
3941 Downloadable bool
40- // points to other digests
41- References []string
42+ References []* Referrer
4243}
4344
4445// Actual referrer stored in the DB which includes a nested list of storedReferences
4546type StoredReferrer struct {
46- ID uuid.UUID
47- Digest string
48- Kind string
49- // Wether the item is downloadable from CAS or not
50- Downloadable bool
51- CreatedAt * time.Time
47+ * Referrer
48+ ID uuid.UUID
49+ CreatedAt * time.Time
5250 // Fully expanded list of 1-level off references
5351 References []* StoredReferrer
5452 OrgIDs []uuid.UUID
5553}
5654
57- type ReferrerMap map [string ]* Referrer
58-
5955type ReferrerRepo interface {
60- Save (ctx context.Context , input ReferrerMap , orgID uuid.UUID ) error
56+ Save (ctx context.Context , input [] * Referrer , orgID uuid.UUID ) error
6157 // GetFromRoot returns the referrer identified by the provided content digest, including its first-level references
6258 // For example if sha:deadbeef represents an attestation, the result will contain the attestation + materials associated to it
6359 // OrgIDs represent an allowList of organizations where the referrers should be looked for
@@ -129,6 +125,10 @@ func (s *ReferrerUseCase) GetFromRoot(ctx context.Context, digest string, userID
129125
130126 ref , err := s .repo .GetFromRoot (ctx , digest , orgIDs )
131127 if err != nil {
128+ if errors .As (err , & ErrAmbiguousReferrer {}) {
129+ return nil , NewErrValidation (fmt .Errorf ("please provide the referrer kind: %w" , err ))
130+ }
131+
132132 return nil , fmt .Errorf ("getting referrer from root: %w" , err )
133133 } else if ref == nil {
134134 return nil , NewErrNotFound ("referrer" )
@@ -142,14 +142,22 @@ const (
142142 referrerGitHeadType = "GIT_HEAD_COMMIT"
143143)
144144
145+ func newRef (digest , kind string ) string {
146+ return fmt .Sprintf ("%s-%s" , kind , digest )
147+ }
148+
149+ func (r * Referrer ) MapID () string {
150+ return newRef (r .Digest , r .Kind )
151+ }
152+
145153// ExtractReferrers extracts the referrers from the given attestation
146154// this means
147155// 1 - write an entry for the attestation itself
148156// 2 - then to all the materials contained in the predicate
149157// 3 - and the subjects (some of them)
150158// 4 - creating link between the attestation and the materials/subjects as needed
151159// see tests for examples
152- func extractReferrers (att * dsse.Envelope ) (ReferrerMap , error ) {
160+ func extractReferrers (att * dsse.Envelope ) ([] * Referrer , error ) {
153161 // Calculate the attestation hash
154162 jsonAtt , err := json .Marshal (att )
155163 if err != nil {
@@ -162,16 +170,18 @@ func extractReferrers(att *dsse.Envelope) (ReferrerMap, error) {
162170 return nil , fmt .Errorf ("calculating attestation hash: %w" , err )
163171 }
164172
165- referrers := make (ReferrerMap )
173+ referrersMap := make (map [ string ] * Referrer )
166174 // 1 - Attestation referrer
167175 // Add the attestation itself as a referrer to the map without references yet
168176 attestationHash := h .String ()
169- referrers [ attestationHash ] = & Referrer {
177+ attestationReferrer : = & Referrer {
170178 Digest : attestationHash ,
171179 Kind : referrerAttestationType ,
172180 Downloadable : true ,
173181 }
174182
183+ referrersMap [newRef (attestationHash , referrerAttestationType )] = attestationReferrer
184+
175185 // 2 - Predicate that's referenced from the attestation
176186 predicate , err := chainloop .ExtractPredicate (att )
177187 if err != nil {
@@ -189,23 +199,23 @@ func extractReferrers(att *dsse.Envelope) (ReferrerMap, error) {
189199 // Create its referrer entry if it doesn't exist yet
190200 // the reason it might exist is because you might be attaching the same material twice
191201 // i.e the same SBOM twice, in that case we don't want to create a new referrer
192- // If we are providing different types for the same digest, we should error out
193- if r , ok := referrers [material .Hash .String ()]; ok {
194- if r .Kind != material .Type {
195- return nil , fmt .Errorf ("material %s has different types: %s and %s" , material .Hash .String (), r .Kind , material .Type )
196- }
197-
202+ materialRef := newRef (material .Hash .String (), material .Type )
203+ if _ , ok := referrersMap [materialRef ]; ok {
198204 continue
199205 }
200206
201- referrers [ material . Hash . String () ] = & Referrer {
207+ referrersMap [ materialRef ] = & Referrer {
202208 Digest : material .Hash .String (),
203209 Kind : material .Type ,
204210 Downloadable : material .UploadedToCAS ,
205211 }
206212
213+ materialReferrer := referrersMap [materialRef ]
214+
207215 // Add the reference to the attestation
208- referrers [attestationHash ].References = append (referrers [attestationHash ].References , material .Hash .String ())
216+ attestationReferrer .References = append (attestationReferrer .References , & Referrer {
217+ Digest : materialReferrer .Digest , Kind : materialReferrer .Kind ,
218+ })
209219 }
210220
211221 // 3 - Subject that points to the attestation
@@ -215,25 +225,42 @@ func extractReferrers(att *dsse.Envelope) (ReferrerMap, error) {
215225 }
216226
217227 for _ , subject := range statement .Subject {
218- subjectRef , err := intotoSubjectToReferrer (subject )
228+ subjectReferrer , err := intotoSubjectToReferrer (subject )
219229 if err != nil {
220230 return nil , fmt .Errorf ("transforming subject to referrer: %w" , err )
221231 }
222232
223- if subjectRef == nil {
233+ if subjectReferrer == nil {
224234 continue
225235 }
226236
237+ subjectRef := newRef (subjectReferrer .Digest , subjectReferrer .Kind )
238+
227239 // check if we already have a referrer for this digest and set it otherwise
228240 // this is the case for example for git.Head ones
229- if _ , ok := referrers [subjectRef . Digest ]; ! ok {
230- referrers [subjectRef . Digest ] = subjectRef
241+ if _ , ok := referrersMap [subjectRef ]; ! ok {
242+ referrersMap [subjectRef ] = subjectReferrer
231243 // add it to the list of of attestation-referenced digests
232- referrers [attestationHash ].References = append (referrers [attestationHash ].References , subjectRef .Digest )
244+ attestationReferrer .References = append (attestationReferrer .References ,
245+ & Referrer {
246+ Digest : subjectReferrer .Digest , Kind : subjectReferrer .Kind ,
247+ })
233248 }
234249
235250 // Update referrer to point to the attestation
236- referrers [subjectRef .Digest ].References = []string {attestationHash }
251+ referrersMap [subjectRef ].References = []* Referrer {{Digest : attestationReferrer .Digest , Kind : attestationReferrer .Kind }}
252+ }
253+
254+ // Return a sorted list of referrers
255+ mapKeys := make ([]string , 0 , len (referrersMap ))
256+ for k := range referrersMap {
257+ mapKeys = append (mapKeys , k )
258+ }
259+ sort .Strings (mapKeys )
260+
261+ referrers := make ([]* Referrer , 0 , len (referrersMap ))
262+ for _ , k := range mapKeys {
263+ referrers = append (referrers , referrersMap [k ])
237264 }
238265
239266 return referrers , nil
0 commit comments