Skip to content

Commit

Permalink
Merge pull request #1 from errordeveloper/semver-tagger
Browse files Browse the repository at this point in the history
Automatic VCS tagging
  • Loading branch information
errordeveloper authored Jul 5, 2024
2 parents 2d961d7 + 61583a2 commit 0da9d6a
Show file tree
Hide file tree
Showing 10 changed files with 560 additions and 66 deletions.
29 changes: 22 additions & 7 deletions attest/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import (
"cmp"

"github.com/errordeveloper/tape/attest/types"
attestTypes "github.com/errordeveloper/tape/attest/types"
)

const (
ManifestDirPredicateType = "docker.com/tape/ManifestDir/v0.1"
ManifestDirPredicateType = "docker.com/tape/ManifestDir/v0.2"
)

var (
_ types.Statement = (*DirContents)(nil)
)

type DirContents struct {
types.GenericStatement[SourceDirectory]
types.GenericStatement[SourceDirectoryContents]
}

type SourceDirectory struct {
Expand All @@ -24,14 +25,16 @@ type SourceDirectory struct {
VCSEntries *types.PathCheckSummaryCollection `json:"vcsEntries"`
}

type SourceDirectoryContents struct {
SourceDirectory `json:"containedInDirectory"`
}

func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollection) types.Statement {
return &DirContents{
types.MakeStatement[SourceDirectory](
types.MakeStatement[SourceDirectoryContents](
ManifestDirPredicateType,
struct {
SourceDirectory `json:"containedInDirectory"`
}{
SourceDirectory{
SourceDirectoryContents{
SourceDirectory: SourceDirectory{
Path: dir,
VCSEntries: entries,
},
Expand All @@ -41,6 +44,14 @@ func MakeDirContentsStatement(dir string, entries *types.PathCheckSummaryCollect
}
}

func MakeDirContentsStatementFrom(statement types.Statement) DirContents {
dirContents := DirContents{
GenericStatement: attestTypes.GenericStatement[SourceDirectoryContents]{},
}
dirContents.ConvertFrom(statement)
return dirContents
}

func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
if cmp := cmp.Compare(a.Path, b.Path); cmp != 0 {
return &cmp
Expand All @@ -54,3 +65,7 @@ func (a SourceDirectory) Compare(b SourceDirectory) types.Cmp {
cmp := a.VCSEntries.Compare(*b.VCSEntries)
return &cmp
}

func (a SourceDirectoryContents) Compare(b SourceDirectoryContents) types.Cmp {
return a.SourceDirectory.Compare(b.SourceDirectory)
}
4 changes: 2 additions & 2 deletions attest/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ func makeRegistryTest(tc testdata.TestCase) func(t *testing.T) {
loader := loader.NewRecursiveManifestDirectoryLoader(tc.Directory)
g.Expect(loader.Load()).To(Succeed())

pathChecker, attreg, err := DetectVCS(tc.Directory)
repoDetected, attreg, err := DetectVCS(tc.Directory)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(pathChecker).ToNot(BeNil())
g.Expect(repoDetected).To(BeTrue())
g.Expect(attreg).ToNot(BeNil())

scanner := imagescanner.NewDefaultImageScanner()
Expand Down
29 changes: 27 additions & 2 deletions attest/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ func Export(s ExportableStatement) toto.Statement {
}
}

func FilterByPredicateType(t string, s Statements) Statements {
results := Statements{}
for i := range s {
if s[i].GetType() == t {
results = append(results, s[i])
}
}
return results
}

type StamentConverter[T any] struct {
Statement
}

func (s *GenericStatement[T]) ConvertFrom(statement Statement) error {
predicate, ok := statement.GetPredicate().(ComparablePredicate[T])
if !ok {
return fmt.Errorf("cannot convert statement with predicte of type %T into %T", statement.GetPredicate(), GenericStatement[T]{})
}

*s = MakeStatement[T](statement.GetType(), predicate, statement.GetSubject()...)
return nil
}

func (s Statements) Export() []toto.Statement {
statements := make([]toto.Statement, len(s))
for i := range s {
Expand Down Expand Up @@ -368,8 +392,9 @@ func comparePathCheckSummaries(a, b PathCheckSummary) int {
return cmp.Compare(a.Common().Path, b.Common().Path)
}

func (p Predicate[T]) GetType() string { return p.Type }
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
func (p Predicate[T]) GetType() string { return p.Type }
func (p Predicate[T]) GetPredicate() any { return p.ComparablePredicate }
func (p Predicate[T]) GetUnderlyingPredicate() T { return p.ComparablePredicate.(T) }

func (p Predicate[T]) Compare(b any) Cmp {
if b, ok := b.(Predicate[T]); ok {
Expand Down
162 changes: 129 additions & 33 deletions attest/vcs/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ const (
DefaultPrimaryRemoteName = "origin"
)

// TODO: need a way to detect multiple repos, for now PathChecker is only meant
// to be used for the manifest dir iteself, and assume there is no nested repos

func NewPathChecker(path string, digest digest.SHA256) types.PathChecker {
return &PathChecker{
path: path,
Expand All @@ -47,16 +44,35 @@ type (
}

GitSummary struct {
ObjectHash *string `json:"objectHash,omitempty"`
Remotes map[string][]string `json:"remotes,omitempty"`
Reference GitReference `json:"reference,omitempty"`
Object GitObject `json:"object,omitempty"`
Remotes map[string][]string `json:"remotes,omitempty"`
Reference GitReference `json:"reference,omitempty"`
}

GitObject struct {
TreeHash string `json:"treeHash,omitempty"`
CommitHash string `json:"commitHash,omitempty"`
}

Signature struct {
PGP []byte `json:"pgp"`
Validated bool `json:"validated"`
}

GitTag struct {
Name string `json:"name"`
Hash string `json:"hash,omitempty"`
Target string `json:"target,omitempty"`
Signature *Signature `json:"signature,omitempty"`
}

GitReference struct {
Name string `json:"name,omitempty"`
Hash string `json:"hash,omitempty"`
Type string `json:"type,omitempty"`
Target string `json:"target,omitempty"`
Name string `json:"name,omitempty"`
Hash string `json:"hash,omitempty"`
Type string `json:"type,omitempty"`
Target string `json:"target,omitempty"`
Tags []GitTag `json:"tags,omitempty"`
Signature *Signature `json:"signature,omitempty"`
}
)

Expand Down Expand Up @@ -142,19 +158,101 @@ func (c *PathChecker) MakeSummary() (types.PathCheckSummary, error) {
Git: &git,
}

// TODO: determine position of local branch against remote
// TODO: introduce notion of primary remote branch to determine the possition of the working branch
// TODO: determine if a tag is used
// TODO: also check if local tag in sync wirth remote tag
// TODO: provide info on singed tags/commits
head, err := c.cache.repo.Head()
if err != nil {
return nil, err
}

ref := GitReference{
Name: head.Name().String(),
Hash: head.Hash().String(),
Type: head.Type().String(),
Target: head.Target().String(),
}

obj := &GitObject{}
if summary.Unmodified {
git.ObjectHash = new(string)
*git.ObjectHash = c.cache.obj.ID().String()
obj.TreeHash = c.cache.obj.ID().String()
} else if c.IsBlob() {
// there is currently no easy way to obtain a hash for a subtree
git.ObjectHash = new(string)
*git.ObjectHash = c.cache.blobHash
obj.TreeHash = c.cache.blobHash
}

headCommit, err := c.cache.repo.CommitObject(head.Hash())
if err != nil {
return nil, err
}
if headCommit.PGPSignature != "" {
ref.Signature = &Signature{
PGP: []byte(headCommit.PGPSignature),
Validated: false,
}
}

if summary.Unmodified {
commitIter := object.NewCommitPathIterFromIter(
func(path string) bool {
switch {
case c.IsTree():
return strings.HasPrefix(c.cache.repoPath, path)
case c.IsBlob():
return c.cache.repoPath == path
default:
return false
}
},
object.NewCommitIterCTime(headCommit, nil, nil),
true,
)
defer commitIter.Close()
// only need first commit, avoid looping over all commits with ForEach
commit, err := commitIter.Next()
if err == nil {
obj.CommitHash = commit.Hash.String()
} else if err != io.EOF {
return nil, err
}
}

tags, err := c.cache.repo.Tags()
if err != nil {
return nil, err
}

if err := tags.ForEach(func(t *plumbing.Reference) error {
target, err := c.cache.repo.ResolveRevision(plumbing.Revision(t.Name()))
if err != nil {
return err
}
if *target != head.Hash() {
// doesn't point to HEAD
return nil
}

tag := GitTag{
Name: t.Name().Short(),
Hash: t.Hash().String(),
Target: target.String(),
}

if tag.Target != tag.Hash {
// annotated tags have own object hash, while has of a leightweight tag is the same as target
tagObject, err := c.cache.repo.TagObject(t.Hash())
if err != nil {
return err
}
if tagObject.PGPSignature != "" {
tag.Signature = &Signature{
PGP: []byte(tagObject.PGPSignature),
Validated: false,
}
}
}

ref.Tags = append(ref.Tags, tag)
return nil
}); err != nil {
return nil, err
}

remotes, err := c.cache.repo.Remotes()
Expand Down Expand Up @@ -189,17 +287,8 @@ func (c *PathChecker) MakeSummary() (types.PathCheckSummary, error) {
git.Remotes[remoteConfig.Name] = remoteConfig.URLs
}

head, err := c.cache.repo.Head()
if err != nil {
return nil, err
}

git.Reference = GitReference{
Name: head.Name().String(),
Hash: head.Hash().String(),
Type: head.Type().String(),
Target: head.Target().String(),
}
git.Reference = ref
git.Object = *obj

return summary, nil
}
Expand Down Expand Up @@ -288,6 +377,10 @@ func (c *PathChecker) Check() (bool, bool, error) {
}
unmodified, _, err := isBlobUnmodified(worktree, &f.Blob, filepath.Join(repoPath, f.Name))
if err != nil {
if f.Mode == filemode.Symlink {
// TODO: should at least log a warning for broken symlink
return nil
}
return err
}
if !unmodified {
Expand Down Expand Up @@ -419,6 +512,9 @@ func findByPath(repo *gogit.Repository, path string) (object.Object, error) {
if err != nil {
return nil, err
}
if path == "." {
return tree, nil
}
treeEntry, err := tree.FindEntry(path)
switch err {
case nil:
Expand All @@ -438,12 +534,12 @@ func findByPath(repo *gogit.Repository, path string) (object.Object, error) {
}

func detectRepo(path string) (*gogit.Repository, bool) {
if repo, err := gogit.PlainOpen(path); err == nil {
return repo, true
}
dir := filepath.Dir(path)
if dir == path { // reached root
return nil, false
}
if repo, err := gogit.PlainOpen(dir); err == nil {
return repo, true
}
return detectRepo(dir)
}
Loading

0 comments on commit 0da9d6a

Please sign in to comment.