forked from toolshed/abra
		
	
		
			
				
	
	
		
			284 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package internal
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	appPkg "coopcloud.tech/abra/pkg/app"
 | |
| 	"coopcloud.tech/abra/pkg/config"
 | |
| 	"coopcloud.tech/abra/pkg/formatter"
 | |
| 	"coopcloud.tech/abra/pkg/i18n"
 | |
| 	"coopcloud.tech/abra/pkg/log"
 | |
| 	"coopcloud.tech/tagcmp"
 | |
| 	"github.com/AlecAivazis/survey/v2"
 | |
| 	"github.com/charmbracelet/lipgloss"
 | |
| 	dockerClient "github.com/docker/docker/client"
 | |
| )
 | |
| 
 | |
| var borderStyle = lipgloss.NewStyle().
 | |
| 	BorderStyle(lipgloss.ThickBorder()).
 | |
| 	Padding(0, 1, 0, 1).
 | |
| 	MaxWidth(79).
 | |
| 	BorderForeground(lipgloss.Color("63"))
 | |
| 
 | |
| var headerStyle = lipgloss.NewStyle().
 | |
| 	Underline(true).
 | |
| 	Bold(true).
 | |
| 	PaddingBottom(1)
 | |
| 
 | |
| var leftStyle = lipgloss.NewStyle().
 | |
| 	Bold(true)
 | |
| 
 | |
| var rightStyle = lipgloss.NewStyle()
 | |
| 
 | |
| // horizontal is a JoinHorizontal helper function.
 | |
| func horizontal(left, mid, right string) string {
 | |
| 	return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right)
 | |
| }
 | |
| 
 | |
| func formatComposeFiles(composeFiles string) string {
 | |
| 	return strings.ReplaceAll(composeFiles, ":", "\n")
 | |
| }
 | |
| 
 | |
| // DeployOverview shows a deployment overview
 | |
| func DeployOverview(
 | |
| 	app appPkg.App,
 | |
| 	deployedVersion string,
 | |
| 	toDeployVersion string,
 | |
| 	releaseNotes string,
 | |
| 	warnMessages []string,
 | |
| ) error {
 | |
| 	deployConfig := "compose.yml"
 | |
| 	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
 | |
| 		deployConfig = formatComposeFiles(composeFiles)
 | |
| 	}
 | |
| 
 | |
| 	server := app.Server
 | |
| 	if app.Server == "default" {
 | |
| 		server = "local"
 | |
| 	}
 | |
| 
 | |
| 	domain := app.Domain
 | |
| 	if domain == "" {
 | |
| 		domain = config.NO_DOMAIN_DEFAULT
 | |
| 	}
 | |
| 
 | |
| 	envVersion := app.Recipe.EnvVersionRaw
 | |
| 	if envVersion == "" {
 | |
| 		envVersion = config.NO_VERSION_DEFAULT
 | |
| 	}
 | |
| 
 | |
| 	rows := [][]string{
 | |
| 		{i18n.G("DOMAIN"), domain},
 | |
| 		{i18n.G("RECIPE"), app.Recipe.Name},
 | |
| 		{i18n.G("SERVER"), server},
 | |
| 		{i18n.G("CONFIG"), deployConfig},
 | |
| 		{"", ""},
 | |
| 		{i18n.G("CURRENT DEPLOYMENT"), formatter.BoldDirtyDefault(deployedVersion)},
 | |
| 		{i18n.G("ENV VERSION"), formatter.BoldDirtyDefault(envVersion)},
 | |
| 		{i18n.G("NEW DEPLOYMENT"), formatter.BoldDirtyDefault(toDeployVersion)},
 | |
| 	}
 | |
| 
 | |
| 	deployType := getDeployType(deployedVersion, toDeployVersion)
 | |
| 	overview := formatter.CreateOverview(i18n.G("%s OVERVIEW", deployType), rows)
 | |
| 
 | |
| 	fmt.Println(overview)
 | |
| 
 | |
| 	if releaseNotes != "" {
 | |
| 		fmt.Print(releaseNotes)
 | |
| 	}
 | |
| 
 | |
| 	for _, msg := range warnMessages {
 | |
| 		log.Warn(msg)
 | |
| 	}
 | |
| 
 | |
