abra/cli/cli.go
2024-10-21 16:39:27 +02:00

207 lines
5.8 KiB
Go

// Package cli provides the interface for the command-line.
package cli
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path"
"coopcloud.tech/abra/cli/app"
"coopcloud.tech/abra/cli/catalogue"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/cli/recipe"
"coopcloud.tech/abra/cli/server"
"coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/web"
charmLog "github.com/charmbracelet/log"
"github.com/urfave/cli/v3"
)
// AutoCompleteCommand helps people set up auto-complete in their shells
var AutoCompleteCommand = cli.Command{
Name: "autocomplete",
Aliases: []string{"ac"},
Usage: "Configure shell autocompletion",
UsageText: "abra autocomplete <shell> [options]",
Description: `Set up shell auto-completion.
Supported shells are: bash, fish, fizsh & zsh.`,
HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
shellType := cmd.Args().First()
if shellType == "" {
internal.ShowSubcommandHelpAndError(cmd, errors.New("no shell provided"))
}
supportedShells := map[string]bool{
"bash": true,
"zsh": true,
"fizsh": true,
"fish": true,
}
if _, ok := supportedShells[shellType]; !ok {
log.Fatalf("%s is not a supported shell right now, sorry", shellType)
}
if shellType == "fizsh" {
shellType = "zsh" // handled the same on the autocompletion side
}
autocompletionDir := path.Join(config.ABRA_DIR, "autocompletion")
if err := os.Mkdir(autocompletionDir, 0764); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
}
log.Debugf("%s already created", autocompletionDir)
}
autocompletionFile := path.Join(config.ABRA_DIR, "autocompletion", shellType)
if _, err := os.Stat(autocompletionFile); err != nil && os.IsNotExist(err) {
url := fmt.Sprintf("https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/autocomplete/%s", shellType)
log.Infof("fetching %s", url)
if err := web.GetFile(autocompletionFile, url); err != nil {
log.Fatal(err)
}
}
switch shellType {
case "bash":
fmt.Println(fmt.Sprintf(`
# run the following commands once to install auto-completion
sudo mkdir -p /etc/bash_completion.d/
sudo cp %s /etc/bash_completion.d/abra
echo "source /etc/bash_completion.d/abra" >> ~/.bashrc
source /etc/bash_completion.d/abra
# To test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile))
case "zsh":
fmt.Println(fmt.Sprintf(`
# run the following commands to once install auto-completion
sudo mkdir -p /etc/zsh/completion.d/
sudo cp %s /etc/zsh/completion.d/abra
echo "PROG=abra\n_CLI_ZSH_AUTOCOMPLETE_HACK=1\nsource /etc/zsh/completion.d/abra" >> ~/.zshrc
source /etc/zsh/completion.d/abra
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile))
case "fish":
fmt.Println(fmt.Sprintf(`
# run the following commands once to install auto-completion
sudo mkdir -p /etc/fish/completions
sudo cp %s /etc/fish/completions/abra
echo "source /etc/fish/completions/abra" >> ~/.config/fish/config.fish
source /etc/fish/completions/abra
# to test, run the following: "abra app <hit tab key>" - you should see command completion!
`, autocompletionFile))
}
return nil
},
}
// UpgradeCommand upgrades abra in-place.
var UpgradeCommand = cli.Command{
Name: "upgrade",
Aliases: []string{"u"},
Usage: "Upgrade abra",
UsageText: "abra upgrade [options]",
Description: `Upgrade abra in-place with the latest stable or release candidate.
Use "--rc/-r" to install the latest release candidate. Please bear in mind that
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
for the testing efforts 💗`,
Flags: []cli.Flag{internal.RCFlag},
HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
mainURL := "https://install.abra.coopcloud.tech"
c := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
if internal.RC {
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
}
log.Debugf("attempting to run %s", c)
if err := internal.RunCmd(c); err != nil {
log.Fatal(err)
}
return nil
},
}
func newAbraApp(version, commit string) *cli.Command {
app := &cli.Command{
Name: "abra",
Usage: "The Co-op Cloud command-line utility belt 🎩🐇",
UsageText: "abra [command] [arguments] [options]",
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
Flags: []cli.Flag{
// NOTE(d1): "GLOBAL OPTIONS" flags
internal.DebugFlag,
},
Commands: []*cli.Command{
&app.AppCommand,
&server.ServerCommand,
&recipe.RecipeCommand,
&catalogue.CatalogueCommand,
&UpgradeCommand,
&AutoCompleteCommand,
},
EnableShellCompletion: true,
UseShortOptionHandling: true,
HideHelpCommand: true,
ShellComplete: autocomplete.SubcommandComplete,
}
app.Before = func(ctx context.Context, cmd *cli.Command) error {
paths := []string{
config.ABRA_DIR,
config.SERVERS_DIR,
config.RECIPES_DIR,
config.VENDOR_DIR,
config.BACKUP_DIR,
}
for _, path := range paths {
if err := os.Mkdir(path, 0764); err != nil {
if !os.IsExist(err) {
log.Fatal(err)
}
continue
}
}
log.Logger.SetStyles(log.Styles())
charmLog.SetDefault(log.Logger)
log.Debugf("abra version %s, commit %s", version, commit)
return nil
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h, H"},
Usage: "Show help",
}
return app
}
// RunApp runs CLI abra app.
func RunApp(version, commit string) {
app := newAbraApp(version, commit)
if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}