package internal import ( "fmt" "os" "sort" "strings" appPkg "coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/formatter" "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, info 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{ {"DOMAIN", domain}, {"RECIPE", app.Recipe.Name}, {"SERVER", server}, {"CONFIG", deployConfig}, {"", ""}, {"CURRENT DEPLOYMENT", formatter.BoldDirtyDefault(deployedVersion)}, {"ENV VERSION", formatter.BoldDirtyDefault(envVersion)}, {"NEW DEPLOYMENT", formatter.BoldDirtyDefault(toDeployVersion)}, } deployType := getDeployType(deployedVersion, toDeployVersion) overview := formatter.CreateOverview(fmt.Sprintf("%s OVERVIEW", deployType), rows) fmt.Println(overview) if info != "" { fmt.Println(info) } 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("deployment cancelled") } return nil } func getDeployType(currentVersion, newVersion string) string { if newVersion == config.NO_DOMAIN_DEFAULT { return "UNDEPLOY" } if strings.Contains(newVersion, "+U") { return "CHAOS DEPLOY" } if strings.Contains(currentVersion, "+U") { return "UNCHAOS DEPLOY" } if currentVersion == newVersion { return "REDEPLOY" } if currentVersion == config.NO_VERSION_DEFAULT { return "NEW DEPLOY" } currentParsed, err := tagcmp.Parse(currentVersion) if err != nil { return "DEPLOY" } newParsed, err := tagcmp.Parse(newVersion) if err != nil { return "DEPLOY" } if currentParsed.IsLessThan(newParsed) { return "UPGRADE" } return "DOWNGRADE" } // PostCmds parses a string of commands and executes them inside of the respective services // the commands string must have the following format: // " | |... " 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 fmt.Errorf(fmt.Sprintf("%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 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:], " ")) } log.Infof("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(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name)) } log.Debugf("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 }