Files
abra/cli/internal/validate.go
Linus Gasser 95220049e8 feat(recipe): normalize git URLs to canonical host/path names
Add NormalizeRecipeName to canonicalize the various ways a recipe can be
referenced - https/http/ssh URLs, SCP-style git@host:path, already-
canonical host/path, and short catalogue names - to a single stable
"host/path" form, preserving any ":version" suffix. Wire it into
recipe.Get and ValidateRecipe so every entry point accepts git URLs, and
add Recipe.ShortName to recover the bare recipe name from a prefixed one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 22:39:53 +02:00

184 lines
3.9 KiB
Go

package internal
import (
"strings"
"coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2"
)
// ValidateRecipe ensures the recipe arg is valid.
func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
var recipeName string
if len(args) > 0 {
recipeName = args[0]
}
var recipes []string
catl, err := recipe.ReadRecipeCatalogue(Offline)
if err != nil {
log.Fatal(err)
}
knownRecipes := make(map[string]bool)
for name := range catl {
knownRecipes[name] = true
}
localRecipes, err := recipe.GetRecipesLocal()
if err != nil {
log.Debug(i18n.G("can't read local recipes: %s", err))
} else {
for _, recipeLocal := range localRecipes {
if _, ok := knownRecipes[recipeLocal]; !ok {
knownRecipes[recipeLocal] = true
}
}
}
for recipeName := range knownRecipes {
recipes = append(recipes, recipeName)
}
if recipeName == "" && !NoInput {
prompt := &survey.Select{
Message: i18n.G("Select recipe"),
Options: recipes,
}
if err := survey.AskOne(prompt, &recipeName); err != nil {
log.Fatal(err)
}
}
if recipeName == "" {
log.Fatal(i18n.G("no recipe name provided"))
}
recipeName = recipe.NormalizeRecipeName(recipeName)
lookupName := recipeName
if i := strings.LastIndex(lookupName, ":"); i >= 0 {
lookupName = lookupName[:i]
}
if _, ok := knownRecipes[lookupName]; !ok {
if !strings.Contains(lookupName, "/") {
log.Fatal(i18n.G("no recipe '%s' exists? pass a git URL (e.g. https://git.example.com/user/recipe) to use a recipe outside the catalogue", lookupName))
}
}
chosenRecipe := recipe.Get(recipeName)
if err := chosenRecipe.EnsureExists(); err != nil {
log.Fatal(err)
}
_, err = chosenRecipe.GetComposeConfig(nil)
if err != nil {
if cmdName == i18n.G("generate") {
if strings.Contains(err.Error(), "missing a compose") {
log.Fatal(err)
}
log.Warn(err)
} else {
if strings.Contains(err.Error(), "template_driver is not allowed") {
log.Warn(i18n.G("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName))
}
log.Fatal(i18n.G("unable to validate recipe: %s", err))
}
}
log.Debug(i18n.G("validated %s as recipe argument", recipeName))
return chosenRecipe
}
// ValidateApp ensures the app name arg is valid.
func ValidateApp(args []string) app.App {
if len(args) == 0 {
log.Fatal(i18n.G("no app provided"))
}
appName := args[0]
app, err := app.Get(appName)
if err != nil {
log.Fatal(err)
}
log.Debug(i18n.G("validated %s as app argument", appName))
return app
}
// ValidateDomain ensures the domain name arg is valid.
func ValidateDomain(args []string) string {
var domainName string
if len(args) > 0 {
domainName = args[0]
}
if domainName == "" && !NoInput {
prompt := &survey.Input{
Message: i18n.G("Specify a domain name"),
Default: "1312.net",
}
if err := survey.AskOne(prompt, &domainName); err != nil {
log.Fatal(err)
}
}
if domainName == "" {
log.Fatal(i18n.G("no domain provided"))
}
log.Debug(i18n.G("validated %s as domain argument", domainName))
return domainName
}
// ValidateServer ensures the server name arg is valid.
func ValidateServer(args []string) string {
var serverName string
if len(args) > 0 {
serverName = args[0]
}
serverNames, err := config.ReadServerNames()
if err != nil {
log.Fatal(err)
}
if serverName == "" && !NoInput {
prompt := &survey.Select{
Message: i18n.G("Specify a server name"),
Options: serverNames,
}
if err := survey.AskOne(prompt, &serverName); err != nil {
log.Fatal(err)
}
}
matched := false
for _, name := range serverNames {
if name == serverName {
matched = true
}
}
if serverName == "" {
log.Fatal(i18n.G("no server provided"))
}
if !matched {
log.Fatal(i18n.G("server doesn't exist?"))
}
log.Debug(i18n.G("validated %s as server argument", serverName))
return serverName
}