forked from coop-cloud/abra
245 lines
5.6 KiB
Go
245 lines
5.6 KiB
Go
package recipe
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/pkg/compose"
|
|
"coopcloud.tech/abra/pkg/config"
|
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
|
loader "coopcloud.tech/abra/pkg/upstream/stack"
|
|
composetypes "github.com/docker/cli/cli/compose/types"
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Recipe represents a recipe.
|
|
type Recipe struct {
|
|
Name string
|
|
Config *composetypes.Config
|
|
}
|
|
|
|
// UpdateLabel updates a recipe label
|
|
func (r Recipe) UpdateLabel(serviceName, label string) error {
|
|
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name)
|
|
if err := compose.UpdateLabel(pattern, serviceName, label, r.Name); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpdateTag updates a recipe tag
|
|
func (r Recipe) UpdateTag(image, tag string) error {
|
|
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, r.Name)
|
|
if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Tags list the recipe tags
|
|
func (r Recipe) Tags() ([]string, error) {
|
|
var tags []string
|
|
|
|
recipeDir := path.Join(config.ABRA_DIR, "apps", r.Name)
|
|
repo, err := git.PlainOpen(recipeDir)
|
|
if err != nil {
|
|
return tags, err
|
|
}
|
|
|
|
gitTags, err := repo.Tags()
|
|
if err != nil {
|
|
return tags, err
|
|
}
|
|
|
|
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
|
|
tags = append(tags, strings.TrimPrefix(string(ref.Name()), "refs/tags/"))
|
|
return nil
|
|
}); err != nil {
|
|
return tags, err
|
|
}
|
|
|
|
logrus.Debugf("detected '%s' as tags for recipe '%s'", strings.Join(tags, ", "), r.Name)
|
|
|
|
return tags, nil
|
|
}
|
|
|
|
// Get retrieves a recipe.
|
|
func Get(recipeName string) (Recipe, error) {
|
|
if err := EnsureExists(recipeName); err != nil {
|
|
return Recipe{}, err
|
|
}
|
|
|
|
pattern := fmt.Sprintf("%s/%s/compose**yml", config.APPS_DIR, recipeName)
|
|
composeFiles, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return Recipe{}, err
|
|
}
|
|
|
|
envSamplePath := path.Join(config.ABRA_DIR, "apps", recipeName, ".env.sample")
|
|
sampleEnv, err := config.ReadEnv(envSamplePath)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
opts := stack.Deploy{Composefiles: composeFiles}
|
|
config, err := loader.LoadComposefile(opts, sampleEnv)
|
|
if err != nil {
|
|
return Recipe{}, err
|
|
}
|
|
|
|
return Recipe{Name: recipeName, Config: config}, nil
|
|
}
|
|
|
|
// EnsureExists ensures that a recipe is locally cloned
|
|
func EnsureExists(recipe string) error {
|
|
recipeDir := path.Join(config.ABRA_DIR, "apps", strings.ToLower(recipe))
|
|
|
|
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
|
|
logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
|
|
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe)
|
|
if err := gitPkg.Clone(recipeDir, url); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureVersion checks whether a specific version exists for a recipe.
|
|
func EnsureVersion(recipeName, version string) error {
|
|
recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName)
|
|
|
|
isClean, err := gitPkg.IsClean(recipeName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !isClean {
|
|
return fmt.Errorf("'%s' has locally unstaged changes", recipeName)
|
|
}
|
|
|
|
repo, err := git.PlainOpen(recipeDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tags, err := repo.Tags()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
var parsedTags []string
|
|
var tagRef plumbing.ReferenceName
|
|
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
|
|
parsedTags = append(parsedTags, ref.Name().Short())
|
|
if ref.Name().Short() == version {
|
|
tagRef = ref.Name()
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
logrus.Debugf("read '%s' as tags for recipe '%s'", strings.Join(parsedTags, ", "), recipeName)
|
|
|
|
if tagRef.String() == "" {
|
|
return fmt.Errorf("%s is not available?", version)
|
|
}
|
|
|
|
worktree, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
opts := &git.CheckoutOptions{
|
|
Branch: tagRef,
|
|
Create: false,
|
|
Force: true,
|
|
}
|
|
if err := worktree.Checkout(opts); err != nil {
|
|
return err
|
|
}
|
|
|
|
logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef.Short(), recipeDir)
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureLatest makes sure the latest commit is checkout on for a local recipe repository.
|
|
func EnsureLatest(recipeName string) error {
|
|
recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName)
|
|
|
|
isClean, err := gitPkg.IsClean(recipeName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !isClean {
|
|
return fmt.Errorf("'%s' has locally unstaged changes", recipeName)
|
|
}
|
|
|
|
logrus.Debugf("attempting to open git repository in '%s'", recipeDir)
|
|
|
|
repo, err := git.PlainOpen(recipeDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
worktree, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
branch := "master"
|
|
if _, err := repo.Branch("master"); err != nil {
|
|
if _, err := repo.Branch("main"); err != nil {
|
|
logrus.Debugf("failed to select branch in '%s'", path.Join(config.APPS_DIR, recipeName))
|
|
return err
|
|
}
|
|
branch = "main"
|
|
}
|
|
|
|
refName := fmt.Sprintf("refs/heads/%s", branch)
|
|
checkOutOpts := &git.CheckoutOptions{
|
|
Create: false,
|
|
Force: true,
|
|
Branch: plumbing.ReferenceName(refName),
|
|
}
|
|
|
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
|
logrus.Debugf("failed to check out '%s' in '%s'", branch, recipeDir)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ChaosVersion constructs a chaos mode recipe version.
|
|
func ChaosVersion(recipeName string) (string, error) {
|
|
var version string
|
|
|
|
head, err := gitPkg.GetRecipeHead(recipeName)
|
|
if err != nil {
|
|
return version, err
|
|
}
|
|
|
|
version = head.String()[:8]
|
|
|
|
isClean, err := gitPkg.IsClean(recipeName)
|
|
if err != nil {
|
|
return version, err
|
|
}
|
|
|
|
if !isClean {
|
|
version = fmt.Sprintf("%s + unstaged changes", version)
|
|
}
|
|
|
|
return version, nil
|
|
}
|