2020-11-17 05:41:34 +00:00
|
|
|
package registry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
|
|
"github.com/mholt/archiver/v3"
|
2022-06-14 19:59:53 +00:00
|
|
|
"gopkg.in/yaml.v3"
|
2020-11-17 05:41:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Registry is a collection of Template
|
|
|
|
type Registry struct {
|
|
|
|
dir string
|
2022-06-14 19:59:53 +00:00
|
|
|
Sources []*Source `yaml:"sources"`
|
|
|
|
Templates []*Template `yaml:"templates"`
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Registry) save() error {
|
|
|
|
fi, err := os.Create(r.MetaFilePath())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-06-14 19:59:53 +00:00
|
|
|
if err := yaml.NewEncoder(fi).Encode(r); err != nil {
|
2020-11-17 05:41:34 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return fi.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MetaFilePath is the path to the Registry meta-file
|
|
|
|
func (r *Registry) MetaFilePath() string {
|
2022-06-14 19:59:53 +00:00
|
|
|
return filepath.Join(r.dir, "registry.yaml")
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetTemplate retrieves a Template from the Registry
|
|
|
|
func (r *Registry) GetTemplate(name string) (*Template, error) {
|
|
|
|
for _, t := range r.Templates {
|
|
|
|
if strings.EqualFold(name, t.Name) {
|
|
|
|
t.reg = r
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, ErrTemplateNotFound{Name: name}
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
// DownloadTemplate downloads and adds a new Template to the Registry
|
|
|
|
func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error) {
|
2020-11-30 05:16:25 +00:00
|
|
|
if _, err := r.GetTemplate(name); err == nil {
|
|
|
|
return nil, ErrTemplateExists{Name: name}
|
|
|
|
}
|
|
|
|
|
2020-11-17 05:41:34 +00:00
|
|
|
t := &Template{
|
|
|
|
reg: r,
|
|
|
|
Name: name,
|
|
|
|
Repository: repo,
|
|
|
|
Branch: branch,
|
2020-11-30 05:16:25 +00:00
|
|
|
LastUpdate: time.Now(),
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
r.Templates = append(r.Templates, t)
|
|
|
|
|
|
|
|
if err := download(repo, branch, t.ArchivePath()); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, r.save()
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
// SaveTemplate saves a local Template to the Registry
|
|
|
|
func (r *Registry) SaveTemplate(name, path string) (*Template, error) {
|
2020-11-30 05:16:25 +00:00
|
|
|
if _, err := r.GetTemplate(name); err == nil {
|
|
|
|
return nil, ErrTemplateExists{Name: name}
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
t := &Template{
|
2020-11-30 05:16:25 +00:00
|
|
|
reg: r,
|
|
|
|
Name: name,
|
|
|
|
Path: path,
|
|
|
|
LastUpdate: time.Now(),
|
2020-11-18 05:28:07 +00:00
|
|
|
}
|
|
|
|
r.Templates = append(r.Templates, t)
|
|
|
|
|
|
|
|
if err := save(path, t.ArchivePath()); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return t, r.save()
|
|
|
|
}
|
|
|
|
|
2020-11-17 05:41:34 +00:00
|
|
|
// RemoveTemplate removes the Template from disk and meta
|
|
|
|
func (r *Registry) RemoveTemplate(name string) error {
|
|
|
|
_, err := r.GetTemplate(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-30 05:16:25 +00:00
|
|
|
|
2020-11-17 05:41:34 +00:00
|
|
|
for idx, t := range r.Templates {
|
|
|
|
if strings.EqualFold(name, t.Name) {
|
|
|
|
r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...)
|
|
|
|
if err := os.Remove(t.ArchivePath()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-11-30 05:16:25 +00:00
|
|
|
return r.save()
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-30 05:16:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-06-14 19:59:53 +00:00
|
|
|
// UpdateTemplate updates the Template on disk and in meta
|
2020-11-30 05:16:25 +00:00
|
|
|
func (r *Registry) UpdateTemplate(name string) error {
|
|
|
|
_, err := r.GetTemplate(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx, t := range r.Templates {
|
|
|
|
if strings.EqualFold(name, t.Name) {
|
|
|
|
// If the path doesn't exist, we are replacing it regardless
|
|
|
|
if err := os.Remove(t.ArchivePath()); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cut it out of the template list so we don't get a duplicate
|
|
|
|
r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...)
|
|
|
|
|
|
|
|
// If path exists, it is local
|
|
|
|
if t.Path != "" {
|
|
|
|
_, err = r.SaveTemplate(t.Name, t.Path)
|
|
|
|
} else {
|
|
|
|
_, err = r.DownloadTemplate(t.Name, t.Repository, t.Branch)
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetSource retrieves a Source from the Registry
|
|
|
|
func (r *Registry) GetSource(name string) (*Source, error) {
|
|
|
|
for _, s := range r.Sources {
|
|
|
|
if strings.EqualFold(name, s.Name) {
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, ErrSourceNotFound{Name: name}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddSource adds a new Source to the Registry
|
|
|
|
func (r *Registry) AddSource(url, name string) (*Source, error) {
|
|
|
|
url = strings.TrimSuffix(url, "/") + "/"
|
|
|
|
s := &Source{
|
|
|
|
Name: name,
|
|
|
|
URL: url,
|
|
|
|
}
|
|
|
|
r.Sources = append(r.Sources, s)
|
|
|
|
|
|
|
|
return s, r.save()
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveSource removes the Source from the registry meta
|
|
|
|
func (r *Registry) RemoveSource(name string) error {
|
|
|
|
_, err := r.GetSource(name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for idx, s := range r.Sources {
|
|
|
|
if strings.EqualFold(name, s.Name) {
|
|
|
|
r.Sources = append(r.Sources[:idx], r.Sources[idx+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r.save()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open opens a Registry, creating one if none exists at dir
|
|
|
|
func Open(dir string) (*Registry, error) {
|
|
|
|
reg := Registry{
|
|
|
|
dir: dir,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := os.Lstat(reg.MetaFilePath())
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
if err := create(reg.MetaFilePath()); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-14 19:59:53 +00:00
|
|
|
contents, err := os.ReadFile(reg.MetaFilePath())
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-11-30 05:16:25 +00:00
|
|
|
|
2022-06-14 19:59:53 +00:00
|
|
|
if err := yaml.Unmarshal(contents, ®); err != nil {
|
2020-11-30 05:16:25 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tmpl := range reg.Templates {
|
|
|
|
tmpl.reg = ®
|
|
|
|
}
|
|
|
|
|
|
|
|
return ®, nil
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func create(regFile string) error {
|
|
|
|
if err := os.MkdirAll(filepath.Dir(regFile), os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fi, err := os.Create(regFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return fi.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func download(cloneURL, branch, dest string) error {
|
2023-08-20 19:30:50 +00:00
|
|
|
tmp, err := os.MkdirTemp(os.TempDir(), "tmpl")
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmp)
|
|
|
|
|
|
|
|
// Clone the repo
|
|
|
|
if _, err := git.PlainClone(tmp, false, &git.CloneOptions{
|
|
|
|
URL: cloneURL,
|
|
|
|
ReferenceName: plumbing.NewBranchReferenceName(branch),
|
|
|
|
SingleBranch: true,
|
|
|
|
Depth: 1,
|
2020-11-28 23:07:18 +00:00
|
|
|
Progress: os.Stdout,
|
2020-11-17 05:41:34 +00:00
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-30 05:16:25 +00:00
|
|
|
// Remove .git
|
2020-11-17 05:41:34 +00:00
|
|
|
if err := os.RemoveAll(filepath.Join(tmp, ".git")); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
// Save the template
|
|
|
|
if err := save(tmp, dest); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func save(source, dest string) error {
|
2020-11-17 05:41:34 +00:00
|
|
|
// Make sure it's a valid template
|
2022-06-14 19:59:53 +00:00
|
|
|
if _, err := os.Lstat(filepath.Join(source, "tmpl.yaml")); err != nil {
|
2020-11-17 05:41:34 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-11-18 05:28:07 +00:00
|
|
|
fi, err := os.Lstat(filepath.Join(source, "template"))
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
|
|
return errors.New("template found, expected directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create archive
|
2020-11-18 05:28:07 +00:00
|
|
|
glob, err := filepath.Glob(filepath.Join(source, "*"))
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := archiver.Archive(glob, dest); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|