From 8cbf118f660252b1aba9bb01cd49e3d86fe58af5 Mon Sep 17 00:00:00 2001 From: p4u1 Date: Tue, 4 Nov 2025 09:18:29 +0100 Subject: [PATCH] feat: introduce local recipes --- pkg/app/app.go | 7 ++++- pkg/lint/recipe.go | 4 +++ pkg/recipe/git.go | 48 +++++++++++++++++++++++++++++++ pkg/recipe/recipe.go | 67 ++++++++++++++++++++++++++------------------ 4 files changed, 97 insertions(+), 29 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index dc91797c..62746d7b 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -506,7 +506,8 @@ func ExposeAllEnv( stackName string, compose *composetypes.Config, appEnv envfile.AppEnv, - toDeployVersion string) { + toDeployVersion string, +) { for _, service := range compose.Services { if service.Name == "app" { log.Debug(i18n.G("adding env vars to %s service config", stackName)) @@ -633,6 +634,10 @@ func (a App) WipeRecipeVersion() error { // WriteRecipeVersion writes the recipe version to the app .env file. func (a App) WriteRecipeVersion(version string, dryRun bool) error { + if a.Recipe.Local { + return nil + } + file, err := os.Open(a.Path) if err != nil { return err diff --git a/pkg/lint/recipe.go b/pkg/lint/recipe.go index 82ef3eb8..517630d4 100644 --- a/pkg/lint/recipe.go +++ b/pkg/lint/recipe.go @@ -486,6 +486,10 @@ func LintSecretLengths(recipe recipe.Recipe) (bool, error) { } func LintValidTags(recipe recipe.Recipe) (bool, error) { + if recipe.Local { + return true, nil + } + repo, err := git.PlainOpen(recipe.Dir) if err != nil { return false, errors.New(i18n.G("unable to open %s: %s", recipe.Dir, err)) diff --git a/pkg/recipe/git.go b/pkg/recipe/git.go index e487e011..b534e572 100644 --- a/pkg/recipe/git.go +++ b/pkg/recipe/git.go @@ -28,6 +28,10 @@ type EnsureContext struct { // Ensure makes sure the recipe exists, is up to date and has the specific // version checked out. func (r Recipe) Ensure(ctx EnsureContext) error { + if r.Local { + return nil + } + if err := r.EnsureExists(); err != nil { return err } @@ -68,6 +72,10 @@ func (r Recipe) Ensure(ctx EnsureContext) error { // EnsureExists ensures that the recipe is locally cloned func (r Recipe) EnsureExists() error { + if r.Local { + return nil + } + if _, err := os.Stat(r.Dir); os.IsNotExist(err) { if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil { return err @@ -83,6 +91,10 @@ func (r Recipe) EnsureExists() error { // IsChaosCommit determines if a version sttring is a chaos commit or not. func (r Recipe) IsChaosCommit(version string) (bool, error) { + if r.Local { + return false, nil + } + isChaosCommit := false if err := gitPkg.EnsureGitRepo(r.Dir); err != nil { @@ -118,6 +130,10 @@ func (r Recipe) IsChaosCommit(version string) (bool, error) { // EnsureVersion checks whether a specific version exists for a recipe. func (r Recipe) EnsureVersion(version string) (bool, error) { + if r.Local { + return false, nil + } + isChaosCommit := false if err := gitPkg.EnsureGitRepo(r.Dir); err != nil { @@ -182,6 +198,10 @@ func (r Recipe) EnsureVersion(version string) (bool, error) { // EnsureIsClean makes sure that the recipe repository has no unstaged changes. func (r Recipe) EnsureIsClean() error { + if r.Local { + return nil + } + isClean, err := gitPkg.IsClean(r.Dir) if err != nil { return errors.New(i18n.G("unable to check git clean status in %s: %s", r.Dir, err)) @@ -196,6 +216,10 @@ func (r Recipe) EnsureIsClean() error { // EnsureLatest makes sure the latest commit is checked out for the local recipe repository func (r Recipe) EnsureLatest() error { + if r.Local { + return nil + } + if err := gitPkg.EnsureGitRepo(r.Dir); err != nil { return err } @@ -231,6 +255,10 @@ func (r Recipe) EnsureLatest() error { // EnsureUpToDate ensures that the local repo is synced to the remote func (r Recipe) EnsureUpToDate() error { + if r.Local { + return nil + } + repo, err := git.PlainOpen(r.Dir) if err != nil { return errors.New(i18n.G("unable to open %s: %s", r.Dir, err)) @@ -282,6 +310,10 @@ func (r Recipe) EnsureUpToDate() error { // IsDirty checks whether a recipe is dirty or not. func (r *Recipe) IsDirty() (bool, error) { + if r.Local { + return false, nil + } + isClean, err := gitPkg.IsClean(r.Dir) if err != nil { return false, err @@ -292,6 +324,10 @@ func (r *Recipe) IsDirty() (bool, error) { // ChaosVersion constructs a chaos mode recipe version. func (r *Recipe) ChaosVersion() (string, error) { + if r.Local { + return "", nil + } + var version string head, err := r.Head() @@ -315,6 +351,10 @@ func (r *Recipe) ChaosVersion() (string, error) { // Push pushes the latest changes to a SSH URL remote. You need to have your // local SSH configuration for git.coopcloud.tech working for this to work func (r Recipe) Push(dryRun bool) error { + if r.Local { + return nil + } + repo, err := git.PlainOpen(r.Dir) if err != nil { return err @@ -333,6 +373,10 @@ func (r Recipe) Push(dryRun bool) error { // Tags list the recipe tags func (r Recipe) Tags() ([]string, error) { + if r.Local { + return nil, nil + } + var tags []string repo, err := git.PlainOpen(r.Dir) @@ -371,6 +415,10 @@ func (r Recipe) Tags() ([]string, error) { // GetRecipeVersions retrieves all recipe versions. func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) { + if r.Local { + return nil, nil, nil + } + var warnMsg []string versions := RecipeVersions{} diff --git a/pkg/recipe/recipe.go b/pkg/recipe/recipe.go index 592e2452..259597d3 100644 --- a/pkg/recipe/recipe.go +++ b/pkg/recipe/recipe.go @@ -124,38 +124,47 @@ type Features struct { func Get(name string) Recipe { version := "" versionRaw := "" - if strings.Contains(name, ":") { - split := strings.Split(name, ":") - if len(split) > 2 { - log.Fatal(i18n.G("version seems invalid: %s", name)) - } - name = split[0] + gitURL := "" + sshURL := "" + dir := "" + local := false + if strings.HasPrefix(name, "./") { + dir = path.Join(config.ABRA_DIR, name) + local = true + } else { + if strings.Contains(name, ":") { + split := strings.Split(name, ":") + if len(split) > 2 { + log.Fatal(i18n.G("version seems invalid: %s", name)) + } + name = split[0] - version = split[1] - versionRaw = version - if strings.HasSuffix(version, config.DIRTY_DEFAULT) { - version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1) - log.Debug(i18n.G("removed dirty suffix from .env version: %s -> %s", split[1], version)) + version = split[1] + versionRaw = version + if strings.HasSuffix(version, config.DIRTY_DEFAULT) { + version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1) + log.Debug(i18n.G("removed dirty suffix from .env version: %s -> %s", split[1], version)) + } } + + gitURL = fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name) + sshURL = fmt.Sprintf(config.RECIPES_SSH_URL_TEMPLATE, name) + if strings.Contains(name, "/") { + u, err := url.Parse(name) + if err != nil { + log.Fatal(i18n.G("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)) } - gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name) - sshURL := fmt.Sprintf(config.RECIPES_SSH_URL_TEMPLATE, name) - if strings.Contains(name, "/") { - u, err := url.Parse(name) - if err != nil { - log.Fatal(i18n.G("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)) - r := Recipe{ Name: name, EnvVersion: version, @@ -163,6 +172,7 @@ func Get(name string) Recipe { Dir: dir, GitURL: gitURL, SSHURL: sshURL, + Local: local, ComposePath: path.Join(dir, "compose.yml"), ReadmePath: path.Join(dir, "README.md"), @@ -187,6 +197,7 @@ type Recipe struct { Dir string GitURL string SSHURL string + Local bool ComposePath string ReadmePath string