forked from toolshed/abra
		
	Compare commits
	
		
			19 Commits
		
	
	
		
			0.1.8-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 | 
| @ -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 { | ||||
| @ -38,7 +92,7 @@ var appDeployCommand = &cli.Command{ | ||||
| 		} | ||||
| 		deployOpts := stack.Deploy{ | ||||
| 			Composefiles: composeFiles, | ||||
| 			Namespace:    app.StackName(), | ||||
| 			Namespace:    stackName, | ||||
| 			Prune:        false, | ||||
| 			ResolveImage: stack.ResolveImageAlways, | ||||
| 		} | ||||
| @ -47,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) | ||||
| 		} | ||||
| @ -66,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,6 +1,7 @@ | ||||
| package app | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"sort" | ||||
| 	"strings" | ||||
|  | ||||
| @ -81,6 +82,13 @@ 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 == "" { | ||||
| @ -98,8 +106,10 @@ can take some time. | ||||
| 							status = statusMeta["status"] | ||||
| 						} | ||||
| 						tableRow = append(tableRow, status, version) | ||||
| 						versionedAppsCount++ | ||||
| 					} else { | ||||
| 						tableRow = append(tableRow, status, version) | ||||
| 						unversionedAppsCount++ | ||||
| 					} | ||||
|  | ||||
| 					var newUpdates []string | ||||
| @ -131,6 +141,7 @@ can take some time. | ||||
| 							tableRow = append(tableRow, "unknown") | ||||
| 						} else { | ||||
| 							tableRow = append(tableRow, "on latest") | ||||
| 							onLatestCount++ | ||||
| 						} | ||||
| 					} else { | ||||
| 						// FIXME: jeezus golang why do you not have a list reverse function | ||||
| @ -138,12 +149,23 @@ can take some time. | ||||
| 							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() | ||||
| } | ||||
|  | ||||
| @ -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{ | ||||
|  | ||||
							
								
								
									
										61
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										61
									
								
								go.mod
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| module coopcloud.tech/abra | ||||
|  | ||||
| go 1.17 | ||||
| go 1.16 | ||||
|  | ||||
| require ( | ||||
| 	coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb | ||||
| @ -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 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @ -21,8 +21,6 @@ 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-20211007122926-aa7b4ff851f6 h1:EV1aomKx6n0jLZ847ejeqroE4K9E+yX5NvEyQufkZVk= | ||||
| coopcloud.tech/tagcmp v0.0.0-20211007122926-aa7b4ff851f6/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= | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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()) | ||||
|  | ||||
| @ -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.8-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 'linux_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" | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user