Skip to content

Commit ae6b500

Browse files
Add support for deriving VCS tags from attestations
- extend Git attestations to store tags - implement conversion of generic statement type to concrete types - parse tags from attestations and use for artifacts
1 parent 336502b commit ae6b500

File tree

3 files changed

+99
-12
lines changed

3 files changed

+99
-12
lines changed

attest/manifest/manifest.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ var (
1616
)
1717

1818
type DirContents struct {
19-
types.GenericStatement[SourceDirectory]
19+
types.GenericStatement[SourceDirectoryContents]
2020
}
2121

2222
type SourceDirectory struct {
@@ -25,14 +25,16 @@ type SourceDirectory struct {
2525
VCSEntries *types.PathCheckSummaryCollection `json:"vcsEntries"`
2626
}
2727

28+
type SourceDirectoryContents struct {
29+
SourceDirectory `json:"containedInDirectory"`
30+
}
31+
2832
func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollection) types.Statement {
2933
return &DirContents{
30-
types.MakeStatement[SourceDirectory](
34+
types.MakeStatement[SourceDirectoryContents](
3135
ManifestDirPredicateType,
32-
struct {
33-
SourceDirectory `json:"containedInDirectory"`
34-
}{
35-
SourceDirectory{
36+
SourceDirectoryContents{
37+
SourceDirectory: SourceDirectory{
3638
Path: dir,
3739
VCSEntries: entries,
3840
},
@@ -42,6 +44,14 @@ func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollect
4244
}
4345
}
4446

47+
func MakeDirContentsStatementFrom(statement types.Statement) DirContents {
48+
dirContents := DirContents{
49+
GenericStatement: attestTypes.GenericStatement[SourceDirectoryContents]{},
50+
}
51+
dirContents.ConvertFrom(statement)
52+
return dirContents
53+
}
54+
4555
func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
4656
if cmp := cmp.Compare(a.Path, b.Path); cmp != 0 {
4757
return &cmp
@@ -55,3 +65,7 @@ func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
5565
cmp := a.VCSEntries.Compare(*b.VCSEntries)
5666
return &cmp
5767
}
68+
69+
func (a SourceDirectoryContents) Compare(b SourceDirectoryContents) types.Cmp {
70+
return a.SourceDirectory.Compare(b.SourceDirectory)
71+
}

attest/types/types.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,30 @@ func Export(s ExportableStatement) toto.Statement {
119119
}
120120
}
121121

122+
func FilterByPredicateType(t string, s Statements) Statements {
123+
results := Statements{}
124+
for i := range s {
125+
if s[i].GetType() == t {
126+
results = append(results, s[i])
127+
}
128+
}
129+
return results
130+
}
131+
132+
type StamentConverter[T any] struct {
133+
Statement
134+
}
135+
136+
func (s *GenericStatement[T]) ConvertFrom(statement Statement) error {
137+
predicate, ok := statement.GetPredicate().(ComparablePredicate[T])
138+
if !ok {
139+
return fmt.Errorf("cannot convert statement with predicte of type %T into %T", statement.GetPredicate(), GenericStatement[T]{})
140+
}
141+
142+
*s = MakeStatement[T](statement.GetType(), predicate, statement.GetSubject()...)
143+
return nil
144+
}
145+
122146
func (s Statements) Export() []toto.Statement {
123147
statements := make([]toto.Statement, len(s))
124148
for i := range s {
@@ -368,8 +392,9 @@ func comparePathCheckSummaries(a, b PathCheckSummary) int {
368392
return cmp.Compare(a.Common().Path, b.Common().Path)
369393
}
370394

371-
func (p Predicate[T]) GetType() string { return p.Type }
372-
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
395+
func (p Predicate[T]) GetType() string { return p.Type }
396+
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
397+
func (p Predicate[T]) GetUnderlyingPredicate() T { return p.ComparablePredicate.(T) }
373398

374399
func (p Predicate[T]) Compare(b any) Cmp {
375400
if b, ok := b.(Predicate[T]); ok {

oci/artefact.go

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"path/filepath"
1414
"time"
1515

16+
"golang.org/x/mod/semver"
17+
1618
ociclient "github.com/fluxcd/pkg/oci"
1719
"github.com/go-git/go-git/v5/utils/ioutil"
1820
"github.com/google/go-containerregistry/pkg/compression"
@@ -23,7 +25,9 @@ import (
2325
"github.com/google/go-containerregistry/pkg/v1/tarball"
2426
typesv1 "github.com/google/go-containerregistry/pkg/v1/types"
2527

28+
"github.com/errordeveloper/tape/attest/manifest"
2629
attestTypes "github.com/errordeveloper/tape/attest/types"
30+
"github.com/errordeveloper/tape/attest/vcs/git"
2731
manifestTypes "github.com/errordeveloper/tape/manifest/types"
2832
)
2933

@@ -250,7 +254,11 @@ func (c *Client) PushArtefact(ctx context.Context, destinationRef, sourceDir str
250254
}
251255
hash := hex.EncodeToString(c.hash.Sum(nil))
252256
tag := repo.Tag(manifestTypes.ConfigImageTagPrefix + hash)
253-
tagAlias := tag.Context().Tag(manifestTypes.ConfigImageTagPrefix + hash[:7])
257+
258+
tagAliases := append(
259+
SemVerTagsFromAttestations(ctx, tag, sourceAttestations...),
260+
tag.Context().Tag(manifestTypes.ConfigImageTagPrefix+hash[:7]),
261+
)
254262

255263
if timestamp == nil {
256264
timestamp = new(time.Time)
@@ -341,11 +349,51 @@ func (c *Client) PushArtefact(ctx context.Context, destinationRef, sourceDir str
341349
return "", fmt.Errorf("pushing index failed: %w", err)
342350
}
343351

344-
if err := remote.Tag(tagAlias, index, c.remoteWithContext(ctx)...); err != nil {
345-
return "", fmt.Errorf("adding alias tagging failed: %w", err)
352+
for i := range tagAliases {
353+
if err := remote.Tag(tagAliases[i], index, c.remoteWithContext(ctx)...); err != nil {
354+
return "", fmt.Errorf("adding alias tagging failed: %w", err)
355+
}
356+
}
357+
// TODO: reteurn tag and all of its aliases
358+
return tagAliases[0].String() + "@" + digest.String(), err
359+
}
360+
361+
func SemVerTagsFromAttestations(ctx context.Context, tag name.Tag, sourceAttestations ...attestTypes.Statement) []name.Tag {
362+
statements := attestTypes.FilterByPredicateType(manifest.ManifestDirPredicateType, sourceAttestations)
363+
if len(statements) != 1 {
364+
return []name.Tag{}
346365
}
347366

348-
return tagAlias.String() + "@" + digest.String(), err
367+
entries := manifest.MakeDirContentsStatementFrom(statements[0]).GetUnderlyingPredicate().VCSEntries
368+
if len(entries.EntryGroups) != 1 && len(entries.Providers) != 1 ||
369+
entries.Providers[0] != git.ProviderName {
370+
return []name.Tag{}
371+
}
372+
if len(entries.EntryGroups[0]) == 0 {
373+
return []name.Tag{}
374+
}
375+
376+
// TODO: try to use generics for this?
377+
groupSummary, ok := entries.EntryGroups[0][0].Full().(*git.Summary)
378+
if !ok {
379+
return []name.Tag{}
380+
}
381+
ref := groupSummary.Git.Reference
382+
if len(ref.Tags) == 0 {
383+
return []name.Tag{}
384+
}
385+
// TODO: detect tags with groupSummary.Path+"/" as prefix and priorities them
386+
tags := make([]name.Tag, 0, len(ref.Tags))
387+
for i := range ref.Tags {
388+
t := ref.Tags[i].Name
389+
if semver.IsValid(t) || semver.IsValid("v"+t) {
390+
tags = append(tags, tag.Context().Tag(ref.Tags[i].Name))
391+
}
392+
}
393+
if len(tags) == 0 {
394+
return []name.Tag{}
395+
}
396+
return tags
349397
}
350398

351399
func makeDescriptorWithPlatform() Descriptor {

0 commit comments

Comments
 (0)