| 	if NoInput {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	response := false
 | |
| 	prompt := &survey.Confirm{Message: "proceed?"}
 | |
| 	if err := survey.AskOne(prompt, &response); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if !response {
 | |
| 		log.Fatal(i18n.G("deployment cancelled"))
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func getDeployType(currentVersion, newVersion string) string {
 | |
| 	if newVersion == config.NO_DOMAIN_DEFAULT {
 | |
| 		return i18n.G("UNDEPLOY")
 | |
| 	}
 | |
| 	if strings.Contains(newVersion, "+U") {
 | |
| 		return i18n.G("CHAOS DEPLOY")
 | |
| 	}
 | |
| 	if strings.Contains(currentVersion, "+U") {
 | |
| 		return i18n.G("UNCHAOS DEPLOY")
 | |
| 	}
 | |
| 	if currentVersion == newVersion {
 | |
| 		return ("REDEPLOY")
 | |
| 	}
 | |
| 	if currentVersion == config.NO_VERSION_DEFAULT {
 | |
| 		return i18n.G("NEW DEPLOY")
 | |
| 	}
 | |
| 	currentParsed, err := tagcmp.Parse(currentVersion)
 | |
| 	if err != nil {
 | |
| 		return i18n.G("DEPLOY")
 | |
| 	}
 | |
| 	newParsed, err := tagcmp.Parse(newVersion)
 | |
| 	if err != nil {
 | |
| 		return i18n.G("DEPLOY")
 | |
| 	}
 | |
| 	if currentParsed.IsLessThan(newParsed) {
 | |
| 		return i18n.G("UPGRADE")
 | |
| 	}
 | |
| 	return i18n.G("DOWNGRADE")
 | |
| }
 | |
| 
 | |
| // MoveOverview shows a overview before moving an app to a different server
 | |
| func MoveOverview(
 | |
| 	app appPkg.App,
 | |
| 	newServer string,
 | |
| 	secrets []string,
 | |
| 	volumes []string,
 | |
| ) {
 | |
| 	server := app.Server
 | |
| 	if app.Server == "default" {
 | |
| 		server = "local"
 | |
| 	}
 | |
| 
 | |
| 	domain := app.Domain
 | |
| 	if domain == "" {
 | |
| 		domain = config.NO_DOMAIN_DEFAULT
 | |
| 	}
 | |
| 
 | |
| 	secretsOverview := strings.Join(secrets, "\n")
 | |
| 	if len(secrets) == 0 {
 | |
| 		secretsOverview = config.NO_SECRETS_DEFAULT
 | |
| 	}
 | |
| 
 | |
| 	volumesOverview := strings.Join(volumes, "\n")
 | |
| 	if len(volumes) == 0 {
 | |
| 		volumesOverview = config.NO_VOLUMES_DEFAULT
 | |
| 	}
 | |
| 
 | |
| 	rows := [][]string{
 | |
| 		{i18n.G("DOMAIN"), domain},
 | |
| 		{i18n.G("RECIPE"), app.Recipe.Name},
 | |
| 		{i18n.G("OLD SERVER"), server},
 | |
| 		{i18n.G("NEW SERVER"), newServer},
 | |
| 		{i18n.G("SECRETS"), secretsOverview},
 | |
| 		{i18n.G("VOLUMES"), volumesOverview},
 | |
| 	}
 | |
| 
 | |
| 	overview := formatter.CreateOverview(i18n.G("MOVE OVERVIEW"), rows)
 | |
| 
 | |
| 	fmt.Println(overview)
 | |
| }
 | |
| 
 | |
| func PromptProcced() error {
 | |
| 	if NoInput {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if Dry {
 | |
| 		return errors.New(i18n.G("dry run"))
 | |
| 	}
 | |
| 
 | |
| 	response := false
 | |
| 	prompt := &survey.Confirm{Message: i18n.G("proceed?")}
 | |
| 	if err := survey.AskOne(prompt, &response); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if !response {
 | |
| 		return errors.New(i18n.G("cancelled"))
 | |
| 	}
 | |
| 
 | |
| 	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 appPkg.App, commands string) error {
 | |
| 	if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			return errors.New(i18n.G("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
 | |
| 		}
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	for _, command := range strings.Split(commands, "|") {
 | |
| 		commandParts := strings.Split(command, " ")
 | |
| 		if len(commandParts) < 2 {
 | |
| 			return errors.New(i18n.G("not enough arguments: %s", command))
 | |
| 		}
 | |
| 		targetServiceName := commandParts[0]
 | |
| 		cmdName := commandParts[1]
 | |
| 		parsedCmdArgs := ""
 | |
| 		if len(commandParts) > 2 {
 | |
| 			parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " "))
 | |
| 		}
 | |
| 		log.Info(i18n.G("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName))
 | |
| 
 | |
| 		if err := EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		serviceNames, err := appPkg.GetAppServiceNames(app.Name)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		matchingServiceName := false
 | |
| 		for _, serviceName := range serviceNames {
 | |
| 			if serviceName == targetServiceName {
 | |
| 				matchingServiceName = true
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if !matchingServiceName {
 | |
| 			return fmt.Errorf("no service %s for %s?", targetServiceName, app.Name)
 | |
| 		}
 | |
| 
 | |
| 		log.Debug(i18n.G("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName))
 | |
| 
 | |
| 		requestTTY := true
 | |
| 		if err := RunCmdRemote(
 | |
| 			cl,
 | |
| 			app,
 | |
| 			requestTTY,
 | |
| 			app.Recipe.AbraShPath, targetServiceName, cmdName, parsedCmdArgs, ""); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SortVersionsDesc sorts versions in descending order.
 | |
| func SortVersionsDesc(versions []string) []string {
 | |
| 	var tags []tagcmp.Tag
 | |
| 
 | |
| 	for _, v := range versions {
 | |
| 		parsed, _ := tagcmp.Parse(v) // skips unsupported tags
 | |
| 		tags = append(tags, parsed)
 | |
| 	}
 | |
| 
 | |
| 	sort.Sort(tagcmp.ByTagDesc(tags))
 | |
| 
 | |
| 	var desc []string
 | |
| 	for _, t := range tags {
 | |
| 		desc = append(desc, t.String())
 | |
| 	}
 | |
| 
 | |
| 	return desc
 | |
| }
 |