diff --git a/cli/app/cmd.go b/cli/app/cmd.go index 47c4f54d..9073ce92 100644 --- a/cli/app/cmd.go +++ b/cli/app/cmd.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path" + "sort" "strings" "coopcloud.tech/abra/cli/internal" @@ -22,8 +23,7 @@ var appCmdCommand = cli.Command{ Name: "command", Aliases: []string{"cmd"}, Usage: "Run app commands", - Description: ` -Run an app specific command. + Description: `Run an app specific command. These commands are bash functions, defined in the abra.sh of the recipe itself. They can be run within the context of a service (e.g. app) or locally on your @@ -43,8 +43,8 @@ Example: internal.OfflineFlag, internal.ChaosFlag, }, - BashComplete: autocomplete.AppNameComplete, - Before: internal.SubCommandBefore, + Before: internal.SubCommandBefore, + Subcommands: []cli.Command{appCmdListCommand}, Action: func(c *cli.Context) error { app := internal.ValidateApp(c) @@ -186,3 +186,53 @@ func parseCmdArgs(args []string, isLocal bool) (bool, string) { return hasCmdArgs, parsedCmdArgs } + +var appCmdListCommand = cli.Command{ + Name: "list", + Aliases: []string{"ls"}, + Usage: "List all available commands", + ArgsUsage: "", + Flags: []cli.Flag{ + internal.DebugFlag, + internal.OfflineFlag, + internal.ChaosFlag, + }, + BashComplete: autocomplete.AppNameComplete, + Before: internal.SubCommandBefore, + Action: func(c *cli.Context) error { + app := internal.ValidateApp(c) + + if err := recipe.EnsureExists(app.Recipe); err != nil { + logrus.Fatal(err) + } + + if !internal.Chaos { + if err := recipePkg.EnsureIsClean(app.Recipe); err != nil { + logrus.Fatal(err) + } + + if !internal.Offline { + if err := recipePkg.EnsureUpToDate(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + if err := recipePkg.EnsureLatest(app.Recipe); err != nil { + logrus.Fatal(err) + } + } + + abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "abra.sh") + cmdNames, err := config.ReadAbraShCmdNames(abraShPath) + if err != nil { + logrus.Fatal(err) + } + + sort.Strings(cmdNames) + for _, cmdName := range cmdNames { + fmt.Println(cmdName) + } + + return nil + }, +} diff --git a/pkg/config/env.go b/pkg/config/env.go index f6fbbd51..2d9b0228 100644 --- a/pkg/config/env.go +++ b/pkg/config/env.go @@ -249,3 +249,39 @@ func CheckEnv(app App) ([]EnvVar, error) { return envVars, nil } + +// ReadAbraShCmdNames reads the names of commands. +func ReadAbraShCmdNames(abraSh string) ([]string, error) { + var cmdNames []string + + file, err := os.Open(abraSh) + if err != nil { + if os.IsNotExist(err) { + return cmdNames, nil + } + return cmdNames, err + } + defer file.Close() + + cmdNameRegex, err := regexp.Compile(`(\w+)(\(\).*\{)`) + if err != nil { + return cmdNames, err + } + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + matches := cmdNameRegex.FindStringSubmatch(line) + if len(matches) > 0 { + cmdNames = append(cmdNames, matches[1]) + } + } + + if len(cmdNames) > 0 { + logrus.Debugf("read %s from %s", strings.Join(cmdNames, " "), abraSh) + } else { + logrus.Debugf("read 0 command names from %s", abraSh) + } + + return cmdNames, nil +} diff --git a/pkg/config/env_test.go b/pkg/config/env_test.go index 6c62e108..cbc5aecd 100644 --- a/pkg/config/env_test.go +++ b/pkg/config/env_test.go @@ -5,6 +5,7 @@ import ( "os" "path" "reflect" + "slices" "strings" "testing" @@ -115,6 +116,31 @@ func TestReadAbraShEnvVars(t *testing.T) { } } +func TestReadAbraShCmdNames(t *testing.T) { + offline := true + r, err := recipe.Get("abra-test-recipe", offline) + if err != nil { + t.Fatal(err) + } + + abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, r.Name, "abra.sh") + cmdNames, err := config.ReadAbraShCmdNames(abraShPath) + if err != nil { + t.Fatal(err) + } + + if len(cmdNames) == 0 { + t.Error("at least one command name should be found") + } + + expectedCmdNames := []string{"test_cmd", "test_cmd_args"} + for _, cmdName := range expectedCmdNames { + if !slices.Contains(cmdNames, cmdName) { + t.Fatalf("%s should have been found in %s", cmdName, abraShPath) + } + } +} + func TestCheckEnv(t *testing.T) { offline := true r, err := recipe.Get("abra-test-recipe", offline)