forked from toolshed/abra
		
	Compare commits
	
		
			39 Commits
		
	
	
		
			0.1.7-alph
			...
			0.2.0-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 75fb9a2774 | |||
| 0d500b636d | |||
| 5dd97cace0 | |||
| ae32b1eed2 | |||
| 113bdf9e86 | |||
| d4d4da19b7 | |||
| 454ee696d6 | |||
| ca16c002ba | |||
| 91cc8b00b3 | |||
| d0828c4d8d | |||
| b69aed3bcf | |||
| 875255fd8c | |||
| 2dca602c0b | |||
| 1dca8a1067 | |||
| 37022bf0c8 | |||
| 
						
						
							
						
						eb5b35d47f
	
				 | 
					
					
						|||
| 
						
						
							
						
						ece1130797
	
				 | 
					
					
						|||
| 
						
						
							
						
						c266316f7e
	
				 | 
					
					
						|||
| d804276cf2 | |||
| 4235e06943 | |||
| a9af0b3627 | |||
| a0b4886eba | |||
| 84489495dc | |||
| a8683dc38a | |||
| e2128ea5b6 | |||
| ca3c5fef0f | |||
| 4a01e411be | |||
| 777d49ac1d | |||
| deb7d21158 | |||
| 6db1fdcfba | |||
| 44dc0edf7b | |||
| 
						
						
							
						
						36ff50312c
	
				 | 
					
					
						|||
| ff4b978876 | |||
| b68547b2c2 | |||
| 0140f96ca1 | |||
| 1cb45113db | |||
| c764243f3a | |||
| dde8afcd43 | |||
| 98ffc210e1 | 
@ -28,7 +28,7 @@ checksum:
 | 
			
		||||
snapshot:
 | 
			
		||||
  name_template: "{{ incpatch .Version }}-next"
 | 
			
		||||
