mirror of
https://github.com/Luzifer/ansible-role-version.git
synced 2024-12-22 18:41:22 +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"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -14,17 +15,26 @@ type ansibleRoleDefinition struct {
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func patchRoleFile(rolesFile string, updates map[string]string) error {
|
func getRoleDefinitions(rolesFile string) ([]ansibleRoleDefinition, error) {
|
||||||
var (
|
rf, err := os.Open(rolesFile)
|
||||||
inFileContent []byte
|
if err != nil {
|
||||||
err error
|
return nil, err
|
||||||
)
|
}
|
||||||
if inFileContent, err = ioutil.ReadFile(rolesFile); err != nil {
|
defer rf.Close()
|
||||||
return fmt.Errorf("Roles file not found: %s", err)
|
|
||||||
|
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{}
|
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)
|
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)
|
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"))
|
buf.Write([]byte("\n...\n"))
|
||||||
|
|
||||||
if err = ioutil.WriteFile(rolesFile, buf.Bytes(), 0644); err != nil {
|
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