forked from toolshed/abra
		
	refactor: drop internal deploy package
This commit is contained in:
		| @ -1,8 +1,28 @@ | ||||
| package app | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | ||||
|  | ||||
| 	"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/runtime" | ||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	dockerClient "github.com/docker/docker/client" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli" | ||||
| ) | ||||
|  | ||||
| @ -31,6 +51,326 @@ Chas mode ("--chaos") will deploy your local checkout of a recipe as-is, | ||||
| including unstaged changes and can be useful for live hacking and testing new | ||||
| recipes. | ||||
| `, | ||||
| 	Action:       internal.DeployAction, | ||||
| 	BashComplete: autocomplete.AppNameComplete, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		app := internal.ValidateApp(c) | ||||
| 		stackName := app.StackName() | ||||
| 		conf := runtime.New() | ||||
|  | ||||
| 		cl, err := client.New(app.Server) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if !internal.Chaos { | ||||
| 			if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		r, err := recipe.Get(app.Recipe, conf) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if err := lint.LintForErrors(r); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		logrus.Debugf("checking whether %s is already deployed", stackName) | ||||
|  | ||||
| 		isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if isDeployed { | ||||
| 			if internal.Force || internal.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" && !internal.Chaos { | ||||
| 			catl, err := recipe.ReadRecipeCatalogue() | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 			versions, err := recipe.GetRecipeCatalogueVersions(app.Recipe, 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.Recipe, version); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				head, err := git.GetRecipeHead(app.Recipe) | ||||
| 				if err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 				version = formatter.SmallSHA(head.String()) | ||||
| 				logrus.Warn("no versions detected, using latest commit") | ||||
| 				if err := recipe.EnsureLatest(app.Recipe, conf); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if version == "unknown" && !internal.Chaos { | ||||
| 			logrus.Debugf("choosing %s as version to deploy", version) | ||||
| 			if err := recipe.EnsureVersion(app.Recipe, version); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if version != "unknown" && !internal.Chaos { | ||||
| 			if err := recipe.EnsureVersion(app.Recipe, version); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if internal.Chaos { | ||||
| 			logrus.Warnf("chaos mode engaged") | ||||
| 			var err error | ||||
| 			version, err = recipe.ChaosVersion(app.Recipe) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "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.Recipe, app.Env) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 		deployOpts := stack.Deploy{ | ||||
| 			Composefiles: composeFiles, | ||||
| 			Namespace:    stackName, | ||||
| 			Prune:        false, | ||||
| 			ResolveImage: stack.ResolveImageAlways, | ||||
| 		} | ||||
| 		compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 		config.ExposeAllEnv(stackName, compose, app.Env) | ||||
| 		config.SetRecipeLabel(compose, stackName, app.Recipe) | ||||
| 		config.SetChaosLabel(compose, stackName, internal.Chaos) | ||||
| 		config.SetChaosVersionLabel(compose, stackName, version) | ||||
| 		config.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 		if err := DeployOverview(app, version, "continue with deployment?"); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if !internal.NoDomainChecks { | ||||
| 			domainName, ok := app.Env["DOMAIN"] | ||||
| 			if ok { | ||||
| 				if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil { | ||||
| 					logrus.Fatal(err) | ||||
| 				} | ||||
| 			} else { | ||||
| 				logrus.Warn("skipping domain checks as no DOMAIN=... configured for app") | ||||
| 			} | ||||
| 		} else { | ||||
| 			logrus.Warn("skipping domain checks as requested") | ||||
| 		} | ||||
|  | ||||
| 		stack.WaitTimeout, err = config.GetTimeoutFromLabel(compose, stackName) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 		logrus.Debugf("set waiting timeout to %d s", stack.WaitTimeout) | ||||
|  | ||||
| 		if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, internal.DontWaitConverge); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"] | ||||
| 		if ok && !internal.DontWaitConverge { | ||||
| 			logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds) | ||||
| 			if err := PostCmds(cl, app, postDeployCmds); err != nil { | ||||
| 				logrus.Fatalf("attempting to run post deploy commands, saw: %s", err) | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| // PostCmds parses a string of commands and executes them inside of the respective services | ||||
| // the commands string must have the following format: | ||||
| // "<service> <command> <arguments>|<service> <command> <arguments>|... " | ||||
| func PostCmds(cl *dockerClient.Client, app config.App, commands string) error { | ||||
| 	abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh") | ||||
| 	if _, err := os.Stat(abraSh); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return fmt.Errorf(fmt.Sprintf("%s does not exist for %s?", abraSh, app.Name)) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, command := range strings.Split(commands, "|") { | ||||
| 		commandParts := strings.Split(command, " ") | ||||
| 		if len(commandParts) < 2 { | ||||
| 			return fmt.Errorf(fmt.Sprintf("not enough arguments: %s", command)) | ||||
| 		} | ||||
| 		targetServiceName := commandParts[0] | ||||
| 		cmdName := commandParts[1] | ||||
| 		parsedCmdArgs := "" | ||||
| 		if len(commandParts) > 2 { | ||||
| 			parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " ")) | ||||
| 		} | ||||
| 		logrus.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName) | ||||
|  | ||||
| 		if err := internal.EnsureCommand(abraSh, app.Recipe, cmdName); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		serviceNames, err := config.GetAppServiceNames(app.Name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		matchingServiceName := false | ||||
| 		for _, serviceName := range serviceNames { | ||||
| 			if serviceName == targetServiceName { | ||||
| 				matchingServiceName = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if !matchingServiceName { | ||||
| 			return fmt.Errorf(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name)) | ||||
| 		} | ||||
|  | ||||
| 		logrus.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName) | ||||
|  | ||||
| 		internal.Tty = true | ||||
| 		if err := internal.RunCmdRemote(cl, app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeployOverview shows a deployment overview | ||||
| func DeployOverview(app config.App, version, message string) error { | ||||
| 	tableCol := []string{"server", "recipe", "config", "domain", "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, app.Recipe, deployConfig, app.Domain, version}) | ||||
| 	table.Render() | ||||
|  | ||||
| 	if internal.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", "recipe", "config", "domain", "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, app.Recipe, deployConfig, app.Domain, currentVersion, newVersion}) | ||||
| 	table.Render() | ||||
|  | ||||
| 	if releaseNotes == "" { | ||||
| 		var err error | ||||
| 		releaseNotes, err = GetReleaseNotes(app.Recipe, 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 internal.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 | ||||
| } | ||||
|  | ||||
| @ -184,7 +184,7 @@ recipes. | ||||
| 		config.SetChaosVersionLabel(compose, stackName, chosenDowngrade) | ||||
| 		config.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 		if err := internal.NewVersionOverview(app, deployedVersion, chosenDowngrade, ""); err != nil { | ||||
| 		if err := NewVersionOverview(app, deployedVersion, chosenDowngrade, ""); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
|  | ||||
| @ -117,7 +117,7 @@ Passing "-p/--prune" does not remove those volumes. | ||||
| 			logrus.Fatalf("%s is not deployed?", app.Name) | ||||
| 		} | ||||
|  | ||||
| 		if err := internal.DeployOverview(app, deployedVersion, "continue with undeploy?"); err != nil { | ||||
| 		if err := DeployOverview(app, deployedVersion, "continue with undeploy?"); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
|  | ||||
| @ -151,7 +151,7 @@ recipes. | ||||
| 		// if release notes written after git tag published, read them before we | ||||
| 		// check out the tag and then they'll appear to be missing. this covers | ||||
| 		// when we obviously will forget to write release notes before publishing | ||||
| 		releaseNotes, err := internal.GetReleaseNotes(app.Recipe, chosenUpgrade) | ||||
| 		releaseNotes, err := GetReleaseNotes(app.Recipe, chosenUpgrade) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| @ -200,7 +200,7 @@ recipes. | ||||
| 		config.SetChaosVersionLabel(compose, stackName, chosenUpgrade) | ||||
| 		config.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 		if err := internal.NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil { | ||||
| 		if err := NewVersionOverview(app, deployedVersion, chosenUpgrade, releaseNotes); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| @ -217,7 +217,7 @@ recipes. | ||||
| 		postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"] | ||||
| 		if ok && !internal.DontWaitConverge { | ||||
| 			logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds) | ||||
| 			if err := internal.PostCmds(cl, app, postDeployCmds); err != nil { | ||||
| 			if err := PostCmds(cl, app, postDeployCmds); err != nil { | ||||
| 				logrus.Fatalf("attempting to run post deploy commands, saw: %s", err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -1,348 +0,0 @@ | ||||
| package internal | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"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/runtime" | ||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||
| 	"github.com/AlecAivazis/survey/v2" | ||||
| 	dockerClient "github.com/docker/docker/client" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli" | ||||
| ) | ||||
|  | ||||
| // DeployAction is the main command-line action for this package | ||||
| func DeployAction(c *cli.Context) error { | ||||
| 	app := ValidateApp(c) | ||||
| 	stackName := app.StackName() | ||||
| 	conf := runtime.New() | ||||
|  | ||||
| 	cl, err := client.New(app.Server) | ||||
| 	if err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !Chaos { | ||||
| 		if err := recipe.EnsureUpToDate(app.Recipe, conf); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r, err := recipe.Get(app.Recipe, conf) | ||||
| 	if err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if err := lint.LintForErrors(r); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	logrus.Debugf("checking whether %s is already deployed", stackName) | ||||
|  | ||||
| 	isDeployed, deployedVersion, err := stack.IsDeployed(context.Background(), cl, 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.Recipe, 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.Recipe, version); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			head, err := git.GetRecipeHead(app.Recipe) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 			version = formatter.SmallSHA(head.String()) | ||||
| 			logrus.Warn("no versions detected, using latest commit") | ||||
| 			if err := recipe.EnsureLatest(app.Recipe, conf); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if version == "unknown" && !Chaos { | ||||
| 		logrus.Debugf("choosing %s as version to deploy", version) | ||||
| 		if err := recipe.EnsureVersion(app.Recipe, version); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if version != "unknown" && !Chaos { | ||||
| 		if err := recipe.EnsureVersion(app.Recipe, version); err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if Chaos { | ||||
| 		logrus.Warnf("chaos mode engaged") | ||||
| 		var err error | ||||
| 		version, err = recipe.ChaosVersion(app.Recipe) | ||||
| 		if err != nil { | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	abraShPath := fmt.Sprintf("%s/%s/%s", config.RECIPES_DIR, app.Recipe, "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.Recipe, app.Env) | ||||
| 	if err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
| 	deployOpts := stack.Deploy{ | ||||
| 		Composefiles: composeFiles, | ||||
| 		Namespace:    stackName, | ||||
| 		Prune:        false, | ||||
| 		ResolveImage: stack.ResolveImageAlways, | ||||
| 	} | ||||
| 	compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env) | ||||
| 	if err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
| 	config.ExposeAllEnv(stackName, compose, app.Env) | ||||
| 	config.SetRecipeLabel(compose, stackName, app.Recipe) | ||||
| 	config.SetChaosLabel(compose, stackName, Chaos) | ||||
| 	config.SetChaosVersionLabel(compose, stackName, version) | ||||
| 	config.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 	if err := DeployOverview(app, version, "continue with deployment?"); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !NoDomainChecks { | ||||
| 		domainName, ok := app.Env["DOMAIN"] | ||||
| 		if ok { | ||||
| 			if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			logrus.Warn("skipping domain checks as no DOMAIN=... configured for app") | ||||
| 		} | ||||
| 	} else { | ||||
| 		logrus.Warn("skipping domain checks as requested") | ||||
| 	} | ||||
|  | ||||
| 	stack.WaitTimeout, err = config.GetTimeoutFromLabel(compose, stackName) | ||||
| 	if err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
| 	logrus.Debugf("set waiting timeout to %d s", stack.WaitTimeout) | ||||
|  | ||||
| 	if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, DontWaitConverge); err != nil { | ||||
| 		logrus.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"] | ||||
| 	if ok && !DontWaitConverge { | ||||
| 		logrus.Debugf("run the following post-deploy commands: %s", postDeployCmds) | ||||
| 		if err := PostCmds(cl, app, postDeployCmds); err != nil { | ||||
| 			logrus.Fatalf("attempting to run post deploy commands, saw: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // PostCmds parses a string of commands and executes them inside of the respective services | ||||
| // the commands string must have the following format: | ||||
| // "<service> <command> <arguments>|<service> <command> <arguments>|... " | ||||
| func PostCmds(cl *dockerClient.Client, app config.App, commands string) error { | ||||
| 	abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh") | ||||
| 	if _, err := os.Stat(abraSh); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return fmt.Errorf(fmt.Sprintf("%s does not exist for %s?", abraSh, app.Name)) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	for _, command := range strings.Split(commands, "|") { | ||||
| 		commandParts := strings.Split(command, " ") | ||||
| 		if len(commandParts) < 2 { | ||||
| 			return fmt.Errorf(fmt.Sprintf("not enough arguments: %s", command)) | ||||
| 		} | ||||
| 		targetServiceName := commandParts[0] | ||||
| 		cmdName := commandParts[1] | ||||
| 		parsedCmdArgs := "" | ||||
| 		if len(commandParts) > 2 { | ||||
| 			parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " ")) | ||||
| 		} | ||||
| 		logrus.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName) | ||||
|  | ||||
| 		if err := EnsureCommand(abraSh, app.Recipe, cmdName); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		serviceNames, err := config.GetAppServiceNames(app.Name) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | ||||
| 		matchingServiceName := false | ||||
| 		for _, serviceName := range serviceNames { | ||||
| 			if serviceName == targetServiceName { | ||||
| 				matchingServiceName = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if !matchingServiceName { | ||||
| 			return fmt.Errorf(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name)) | ||||
| 		} | ||||
|  | ||||
| 		logrus.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName) | ||||
|  | ||||
| 		Tty = true | ||||
| 		if err := RunCmdRemote(cl, app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DeployOverview shows a deployment overview | ||||
| func DeployOverview(app config.App, version, message string) error { | ||||
| 	tableCol := []string{"server", "recipe", "config", "domain", "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, app.Recipe, deployConfig, app.Domain, 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", "recipe", "config", "domain", "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, app.Recipe, deployConfig, app.Domain, currentVersion, newVersion}) | ||||
| 	table.Render() | ||||
|  | ||||
| 	if releaseNotes == "" { | ||||
| 		var err error | ||||
| 		releaseNotes, err = GetReleaseNotes(app.Recipe, 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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user