changelog:
 | 
			
		||||
  sort: asc
 | 
			
		||||
  sort: desc
 | 
			
		||||
  filters:
 | 
			
		||||
    exclude:
 | 
			
		||||
      - "^WIP:"
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ to scaling apps up and spinning them down.
 | 
			
		||||
		appNewCommand,
 | 
			
		||||
		appConfigCommand,
 | 
			
		||||
		appDeployCommand,
 | 
			
		||||
		appUpgradeCommand,
 | 
			
		||||
		appUndeployCommand,
 | 
			
		||||
		appBackupCommand,
 | 
			
		||||
		appRestoreCommand,
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,16 @@ package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	abraFormatter "coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/client/stack"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
@ -15,14 +20,63 @@ var appDeployCommand = &cli.Command{
 | 
			
		||||
	Name:    "deploy",
 | 
			
		||||
	Aliases: []string{"d"},
 | 
			
		||||
	Usage:   "Deploy an app",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.ForceFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Description: `
 | 
			
		||||
This command deploys a new instance of an app. It does not support changing the
 | 
			
		||||
version of an existing deployed app, for this you need to look at the "abra app
 | 
			
		||||
upgrade <app>" command.
 | 
			
		||||
 | 
			
		||||
You may pass "--force" to re-deploy the same version again. This can be useful
 | 
			
		||||
if the container runtime has gotten into a weird state or your doing some live
 | 
			
		||||
hacking.
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("checking whether '%s' is already deployed", stackName)
 | 
			
		||||
 | 
			
		||||
		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if isDeployed {
 | 
			
		||||
			if internal.Force {
 | 
			
		||||
				logrus.Infof("continuing with deployment due to '--force/-f' being set")
 | 
			
		||||
			} else {
 | 
			
		||||
				logrus.Fatalf("'%s' is already deployed", stackName)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		version := deployedVersion
 | 
			
		||||
		if version == "" {
 | 
			
		||||
			versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			if len(versions) > 0 {
 | 
			
		||||
				version = versions[len(versions)-1]
 | 
			
		||||
				logrus.Infof("choosing '%s' as version to deploy", version)
 | 
			
		||||
				if err := recipe.EnsureVersion(app.Type, version); err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				version = "latest commit"
 | 
			
		||||
				logrus.Warning("no versions detected, using latest commit")
 | 
			
		||||
				if err := recipe.EnsureLatest(app.Type); err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_DIR, app.Type, "abra.sh")
 | 
			
		||||
		abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -32,17 +86,13 @@ var appDeployCommand = &cli.Command{
 | 
			
		||||
			app.Env[k] = v
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, exists := app.Env["STACK_NAME"]; !exists {
 | 
			
		||||
			app.Env["STACK_NAME"] = app.StackName()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		deployOpts := stack.Deploy{
 | 
			
		||||
			Composefiles: composeFiles,
 | 
			
		||||
			Namespace:    app.Env["STACK_NAME"],
 | 
			
		||||
			Namespace:    stackName,
 | 
			
		||||
			Prune:        false,
 | 
			
		||||
			ResolveImage: stack.ResolveImageAlways,
 | 
			
		||||
		}
 | 
			
		||||
@ -51,6 +101,10 @@ var appDeployCommand = &cli.Command{
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DeployOverview(app, version); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
@ -70,3 +124,61 @@ var appDeployCommand = &cli.Command{
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeployOverview shows a deployment overview
 | 
			
		||||
func DeployOverview(app config.App, version string) error {
 | 
			
		||||
	tableCol := []string{"server", "compose", "domain", "stack", "version"}
 | 
			
		||||
	table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
	deployConfig := "compose.yml"
 | 
			
		||||
	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
 | 
			
		||||
		deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table.Append([]string{app.Server, deployConfig, app.Domain, app.StackName(), version})
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewVersionOverview shows an upgrade or downgrade overview
 | 
			
		||||
func NewVersionOverview(app config.App, currentVersion, newVersion string) error {
 | 
			
		||||
	tableCol := []string{"server", "compose", "domain", "stack", "current version", "to be deployed"}
 | 
			
		||||
	table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
	deployConfig := "compose.yml"
 | 
			
		||||
	if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
 | 
			
		||||
		deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table.Append([]string{app.Server, deployConfig, app.Domain, app.StackName(), currentVersion, newVersion})
 | 
			
		||||
	table.Render()
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,14 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	abraFormatter "coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
@ -65,10 +69,10 @@ can take some time.
 | 
			
		||||
		}
 | 
			
		||||
		sort.Sort(config.ByServerAndType(apps))
 | 
			
		||||
 | 
			
		||||
		statuses := map[string]string{}
 | 
			
		||||
		statuses := make(map[string]map[string]string)
 | 
			
		||||
		tableCol := []string{"Server", "Type", "Domain"}
 | 
			
		||||
		if status {
 | 
			
		||||
			tableCol = append(tableCol, "Status")
 | 
			
		||||
			tableCol = append(tableCol, "Status", "Version", "Updates")
 | 
			
		||||
			statuses, err = config.GetAppStatuses(appFiles)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
@ -78,22 +82,90 @@ can take some time.
 | 
			
		||||
		table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
		table.SetAutoMergeCellsByColumnIndex([]int{0})
 | 
			
		||||
 | 
			
		||||
		var (
 | 
			
		||||
			versionedAppsCount   int
 | 
			
		||||
			unversionedAppsCount int
 | 
			
		||||
			onLatestCount        int
 | 
			
		||||
			canUpgradeCount      int
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		for _, app := range apps {
 | 
			
		||||
			var tableRow []string
 | 
			
		||||
			if app.Type == appType || appType == "" {
 | 
			
		||||
				// If type flag is set, check for it, if not, Type == ""
 | 
			
		||||
				tableRow = []string{app.Server, app.Type, app.Domain}
 | 
			
		||||
				if status {
 | 
			
		||||
					if status, ok := statuses[app.StackName()]; ok {
 | 
			
		||||
						tableRow = append(tableRow, status)
 | 
			
		||||
					stackName := app.StackName()
 | 
			
		||||
					status := "unknown"
 | 
			
		||||
					version := "unknown"
 | 
			
		||||
					if statusMeta, ok := statuses[stackName]; ok {
 | 
			
		||||
						if currentVersion, exists := statusMeta["version"]; exists {
 | 
			
		||||
							version = currentVersion
 | 
			
		||||
						}
 | 
			
		||||
						if statusMeta["status"] != "" {
 | 
			
		||||
							status = statusMeta["status"]
 | 
			
		||||
						}
 | 
			
		||||
						tableRow = append(tableRow, status, version)
 | 
			
		||||
						versionedAppsCount++
 | 
			
		||||
					} else {
 | 
			
		||||
						tableRow = append(tableRow, "unknown")
 | 
			
		||||
						tableRow = append(tableRow, status, version)
 | 
			
		||||
						unversionedAppsCount++
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					var newUpdates []string
 | 
			
		||||
					if version != "unknown" {
 | 
			
		||||
						updates, err := catalogue.GetRecipeCatalogueVersions(app.Type)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							logrus.Fatal(err)
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						parsedVersion, err := tagcmp.Parse(version)
 | 
			
		||||
						if err != nil {
 | 
			
		||||
							logrus.Fatal(err)
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						for _, update := range updates {
 | 
			
		||||
							parsedUpdate, err := tagcmp.Parse(update)
 | 
			
		||||
							if err != nil {
 | 
			
		||||
								logrus.Fatal(err)
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
 | 
			
		||||
								newUpdates = append(newUpdates, update)
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if len(newUpdates) == 0 {
 | 
			
		||||
						if version == "unknown" {
 | 
			
		||||
							tableRow = append(tableRow, "unknown")
 | 
			
		||||
						} else {
 | 
			
		||||
							tableRow = append(tableRow, "on latest")
 | 
			
		||||
							onLatestCount++
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						// FIXME: jeezus golang why do you not have a list reverse function
 | 
			
		||||
						for i, j := 0, len(newUpdates)-1; i < j; i, j = i+1, j-1 {
 | 
			
		||||
							newUpdates[i], newUpdates[j] = newUpdates[j], newUpdates[i]
 | 
			
		||||
						}
 | 
			
		||||
						tableRow = append(tableRow, strings.Join(newUpdates, "\n"))
 | 
			
		||||
						canUpgradeCount++
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			table.Append(tableRow)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		stats := fmt.Sprintf(
 | 
			
		||||
			"Total apps: %v | Versioned: %v | Unversioned: %v | On latest: %v | Can upgrade: %v",
 | 
			
		||||
			len(apps),
 | 
			
		||||
			versionedAppsCount,
 | 
			
		||||
			unversionedAppsCount,
 | 
			
		||||
			onLatestCount,
 | 
			
		||||
			canUpgradeCount,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		table.SetCaption(true, stats)
 | 
			
		||||
		table.Render()
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package app
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	abraFormatter "coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
@ -15,44 +16,33 @@ import (
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var watch bool
 | 
			
		||||
var watchFlag = &cli.BoolFlag{
 | 
			
		||||
	Name:        "watch",
 | 
			
		||||
	Aliases:     []string{"w"},
 | 
			
		||||
	Value:       false,
 | 
			
		||||
	Usage:       "Watch status by polling repeatedly",
 | 
			
		||||
	Destination: &watch,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var appPsCommand = &cli.Command{
 | 
			
		||||
	Name:    "ps",
 | 
			
		||||
	Usage:   "Check app status",
 | 
			
		||||
	Aliases: []string{"p"},
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		watchFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		if !watch {
 | 
			
		||||
			showPSOutput(c)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		filters := filters.NewArgs()
 | 
			
		||||
		filters.Add("name", app.StackName())
 | 
			
		||||
 | 
			
		||||
		containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		// TODO: how do we make this update in-place in an x-platform way?
 | 
			
		||||
		for {
 | 
			
		||||
			showPSOutput(c)
 | 
			
		||||
			time.Sleep(2 * time.Second)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tableCol := []string{"id", "image", "command", "created", "status", "ports", "names"}
 | 
			
		||||
		table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
		for _, container := range containers {
 | 
			
		||||
			tableRow := []string{
 | 
			
		||||
				abraFormatter.ShortenID(container.ID),
 | 
			
		||||
				abraFormatter.RemoveSha(container.Image),
 | 
			
		||||
				abraFormatter.Truncate(container.Command),
 | 
			
		||||
				abraFormatter.HumanDuration(container.Created),
 | 
			
		||||
				container.Status,
 | 
			
		||||
				formatter.DisplayablePorts(container.Ports),
 | 
			
		||||
				strings.Join(container.Names, ", "),
 | 
			
		||||
			}
 | 
			
		||||
			table.Append(tableRow)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		table.Render()
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	BashComplete: func(c *cli.Context) {
 | 
			
		||||
		appNames, err := config.GetAppNames()
 | 
			
		||||
@ -67,3 +57,43 @@ var appPsCommand = &cli.Command{
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// showPSOutput renders ps output.
 | 
			
		||||
func showPSOutput(c *cli.Context) {
 | 
			
		||||
	app := internal.ValidateApp(c)
 | 
			
		||||
 | 
			
		||||
	cl, err := client.New(app.Server)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filters := filters.NewArgs()
 | 
			
		||||
	filters.Add("name", app.StackName())
 | 
			
		||||
 | 
			
		||||
	containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tableCol := []string{"image", "created", "status", "ports", "names"}
 | 
			
		||||
	table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
	for _, container := range containers {
 | 
			
		||||
		var containerNames []string
 | 
			
		||||
		for _, containerName := range container.Names {
 | 
			
		||||
			trimmed := strings.TrimPrefix(containerName, "/")
 | 
			
		||||
			containerNames = append(containerNames, trimmed)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tableRow := []string{
 | 
			
		||||
			abraFormatter.RemoveSha(container.Image),
 | 
			
		||||
			abraFormatter.HumanDuration(container.Created),
 | 
			
		||||
			container.Status,
 | 
			
		||||
			formatter.DisplayablePorts(container.Ports),
 | 
			
		||||
			strings.Join(containerNames, "\n"),
 | 
			
		||||
		}
 | 
			
		||||
		table.Append(tableRow)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	table.Render()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -63,7 +63,7 @@ var appRemoveCommand = &cli.Command{
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			if statuses[app.Name] == "deployed" {
 | 
			
		||||
			if statuses[app.Name]["status"] == "deployed" {
 | 
			
		||||
				logrus.Fatalf("'%s' is still deployed. Run \"abra app %s undeploy\" or pass --force", app.Name, app.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -3,14 +3,15 @@ package app
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/client/stack"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	appPkg "coopcloud.tech/abra/pkg/app"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
@ -18,8 +19,21 @@ import (
 | 
			
		||||
var appRollbackCommand = &cli.Command{
 | 
			
		||||
	Name:      "rollback",
 | 
			
		||||
	Usage:     "Roll an app back to a previous version",
 | 
			
		||||
	Aliases:   []string{"r"},
 | 
			
		||||
	ArgsUsage: "[<version>]",
 | 
			
		||||
	Aliases:   []string{"r", "downgrade"},
 | 
			
		||||
	ArgsUsage: "<app>",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.ForceFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Description: `
 | 
			
		||||
This command rolls an app back to a previous version if one exists.
 | 
			
		||||
 | 
			
		||||
You may pass "--force/-f" to downgrade to the same version again. This can be
 | 
			
		||||
useful if the container runtime has gotten into a weird state or your doing
 | 
			
		||||
some live hacking.
 | 
			
		||||
 | 
			
		||||
This action could be destructive, please ensure you have a copy of your app
 | 
			
		||||
data beforehand - see "abra app backup <app>" for more.
 | 
			
		||||
`,
 | 
			
		||||
	BashComplete: func(c *cli.Context) {
 | 
			
		||||
		appNames, err := config.GetAppNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -34,48 +48,110 @@ var appRollbackCommand = &cli.Command{
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipeMeta, err := catalogue.GetRecipeMeta(app.Type)
 | 
			
		||||
		logrus.Debugf("checking whether '%s' is already deployed", stackName)
 | 
			
		||||
 | 
			
		||||
		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if len(recipeMeta.Versions) == 0 {
 | 
			
		||||
			logrus.Fatalf("no catalogue versions available for '%s'", app.Type)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deployedVersions, isDeployed, err := appPkg.DeployedVersions(ctx, cl, app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		if deployedVersion == "" {
 | 
			
		||||
			logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isDeployed {
 | 
			
		||||
			logrus.Fatalf("'%s' is not deployed?", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if _, exists := deployedVersions["app"]; !exists {
 | 
			
		||||
			logrus.Fatalf("no versioned 'app' service for '%s', cannot determine version", app.Name)
 | 
			
		||||
 | 
			
		||||
		versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		version := c.Args().Get(1)
 | 
			
		||||
		if version == "" {
 | 
			
		||||
			// TODO:
 | 
			
		||||
			// using deployedVersions["app"], get index+1 version from catalogue
 | 
			
		||||
			// otherwise bail out saying there is nothing to rollback to
 | 
			
		||||
		} else {
 | 
			
		||||
			// TODO
 | 
			
		||||
			// ensure this version is listed in the catalogue
 | 
			
		||||
			// ensure this version is "older" (lower down in the list)
 | 
			
		||||
		var availableDowngrades []string
 | 
			
		||||
		for _, version := range versions {
 | 
			
		||||
			parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			parsedVersion, err := tagcmp.Parse(version)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			if parsedVersion != parsedDeployedVersion && parsedVersion.IsLessThan(parsedDeployedVersion) {
 | 
			
		||||
				availableDowngrades = append(availableDowngrades, version)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// TODO
 | 
			
		||||
		// display table of existing state and expected state and prompt
 | 
			
		||||
		// run the deployment with this target version!
 | 
			
		||||
		if len(availableDowngrades) == 0 {
 | 
			
		||||
			logrus.Fatal("no available downgrades, you're on latest")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Fatal("command not implemented yet, coming soon TM")
 | 
			
		||||
		// FIXME: jeezus golang why do you not have a list reverse function
 | 
			
		||||
		for i, j := 0, len(availableDowngrades)-1; i < j; i, j = i+1, j-1 {
 | 
			
		||||
			availableDowngrades[i], availableDowngrades[j] = availableDowngrades[j], availableDowngrades[i]
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var chosenDowngrade string
 | 
			
		||||
		if !internal.Force {
 | 
			
		||||
			prompt := &survey.Select{
 | 
			
		||||
				Message: fmt.Sprintf("Please select a downgrade (current version: '%s'):", deployedVersion),
 | 
			
		||||
				Options: availableDowngrades,
 | 
			
		||||
			}
 | 
			
		||||
			if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if internal.Force {
 | 
			
		||||
			chosenDowngrade = availableDowngrades[0]
 | 
			
		||||
			logrus.Debugf("choosing '%s' as version to downgrade to", chosenDowngrade)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := recipe.EnsureVersion(app.Type, chosenDowngrade); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_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:    stackName,
 | 
			
		||||
			Prune:        false,
 | 
			
		||||
			ResolveImage: stack.ResolveImageAlways,
 | 
			
		||||
		}
 | 
			
		||||
		compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Force {
 | 
			
		||||
			if err := NewVersionOverview(app, deployedVersion, chosenDowngrade); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										157
									
								
								cli/app/upgrade.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								cli/app/upgrade.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	stack "coopcloud.tech/abra/pkg/client/stack"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var appUpgradeCommand = &cli.Command{
 | 
			
		||||
	Name:      "upgrade",
 | 
			
		||||
	Aliases:   []string{"u"},
 | 
			
		||||
	Usage:     "Upgrade an app",
 | 
			
		||||
	ArgsUsage: "<app>",
 | 
			
		||||
	Flags: []cli.Flag{
 | 
			
		||||
		internal.ForceFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Description: `
 | 
			
		||||
This command supports upgrading an app. You can use it to choose and roll out a
 | 
			
		||||
new upgrade to an existing app.
 | 
			
		||||
 | 
			
		||||
This command specifically supports changing the version of running apps, as
 | 
			
		||||
opposed to "abra app deploy <app>" which will not change the version of a
 | 
			
		||||
deployed app.
 | 
			
		||||
 | 
			
		||||
You may pass "--force/-f" to upgrade to the same version again. This can be
 | 
			
		||||
useful if the container runtime has gotten into a weird state or your doing
 | 
			
		||||
some live hacking.
 | 
			
		||||
 | 
			
		||||
This action could be destructive, please ensure you have a copy of your app
 | 
			
		||||
data beforehand - see "abra app backup <app>" for more.
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("checking whether '%s' is already deployed", stackName)
 | 
			
		||||
 | 
			
		||||
		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if deployedVersion == "" {
 | 
			
		||||
			logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isDeployed {
 | 
			
		||||
			logrus.Fatalf("'%s' is not deployed?", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var availableUpgrades []string
 | 
			
		||||
		for _, version := range versions {
 | 
			
		||||
			parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			parsedVersion, err := tagcmp.Parse(version)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			if parsedVersion.IsGreaterThan(parsedDeployedVersion) {
 | 
			
		||||
				availableUpgrades = append(availableUpgrades, version)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(availableUpgrades) == 0 {
 | 
			
		||||
			logrus.Fatal("no available upgrades, you're on latest")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var chosenUpgrade string
 | 
			
		||||
		if !internal.Force {
 | 
			
		||||
			prompt := &survey.Select{
 | 
			
		||||
				Message: fmt.Sprintf("Please select an upgrade (current version: '%s'):", deployedVersion),
 | 
			
		||||
				Options: availableUpgrades,
 | 
			
		||||
			}
 | 
			
		||||
			if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if internal.Force {
 | 
			
		||||
			chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
 | 
			
		||||
			logrus.Debugf("choosing '%s' as version to upgrade to", chosenUpgrade)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := recipe.EnsureVersion(app.Type, chosenUpgrade); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_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:    stackName,
 | 
			
		||||
			Prune:        false,
 | 
			
		||||
			ResolveImage: stack.ResolveImageAlways,
 | 
			
		||||
		}
 | 
			
		||||
		compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !internal.Force {
 | 
			
		||||
			if err := NewVersionOverview(app, deployedVersion, chosenUpgrade); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	BashComplete: func(c *cli.Context) {
 | 
			
		||||
		appNames, err := config.GetAppNames()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Warn(err)
 | 
			
		||||
		}
 | 
			
		||||
		if c.NArg() > 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for _, a := range appNames {
 | 
			
		||||
			fmt.Println(a)
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
@ -2,12 +2,12 @@ package app
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	abraFormatter "coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	appPkg "coopcloud.tech/abra/pkg/app"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/catalogue"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/client/stack"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"github.com/docker/distribution/reference"
 | 
			
		||||
@ -32,65 +32,61 @@ func getImagePath(image string) (string, error) {
 | 
			
		||||
var appVersionCommand = &cli.Command{
 | 
			
		||||
	Name:    "version",
 | 
			
		||||
	Aliases: []string{"v"},
 | 
			
		||||
	Usage:   "Show version of all services in app",
 | 
			
		||||
	Usage:   "Show app versions",
 | 
			
		||||
	Description: `
 | 
			
		||||
This command shows all information about versioning related to a deployed app.
 | 
			
		||||
This includes the individual image names, tags and digests. But also the Co-op
 | 
			
		||||
Cloud recipe version.
 | 
			
		||||
`,
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		app := internal.ValidateApp(c)
 | 
			
		||||
		stackName := app.StackName()
 | 
			
		||||
 | 
			
		||||
		composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		opts := stack.Deploy{Composefiles: composeFiles}
 | 
			
		||||
		compose, err := config.GetAppComposeConfig(app.Type, opts, app.Env)
 | 
			
		||||
		cl, err := client.New(app.Server)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ch := make(chan stack.StackStatus, len(compose.Services))
 | 
			
		||||
		for _, service := range compose.Services {
 | 
			
		||||
			label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name)
 | 
			
		||||
			go func(s string, l string) {
 | 
			
		||||
				ch <- stack.GetDeployedServicesByLabel(s, l)
 | 
			
		||||
			}(app.Server, label)
 | 
			
		||||
		logrus.Debugf("checking whether '%s' is already deployed", stackName)
 | 
			
		||||
 | 
			
		||||
		isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tableCol := []string{"name", "image", "version", "digest"}
 | 
			
		||||
		if deployedVersion == "" {
 | 
			
		||||
			logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isDeployed {
 | 
			
		||||
			logrus.Fatalf("'%s' is not deployed?", app.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipeMeta, err := catalogue.GetRecipeMeta(app.Type)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		versionsMeta := make(map[string]catalogue.ServiceMeta)
 | 
			
		||||
		for _, recipeVersion := range recipeMeta.Versions {
 | 
			
		||||
			if currentVersion, exists := recipeVersion[deployedVersion]; exists {
 | 
			
		||||
				versionsMeta = currentVersion
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(versionsMeta) == 0 {
 | 
			
		||||
			logrus.Fatalf("PANIC: could not retrieve deployed version ('%s') from recipe catalogue?", deployedVersion)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tableCol := []string{"name", "image", "version", "tag", "digest"}
 | 
			
		||||
		table := abraFormatter.CreateTable(tableCol)
 | 
			
		||||
 | 
			
		||||
		statuses := make(map[string]stack.StackStatus)
 | 
			
		||||
		for range compose.Services {
 | 
			
		||||
			status := <-ch
 | 
			
		||||
			if len(status.Services) > 0 {
 | 
			
		||||
				serviceName := appPkg.ParseServiceName(status.Services[0].Spec.Name)
 | 
			
		||||
				statuses[serviceName] = status
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sort.SliceStable(compose.Services, func(i, j int) bool {
 | 
			
		||||
			return compose.Services[i].Name < compose.Services[j].Name
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		for _, service := range compose.Services {
 | 
			
		||||
			if status, ok := statuses[service.Name]; ok {
 | 
			
		||||
				statusService := status.Services[0]
 | 
			
		||||
				label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name)
 | 
			
		||||
				version, digest := appPkg.ParseVersionLabel(statusService.Spec.Labels[label])
 | 
			
		||||
				image, err := getImagePath(statusService.Spec.Labels["com.docker.stack.image"])
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				table.Append([]string{service.Name, image, version, digest})
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			image, err := getImagePath(service.Image)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
			table.Append([]string{service.Name, image, "?", "?"})
 | 
			
		||||
		for serviceName, versionMeta := range versionsMeta {
 | 
			
		||||
			table.Append([]string{serviceName, versionMeta.Image, deployedVersion, versionMeta.Tag, versionMeta.Digest})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		table.Render()
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
	BashComplete: func(c *cli.Context) {
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ var appVolumeRemoveCommand = &cli.Command{
 | 
			
		||||
 | 
			
		||||
var appVolumeCommand = &cli.Command{
 | 
			
		||||
	Name:      "volume",
 | 
			
		||||
	Aliases:   []string{"v"},
 | 
			
		||||
	Aliases:   []string{"vl"},
 | 
			
		||||
	Usage:     "Manage app volumes",
 | 
			
		||||
	ArgsUsage: "<command>",
 | 
			
		||||
	Subcommands: []*cli.Command{
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
@ -143,8 +144,26 @@ var catalogueGenerateCommand = &cli.Command{
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		if _, err := os.Stat(config.APPS_JSON); err != nil && os.IsNotExist(err) {
 | 
			
		||||
			if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil {
 | 
			
		||||
				logrus.Fatal(err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if recipeName != "" {
 | 
			
		||||
				catlFS, err := catalogue.ReadRecipeCatalogue()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				catlFS[recipeName] = catl[recipeName]
 | 
			
		||||
 | 
			
		||||
				updatedRecipesJSON, err := json.MarshalIndent(catlFS, "", "    ")
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
				if err := ioutil.WriteFile(config.APPS_JSON, updatedRecipesJSON, 0644); err != nil {
 | 
			
		||||
					logrus.Fatal(err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Infof("generated new recipe catalogue in '%s'", config.APPS_JSON)
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,10 @@ func ValidateApp(c *cli.Context) config.App {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := recipe.EnsureExists(app.Type); err != nil {
 | 
			
		||||
		logrus.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("validated '%s' as app argument", appName)
 | 
			
		||||
 | 
			
		||||
	return app
 | 
			
		||||
 | 
			
		||||
@ -9,11 +9,11 @@ import (
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/config"
 | 
			
		||||
	"coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	recipePkg "coopcloud.tech/abra/pkg/recipe"
 | 
			
		||||
	"coopcloud.tech/tagcmp"
 | 
			
		||||
	"github.com/AlecAivazis/survey/v2"
 | 
			
		||||
	"github.com/go-git/go-git/v5"
 | 
			
		||||
	"github.com/go-git/go-git/v5/plumbing"
 | 
			
		||||
	"github.com/go-git/go-git/v5/plumbing/object"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/urfave/cli/v2"
 | 
			
		||||
)
 | 
			
		||||
@ -50,6 +50,14 @@ var CommitFlag = &cli.BoolFlag{
 | 
			
		||||
	Destination: &Commit,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var TagMessage string
 | 
			
		||||
var TagMessageFlag = &cli.StringFlag{
 | 
			
		||||
	Name:        "tag-comment",
 | 
			
		||||
	Usage:       "tag comment. If not given, user will be asked for it",
 | 
			
		||||
	Aliases:     []string{"t", "tm"},
 | 
			
		||||
	Destination: &TagMessage,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var recipeReleaseCommand = &cli.Command{
 | 
			
		||||
	Name:      "release",
 | 
			
		||||
	Usage:     "tag a recipe",
 | 
			
		||||
@ -84,6 +92,7 @@ or a rollback of an app.
 | 
			
		||||
		PushFlag,
 | 
			
		||||
		CommitFlag,
 | 
			
		||||
		CommitMessageFlag,
 | 
			
		||||
		TagMessageFlag,
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		recipe := internal.ValidateRecipe(c)
 | 
			
		||||
@ -92,17 +101,31 @@ or a rollback of an app.
 | 
			
		||||
		imagesTmp := getImageVersions(recipe)
 | 
			
		||||
		mainApp := getMainApp(recipe)
 | 
			
		||||
		mainAppVersion := imagesTmp[mainApp]
 | 
			
		||||
 | 
			
		||||
		if err := recipePkg.EnsureExists(recipe.Name); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if mainAppVersion == "" {
 | 
			
		||||
			logrus.Fatal("main app version is empty?")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if tagstring != "" {
 | 
			
		||||
			_, err := tagcmp.Parse(tagstring)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
			if _, err := tagcmp.Parse(tagstring); err != nil {
 | 
			
		||||
				logrus.Fatal("invalid tag specified")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if TagMessage == "" {
 | 
			
		||||
			prompt := &survey.Input{
 | 
			
		||||
				Message: "tag message",
 | 
			
		||||
			}
 | 
			
		||||
			survey.AskOne(prompt, &TagMessage)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var createTagOptions git.CreateTagOptions
 | 
			
		||||
		createTagOptions.Message = TagMessage
 | 
			
		||||
 | 
			
		||||
		if Commit || (CommitMessage != "") {
 | 
			
		||||
			commitRepo, err := git.PlainOpen(directory)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
@ -172,9 +195,7 @@ or a rollback of an app.
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{
 | 
			
		||||
				Message: tag,
 | 
			
		||||
			})*/
 | 
			
		||||
			repo.CreateTag(tagstring, head.Hash(), &createTagOptions)
 | 
			
		||||
			logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash()))
 | 
			
		||||
			if Push {
 | 
			
		||||
				if err := repo.Push(&git.PushOptions{}); err != nil {
 | 
			
		||||
@ -187,27 +208,33 @@ or a rollback of an app.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get the latest tag with its hash, name etc
 | 
			
		||||
		var lastGitTag *object.Tag
 | 
			
		||||
		var lastGitTag tagcmp.Tag
 | 
			
		||||
		iter, err := repo.Tags()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if err := iter.ForEach(func(ref *plumbing.Reference) error {
 | 
			
		||||
			obj, err := repo.TagObject(ref.Hash())
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				lastGitTag = obj
 | 
			
		||||
				return nil
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			return err
 | 
			
		||||
 | 
			
		||||
			tagcmpTag, err := tagcmp.Parse(obj.Name)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			if (lastGitTag == tagcmp.Tag{}) {
 | 
			
		||||
				lastGitTag = tagcmpTag
 | 
			
		||||
			} else if tagcmpTag.IsGreaterThan(lastGitTag) {
 | 
			
		||||
				lastGitTag = tagcmpTag
 | 
			
		||||
			}
 | 
			
		||||
			return nil
 | 
			
		||||
		}); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		newTag, err := tagcmp.Parse(lastGitTag.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		fmt.Println(lastGitTag)
 | 
			
		||||
 | 
			
		||||
		newTag := lastGitTag
 | 
			
		||||
		var newTagString string
 | 
			
		||||
		if bumpType > 0 {
 | 
			
		||||
			if Patch {
 | 
			
		||||
@ -232,20 +259,17 @@ or a rollback of an app.
 | 
			
		||||
				newTag.Minor = "0"
 | 
			
		||||
				newTag.Major = strconv.Itoa(now + 1)
 | 
			
		||||
			}
 | 
			
		||||
			newTagString = newTag.String()
 | 
			
		||||
		} else {
 | 
			
		||||
			logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion)
 | 
			
		||||
		newTag.Metadata = mainAppVersion
 | 
			
		||||
		newTagString = newTag.String()
 | 
			
		||||
		if Dry {
 | 
			
		||||
			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash()))
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{
 | 
			
		||||
			Message: tag,
 | 
			
		||||
		})*/
 | 
			
		||||
		repo.CreateTag(newTagString, head.Hash(), &createTagOptions)
 | 
			
		||||
		logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash()))
 | 
			
		||||
		if Push {
 | 
			
		||||
			if err := repo.Push(&git.PushOptions{}); err != nil {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package recipe
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
@ -11,20 +12,21 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var recipeSyncCommand = &cli.Command{
 | 
			
		||||
	Name:    "sync",
 | 
			
		||||
	Usage:   "Ensure recipe version labels are up-to-date",
 | 
			
		||||
	Aliases: []string{"s"},
 | 
			
		||||
	Name:      "sync",
 | 
			
		||||
	Usage:     "Ensure recipe version labels are up-to-date",
 | 
			
		||||
	Aliases:   []string{"s"},
 | 
			
		||||
	ArgsUsage: "<recipe> <version>",
 | 
			
		||||
	Description: `
 | 
			
		||||
This command will generate labels for the main recipe service (i.e. the service
 | 
			
		||||
named "app", by convention) which corresponds to the following format:
 | 
			
		||||
This command will generate labels for the main recipe service (i.e. by
 | 
			
		||||
convention, typically the service named "app") which corresponds to the
 | 
			
		||||
following format:
 | 
			
		||||
 | 
			
		||||
    coop-cloud.${STACK_NAME}.version=${RECIPE_TAG}
 | 
			
		||||
    coop-cloud.${STACK_NAME}.version=<version>
 | 
			
		||||
 | 
			
		||||
The ${RECIPE_TAG} is determined by the recipe maintainer and is retrieved by
 | 
			
		||||
this command by asking for the list of git tags on the local git repository.
 | 
			
		||||
The <recipe> configuration will be updated on the local file system.
 | 
			
		||||
The <version> is determined by the recipe maintainer and is specified on the
 | 
			
		||||
command-line. The <recipe> configuration will be updated on the local file
 | 
			
		||||
system.
 | 
			
		||||
`,
 | 
			
		||||
	ArgsUsage: "<recipe>",
 | 
			
		||||
	BashComplete: func(c *cli.Context) {
 | 
			
		||||
		catl, err := catalogue.ReadRecipeCatalogue()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@ -38,10 +40,17 @@ The <recipe> configuration will be updated on the local file system.
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	Action: func(c *cli.Context) error {
 | 
			
		||||
		if c.Args().Len() != 2 {
 | 
			
		||||
			internal.ShowSubcommandHelpAndError(c, errors.New("missing <recipe>/<version> arguments?"))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		recipe := internal.ValidateRecipe(c)
 | 
			
		||||
 | 
			
		||||
		mainService := "app"
 | 
			
		||||
		// TODO: validate with tagcmp when new commits come in
 | 
			
		||||
		// See https://git.coopcloud.tech/coop-cloud/abra/pulls/109
 | 
			
		||||
		nextTag := c.Args().Get(1)
 | 
			
		||||
 | 
			
		||||
		mainService := "app"
 | 
			
		||||
		var services []string
 | 
			
		||||
		hasAppService := false
 | 
			
		||||
		for _, service := range recipe.Config.Services {
 | 
			
		||||
@ -67,24 +76,12 @@ The <recipe> configuration will be updated on the local file system.
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("selecting '%s' as the service to sync version labels", mainService)
 | 
			
		||||
 | 
			
		||||
		tags, err := recipe.Tags()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(tags) == 0 {
 | 
			
		||||
			logrus.Fatalf("no tags detected for '%s'", recipe.Name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		latestTag := tags[len(tags)-1]
 | 
			
		||||
		logrus.Infof("choosing '%s' as latest tag for recipe '%s'", latestTag, recipe.Name)
 | 
			
		||||
 | 
			
		||||
		label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", latestTag)
 | 
			
		||||
		label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
 | 
			
		||||
		if err := recipe.UpdateLabel(mainService, label); err != nil {
 | 
			
		||||
			logrus.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Infof("added label '%s' to service '%s'", label, mainService)
 | 
			
		||||
		logrus.Infof("synced label '%s' to service '%s'", label, mainService)
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@ -120,11 +120,11 @@ is up to the end-user to decide.
 | 
			
		||||
			var upgradeTag string
 | 
			
		||||
			if bumpType != 0 {
 | 
			
		||||
				for _, upTag := range compatible {
 | 
			
		||||
					upElement, err := tag.UpgradeElement(upTag)
 | 
			
		||||
					upElement, err := tag.UpgradeDelta(upTag)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						return err
 | 
			
		||||
					}
 | 
			
		||||
					delta := tagcmp.UpgradeType(upElement)
 | 
			
		||||
					delta := upElement.UpgradeType()
 | 
			
		||||
					if delta <= bumpType {
 | 
			
		||||
						upgradeTag = upTag.String()
 | 
			
		||||
						break
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										63
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								go.mod
									
									
									
									
									
								
							@ -1,9 +1,9 @@
 | 
			
		||||
module coopcloud.tech/abra
 | 
			
		||||
 | 
			
		||||
go 1.17
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182
 | 
			
		||||
	coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb
 | 
			
		||||
	github.com/AlecAivazis/survey/v2 v2.3.1
 | 
			
		||||
	github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4
 | 
			
		||||
	github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4
 | 
			
		||||
@ -25,75 +25,16 @@ require (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
 | 
			
		||||
	github.com/Microsoft/go-winio v0.4.17 // indirect
 | 
			
		||||
	github.com/Microsoft/hcsshim v0.8.21 // indirect
 | 
			
		||||
	github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
 | 
			
		||||
	github.com/acomagu/bufpipe v1.0.3 // indirect
 | 
			
		||||
	github.com/beorn7/perks v1.0.1 // indirect
 | 
			
		||||
	github.com/cespare/xxhash/v2 v2.1.1 // indirect
 | 
			
		||||
	github.com/containerd/cgroups v1.0.1 // indirect
 | 
			
		||||
	github.com/containerd/containerd v1.5.5 // indirect
 | 
			
		||||
	github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
 | 
			
		||||
	github.com/docker/docker-credential-helpers v0.6.4 // indirect
 | 
			
		||||
	github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
 | 
			
		||||
	github.com/docker/go-connections v0.4.0 // indirect
 | 
			
		||||
	github.com/docker/go-metrics v0.0.1 // indirect
 | 
			
		||||
	github.com/emirpasic/gods v1.12.0 // indirect
 | 
			
		||||
	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 | 
			
		||||
	github.com/fvbommel/sortorder v1.0.2 // indirect
 | 
			
		||||
	github.com/go-git/gcfg v1.5.0 // indirect
 | 
			
		||||
	github.com/go-git/go-billy/v5 v5.3.1 // indirect
 | 
			
		||||
	github.com/gogo/protobuf v1.3.2 // indirect
 | 
			
		||||
	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.0 // indirect
 | 
			
		||||
	github.com/google/go-cmp v0.5.5 // indirect
 | 
			
		||||
	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.8.0 // indirect
 | 
			
		||||
	github.com/imdario/mergo v0.3.12 // indirect
 | 
			
		||||
	github.com/inconshreveable/mousetrap v1.0.0 // indirect
 | 
			
		||||
	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
 | 
			
		||||
	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 | 
			
		||||
	github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
 | 
			
		||||
	github.com/mattn/go-colorable v0.1.2 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.14 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
			
		||||
	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 | 
			
		||||
	github.com/miekg/pkcs11 v1.0.3 // indirect
 | 
			
		||||
	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
 | 
			
		||||
	github.com/mitchellh/go-homedir v1.1.0 // indirect
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.1.2 // indirect
 | 
			
		||||
	github.com/moby/sys/mount v0.2.0 // indirect
 | 
			
		||||
	github.com/moby/sys/mountinfo v0.4.1 // indirect
 | 
			
		||||
	github.com/morikuni/aec v1.0.0 // indirect
 | 
			
		||||
	github.com/opencontainers/go-digest v1.0.0 // indirect
 | 
			
		||||
	github.com/opencontainers/image-spec v1.0.1 // indirect
 | 
			
		||||
	github.com/opencontainers/runc v1.0.2 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.11.0 // indirect
 | 
			
		||||
	github.com/prometheus/client_model v0.2.0 // indirect
 | 
			
		||||
	github.com/prometheus/common v0.26.0 // indirect
 | 
			
		||||
	github.com/prometheus/procfs v0.6.0 // indirect
 | 
			
		||||
	github.com/rivo/uniseg v0.2.0 // indirect
 | 
			
		||||
	github.com/russross/blackfriday/v2 v2.0.1 // indirect
 | 
			
		||||
	github.com/sergi/go-diff v1.1.0 // indirect
 | 
			
		||||
	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 | 
			
		||||
	github.com/spf13/cobra v1.0.0 // indirect
 | 
			
		||||
	github.com/spf13/pflag v1.0.5 // indirect
 | 
			
		||||
	github.com/theupdateframework/notary v0.7.0 // indirect
 | 
			
		||||
	github.com/xanzy/ssh-agent v0.3.0 // indirect
 | 
			
		||||
	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 | 
			
		||||
	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 | 
			
		||||
	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 | 
			
		||||
	go.opencensus.io v0.22.3 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
 | 
			
		||||
	golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
 | 
			
		||||
	golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
 | 
			
		||||
	golang.org/x/text v0.3.4 // indirect
 | 
			
		||||
	google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
 | 
			
		||||
	google.golang.org/grpc v1.33.2 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.26.0 // indirect
 | 
			
		||||
	gopkg.in/warnings.v0 v0.1.2 // indirect
 | 
			
		||||
	gopkg.in/yaml.v2 v2.4.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -21,12 +21,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
 | 
			
		||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 | 
			
		||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
 | 
			
		||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d h1:5jeUIiToqQ7vTlLeycdGp4Ezurd6/RTNl5K38usHtoo=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211003074705-03d2daced95c h1:7kCjnhjrOevcJeA/koCyyv20E6AglvqC7biGbLzyrbU=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211003074705-03d2daced95c/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182 h1:VGFzudsoGXGRaw5eJE3rErHHUDsmuIJpQkdfKJgrNs4=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb h1:Jf+Dnna2kXcNQvcA5JMp6d2Uyvg2pIVJfip9+X5FrH0=
 | 
			
		||||
coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
 | 
			
		||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 | 
			
		||||
github.com/AlecAivazis/survey/v2 v2.3.1 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA=
 | 
			
		||||
github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
 | 
			
		||||
 | 
			
		||||
@ -54,15 +54,15 @@ type tag = string
 | 
			
		||||
// service represents a service within a recipe.
 | 
			
		||||
type service = string
 | 
			
		||||
 | 
			
		||||
// serviceMeta represents meta info associated with a service.
 | 
			
		||||
type serviceMeta struct {
 | 
			
		||||
// ServiceMeta represents meta info associated with a service.
 | 
			
		||||
type ServiceMeta struct {
 | 
			
		||||
	Digest string `json:"digest"`
 | 
			
		||||
	Image  string `json:"image"`
 | 
			
		||||
	Tag    string `json:"tag"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RecipeVersions are the versions associated with a recipe.
 | 
			
		||||
type RecipeVersions []map[tag]map[service]serviceMeta
 | 
			
		||||
type RecipeVersions []map[tag]map[service]ServiceMeta
 | 
			
		||||
 | 
			
		||||
// RecipeMeta represents metadata for a recipe in the abra catalogue.
 | 
			
		||||
type RecipeMeta struct {
 | 
			
		||||
@ -405,7 +405,6 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
		checkOutOpts := &git.CheckoutOptions{
 | 
			
		||||
			Create: false,
 | 
			
		||||
			Force:  true,
 | 
			
		||||
			Keep:   false,
 | 
			
		||||
			Branch: plumbing.ReferenceName(ref.Name()),
 | 
			
		||||
		}
 | 
			
		||||
		if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
			
		||||
@ -420,7 +419,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		versionMeta := make(map[string]serviceMeta)
 | 
			
		||||
		versionMeta := make(map[string]ServiceMeta)
 | 
			
		||||
		for _, service := range recipe.Config.Services {
 | 
			
		||||
 | 
			
		||||
			img, err := reference.ParseNormalizedNamed(service.Image)
 | 
			
		||||
@ -438,7 +437,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			versionMeta[service.Name] = serviceMeta{
 | 
			
		||||
			versionMeta[service.Name] = ServiceMeta{
 | 
			
		||||
				Digest: digest,
 | 
			
		||||
				Image:  path,
 | 
			
		||||
				Tag:    img.(reference.NamedTagged).Tag(),
 | 
			
		||||
@ -447,7 +446,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
			logrus.Debugf("collecting digest: '%s', image: '%s', tag: '%s'", digest, path, tag)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		versions = append(versions, map[string]map[string]serviceMeta{tag: versionMeta})
 | 
			
		||||
		versions = append(versions, map[string]map[string]ServiceMeta{tag: versionMeta})
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
@ -467,7 +466,6 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
	checkOutOpts := &git.CheckoutOptions{
 | 
			
		||||
		Create: false,
 | 
			
		||||
		Force:  true,
 | 
			
		||||
		Keep:   false,
 | 
			
		||||
		Branch: plumbing.ReferenceName(refName),
 | 
			
		||||
	}
 | 
			
		||||
	if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
			
		||||
@ -480,3 +478,23 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
 | 
			
		||||
 | 
			
		||||
	return versions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetRecipeCatalogueVersions list the recipe versions listed in the recipe catalogue.
 | 
			
		||||
func GetRecipeCatalogueVersions(recipeName string) ([]string, error) {
 | 
			
		||||
	var versions []string
 | 
			
		||||
 | 
			
		||||
	catl, err := ReadRecipeCatalogue()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return versions, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if recipeMeta, exists := catl[recipeName]; exists {
 | 
			
		||||
		for _, versionMeta := range recipeMeta.Versions {
 | 
			
		||||
			for tag := range versionMeta {
 | 
			
		||||
				versions = append(versions, tag)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return versions, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package stack
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	abraClient "coopcloud.tech/abra/pkg/client"
 | 
			
		||||
@ -92,6 +93,50 @@ func GetAllDeployedServices(contextName string) StackStatus {
 | 
			
		||||
	return StackStatus{services, nil}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDeployedServicesByName filters services by name
 | 
			
		||||
func GetDeployedServicesByName(ctx context.Context, cl *dockerclient.Client, stackName, serviceName string) StackStatus {
 | 
			
		||||
	filters := filters.NewArgs()
 | 
			
		||||
	filters.Add("name", fmt.Sprintf("%s_%s", stackName, serviceName))
 | 
			
		||||
 | 
			
		||||
	services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filters})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return StackStatus{[]swarm.Service{}, err}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return StackStatus{services, nil}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsDeployed chekcks whether an appp is deployed or not.
 | 
			
		||||
func IsDeployed(ctx context.Context, cl *dockerclient.Client, stackName string) (bool, string, error) {
 | 
			
		||||
	version := ""
 | 
			
		||||
	isDeployed := false
 | 
			
		||||
 | 
			
		||||
	filter := filters.NewArgs()
 | 
			
		||||
	filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
 | 
			
		||||
 | 
			
		||||
	services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, version, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(services) > 0 {
 | 
			
		||||
		for _, service := range services {
 | 
			
		||||
			labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
 | 
			
		||||
			if deployedVersion, ok := service.Spec.Labels[labelKey]; ok {
 | 
			
		||||
				version = deployedVersion
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logrus.Debugf("'%s' has been detected as deployed with version '%s'", stackName, version)
 | 
			
		||||
 | 
			
		||||
		return true, version, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("'%s' has been detected as not deployed", stackName)
 | 
			
		||||
	return isDeployed, version, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// pruneServices removes services that are no longer referenced in the source
 | 
			
		||||
func pruneServices(ctx context.Context, cl *dockerclient.Client, namespace convert.Namespace, services map[string]struct{}) {
 | 
			
		||||
	oldServices, err := GetStackServices(ctx, cl, namespace.Name())
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,7 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				old := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s", service.Name, value)
 | 
			
		||||
				old := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", value)
 | 
			
		||||
				replacedBytes := strings.Replace(string(bytes), old, label, -1)
 | 
			
		||||
 | 
			
		||||
				logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename)
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/formatter"
 | 
			
		||||
@ -46,7 +45,12 @@ type App struct {
 | 
			
		||||
 | 
			
		||||
// StackName gets what the docker safe stack name is for the app
 | 
			
		||||
func (a App) StackName() string {
 | 
			
		||||
	return SanitiseAppName(a.Name)
 | 
			
		||||
	if _, exists := a.Env["STACK_NAME"]; exists {
 | 
			
		||||
		return a.Env["STACK_NAME"]
 | 
			
		||||
	}
 | 
			
		||||
	stackName := SanitiseAppName(a.Name)
 | 
			
		||||
	a.Env["STACK_NAME"] = stackName
 | 
			
		||||
	return stackName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SORTING TYPES
 | 
			
		||||
@ -276,8 +280,8 @@ func SanitiseAppName(name string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAppStatuses queries servers to check the deployment status of given apps
 | 
			
		||||
func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
 | 
			
		||||
	statuses := map[string]string{}
 | 
			
		||||
func GetAppStatuses(appFiles AppFiles) (map[string]map[string]string, error) {
 | 
			
		||||
	statuses := make(map[string]map[string]string)
 | 
			
		||||
 | 
			
		||||
	var unique []string
 | 
			
		||||
	servers := make(map[string]struct{})
 | 
			
		||||
@ -300,10 +304,24 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
 | 
			
		||||
	for range servers {
 | 
			
		||||
		status := <-ch
 | 
			
		||||
		for _, service := range status.Services {
 | 
			
		||||
			result := make(map[string]string)
 | 
			
		||||
			name := service.Spec.Labels[convert.LabelNamespace]
 | 
			
		||||
 | 
			
		||||
			if _, ok := statuses[name]; !ok {
 | 
			
		||||
				statuses[name] = "deployed"
 | 
			
		||||
				result["status"] = "deployed"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelKey := fmt.Sprintf("coop-cloud.%s.version", name)
 | 
			
		||||
			if version, ok := service.Spec.Labels[labelKey]; ok {
 | 
			
		||||
				result["version"] = version
 | 
			
		||||
			} else {
 | 
			
		||||
				//FIXME: we only need to check containers with the version label not
 | 
			
		||||
				//       every single container and then skip when we see no label perf gains
 | 
			
		||||
				//       to be had here
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			statuses[name] = result
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -315,20 +333,18 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) {
 | 
			
		||||
// GetAppComposeFiles gets the list of compose files for an app which should be
 | 
			
		||||
// merged into a composetypes.Config while respecting the COMPOSE_FILE env var.
 | 
			
		||||
func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) {
 | 
			
		||||
	var composeFiles []string
 | 
			
		||||
 | 
			
		||||
	if _, ok := appEnv["COMPOSE_FILE"]; !ok {
 | 
			
		||||
		logrus.Debug("no COMPOSE_FILE detected, loading all compose files")
 | 
			
		||||
		pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe)
 | 
			
		||||
		composeFiles, err := filepath.Glob(pattern)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return composeFiles, err
 | 
			
		||||
		}
 | 
			
		||||
		logrus.Debug("no COMPOSE_FILE detected, loading compose.yml")
 | 
			
		||||
		path := fmt.Sprintf("%s/%s/compose.yml", APPS_DIR, recipe)
 | 
			
		||||
		composeFiles = append(composeFiles, path)
 | 
			
		||||
		return composeFiles, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var composeFiles []string
 | 
			
		||||
	composeFileEnvVar := appEnv["COMPOSE_FILE"]
 | 
			
		||||
	envVars := strings.Split(composeFileEnvVar, ":")
 | 
			
		||||
	logrus.Debugf("COMPOSE_FILE detected ('%s'), loading '%s'", composeFileEnvVar, envVars)
 | 
			
		||||
	logrus.Debugf("COMPOSE_FILE detected ('%s'), loading '%s'", composeFileEnvVar, strings.Join(envVars, ", "))
 | 
			
		||||
	for _, file := range strings.Split(composeFileEnvVar, ":") {
 | 
			
		||||
		path := fmt.Sprintf("%s/%s/%s", APPS_DIR, recipe, file)
 | 
			
		||||
		composeFiles = append(composeFiles, path)
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,6 @@ func EnsureUpToDate(dir string) error {
 | 
			
		||||
	checkOutOpts := &git.CheckoutOptions{
 | 
			
		||||
		Create: false,
 | 
			
		||||
		Force:  true,
 | 
			
		||||
		Keep:   false,
 | 
			
		||||
		Branch: plumbing.ReferenceName(refName),
 | 
			
		||||
	}
 | 
			
		||||
	if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
			
		||||
 | 
			
		||||
@ -119,10 +119,10 @@ func EnsureVersion(recipeName, version string) error {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("read '%s' as tags for recipe '%s'", tags, recipeName)
 | 
			
		||||
 | 
			
		||||
	var parsedTags []string
 | 
			
		||||
	var tagRef plumbing.ReferenceName
 | 
			
		||||
	if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
 | 
			
		||||
		parsedTags = append(parsedTags, ref.Name().Short())
 | 
			
		||||
		if ref.Name().Short() == version {
 | 
			
		||||
			tagRef = ref.Name()
 | 
			
		||||
		}
 | 
			
		||||
@ -131,6 +131,8 @@ func EnsureVersion(recipeName, version string) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("read '%s' as tags for recipe '%s'", strings.Join(parsedTags, ", "), recipeName)
 | 
			
		||||
 | 
			
		||||
	if tagRef.String() == "" {
 | 
			
		||||
		return fmt.Errorf("%s is not available?", version)
 | 
			
		||||
	}
 | 
			
		||||
@ -140,12 +142,56 @@ func EnsureVersion(recipeName, version string) error {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts := &git.CheckoutOptions{Branch: tagRef, Keep: true}
 | 
			
		||||
	opts := &git.CheckoutOptions{
 | 
			
		||||
		Branch: tagRef,
 | 
			
		||||
		Create: false,
 | 
			
		||||
		Force:  true,
 | 
			
		||||
	}
 | 
			
		||||
	if err := worktree.Checkout(opts); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef, recipeDir)
 | 
			
		||||
	logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef.Short(), recipeDir)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureLatest makes sure the latest commit is checkout on for a local recipe repository.
 | 
			
		||||
func EnsureLatest(recipeName string) error {
 | 
			
		||||
	recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName)
 | 
			
		||||
 | 
			
		||||
	logrus.Debugf("attempting to open git repository in '%s'", recipeDir)
 | 
			
		||||
 | 
			
		||||
	repo, err := git.PlainOpen(recipeDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	worktree, err := repo.Worktree()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	branch := "master"
 | 
			
		||||
	if _, err := repo.Branch("master"); err != nil {
 | 
			
		||||
		if _, err := repo.Branch("main"); err != nil {
 | 
			
		||||
			logrus.Debugf("failed to select branch in '%s'", path.Join(config.APPS_DIR, recipeName))
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		branch = "main"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refName := fmt.Sprintf("refs/heads/%s", branch)
 | 
			
		||||
	checkOutOpts := &git.CheckoutOptions{
 | 
			
		||||
		Create: false,
 | 
			
		||||
		Force:  true,
 | 
			
		||||
		Branch: plumbing.ReferenceName(refName),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := worktree.Checkout(checkOutOpts); err != nil {
 | 
			
		||||
		logrus.Debugf("failed to check out '%s' in '%s'", branch, recipeDir)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
ABRA_VERSION="0.1.7-alpha"
 | 
			
		||||
ABRA_VERSION="0.2.0-alpha"
 | 
			
		||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
 | 
			
		||||
 | 
			
		||||
function show_banner {
 | 
			
		||||
@ -35,13 +35,11 @@ function install_abra_release {
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  # FIXME: support different architectures
 | 
			
		||||
  release_url=$(curl -s "$ABRA_RELEASE_URL" |
 | 
			
		||||
    python3 -c "import sys, json; \
 | 
			
		||||
                payload = json.load(sys.stdin); \
 | 
			
		||||
                url = [a['browser_download_url'] for a in payload['assets'] if 'x86_64' in a['name']][0]; \
 | 
			
		||||
                print(url)")
 | 
			
		||||
  PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)
 | 
			
		||||
  sed_command='s/.*"assets":\[\{[^}]*"name":"abra.*_'"$PLATFORM"'"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
 | 
			
		||||
  release_url=$(curl -s $ABRA_RELEASE_URL | sed -En $sed_command)
 | 
			
		||||
 | 
			
		||||
  echo "downloading $ABRA_VERSION x86_64 binary release for abra..."
 | 
			
		||||
  echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
 | 
			
		||||
  curl --progress-bar "$release_url" --output "$HOME/.local/bin/abra"
 | 
			
		||||
  chmod +x "$HOME/.local/bin/abra"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,5 +3,5 @@ STACK := abra_installer_script
 | 
			
		||||
default: deploy
 | 
			
		||||
 | 
			
		||||
deploy:
 | 
			
		||||
	@docker stack rm $(STACK) && \
 | 
			
		||||
		docker stack deploy -c compose.yml $(STACK)
 | 
			
		||||
	@DOCKER_CONTEXT=swarm.autonomic.zone docker stack rm $(STACK) && \
 | 
			
		||||
		DOCKER_CONTEXT=swarm.autonomic.zone docker stack deploy -c compose.yml $(STACK)
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user