abra/pkg/recipe/recipe.go

336 lines
7.4 KiB
Go
Raw Normal View History

2021-09-04 23:55:10 +00:00
package recipe
import (
"fmt"
"os"
2021-09-04 23:55:10 +00:00
"path"
2021-09-05 23:15:59 +00:00
"path/filepath"
2021-09-04 23:55:10 +00:00
"strings"
"coopcloud.tech/abra/pkg/compose"
2021-09-05 19:37:03 +00:00
"coopcloud.tech/abra/pkg/config"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/upstream/stack"
loader "coopcloud.tech/abra/pkg/upstream/stack"
2021-09-05 23:15:59 +00:00
composetypes "github.com/docker/cli/cli/compose/types"
2021-09-04 23:55:10 +00:00
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/sirupsen/logrus"
2021-09-04 23:55:10 +00:00
)
// Recipe represents a recipe.
type Recipe struct {
Name string
Config *composetypes.Config
}
2021-12-25 13:04:07 +00:00
// Dir retrieves the recipe repository path
func (r Recipe) Dir() string {
return path.Join(config.RECIPES_DIR, r.Name)
}
// UpdateLabel updates a recipe label
2021-12-21 00:48:37 +00:00
func (r Recipe) UpdateLabel(pattern, serviceName, label string) error {
2021-12-25 13:04:07 +00:00
fullPattern := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, pattern)
2021-12-21 00:48:37 +00:00
if err := compose.UpdateLabel(fullPattern, serviceName, label, r.Name); err != nil {
return err
}
return nil
}
// UpdateTag updates a recipe tag
func (r Recipe) UpdateTag(image, tag string) error {
2021-12-25 13:04:07 +00:00
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_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
2021-12-25 13:04:07 +00:00
repo, err := git.PlainOpen(r.Dir())
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
}
2021-12-25 01:03:09 +00:00
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
}
2021-12-25 13:04:07 +00:00
pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, recipeName)
composeFiles, err := filepath.Glob(pattern)
if err != nil {
return Recipe{}, err
}
if len(composeFiles) == 0 {
return Recipe{}, fmt.Errorf("%s is missing a compose.yml or compose.*.yml file?", recipeName)
}
2021-12-25 13:04:07 +00:00
envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample")
sampleEnv, err := config.ReadEnv(envSamplePath)
if err != nil {
return Recipe{}, 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
2021-12-25 13:04:07 +00:00
func EnsureExists(recipeName string) error {
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
logrus.Debugf("%s does not exist, attemmpting to clone", recipeDir)
2021-12-25 13:04:07 +00:00
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipeName)
if err := gitPkg.Clone(recipeDir, url); err != nil {
return err
}
2021-09-04 23:55:10 +00:00
}
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
return err
}
2021-09-04 23:55:10 +00:00
return nil
}
// EnsureVersion checks whether a specific version exists for a recipe.
2021-09-07 06:12:37 +00:00
func EnsureVersion(recipeName, version string) error {
2021-12-25 13:04:07 +00:00
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
2021-09-04 23:55:10 +00:00
isClean, err := gitPkg.IsClean(recipeName)
if err != nil {
return err
}
if !isClean {
2021-12-25 01:03:09 +00:00
return fmt.Errorf("%s has locally unstaged changes", recipeName)
}
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
return err
}
2021-09-04 23:55:10 +00:00
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return err
}
tags, err := repo.Tags()
if err != nil {
return nil
}
var parsedTags []string
2021-09-04 23:55:10 +00:00
var tagRef plumbing.ReferenceName
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
parsedTags = append(parsedTags, ref.Name().Short())
2021-09-04 23:55:10 +00:00
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)
2021-09-04 23:55:10 +00:00
if tagRef.String() == "" {
2021-12-23 23:43:51 +00:00
logrus.Warnf("no git tag discovered for %s, assuming unreleased recipe", recipeName)
return nil
2021-09-04 23:55:10 +00:00
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
2021-10-14 10:17:58 +00:00
opts := &git.CheckoutOptions{
Branch: tagRef,
Create: false,
Force: true,
2021-10-14 10:17:58 +00:00
}
2021-09-04 23:55:10 +00:00
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
}
2021-12-24 15:06:29 +00:00
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
func EnsureLatest(recipeName string) error {
2021-12-25 13:04:07 +00:00
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
isClean, err := gitPkg.IsClean(recipeName)
if err != nil {
return err
}
if !isClean {
return fmt.Errorf("%s has locally unstaged changes", recipeName)
}
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
return err
}
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
}
2021-12-23 23:43:24 +00:00
branch, err := gitPkg.GetCurrentBranch(repo)
if err != nil {
return err
}
checkOutOpts := &git.CheckoutOptions{
Create: false,
Force: true,
2021-12-23 23:43:24 +00:00
Branch: plumbing.ReferenceName(branch),
}
if err := worktree.Checkout(checkOutOpts); err != nil {
logrus.Debugf("failed to check out %s in %s", branch, recipeDir)
return err
}
2021-09-04 23:55:10 +00:00
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
}
2021-12-19 23:50:09 +00:00
// GetRecipesLocal retrieves all local recipe directories
func GetRecipesLocal() ([]string, error) {
var recipes []string
2021-12-25 13:04:07 +00:00
recipes, err := config.GetAllFoldersInDirectory(config.RECIPES_DIR)
2021-12-19 23:50:09 +00:00
if err != nil {
return recipes, err
}
return recipes, nil
}
// GetVersionLabelLocal retrieves the version label on the local recipe config
func GetVersionLabelLocal(recipe Recipe) (string, error) {
var label string
for _, service := range recipe.Config.Services {
for label, value := range service.Deploy.Labels {
if strings.HasPrefix(label, "coop-cloud") {
return value, nil
}
}
}
if label == "" {
2021-12-25 16:02:47 +00:00
return label, fmt.Errorf("%s has no version label? try running \"abra recipe sync %s\" first?", recipe.Name, recipe.Name)
}
return label, nil
}
2021-12-24 15:06:29 +00:00
// EnsureUpToDate ensures that the local repo is synced to the remote
func EnsureUpToDate(recipeName string) error {
2021-12-25 13:04:07 +00:00
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
2021-12-24 15:06:29 +00:00
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return err
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
branch, err := gitPkg.GetCurrentBranch(repo)
if err != nil {
return err
}
2021-12-25 13:59:58 +00:00
remotes, err := repo.Remotes()
if err != nil {
return err
}
if len(remotes) == 0 {
logrus.Debugf("cannot ensure %s is up-to-date, no git remotes configured", recipeName)
return nil
}
2021-12-24 15:06:29 +00:00
opts := &git.PullOptions{
ReferenceName: plumbing.ReferenceName(branch),
}
if err := worktree.Pull(opts); err != nil {
if !strings.Contains(err.Error(), "already up-to-date") {
return err
}
}
logrus.Debugf("fetched latest git changes for %s", recipeName)
return nil
}