mirror of
https://github.com/Luzifer/ansible-role-version.git
synced 2024-12-22 10:31:20 +00:00
Add update command to update from git tags
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
0695755d75
commit
fe680c31db
4 changed files with 196 additions and 12 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
@ -14,17 +15,26 @@ type ansibleRoleDefinition struct {
|
|||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
func patchRoleFile(rolesFile string, updates map[string]string) error {
|
||||
var (
|
||||
inFileContent []byte
|
||||
err error
|
||||
)
|
||||
if inFileContent, err = ioutil.ReadFile(rolesFile); err != nil {
|
||||
return fmt.Errorf("Roles file not found: %s", err)
|
||||
func getRoleDefinitions(rolesFile string) ([]ansibleRoleDefinition, error) {
|
||||
rf, err := os.Open(rolesFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rf.Close()
|
||||
|
||||
def := []ansibleRoleDefinition{}
|
||||
return def, yaml.NewDecoder(rf).Decode(&def)
|
||||
}
|
||||
|
||||
func patchRoleFile(rolesFile string, updates map[string]string) error {
|
||||
inFile, err := os.Open(rolesFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to open roles files: %s", err)
|
||||
}
|
||||
defer inFile.Close()
|
||||
|
||||
in := []ansibleRoleDefinition{}
|
||||
if err = yaml.Unmarshal(inFileContent, &in); err != nil {
|
||||
if err = yaml.NewDecoder(inFile).Decode(&in); err != nil {
|
||||
return fmt.Errorf("Unable to parse roles file: %s", err)
|
||||
}
|
||||
|
||||
|
@ -36,13 +46,13 @@ func patchRoleFile(rolesFile string, updates map[string]string) error {
|
|||
}
|
||||
}
|
||||
|
||||
if inFileContent, err = yaml.Marshal(in); err != nil {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write([]byte("---\n\n"))
|
||||
|
||||
if err := yaml.NewEncoder(buf).Encode(in); err != nil {
|
||||
return fmt.Errorf("Unable to marshal roles file: %s", err)
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write([]byte("---\n\n"))
|
||||
buf.Write(inFileContent)
|
||||
buf.Write([]byte("\n...\n"))
|
||||
|
||||
if err = ioutil.WriteFile(rolesFile, buf.Bytes(), 0644); err != nil {
|
||||
|
|
50
cmd/update.go
Normal file
50
cmd/update.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Luzifer/ansible-role-version/tags"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// updateCmd represents the update command
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Seek for updates in git repositories and update the roles file",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
roles, err := getRoleDefinitions(cfg.RolesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updates := map[string]string{}
|
||||
|
||||
for _, role := range roles {
|
||||
logger := log.WithFields(log.Fields{
|
||||
"role": role.Name,
|
||||
})
|
||||
|
||||
tag, err := tags.GetLatestTag(role.Src, true)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Failed to fetch latest tag")
|
||||
continue
|
||||
}
|
||||
|
||||
if tag.Name != role.Version {
|
||||
updates[role.Name] = tag.Name
|
||||
logger.WithFields(log.Fields{
|
||||
"from": role.Version,
|
||||
"to": tag.Name,
|
||||
"released": tag.When.Format(time.RFC1123),
|
||||
}).Info("Update queued")
|
||||
}
|
||||
}
|
||||
|
||||
return patchRoleFile(cfg.RolesFile, updates)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
}
|
112
tags/tags.go
Normal file
112
tags/tags.go
Normal file
|
@ -0,0 +1,112 @@
|
|||
package tags
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"gopkg.in/src-d/go-billy.v4/memfs"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
"gopkg.in/src-d/go-git.v4/storage/memory"
|
||||
)
|
||||
|
||||
type Tag struct {
|
||||
Name string
|
||||
When time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoTagsFound = errors.New("No tags found")
|
||||
)
|
||||
|
||||
// GetLatestTag clones a Git repository into memory and resolves latest
|
||||
// leightweight or annotated tag from it
|
||||
func GetLatestTag(repoURL string, includeLightweight bool) (*Tag, error) {
|
||||
fs := memfs.New()
|
||||
// Git objects storer based on memory
|
||||
storer := memory.NewStorage()
|
||||
|
||||
// Clones the repository into the worktree (fs) and storer all the .git
|
||||
// content into the storer
|
||||
r, _ := git.Clone(storer, fs, &git.CloneOptions{
|
||||
URL: repoURL,
|
||||
})
|
||||
|
||||
tags := []Tag{}
|
||||
|
||||
// Get reference iterator for all tags
|
||||
it, err := r.Tags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer it.Close()
|
||||
|
||||
for {
|
||||
ref, err := it.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ref.Name().IsTag() {
|
||||
continue
|
||||
}
|
||||
|
||||
var when time.Time
|
||||
|
||||
// Check whether the hash is resolvable to a tag
|
||||
if t := tryTag(r, ref.Hash()); t != nil {
|
||||
when = *t
|
||||
}
|
||||
|
||||
// If it wasn't and we may include leightweight tags check for commit
|
||||
if when.IsZero() && includeLightweight {
|
||||
if t := tryCommit(r, ref.Hash()); t != nil {
|
||||
when = *t
|
||||
}
|
||||
}
|
||||
|
||||
if when.IsZero() {
|
||||
// We've resolved no tag
|
||||
continue
|
||||
}
|
||||
|
||||
tags = append(tags, Tag{
|
||||
Name: ref.Name().Short(),
|
||||
When: when,
|
||||
})
|
||||
}
|
||||
|
||||
if len(tags) == 0 {
|
||||
return nil, ErrNoTagsFound
|
||||
}
|
||||
|
||||
// Tags may be unsorted by design so we need to sort them
|
||||
sort.SliceStable(tags, func(i, j int) bool {
|
||||
return tags[i].When.Before(tags[j].When)
|
||||
})
|
||||
|
||||
return &tags[len(tags)-1], nil
|
||||
}
|
||||
|
||||
func tryTag(r *git.Repository, hash plumbing.Hash) *time.Time {
|
||||
tag, err := r.TagObject(hash)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &tag.Tagger.When
|
||||
}
|
||||
|
||||
func tryCommit(r *git.Repository, hash plumbing.Hash) *time.Time {
|
||||
commit, err := r.CommitObject(hash)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &commit.Committer.When
|
||||
}
|
12
tags/tags_test.go
Normal file
12
tags/tags_test.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package tags
|
||||
|
||||
import "log"
|
||||
|
||||
func ExampleGetLatestTag() {
|
||||
tag, err := GetLatestTag("https://github.com/luzifer-ansible/deploy-git.git", true)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not resolve latest tag: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("Latest tag: %q, created at %v", tag.Name, tag.When)
|
||||
}
|
Loading…
Reference in a new issue