package internal import ( "fmt" "io/ioutil" "os" "path" "strings" "coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/dns" "coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/upstream/stack" "github.com/AlecAivazis/survey/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" ) // DeployAction is the main command-line action for this package func DeployAction(c *cli.Context) error { app := ValidateApp(c) if err := recipe.EnsureUpToDate(app.Type); err != nil { logrus.Fatal(err) } r, err := recipe.Get(app.Type) if err != nil { logrus.Fatal(err) } if err := lint.LintForErrors(r); err != nil { logrus.Fatal(err) } cl, err := client.New(app.Server) if err != nil { logrus.Fatal(err) } logrus.Debugf("checking whether %s is already deployed", app.StackName()) isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, app.StackName()) if err != nil { logrus.Fatal(err) } if isDeployed { if Force || Chaos { logrus.Warnf("%s is already deployed but continuing (--force/--chaos)", app.Name) } else { logrus.Fatalf("%s is already deployed", app.Name) } } version := deployedVersion if version == "unknown" && !Chaos { catl, err := recipe.ReadRecipeCatalogue() if err != nil { logrus.Fatal(err) } versions, err := recipe.GetRecipeCatalogueVersions(app.Type, catl) if err != nil { logrus.Fatal(err) } if len(versions) > 0 { version = versions[len(versions)-1] logrus.Debugf("choosing %s as version to deploy", version) if err := recipe.EnsureVersion(app.Type, version); err != nil { logrus.Fatal(err) } } else { head, err := git.GetRecipeHead(app.Type) if err != nil { logrus.Fatal(err) } version = formatter.SmallSHA(head.String()) logrus.Warn("no versions detected, using latest commit") if err := recipe.EnsureLatest(app.Type); err != nil { logrus.Fatal(err) } } } if version == "unknown" && !Chaos { logrus.Debugf("choosing %s as version to deploy", version) if err := recipe.EnsureVersion(app.Type, version); err != nil { logrus.Fatal(err) } } if version != "unknown" && !Chaos { if err := recipe.EnsureVersion(app.Type, version); err != nil { logrus.Fatal(err) } } if Chaos { logrus.Warnf("chaos mode engaged") var err error version, err = recipe.ChaosVersion(app.Type) if err != nil { logrus.Fatal(err) } } abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Type, "abra.sh") abraShEnv, err := config.ReadAbraShEnvVars(abraShPath) if err != nil { logrus.Fatal(err) } for k, v := range abraShEnv { app.Env[k] = v } composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) if err != nil { logrus.Fatal(err) } deployOpts := stack.Deploy{ Composefiles: composeFiles, Namespace: app.StackName(), Prune: false, ResolveImage: stack.ResolveImageAlways, } compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env) if err != nil { logrus.Fatal(err) } if err := DeployOverview(app, version, "continue with deployment?"); err != nil { logrus.Fatal(err) } if !NoDomainChecks { domainName := app.Env["DOMAIN"] ipv4, err := dns.EnsureIPv4(domainName) if err != nil || ipv4 == "" { logrus.Fatalf("could not find an IP address assigned to %s?", domainName) } if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil { logrus.Fatal(err) } } else { logrus.Warn("skipping domain checks as requested") } if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, DontWaitConverge); err != nil { logrus.Fatal(err) } return nil } // DeployOverview shows a deployment overview func DeployOverview(app config.App, version, message string) error { tableCol := []string{"server", "compose", "domain", "app name", "version"} table := formatter.CreateTable(tableCol) deployConfig := "compose.yml" if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") } server := app.Server if app.Server == "default" { server = "local" } table.Append([]string{server, deployConfig, app.Domain, app.Name, version}) table.Render() if NoInput { return nil } response := false prompt := &survey.Confirm{ Message: message, } if err := survey.AskOne(prompt, &response); err != nil { return err } if !response { logrus.Fatal("exiting as requested") } return nil } // NewVersionOverview shows an upgrade or downgrade overview func NewVersionOverview(app config.App, currentVersion, newVersion, releaseNotes string) error { tableCol := []string{"server", "compose", "domain", "app name", "current version", "to be deployed"} table := formatter.CreateTable(tableCol) deployConfig := "compose.yml" if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok { deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n") } server := app.Server if app.Server == "default" { server = "local" } table.Append([]string{server, deployConfig, app.Domain, app.Name, currentVersion, newVersion}) table.Render() if releaseNotes == "" { var err error releaseNotes, err = GetReleaseNotes(app.Type, newVersion) if err != nil { return err } } if releaseNotes != "" && newVersion != "" { fmt.Println() fmt.Println(fmt.Sprintf("%s release notes:\n\n%s", newVersion, releaseNotes)) } else { logrus.Warnf("no release notes available for %s", newVersion) } if NoInput { return nil } response := false prompt := &survey.Confirm{ Message: "continue with deployment?", } if err := survey.AskOne(prompt, &response); err != nil { return err } if !response { logrus.Fatal("exiting as requested") } return nil } // GetReleaseNotes prints release notes for a recipe version func GetReleaseNotes(recipeName, version string) (string, error) { if version == "" { return "", nil } fpath := path.Join(config.RECIPES_DIR, recipeName, "release", version) if _, err := os.Stat(fpath); !os.IsNotExist(err) { releaseNotes, err := ioutil.ReadFile(fpath) if err != nil { return "", err } return string(releaseNotes), nil } return "", nil }