2021-09-04 23:55:10 +00:00
|
|
|
package recipe
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2021-12-25 22:11:32 +00:00
|
|
|
"io/ioutil"
|
2021-11-14 21:54:55 +00:00
|
|
|
"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"
|
|
|
|
|
2021-09-05 23:34:28 +00:00
|
|
|
"coopcloud.tech/abra/pkg/compose"
|
2021-09-05 19:37:03 +00:00
|
|
|
"coopcloud.tech/abra/pkg/config"
|
2021-09-10 22:54:02 +00:00
|
|
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
2021-10-21 17:35:13 +00:00
|
|
|
"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"
|
2021-09-10 22:54:02 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2021-09-04 23:55:10 +00:00
|
|
|
)
|
|
|
|
|
2021-12-25 22:11:32 +00:00
|
|
|
// Image represents a recipe container image.
|
|
|
|
type Image struct {
|
|
|
|
Image string `json:"image"`
|
|
|
|
Rating string `json:"rating"`
|
|
|
|
Source string `json:"source"`
|
|
|
|
URL string `json:"url"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Features represent what top-level features a recipe supports (e.g. does this recipe support backups?).
|
|
|
|
type Features struct {
|
|
|
|
Backups string `json:"backups"`
|
|
|
|
Email string `json:"email"`
|
|
|
|
Healthcheck string `json:"healthcheck"`
|
|
|
|
Image Image `json:"image"`
|
|
|
|
Status int `json:"status"`
|
|
|
|
Tests string `json:"tests"`
|
|
|
|
SSO string `json:"sso"`
|
|
|
|
}
|
|
|
|
|
2021-09-05 23:34:28 +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)
|
|
|
|
}
|
|
|
|
|
2021-09-05 23:34:28 +00:00
|
|
|
// 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 {
|
2021-09-05 23:34:28 +00:00
|
|
|
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)
|
2021-10-02 21:00:09 +00:00
|
|
|
if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil {
|
2021-09-05 23:34:28 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-05 08:28:09 +00:00
|
|
|
// 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())
|
2021-10-05 08:28:09 +00:00
|
|
|
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)
|
2021-10-05 08:28:09 +00:00
|
|
|
|
|
|
|
return tags, nil
|
|
|
|
}
|
|
|
|
|
2021-09-05 23:34:28 +00:00
|
|
|
// Get retrieves a recipe.
|
|
|
|
func Get(recipeName string) (Recipe, error) {
|
2021-09-05 23:41:16 +00:00
|
|
|
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)
|
2021-09-05 23:34:28 +00:00
|
|
|
composeFiles, err := filepath.Glob(pattern)
|
|
|
|
if err != nil {
|
|
|
|
return Recipe{}, err
|
|
|
|
}
|
|
|
|
|
2021-12-19 21:44:19 +00:00
|
|
|
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")
|
2021-09-16 06:40:48 +00:00
|
|
|
sampleEnv, err := config.ReadEnv(envSamplePath)
|
|
|
|
if err != nil {
|
2021-11-22 17:38:59 +00:00
|
|
|
return Recipe{}, err
|
2021-09-16 06:40:48 +00:00
|
|
|
}
|
|
|
|
|
2021-09-05 23:34:28 +00:00
|
|
|
opts := stack.Deploy{Composefiles: composeFiles}
|
2021-09-16 06:40:48 +00:00
|
|
|
config, err := loader.LoadComposefile(opts, sampleEnv)
|
2021-09-05 23:34:28 +00:00
|
|
|
if err != nil {
|
|
|
|
return Recipe{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return Recipe{Name: recipeName, Config: config}, nil
|
|
|
|
}
|
|
|
|
|
2021-11-14 21:54:55 +00:00
|
|
|
// 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)
|
2021-11-14 21:54:55 +00:00
|
|
|
|
|
|
|
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)
|
2021-11-14 21:54:55 +00:00
|
|
|
if err := gitPkg.Clone(recipeDir, url); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-09-04 23:55:10 +00:00
|
|
|
}
|
2021-11-14 21:54:55 +00:00
|
|
|
|
2021-11-15 17:54:24 +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
|
|
|
|
2021-10-18 07:30:43 +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)
|
2021-10-18 07:30:43 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 17:54:24 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-10-12 22:24:23 +00:00
|
|
|
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) {
|
2021-10-12 22:24:23 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-21 18:25:44 +00:00
|
|
|
logrus.Debugf("read %s as tags for recipe %s", strings.Join(parsedTags, ", "), recipeName)
|
2021-10-12 22:24:23 +00:00
|
|
|
|
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)
|
2021-11-26 20:34:21 +00:00
|
|
|
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,
|
2021-12-12 01:04:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-21 18:25:44 +00:00
|
|
|
logrus.Debugf("successfully checked %s out to %s in %s", recipeName, tagRef.Short(), recipeDir)
|
2021-10-12 22:24:23 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-24 15:06:29 +00:00
|
|
|
// EnsureLatest makes sure the latest commit is checked out for a local recipe repository
|
2021-10-12 22:24:23 +00:00
|
|
|
func EnsureLatest(recipeName string) error {
|
2021-12-25 13:04:07 +00:00
|
|
|
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
2021-10-12 22:24:23 +00:00
|
|
|
|
2021-10-26 08:52:26 +00:00
|
|
|
isClean, err := gitPkg.IsClean(recipeName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isClean {
|
2021-12-21 18:25:44 +00:00
|
|
|
return fmt.Errorf("%s has locally unstaged changes", recipeName)
|
2021-10-26 08:52:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-15 17:54:24 +00:00
|
|
|
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-12-21 18:25:44 +00:00
|
|
|
logrus.Debugf("attempting to open git repository in %s", recipeDir)
|
2021-10-12 22:24:23 +00:00
|
|
|
|
|
|
|
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
|
2021-10-12 22:24:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
checkOutOpts := &git.CheckoutOptions{
|
|
|
|
Create: false,
|
2021-12-12 01:04:13 +00:00
|
|
|
Force: true,
|
2021-12-23 23:43:24 +00:00
|
|
|
Branch: plumbing.ReferenceName(branch),
|
2021-10-12 22:24:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
2021-12-21 18:25:44 +00:00
|
|
|
logrus.Debugf("failed to check out %s in %s", branch, recipeDir)
|
2021-10-12 22:24:23 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-09-10 22:54:02 +00:00
|
|
|
|
2021-09-04 23:55:10 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-10-18 06:57:25 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
2021-12-21 18:25:44 +00:00
|
|
|
|
|
|
|
// 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)
|
2021-12-21 18:25:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return label, nil
|
|
|
|
}
|
2021-12-24 15:06:29 +00:00
|
|
|
|
2021-12-25 22:11:32 +00:00
|
|
|
func GetRecipeFeaturesAndCategory(recipeName string) (Features, string, error) {
|
|
|
|
feat := Features{}
|
|
|
|
|
|
|
|
var category string
|
|
|
|
|
|
|
|
readmePath := path.Join(config.RECIPES_DIR, recipeName, "README.md")
|
|
|
|
|
|
|
|
logrus.Debugf("attempting to open %s for recipe metadata parsing", readmePath)
|
|
|
|
|
|
|
|
readmeFS, err := ioutil.ReadFile(readmePath)
|
|
|
|
if err != nil {
|
|
|
|
return feat, category, err
|
|
|
|
}
|
|
|
|
|
|
|
|
readmeMetadata, err := GetStringInBetween( // Find text between delimiters
|
|
|
|
string(readmeFS),
|
|
|
|
"<!-- metadata -->", "<!-- endmetadata -->",
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return feat, category, err
|
|
|
|
}
|
|
|
|
|
|
|
|
readmeLines := strings.Split( // Array item from lines
|
|
|
|
strings.ReplaceAll( // Remove \t tabs
|
|
|
|
readmeMetadata, "\t", "",
|
|
|
|
),
|
|
|
|
"\n")
|
|
|
|
|
|
|
|
for _, val := range readmeLines {
|
|
|
|
if strings.Contains(val, "**Category**") {
|
|
|
|
category = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Category**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**Backups**") {
|
|
|
|
feat.Backups = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Backups**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**Email**") {
|
|
|
|
feat.Email = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Email**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**SSO**") {
|
|
|
|
feat.SSO = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **SSO**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**Healthcheck**") {
|
|
|
|
feat.Healthcheck = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Healthcheck**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**Tests**") {
|
|
|
|
feat.Tests = strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Tests**:"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if strings.Contains(val, "**Image**") {
|
|
|
|
imageMetadata, err := GetImageMetadata(strings.TrimSpace(
|
|
|
|
strings.TrimPrefix(val, "* **Image**:"),
|
|
|
|
), recipeName)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
feat.Image = imageMetadata
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return feat, category, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetImageMetadata(imageRowString, recipeName string) (Image, error) {
|
|
|
|
img := Image{}
|
|
|
|
|
|
|
|
imgFields := strings.Split(imageRowString, ",")
|
|
|
|
|
|
|
|
for i, elem := range imgFields {
|
|
|
|
imgFields[i] = strings.TrimSpace(elem)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(imgFields) < 3 {
|
|
|
|
if imageRowString != "" {
|
|
|
|
logrus.Warnf("%s image meta has incorrect format: %s", recipeName, imageRowString)
|
|
|
|
} else {
|
|
|
|
logrus.Warnf("%s image meta is empty?", recipeName)
|
|
|
|
}
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
img.Rating = imgFields[1]
|
|
|
|
img.Source = imgFields[2]
|
|
|
|
|
|
|
|
imgString := imgFields[0]
|
|
|
|
|
|
|
|
imageName, err := GetStringInBetween(imgString, "[", "]")
|
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
img.Image = strings.ReplaceAll(imageName, "`", "")
|
|
|
|
|
|
|
|
imageURL, err := GetStringInBetween(imgString, "(", ")")
|
|
|
|
if err != nil {
|
|
|
|
logrus.Fatal(err)
|
|
|
|
}
|
|
|
|
img.URL = imageURL
|
|
|
|
|
|
|
|
return img, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetStringInBetween returns empty string if no start or end string found
|
|
|
|
func GetStringInBetween(str, start, end string) (result string, err error) {
|
|
|
|
s := strings.Index(str, start)
|
|
|
|
if s == -1 {
|
|
|
|
return "", fmt.Errorf("marker string %s not found", start)
|
|
|
|
}
|
|
|
|
|
|
|
|
s += len(start)
|
|
|
|
e := strings.Index(str[s:], end)
|
|
|
|
|
|
|
|
if e == -1 {
|
|
|
|
return "", fmt.Errorf("end marker %s not found", end)
|
|
|
|
}
|
|
|
|
|
|
|
|
return str[s : s+e], nil
|
|
|
|
}
|
2021-12-26 03:02:40 +00:00
|
|
|
|
|
|
|
// EnsureUpToDate ensures that the local repo is synced to the remote
|
|
|
|
func EnsureUpToDate(recipeName string) error {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
repo, err := git.PlainOpen(recipeDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
worktree, err := repo.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
branch, err := CheckoutDefaultBranch(repo, recipeName)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := &git.PullOptions{
|
|
|
|
Force: true,
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetDefaultBranch(repo *git.Repository, recipeName string) (plumbing.ReferenceName, error) {
|
|
|
|
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
|
|
|
|
|
|
|
branch := "master"
|
|
|
|
if _, err := repo.Branch("master"); err != nil {
|
|
|
|
if _, err := repo.Branch("main"); err != nil {
|
|
|
|
logrus.Debugf("failed to select branch in %s", recipeDir)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
branch = "main"
|
|
|
|
}
|
|
|
|
|
|
|
|
return plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func CheckoutDefaultBranch(repo *git.Repository, recipeName string) (plumbing.ReferenceName, error) {
|
|
|
|
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
|
|
|
|
|
|
|
branch, err := GetDefaultBranch(repo, recipeName)
|
|
|
|
if err != nil {
|
|
|
|
return plumbing.ReferenceName(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
worktree, err := repo.Worktree()
|
|
|
|
if err != nil {
|
|
|
|
return plumbing.ReferenceName(""), err
|
|
|
|
}
|
|
|
|
|
|
|
|
checkOutOpts := &git.CheckoutOptions{
|
|
|
|
Create: false,
|
|
|
|
Force: true,
|
|
|
|
Branch: branch,
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
|
|
|
recipeDir := path.Join(config.RECIPES_DIR, recipeName)
|
|
|
|
logrus.Debugf("failed to check out %s in %s", branch, recipeDir)
|
|
|
|
return branch, err
|
|
|
|
}
|
|
|
|
|
|
|
|
logrus.Debugf("successfully checked out %v in %s", branch, recipeDir)
|
|
|
|
|
|
|
|
return branch, nil
|
|
|
|
}
|