feat: remote recipes

This commit is contained in:
p4u1 2024-07-08 15:13:13 +02:00
parent 1a3ec7a107
commit d7a870b887
11 changed files with 107 additions and 56 deletions

View File

@ -14,7 +14,6 @@ import (
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli"
)
@ -209,24 +208,23 @@ var appCmdListCommand = cli.Command{
Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
r := recipe.Get(app.Recipe.Name)
if err := r.EnsureExists(); err != nil {
if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err)
}
if !internal.Chaos {
if err := r.EnsureIsClean(); err != nil {
if err := app.Recipe.EnsureIsClean(); err != nil {
log.Fatal(err)
}
if !internal.Offline {
if err := r.EnsureUpToDate(); err != nil {
if err := app.Recipe.EnsureUpToDate(); err != nil {
log.Fatal(err)
}
}
if err := r.EnsureLatest(); err != nil {
if err := app.Recipe.EnsureLatest(); err != nil {
log.Fatal(err)
}
}

View File

@ -11,7 +11,6 @@ import (
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
abraService "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack"
dockerFormatter "github.com/docker/cli/cli/command/formatter"
@ -35,6 +34,9 @@ var appPsCommand = cli.Command{
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(false, false); err != nil {
log.Fatal(err)
}
cl, err := client.New(app.Server)
if err != nil {
@ -74,8 +76,7 @@ var appPsCommand = cli.Command{
// showPSOutput renders ps output.
func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chaosVersion string) {
r := recipe.Get(app.Recipe.Name)
composeFiles, err := r.GetComposeFiles(app.Env)
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
if err != nil {
log.Fatal(err)
return

View File

@ -10,7 +10,6 @@ import (
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/formatter"
gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log"
@ -391,7 +390,7 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
if err := recipe.Push(internal.Dry); err != nil {
return err
}
url := fmt.Sprintf("%s/%s/src/tag/%s", config.REPOS_BASE_URL, recipe.Name, tagString)
url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString)
log.Infof("new release published: %s", url)
} else {
log.Info("no -p/--publish passed, not publishing")

View File

@ -433,8 +433,7 @@ func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
}
// upgrade performs all necessary steps to upgrade an app.
func upgrade(cl *dockerclient.Client, stackName, recipeName,
upgradeVersion string) error {
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string) error {
env, err := getEnv(cl, stackName)
if err != nil {
return err

View File

@ -85,8 +85,7 @@ func TestGetComposeFiles(t *testing.T) {
}
for _, test := range tests {
r2 := recipe.Get(r.Name)
composeFiles, err := r2.GetComposeFiles(test.appEnv)
composeFiles, err := r.GetComposeFiles(test.appEnv)
if err != nil {
t.Fatal(err)
}
@ -107,8 +106,7 @@ func TestGetComposeFilesError(t *testing.T) {
}
for _, test := range tests {
r2 := recipe.Get(r.Name)
_, err := r2.GetComposeFiles(test.appEnv)
_, err := r.GetComposeFiles(test.appEnv)
if err == nil {
t.Fatalf("should have failed: %v", test.appEnv)
}

View File

@ -113,8 +113,7 @@ func TestCheckEnv(t *testing.T) {
t.Fatal(err)
}
r2 := recipe.Get(r.Name)
envSample, err := r2.SampleEnv()
envSample, err := r.SampleEnv()
if err != nil {
t.Fatal(err)
}
@ -147,8 +146,7 @@ func TestCheckEnvError(t *testing.T) {
t.Fatal(err)
}
r2 := recipe.Get(r.Name)
envSample, err := r2.SampleEnv()
envSample, err := r.SampleEnv()
if err != nil {
t.Fatal(err)
}
@ -183,8 +181,7 @@ func TestEnvVarCommentsRemoved(t *testing.T) {
t.Fatal(err)
}
r2 := recipe.Get(r.Name)
envSample, err := r2.SampleEnv()
envSample, err := r.SampleEnv()
if err != nil {
t.Fatal(err)
}

View File

@ -6,7 +6,6 @@ import (
"os"
"path"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
recipePkg "coopcloud.tech/abra/pkg/recipe"
@ -214,8 +213,7 @@ func LintComposeVersion(recipe recipe.Recipe) (bool, error) {
}
func LintEnvConfigPresent(r recipe.Recipe) (bool, error) {
r2 := recipe.Get(r.Name)
if _, err := os.Stat(r2.SampleEnvPath); !os.IsNotExist(err) {
if _, err := os.Stat(r.SampleEnvPath); !os.IsNotExist(err) {
return true, nil
}
@ -241,10 +239,9 @@ func LintAppService(recipe recipe.Recipe) (bool, error) {
// the recipe. This typically means that no domain is required to deploy and
// therefore no matching traefik deploy label will be present.
func LintTraefikEnabledSkipCondition(r recipe.Recipe) (bool, error) {
r2 := recipe.Get(r.Name)
sampleEnv, err := r2.SampleEnv()
sampleEnv, err := r.SampleEnv()
if err != nil {
return false, fmt.Errorf("Unable to discover .env.sample for %s", r2.Name)
return false, fmt.Errorf("Unable to discover .env.sample for %s", r.Name)
}
if _, ok := sampleEnv["DOMAIN"]; !ok {
@ -390,8 +387,7 @@ func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
}
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
r2 := recipe.Get(r.Name)
features, category, err := recipe.GetRecipeFeaturesAndCategory(r2)
features, category, err := recipe.GetRecipeFeaturesAndCategory(r)
if err != nil {
return false, err
}
@ -431,9 +427,7 @@ func LintAbraShVendors(recipe recipe.Recipe) (bool, error) {
}
func LintHasRecipeRepo(recipe recipe.Recipe) (bool, error) {
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, recipe.Name)
res, err := http.Get(url)
res, err := http.Get(recipe.GitURL)
if err != nil {
return false, err
}

View File

@ -39,17 +39,14 @@ func (r Recipe) Ensure(chaos bool, offline bool) error {
// EnsureExists ensures that the recipe is locally cloned
func (r Recipe) EnsureExists() error {
recipeDir := path.Join(config.RECIPES_DIR, r.Name)
if _, err := os.Stat(recipeDir); os.IsNotExist(err) {
log.Debugf("%s does not exist, attemmpting to clone", recipeDir)
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, r.Name)
if err := gitPkg.Clone(recipeDir, url); err != nil {
if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
log.Debugf("%s does not exist, attemmpting to clone", r.Dir)
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
return err
}
}
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
return err
}
@ -60,13 +57,11 @@ func (r Recipe) EnsureExists() error {
func (r Recipe) EnsureVersion(version string) (bool, error) {
isChaosCommit := false
recipeDir := path.Join(config.RECIPES_DIR, r.Name)
if err := gitPkg.EnsureGitRepo(recipeDir); err != nil {
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
return isChaosCommit, err
}
repo, err := git.PlainOpen(recipeDir)
repo, err := git.PlainOpen(r.Dir)
if err != nil {
return isChaosCommit, err
}
@ -117,23 +112,21 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
return isChaosCommit, nil
}
log.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), recipeDir)
log.Debugf("successfully checked %s out to %s in %s", r.Name, tagRef.Short(), r.Dir)
return isChaosCommit, nil
}
// EnsureIsClean makes sure that the recipe repository has no unstaged changes.
func (r Recipe) EnsureIsClean() error {
recipeDir := path.Join(config.RECIPES_DIR, r.Name)
isClean, err := gitPkg.IsClean(recipeDir)
isClean, err := gitPkg.IsClean(r.Dir)
if err != nil {
return fmt.Errorf("unable to check git clean status in %s: %s", recipeDir, err)
return fmt.Errorf("unable to check git clean status in %s: %s", r.Dir, err)
}
if !isClean {
msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding"
return fmt.Errorf(msg, r.Name, recipeDir)
return fmt.Errorf(msg, r.Name, r.Dir)
}
return nil

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"slices"
@ -123,11 +124,28 @@ type Features struct {
}
func Get(name string) Recipe {
dir := path.Join(config.RECIPES_DIR, name)
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name)
sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, name)
if strings.Contains(name, "/") {
u, err := url.Parse(name)
if err != nil {
log.Fatalf("invalid recipe: %s", err)
}
u.Scheme = "https"
gitURL = u.String() + ".git"
u.Scheme = "ssh"
u.User = url.User("git")
sshURL = u.String() + ".git"
}
dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
return Recipe{
Name: name,
Dir: dir,
SSHURL: fmt.Sprintf(config.SSH_URL_TEMPLATE, name),
GitURL: gitURL,
SSHURL: sshURL,
ComposePath: path.Join(dir, "compose.yml"),
ReadmePath: path.Join(dir, "README.md"),
@ -139,6 +157,7 @@ func Get(name string) Recipe {
type Recipe struct {
Name string
Dir string
GitURL string
SSHURL string
ComposePath string
@ -147,6 +166,12 @@ type Recipe struct {
AbraShPath string
}
func escapeRecipeName(recipeName string) string {
recipeName = strings.ReplaceAll(recipeName, "/", "_")
recipeName = strings.ReplaceAll(recipeName, ".", "_")
return recipeName
}
// GetRecipesLocal retrieves all local recipe directories
func GetRecipesLocal() ([]string, error) {
var recipes []string

View File

@ -1,11 +1,60 @@
package recipe
import (
"path"
"testing"
"coopcloud.tech/abra/pkg/config"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
)
func TestGet(t *testing.T) {
cfg := config.LoadAbraConfig()
testcases := []struct {
name string
recipe Recipe
}{
{
name: "foo",
recipe: Recipe{
Name: "foo",
Dir: path.Join(cfg.GetAbraDir(), "/recipes/foo"),
GitURL: "https://git.coopcloud.tech/coop-cloud/foo.git",
SSHURL: "ssh://git@git.coopcloud.tech:2222/coop-cloud/foo.git",
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/foo/compose.yml"),
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/foo/README.md"),
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/foo/.env.sample"),
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/foo/abra.sh"),
},
},
{
name: "mygit.org/myorg/cool-recipe",
recipe: Recipe{
Name: "mygit.org/myorg/cool-recipe",
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
GitURL: "https://mygit.org/myorg/cool-recipe.git",
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("ABRA_DIR", "<abraDir>")
recipe := Get(tc.name)
if diff := cmp.Diff(tc.recipe, recipe); diff != "" {
t.Errorf("Recipe mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
offline := true

View File

@ -16,7 +16,6 @@ import (
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/envfile"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/abra/pkg/upstream/stack"
loader "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/decentral1se/passgen"
@ -246,8 +245,7 @@ type secretStatuses []secretStatus
func PollSecretsStatus(cl *dockerClient.Client, app appPkg.App) (secretStatuses, error) {
var secStats secretStatuses
r := recipe.Get(app.Recipe.Name)
composeFiles, err := r.GetComposeFiles(app.Env)
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
if err != nil {
return secStats, err
}