forked from toolshed/abra
.gitea
cli
app
catalogue
internal
backup.go
cli.go
command.go
deploy.go
errors.go
list.go
new.go
recipe.go
validate.go
recipe
record
server
updater
cli.go
cmd
pkg
scripts
tests
.dockerignore
.drone.yml
.e2e.env.sample
.envrc.sample
.gitignore
.goreleaser.yml
AUTHORS.md
Dockerfile
LICENSE
Makefile
README.md
go.mod
go.sum
renovate.json
517 lines
12 KiB
Go
517 lines
12 KiB
Go
package internal
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"coopcloud.tech/abra/pkg/app"
|
|
"coopcloud.tech/abra/pkg/config"
|
|
"coopcloud.tech/abra/pkg/recipe"
|
|
"coopcloud.tech/abra/pkg/runtime"
|
|
"github.com/AlecAivazis/survey/v2"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
// AppName is used for configuring app name programmatically
|
|
var AppName string
|
|
|
|
// ValidateRecipe ensures the recipe arg is valid.
|
|
func ValidateRecipe(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
|
|
recipeName := c.Args().First()
|
|
conf := runtime.New(opts...)
|
|
|
|
if recipeName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
|
}
|
|
|
|
chosenRecipe, err := recipe.Get(recipeName, conf)
|
|
if err != nil {
|
|
if c.Command.Name == "generate" {
|
|
if strings.Contains(err.Error(), "missing a compose") {
|
|
logrus.Fatal(err)
|
|
}
|
|
logrus.Warn(err)
|
|
} else {
|
|
if strings.Contains(err.Error(), "template_driver is not allowed") {
|
|
logrus.Warnf("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName)
|
|
}
|
|
logrus.Fatalf("unable to validate recipe: %s", err)
|
|
}
|
|
}
|
|
|
|
if err := recipe.EnsureLatest(recipeName, conf); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
logrus.Debugf("validated %s as recipe argument", recipeName)
|
|
|
|
return chosenRecipe
|
|
}
|
|
|
|
// ValidateRecipeWithPrompt ensures a recipe argument is present before
|
|
// validating, asking for input if required.
|
|
func ValidateRecipeWithPrompt(c *cli.Context, opts ...runtime.Option) recipe.Recipe {
|
|
recipeName := c.Args().First()
|
|
conf := runtime.New(opts...)
|
|
|
|
if recipeName == "" && !NoInput {
|
|
var recipes []string
|
|
|
|
catl, err := recipe.ReadRecipeCatalogue()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
knownRecipes := make(map[string]bool)
|
|
for name := range catl {
|
|
knownRecipes[name] = true
|
|
}
|
|
|
|
localRecipes, err := recipe.GetRecipesLocal()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
for _, recipeLocal := range localRecipes {
|
|
if _, ok := knownRecipes[recipeLocal]; !ok {
|
|
knownRecipes[recipeLocal] = true
|
|
}
|
|
}
|
|
|
|
for recipeName := range knownRecipes {
|
|
recipes = append(recipes, recipeName)
|
|
}
|
|
|
|
prompt := &survey.Select{
|
|
Message: "Select recipe",
|
|
Options: recipes,
|
|
}
|
|
if err := survey.AskOne(prompt, &recipeName); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if RecipeName != "" {
|
|
recipeName = RecipeName
|
|
logrus.Debugf("programmatically setting recipe name to %s", recipeName)
|
|
}
|
|
|
|
if recipeName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided"))
|
|
}
|
|
|
|
chosenRecipe, err := recipe.Get(recipeName, conf)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if err := recipe.EnsureLatest(recipeName, conf); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
logrus.Debugf("validated %s as recipe argument", recipeName)
|
|
|
|
return chosenRecipe
|
|
}
|
|
|
|
// ValidateApp ensures the app name arg is valid.
|
|
func ValidateApp(c *cli.Context, opts ...runtime.Option) config.App {
|
|
appName := c.Args().First()
|
|
conf := runtime.New(opts...)
|
|
|
|
if AppName != "" {
|
|
appName = AppName
|
|
logrus.Debugf("programmatically setting app name to %s", appName)
|
|
}
|
|
|
|
if appName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no app provided"))
|
|
}
|
|
|
|
app, err := app.Get(appName)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if err := recipe.EnsureExists(app.Recipe, conf); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
logrus.Debugf("validated %s as app argument", appName)
|
|
|
|
return app
|
|
}
|
|
|
|
// ValidateDomain ensures the domain name arg is valid.
|
|
func ValidateDomain(c *cli.Context) string {
|
|
domainName := c.Args().First()
|
|
|
|
if domainName == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "Specify a domain name",
|
|
Default: "example.com",
|
|
}
|
|
if err := survey.AskOne(prompt, &domainName); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
}
|
|
|
|
if domainName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no domain provided"))
|
|
}
|
|
|
|
logrus.Debugf("validated %s as domain argument", domainName)
|
|
|
|
return domainName
|
|
}
|
|
|
|
// ValidateSubCmdFlags ensures flag order conforms to correct order
|
|
func ValidateSubCmdFlags(c *cli.Context) bool {
|
|
for argIdx, arg := range c.Args() {
|
|
if !strings.HasPrefix(arg, "--") {
|
|
for _, flag := range c.Args()[argIdx:] {
|
|
if strings.HasPrefix(flag, "--") {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ValidateServer ensures the server name arg is valid.
|
|
func ValidateServer(c *cli.Context) string {
|
|
serverName := c.Args().First()
|
|
|
|
serverNames, err := config.ReadServerNames()
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if serverName == "" && !NoInput {
|
|
prompt := &survey.Select{
|
|
Message: "Specify a server name",
|
|
Options: serverNames,
|
|
}
|
|
if err := survey.AskOne(prompt, &serverName); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
}
|
|
|
|
matched := false
|
|
for _, name := range serverNames {
|
|
if name == serverName {
|
|
matched = true
|
|
}
|
|
}
|
|
|
|
if !matched {
|
|
ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?"))
|
|
}
|
|
|
|
if serverName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no server provided"))
|
|
}
|
|
|
|
logrus.Debugf("validated %s as server argument", serverName)
|
|
|
|
return serverName
|
|
}
|
|
|
|
// EnsureDNSProvider ensures a DNS provider is chosen.
|
|
func EnsureDNSProvider() error {
|
|
if DNSProvider == "" && !NoInput {
|
|
prompt := &survey.Select{
|
|
Message: "Select DNS provider",
|
|
Options: []string{"gandi"},
|
|
}
|
|
|
|
if err := survey.AskOne(prompt, &DNSProvider); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if DNSProvider == "" {
|
|
return fmt.Errorf("missing DNS provider?")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureDNSTypeFlag ensures a DNS type flag is present.
|
|
func EnsureDNSTypeFlag(c *cli.Context) error {
|
|
if DNSType == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "Specify DNS record type",
|
|
Default: "A",
|
|
}
|
|
if err := survey.AskOne(prompt, &DNSType); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if DNSType == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no record type provided"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureDNSNameFlag ensures a DNS name flag is present.
|
|
func EnsureDNSNameFlag(c *cli.Context) error {
|
|
if DNSName == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "Specify DNS record name",
|
|
Default: "mysubdomain",
|
|
}
|
|
if err := survey.AskOne(prompt, &DNSName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if DNSName == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no record name provided"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureDNSValueFlag ensures a DNS value flag is present.
|
|
func EnsureDNSValueFlag(c *cli.Context) error {
|
|
if DNSValue == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "Specify DNS record value",
|
|
Default: "192.168.1.2",
|
|
}
|
|
if err := survey.AskOne(prompt, &DNSValue); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if DNSValue == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no record value provided"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureZoneArgument ensures a zone argument is present.
|
|
func EnsureZoneArgument(c *cli.Context) (string, error) {
|
|
zone := c.Args().First()
|
|
|
|
if zone == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "Specify a domain name zone",
|
|
Default: "example.com",
|
|
}
|
|
|
|
if err := survey.AskOne(prompt, &zone); err != nil {
|
|
return zone, err
|
|
}
|
|
}
|
|
|
|
if zone == "" {
|
|
ShowSubcommandHelpAndError(c, errors.New("no zone value provided"))
|
|
}
|
|
|
|
return zone, nil
|
|
}
|
|
|
|
// EnsureServerProvider ensures a 3rd party server provider is chosen.
|
|
func EnsureServerProvider() error {
|
|
if ServerProvider == "" && !NoInput {
|
|
prompt := &survey.Select{
|
|
Message: "Select server provider",
|
|
Options: []string{"capsul", "hetzner-cloud"},
|
|
}
|
|
|
|
if err := survey.AskOne(prompt, &ServerProvider); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if ServerProvider == "" {
|
|
return fmt.Errorf("missing server provider?")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureNewCapsulVPSFlags ensure all flags are present.
|
|
func EnsureNewCapsulVPSFlags(c *cli.Context) error {
|
|
if CapsulName == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul name",
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul instance URL",
|
|
Default: CapsulInstanceURL,
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulInstanceURL); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul type",
|
|
Default: CapsulType,
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulType); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul image",
|
|
Default: CapsulImage,
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulImage); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(CapsulSSHKeys.Value()) == 0 && !NoInput {
|
|
var sshKeys string
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul SSH keys (e.g. me@foo.com)",
|
|
Default: "",
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulSSHKeys); err != nil {
|
|
return err
|
|
}
|
|
CapsulSSHKeys = cli.StringSlice(strings.Split(sshKeys, ","))
|
|
}
|
|
|
|
if CapsulAPIToken == "" && !NoInput {
|
|
token, ok := os.LookupEnv("CAPSUL_TOKEN")
|
|
if !ok {
|
|
prompt := &survey.Input{
|
|
Message: "specify capsul API token",
|
|
}
|
|
if err := survey.AskOne(prompt, &CapsulAPIToken); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
CapsulAPIToken = token
|
|
}
|
|
}
|
|
|
|
if CapsulName == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul name?"))
|
|
}
|
|
if CapsulInstanceURL == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul instance url?"))
|
|
}
|
|
if CapsulType == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul type?"))
|
|
}
|
|
if CapsulImage == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul image?"))
|
|
}
|
|
if len(CapsulSSHKeys.Value()) == 0 {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul ssh keys?"))
|
|
}
|
|
if CapsulAPIToken == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing capsul API token?"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// EnsureNewHetznerCloudVPSFlags ensure all flags are present.
|
|
func EnsureNewHetznerCloudVPSFlags(c *cli.Context) error {
|
|
if HetznerCloudName == "" && !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud VPS name",
|
|
}
|
|
if err := survey.AskOne(prompt, &HetznerCloudName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud VPS type",
|
|
Default: HetznerCloudType,
|
|
}
|
|
if err := survey.AskOne(prompt, &HetznerCloudType); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud VPS image",
|
|
Default: HetznerCloudImage,
|
|
}
|
|
if err := survey.AskOne(prompt, &HetznerCloudImage); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(HetznerCloudSSHKeys.Value()) == 0 && !NoInput {
|
|
var sshKeys string
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud SSH keys (e.g. me@foo.com)",
|
|
Default: "",
|
|
}
|
|
if err := survey.AskOne(prompt, &sshKeys); err != nil {
|
|
return err
|
|
}
|
|
HetznerCloudSSHKeys = cli.StringSlice(strings.Split(sshKeys, ","))
|
|
}
|
|
|
|
if !NoInput {
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud VPS location",
|
|
Default: HetznerCloudLocation,
|
|
}
|
|
if err := survey.AskOne(prompt, &HetznerCloudLocation); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if HetznerCloudAPIToken == "" && !NoInput {
|
|
token, ok := os.LookupEnv("HCLOUD_TOKEN")
|
|
if !ok {
|
|
prompt := &survey.Input{
|
|
Message: "specify hetzner cloud API token",
|
|
}
|
|
if err := survey.AskOne(prompt, &HetznerCloudAPIToken); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
HetznerCloudAPIToken = token
|
|
}
|
|
}
|
|
|
|
if HetznerCloudName == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS name?"))
|
|
}
|
|
if HetznerCloudType == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS type?"))
|
|
}
|
|
if HetznerCloudImage == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud image?"))
|
|
}
|
|
if HetznerCloudLocation == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud VPS location?"))
|
|
}
|
|
if HetznerCloudAPIToken == "" {
|
|
ShowSubcommandHelpAndError(c, fmt.Errorf("missing hetzner cloud API token?"))
|
|
}
|
|
|
|
return nil
|
|
}
|