1
0
Fork 0
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:
Knut Ahlers 2018-03-19 18:15:49 +01:00
parent 0695755d75
commit fe680c31db
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
4 changed files with 196 additions and 12 deletions

View file

@ -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
View 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
View 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
View 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)
}