Compare commits
	
		
			34 Commits
		
	
	
		
			0.1.6-alph
			...
			0.1.8-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4235e06943 | |||
| a9af0b3627 | |||
| a0b4886eba | |||
| 84489495dc | |||
| a8683dc38a | |||
| e2128ea5b6 | |||
| ca3c5fef0f | |||
| 4a01e411be | |||
| 777d49ac1d | |||
| deb7d21158 | |||
| 6db1fdcfba | |||
| 44dc0edf7b | |||
| 36ff50312c | |||
| ff4b978876 | |||
| b68547b2c2 | |||
| 0140f96ca1 | |||
| 1cb45113db | |||
| c764243f3a | |||
| dde8afcd43 | |||
| 98ffc210e1 | |||
| 7c0d883135 | |||
| e78ced41fb | |||
| e9113500d8 | |||
| 7368cabc49 | |||
| f75e264811 | |||
| 8bfd76fd04 | |||
| 1cb5e3509d | |||
| 3cd2399cca | |||
| 11c4651a3b | |||
| 49f90674f2 | |||
| 74a70edb03 | |||
| 6fc5c31347 | |||
| c616907b71 | |||
| a58cea3e0a | 
| @ -28,12 +28,13 @@ checksum: | |||||||
| snapshot: | snapshot: | ||||||
|   name_template: "{{ incpatch .Version }}-next" |   name_template: "{{ incpatch .Version }}-next" | ||||||
| changelog: | changelog: | ||||||
|   sort: asc |   sort: desc | ||||||
|   filters: |   filters: | ||||||
|     exclude: |     exclude: | ||||||
|  |       - "^WIP:" | ||||||
|  |       - "^chore:" | ||||||
|       - "^docs:" |       - "^docs:" | ||||||
|       - "^refactor:" |       - "^refactor:" | ||||||
|       - "^style:" |       - "^style:" | ||||||
|       - "^test:" |       - "^test:" | ||||||
|       - "^tests:" |       - "^tests:" | ||||||
|       - "^chore:" |  | ||||||
|  | |||||||
| @ -31,7 +31,6 @@ var appDeployCommand = &cli.Command{ | |||||||
| 		for k, v := range abraShEnv { | 		for k, v := range abraShEnv { | ||||||
| 			app.Env[k] = v | 			app.Env[k] = v | ||||||
| 		} | 		} | ||||||
| 		app.Env["STACK_NAME"] = app.StackName() |  | ||||||
|  |  | ||||||
| 		composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) | 		composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
|  | |||||||
| @ -2,9 +2,12 @@ package app | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"sort" | 	"sort" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	abraFormatter "coopcloud.tech/abra/cli/formatter" | 	abraFormatter "coopcloud.tech/abra/cli/formatter" | ||||||
|  | 	"coopcloud.tech/abra/pkg/catalogue" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
|  | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
| @ -65,10 +68,10 @@ can take some time. | |||||||
| 		} | 		} | ||||||
| 		sort.Sort(config.ByServerAndType(apps)) | 		sort.Sort(config.ByServerAndType(apps)) | ||||||
|  |  | ||||||
| 		statuses := map[string]string{} | 		statuses := make(map[string]map[string]string) | ||||||
| 		tableCol := []string{"Server", "Type", "Domain"} | 		tableCol := []string{"Server", "Type", "Domain"} | ||||||
| 		if status { | 		if status { | ||||||
| 			tableCol = append(tableCol, "Status") | 			tableCol = append(tableCol, "Status", "Version", "Updates") | ||||||
| 			statuses, err = config.GetAppStatuses(appFiles) | 			statuses, err = config.GetAppStatuses(appFiles) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| @ -84,10 +87,57 @@ can take some time. | |||||||
| 				// If type flag is set, check for it, if not, Type == "" | 				// If type flag is set, check for it, if not, Type == "" | ||||||
| 				tableRow = []string{app.Server, app.Type, app.Domain} | 				tableRow = []string{app.Server, app.Type, app.Domain} | ||||||
| 				if status { | 				if status { | ||||||
| 					if status, ok := statuses[app.StackName()]; ok { | 					stackName := app.StackName() | ||||||
| 						tableRow = append(tableRow, status) | 					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) | ||||||
| 					} else { | 					} else { | ||||||
| 						tableRow = append(tableRow, "unknown") | 						tableRow = append(tableRow, status, version) | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					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") | ||||||
|  | 						} | ||||||
|  | 					} 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")) | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -63,7 +63,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				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) | 				logrus.Fatalf("'%s' is still deployed. Run \"abra app %s undeploy\" or pass --force", app.Name, app.Name) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -2,7 +2,9 @@ package catalogue | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/formatter" | 	"coopcloud.tech/abra/cli/formatter" | ||||||
| @ -45,11 +47,22 @@ var CatalogueSkipList = map[string]bool{ | |||||||
| } | } | ||||||
|  |  | ||||||
| var catalogueGenerateCommand = &cli.Command{ | var catalogueGenerateCommand = &cli.Command{ | ||||||
| 	Name:         "generate", | 	Name:      "generate", | ||||||
| 	Aliases:      []string{"g"}, | 	Aliases:   []string{"g"}, | ||||||
| 	Usage:        "Generate a new copy of the catalogue", | 	Usage:     "Generate a new copy of the catalogue", | ||||||
| 	ArgsUsage:    "[<recipe>]", | 	ArgsUsage: "[<recipe>]", | ||||||
| 	BashComplete: func(c *cli.Context) {}, | 	BashComplete: func(c *cli.Context) { | ||||||
|  | 		catl, err := catalogue.ReadRecipeCatalogue() | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Warn(err) | ||||||
|  | 		} | ||||||
|  | 		if c.NArg() > 0 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		for name := range catl { | ||||||
|  | 			fmt.Println(name) | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipeName := c.Args().First() | 		recipeName := c.Args().First() | ||||||
|  |  | ||||||
| @ -60,18 +73,18 @@ var catalogueGenerateCommand = &cli.Command{ | |||||||
|  |  | ||||||
| 		logrus.Debugf("ensuring '%v' recipe(s) are locally present and up-to-date", len(repos)) | 		logrus.Debugf("ensuring '%v' recipe(s) are locally present and up-to-date", len(repos)) | ||||||
|  |  | ||||||
| 		bar := formatter.CreateProgressbar(len(repos), "retrieving recipes...") | 		retrieveBar := formatter.CreateProgressbar(len(repos), "retrieving recipes...") | ||||||
| 		ch := make(chan string, len(repos)) | 		ch := make(chan string, len(repos)) | ||||||
| 		for _, repoMeta := range repos { | 		for _, repoMeta := range repos { | ||||||
| 			go func(rm catalogue.RepoMeta) { | 			go func(rm catalogue.RepoMeta) { | ||||||
| 				if recipeName != "" && recipeName != rm.Name { | 				if recipeName != "" && recipeName != rm.Name { | ||||||
| 					ch <- rm.Name | 					ch <- rm.Name | ||||||
| 					bar.Add(1) | 					retrieveBar.Add(1) | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
| 				if _, exists := CatalogueSkipList[rm.Name]; exists { | 				if _, exists := CatalogueSkipList[rm.Name]; exists { | ||||||
| 					ch <- rm.Name | 					ch <- rm.Name | ||||||
| 					bar.Add(1) | 					retrieveBar.Add(1) | ||||||
| 					return | 					return | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| @ -86,7 +99,7 @@ var catalogueGenerateCommand = &cli.Command{ | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				ch <- rm.Name | 				ch <- rm.Name | ||||||
| 				bar.Add(1) | 				retrieveBar.Add(1) | ||||||
| 			}(repoMeta) | 			}(repoMeta) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -95,14 +108,23 @@ var catalogueGenerateCommand = &cli.Command{ | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		catl := make(catalogue.RecipeCatalogue) | 		catl := make(catalogue.RecipeCatalogue) | ||||||
|  | 		catlBar := formatter.CreateProgressbar(len(repos), "generating catalogue...") | ||||||
| 		for _, recipeMeta := range repos { | 		for _, recipeMeta := range repos { | ||||||
| 			if recipeName != "" && recipeName != recipeMeta.Name { | 			if recipeName != "" && recipeName != recipeMeta.Name { | ||||||
|  | 				catlBar.Add(1) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if _, exists := CatalogueSkipList[recipeMeta.Name]; exists { | 			if _, exists := CatalogueSkipList[recipeMeta.Name]; exists { | ||||||
|  | 				catlBar.Add(1) | ||||||
| 				continue | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			versions, err := catalogue.GetRecipeVersions(recipeMeta.Name) | ||||||
|  | 			if err != nil { | ||||||
|  | 				logrus.Fatal(err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			catl[recipeMeta.Name] = catalogue.RecipeMeta{ | 			catl[recipeMeta.Name] = catalogue.RecipeMeta{ | ||||||
| 				Name:          recipeMeta.Name, | 				Name:          recipeMeta.Name, | ||||||
| 				Repository:    recipeMeta.CloneURL, | 				Repository:    recipeMeta.CloneURL, | ||||||
| @ -110,10 +132,11 @@ var catalogueGenerateCommand = &cli.Command{ | |||||||
| 				DefaultBranch: recipeMeta.DefaultBranch, | 				DefaultBranch: recipeMeta.DefaultBranch, | ||||||
| 				Description:   recipeMeta.Description, | 				Description:   recipeMeta.Description, | ||||||
| 				Website:       recipeMeta.Website, | 				Website:       recipeMeta.Website, | ||||||
| 				// Versions:      ..., // FIXME: once the new versions work goes down | 				Versions:      versions, | ||||||
| 				// Category:      ..., // FIXME: once we sort out the machine-readable catalogue interface | 				// Category:      ..., // FIXME: once we sort out the machine-readable catalogue interface | ||||||
| 				// Features:      ..., // FIXME: once we figure out the machine-readable catalogue interface | 				// Features:      ..., // FIXME: once we figure out the machine-readable catalogue interface | ||||||
| 			} | 			} | ||||||
|  | 			catlBar.Add(1) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		recipesJSON, err := json.MarshalIndent(catl, "", "    ") | 		recipesJSON, err := json.MarshalIndent(catl, "", "    ") | ||||||
| @ -121,11 +144,29 @@ var catalogueGenerateCommand = &cli.Command{ | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if err := ioutil.WriteFile(config.APPS_JSON, recipesJSON, 0644); err != nil { | 		if _, err := os.Stat(config.APPS_JSON); err != nil && os.IsNotExist(err) { | ||||||
| 			logrus.Fatal(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.Debugf("generated new recipe catalogue in '%s'", config.APPS_JSON) | 		logrus.Infof("generated new recipe catalogue in '%s'", config.APPS_JSON) | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}, | 	}, | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								cli/cli.go
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								cli/cli.go
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ package cli | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/app" | 	"coopcloud.tech/abra/cli/app" | ||||||
| 	"coopcloud.tech/abra/cli/catalogue" | 	"coopcloud.tech/abra/cli/catalogue" | ||||||
| @ -82,17 +83,30 @@ func RunApp(version, commit string) { | |||||||
| 			logrus.SetOutput(os.Stderr) | 			logrus.SetOutput(os.Stderr) | ||||||
| 			logrus.AddHook(logrusStack.StandardHook()) | 			logrus.AddHook(logrusStack.StandardHook()) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		paths := []string{ | ||||||
|  | 			config.ABRA_DIR, | ||||||
|  | 			path.Join(config.ABRA_DIR, "servers"), | ||||||
|  | 			path.Join(config.ABRA_DIR, "apps"), | ||||||
|  | 			path.Join(config.ABRA_DIR, "vendor"), | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, path := range paths { | ||||||
|  | 			if err := os.Mkdir(path, 0755); err != nil { | ||||||
|  | 				if !os.IsExist(err) { | ||||||
|  | 					logrus.Fatal(err) | ||||||
|  | 				} | ||||||
|  | 				logrus.Debugf("'%s' already created, moving on...", path) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			logrus.Debugf("'%s' is missing, creating...", path) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		logrus.Debugf("abra version '%s', commit '%s'", version, commit) | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if err := os.Mkdir(config.ABRA_DIR, 0755); err != nil { |  | ||||||
| 		if !os.IsExist(err) { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	logrus.Debugf("abra version '%s', commit '%s'", version, commit) |  | ||||||
|  |  | ||||||
| 	if err := app.Run(os.Args); err != nil { | 	if err := app.Run(os.Args); err != nil { | ||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -41,6 +41,10 @@ func ValidateApp(c *cli.Context) config.App { | |||||||
| 		logrus.Fatal(err) | 		logrus.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if err := recipe.EnsureExists(app.Type); err != nil { | ||||||
|  | 		logrus.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	logrus.Debugf("validated '%s' as app argument", appName) | 	logrus.Debugf("validated '%s' as app argument", appName) | ||||||
|  |  | ||||||
| 	return app | 	return app | ||||||
|  | |||||||
| @ -9,11 +9,11 @@ import ( | |||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
|  | 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"coopcloud.tech/tagcmp" | 	"coopcloud.tech/tagcmp" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/go-git/go-git/v5" | 	"github.com/go-git/go-git/v5" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing" | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
| 	"github.com/go-git/go-git/v5/plumbing/object" |  | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
| @ -36,7 +36,7 @@ var DryFlag = &cli.BoolFlag{ | |||||||
| var CommitMessage string | var CommitMessage string | ||||||
| var CommitMessageFlag = &cli.StringFlag{ | var CommitMessageFlag = &cli.StringFlag{ | ||||||
| 	Name:        "commit-message", | 	Name:        "commit-message", | ||||||
| 	Usage:       "commit message", | 	Usage:       "commit message. Implies --commit", | ||||||
| 	Aliases:     []string{"cm"}, | 	Aliases:     []string{"cm"}, | ||||||
| 	Destination: &CommitMessage, | 	Destination: &CommitMessage, | ||||||
| } | } | ||||||
| @ -44,11 +44,20 @@ var CommitMessageFlag = &cli.StringFlag{ | |||||||
| var Commit bool | var Commit bool | ||||||
| var CommitFlag = &cli.BoolFlag{ | var CommitFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "commit", | 	Name:        "commit", | ||||||
|  | 	Usage:       "add compose.yml to staging area and commit changes", | ||||||
| 	Value:       false, | 	Value:       false, | ||||||
| 	Aliases:     []string{"c"}, | 	Aliases:     []string{"c"}, | ||||||
| 	Destination: &Commit, | 	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{ | var recipeReleaseCommand = &cli.Command{ | ||||||
| 	Name:      "release", | 	Name:      "release", | ||||||
| 	Usage:     "tag a recipe", | 	Usage:     "tag a recipe", | ||||||
| @ -83,6 +92,7 @@ or a rollback of an app. | |||||||
| 		PushFlag, | 		PushFlag, | ||||||
| 		CommitFlag, | 		CommitFlag, | ||||||
| 		CommitMessageFlag, | 		CommitMessageFlag, | ||||||
|  | 		TagMessageFlag, | ||||||
| 	}, | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		recipe := internal.ValidateRecipe(c) | ||||||
| @ -91,17 +101,31 @@ or a rollback of an app. | |||||||
| 		imagesTmp := getImageVersions(recipe) | 		imagesTmp := getImageVersions(recipe) | ||||||
| 		mainApp := getMainApp(recipe) | 		mainApp := getMainApp(recipe) | ||||||
| 		mainAppVersion := imagesTmp[mainApp] | 		mainAppVersion := imagesTmp[mainApp] | ||||||
|  |  | ||||||
|  | 		if err := recipePkg.EnsureExists(recipe.Name); err != nil { | ||||||
|  | 			logrus.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if mainAppVersion == "" { | 		if mainAppVersion == "" { | ||||||
| 			logrus.Fatal("main app version is empty?") | 			logrus.Fatal("main app version is empty?") | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if tagstring != "" { | 		if tagstring != "" { | ||||||
| 			_, err := tagcmp.Parse(tagstring) | 			if _, err := tagcmp.Parse(tagstring); err != nil { | ||||||
| 			if err != nil { |  | ||||||
| 				logrus.Fatal("invalid tag specified") | 				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 != "") { | 		if Commit || (CommitMessage != "") { | ||||||
| 			commitRepo, err := git.PlainOpen(directory) | 			commitRepo, err := git.PlainOpen(directory) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -118,6 +142,12 @@ or a rollback of an app. | |||||||
| 				} | 				} | ||||||
| 				survey.AskOne(prompt, &CommitMessage) | 				survey.AskOne(prompt, &CommitMessage) | ||||||
| 			} | 			} | ||||||
|  | 			err = commitWorktree.AddGlob("compose.**yml") | ||||||
|  | 			if err != nil { | ||||||
|  | 				logrus.Fatal(err) | ||||||
|  | 			} | ||||||
|  | 			logrus.Debug("staged compose.**yml for commit") | ||||||
|  |  | ||||||
| 			_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{}) | 			_, err = commitWorktree.Commit(CommitMessage, &git.CommitOptions{}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| @ -165,9 +195,7 @@ or a rollback of an app. | |||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			repo.CreateTag(tagstring, head.Hash(), nil) /* &git.CreateTagOptions{ | 			repo.CreateTag(tagstring, head.Hash(), &createTagOptions) | ||||||
| 				Message: tag, |  | ||||||
| 			})*/ |  | ||||||
| 			logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash())) | 			logrus.Info(fmt.Sprintf("created tag %s at %s", tagstring, head.Hash())) | ||||||
| 			if Push { | 			if Push { | ||||||
| 				if err := repo.Push(&git.PushOptions{}); err != nil { | 				if err := repo.Push(&git.PushOptions{}); err != nil { | ||||||
| @ -180,27 +208,33 @@ or a rollback of an app. | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// get the latest tag with its hash, name etc | 		// get the latest tag with its hash, name etc | ||||||
| 		var lastGitTag *object.Tag | 		var lastGitTag tagcmp.Tag | ||||||
| 		iter, err := repo.Tags() | 		iter, err := repo.Tags() | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		if err := iter.ForEach(func(ref *plumbing.Reference) error { | 		if err := iter.ForEach(func(ref *plumbing.Reference) error { | ||||||
| 			obj, err := repo.TagObject(ref.Hash()) | 			obj, err := repo.TagObject(ref.Hash()) | ||||||
| 			if err == nil { | 			if err != nil { | ||||||
| 				lastGitTag = obj | 				return err | ||||||
| 				return nil |  | ||||||
| 			} | 			} | ||||||
| 			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 { | 		}); err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		newTag, err := tagcmp.Parse(lastGitTag.Name) |  | ||||||
| 		if err != nil { |  | ||||||
| 			logrus.Fatal(err) |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
|  | 		fmt.Println(lastGitTag) | ||||||
|  |  | ||||||
|  | 		newTag := lastGitTag | ||||||
| 		var newTagString string | 		var newTagString string | ||||||
| 		if bumpType > 0 { | 		if bumpType > 0 { | ||||||
| 			if Patch { | 			if Patch { | ||||||
| @ -214,28 +248,28 @@ or a rollback of an app. | |||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
|  | 				newTag.Patch = "0" | ||||||
| 				newTag.Minor = strconv.Itoa(now + 1) | 				newTag.Minor = strconv.Itoa(now + 1) | ||||||
| 			} else if Major { | 			} else if Major { | ||||||
| 				now, err := strconv.Atoi(newTag.Major) | 				now, err := strconv.Atoi(newTag.Major) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
|  | 				newTag.Patch = "0" | ||||||
|  | 				newTag.Minor = "0" | ||||||
| 				newTag.Major = strconv.Itoa(now + 1) | 				newTag.Major = strconv.Itoa(now + 1) | ||||||
| 			} | 			} | ||||||
| 			newTagString = newTag.String() |  | ||||||
| 		} else { | 		} else { | ||||||
| 			logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch") | 			logrus.Fatal("we don't support automatic tag generation yet - specify a version or use one of: --major --minor --patch") | ||||||
| 		} | 		} | ||||||
|  | 		newTag.Metadata = mainAppVersion | ||||||
| 		newTagString = fmt.Sprintf("%s+%s", newTagString, mainAppVersion) | 		newTagString = newTag.String() | ||||||
| 		if Dry { | 		if Dry { | ||||||
| 			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash())) | 			logrus.Info(fmt.Sprintf("dry run only: NOT creating tag %s at %s", newTagString, head.Hash())) | ||||||
| 			return nil | 			return nil | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		repo.CreateTag(newTagString, head.Hash(), nil) /* &git.CreateTagOptions{ | 		repo.CreateTag(newTagString, head.Hash(), &createTagOptions) | ||||||
| 			Message: tag, |  | ||||||
| 		})*/ |  | ||||||
| 		logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash())) | 		logrus.Info(fmt.Sprintf("created tag %s at %s", newTagString, head.Hash())) | ||||||
| 		if Push { | 		if Push { | ||||||
| 			if err := repo.Push(&git.PushOptions{}); err != nil { | 			if err := repo.Push(&git.PushOptions{}); err != nil { | ||||||
|  | |||||||
| @ -1,65 +1,88 @@ | |||||||
| package recipe | package recipe | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/catalogue" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/urfave/cli/v2" | 	"github.com/urfave/cli/v2" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var recipeSyncCommand = &cli.Command{ | var recipeSyncCommand = &cli.Command{ | ||||||
| 	Name:    "sync", | 	Name:      "sync", | ||||||
| 	Usage:   "Generate new recipe labels", | 	Usage:     "Ensure recipe version labels are up-to-date", | ||||||
| 	Aliases: []string{"s"}, | 	Aliases:   []string{"s"}, | ||||||
|  | 	ArgsUsage: "<recipe> <version>", | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command will generate labels for each service which correspond to the | 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: | following format: | ||||||
|  |  | ||||||
|     coop-cloud.${STACK_NAME}.${SERVICE_NAME}.version=${IMAGE_TAG}-${IMAGE_DIGEST} |     coop-cloud.${STACK_NAME}.version=<version> | ||||||
|  |  | ||||||
| The <recipe> configuration will be updated on the local file system. These | The <version> is determined by the recipe maintainer and is specified on the | ||||||
| labels are consumed by abra in other command invocations and used to determine | command-line. The <recipe> configuration will be updated on the local file | ||||||
| the versioning metadata of up-and-running containers are. | system. | ||||||
| `, | `, | ||||||
| 	ArgsUsage: "<recipe>", | 	BashComplete: func(c *cli.Context) { | ||||||
|  | 		catl, err := catalogue.ReadRecipeCatalogue() | ||||||
|  | 		if err != nil { | ||||||
|  | 			logrus.Warn(err) | ||||||
|  | 		} | ||||||
|  | 		if c.NArg() > 0 { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 		for name := range catl { | ||||||
|  | 			fmt.Println(name) | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
|  | 		if c.Args().Len() != 2 { | ||||||
|  | 			internal.ShowSubcommandHelpAndError(c, errors.New("missing <recipe>/<version> arguments?")) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		recipe := internal.ValidateRecipe(c) | ||||||
|  |  | ||||||
|  | 		// 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 | 		hasAppService := false | ||||||
| 		for _, service := range recipe.Config.Services { | 		for _, service := range recipe.Config.Services { | ||||||
|  | 			services = append(services, service.Name) | ||||||
| 			if service.Name == "app" { | 			if service.Name == "app" { | ||||||
| 				hasAppService = true | 				hasAppService = true | ||||||
|  | 				logrus.Debugf("detected app service in '%s'", recipe.Name) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !hasAppService { | 		if !hasAppService { | ||||||
| 			logrus.Fatal(fmt.Sprintf("no 'app' service defined in '%s'", recipe.Name)) | 			logrus.Warnf("no 'app' service defined in '%s'", recipe.Name) | ||||||
|  | 			var chosenService string | ||||||
|  | 			prompt := &survey.Select{ | ||||||
|  | 				Message: fmt.Sprintf("what is the main service name for '%s'?", recipe.Name), | ||||||
|  | 				Options: services, | ||||||
|  | 			} | ||||||
|  | 			if err := survey.AskOne(prompt, &chosenService); err != nil { | ||||||
|  | 				logrus.Fatal(err) | ||||||
|  | 			} | ||||||
|  | 			mainService = chosenService | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for _, service := range recipe.Config.Services { | 		logrus.Debugf("selecting '%s' as the service to sync version labels", mainService) | ||||||
| 			img, err := reference.ParseNormalizedNamed(service.Image) |  | ||||||
| 			if err != nil { |  | ||||||
| 				logrus.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 			logrus.Debugf("detected image '%s' for service '%s'", img, service.Name) |  | ||||||
|  |  | ||||||
| 			digest, err := client.GetTagDigest(img) | 		label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag) | ||||||
| 			if err != nil { | 		if err := recipe.UpdateLabel(mainService, label); err != nil { | ||||||
| 				logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 			} |  | ||||||
| 			logrus.Debugf("retrieved digest '%s' for '%s'", digest, img) |  | ||||||
|  |  | ||||||
| 			tag := img.(reference.NamedTagged).Tag() |  | ||||||
| 			label := fmt.Sprintf("coop-cloud.${STACK_NAME}.%s.version=%s-%s", service.Name, tag, digest) |  | ||||||
| 			if err := recipe.UpdateLabel(service.Name, label); err != nil { |  | ||||||
| 				logrus.Fatal(err) |  | ||||||
| 			} |  | ||||||
| 			logrus.Debugf("added label '%s' to service '%s'", label, service.Name) |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		logrus.Infof("synced label '%s' to service '%s'", label, mainService) | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
| @ -27,14 +27,24 @@ update the relevant compose file tags on the local file system. | |||||||
| Some image tags cannot be parsed because they do not follow some sort of | Some image tags cannot be parsed because they do not follow some sort of | ||||||
| semver-like convention. In this case, all possible tags will be listed and it | semver-like convention. In this case, all possible tags will be listed and it | ||||||
| is up to the end-user to decide. | is up to the end-user to decide. | ||||||
|  |  | ||||||
| This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync |  | ||||||
| <recipe>". |  | ||||||
| `, | `, | ||||||
| 	ArgsUsage: "<recipe>", | 	ArgsUsage: "<recipe>", | ||||||
|  | 	Flags: []cli.Flag{ | ||||||
|  | 		PatchFlag, | ||||||
|  | 		MinorFlag, | ||||||
|  | 		MajorFlag, | ||||||
|  | 	}, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		recipe := internal.ValidateRecipe(c) | 		recipe := internal.ValidateRecipe(c) | ||||||
|  |  | ||||||
|  | 		bumpType := btoi(Major)*4 + btoi(Minor)*2 + btoi(Patch) | ||||||
|  | 		if bumpType != 0 { | ||||||
|  | 			// a bitwise check if the number is a power of 2 | ||||||
|  | 			if (bumpType & (bumpType - 1)) != 0 { | ||||||
|  | 				logrus.Fatal("you can only use one of: --major, --minor, --patch.") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		for _, service := range recipe.Config.Services { | 		for _, service := range recipe.Config.Services { | ||||||
| 			catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name) | 			catlVersions, err := catalogue.VersionsOfService(recipe.Name, service.Name) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -107,31 +117,48 @@ This is step 1 of upgrading a recipe. Step 2 is running "abra recipe sync | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name) | 			logrus.Debugf("detected compatible upgradable tags '%s' for '%s'", compatibleStrings, service.Name) | ||||||
|  |  | ||||||
| 			msg := fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) |  | ||||||
| 			if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { |  | ||||||
| 				tag := img.(reference.NamedTagged).Tag() |  | ||||||
| 				logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag)) |  | ||||||
| 				msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) |  | ||||||
| 				compatibleStrings = []string{} |  | ||||||
| 				for _, regVersion := range regVersions { |  | ||||||
| 					compatibleStrings = append(compatibleStrings, regVersion.Name) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var upgradeTag string | 			var upgradeTag string | ||||||
| 			prompt := &survey.Select{ | 			if bumpType != 0 { | ||||||
| 				Message: msg, | 				for _, upTag := range compatible { | ||||||
| 				Options: compatibleStrings, | 					upElement, err := tag.UpgradeDelta(upTag) | ||||||
| 			} | 					if err != nil { | ||||||
| 			if err := survey.AskOne(prompt, &upgradeTag); err != nil { | 						return err | ||||||
| 				logrus.Fatal(err) | 					} | ||||||
|  | 					delta := upElement.UpgradeType() | ||||||
|  | 					if delta <= bumpType { | ||||||
|  | 						upgradeTag = upTag.String() | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				if upgradeTag == "" { | ||||||
|  | 					logrus.Warnf("not upgrading from '%s' to '%s' for '%s', because the upgrade type is more serious than what user wants.", tag.String(), compatible[0].String(), image) | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				msg := fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) | ||||||
|  | 				if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { | ||||||
|  | 					tag := img.(reference.NamedTagged).Tag() | ||||||
|  | 					logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of '%s', listing all tags", tag)) | ||||||
|  | 					msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) | ||||||
|  | 					compatibleStrings = []string{} | ||||||
|  | 					for _, regVersion := range regVersions { | ||||||
|  | 						compatibleStrings = append(compatibleStrings, regVersion.Name) | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				prompt := &survey.Select{ | ||||||
|  | 					Message: msg, | ||||||
|  | 					Options: compatibleStrings, | ||||||
|  | 				} | ||||||
|  | 				if err := survey.AskOne(prompt, &upgradeTag); err != nil { | ||||||
|  | 					logrus.Fatal(err) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if err := recipe.UpdateTag(image, upgradeTag); err != nil { | 			if err := recipe.UpdateTag(image, upgradeTag); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			logrus.Debugf("tag updated from '%s' to '%s' for '%s'", image, upgradeTag, recipe.Name) | 			logrus.Infof("tag upgraded from '%s' to '%s' for '%s'", tag.String(), upgradeTag, image) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		return nil | 		return nil | ||||||
|  | |||||||
| @ -35,6 +35,9 @@ later for more advanced use cases. | |||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// https://www.privacy-handbuch.de/handbuch_93d.htm | ||||||
|  | 		freifunkDNS := "5.1.66.255:53" | ||||||
|  |  | ||||||
| 		resolver := &net.Resolver{ | 		resolver := &net.Resolver{ | ||||||
| 			PreferGo: false, | 			PreferGo: false, | ||||||
| 			Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | 			Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | ||||||
| @ -42,10 +45,10 @@ later for more advanced use cases. | |||||||
| 					Timeout: time.Millisecond * time.Duration(10000), | 					Timeout: time.Millisecond * time.Duration(10000), | ||||||
| 				} | 				} | ||||||
| 				// comrade librehosters DNS resolver https://snopyta.org/service/dns/ | 				// comrade librehosters DNS resolver https://snopyta.org/service/dns/ | ||||||
| 				return d.DialContext(ctx, "udp", "95.216.24.230:53") | 				return d.DialContext(ctx, "udp", freifunkDNS) | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
| 		logrus.Debugf("created DNS resolver via 95.216.24.230") | 		logrus.Debugf("created DNS resolver via '%s'", freifunkDNS) | ||||||
|  |  | ||||||
| 		ctx := context.Background() | 		ctx := context.Background() | ||||||
| 		ips, err := resolver.LookupIPAddr(ctx, domainName) | 		ips, err := resolver.LookupIPAddr(ctx, domainName) | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,7 +3,7 @@ module coopcloud.tech/abra | |||||||
| go 1.17 | go 1.17 | ||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d | 	coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb | ||||||
| 	github.com/AlecAivazis/survey/v2 v2.3.1 | 	github.com/AlecAivazis/survey/v2 v2.3.1 | ||||||
| 	github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4 | 	github.com/Autonomic-Cooperative/godotenv v1.3.1-0.20210731170023-c37c0920d1a4 | ||||||
| 	github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 | 	github.com/Gurpartap/logrus-stack v0.0.0-20170710170904-89c00d8a28f4 | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
								
							| @ -21,8 +21,10 @@ 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.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.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= | ||||||
| cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= | 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-20211007122926-aa7b4ff851f6 h1:EV1aomKx6n0jLZ847ejeqroE4K9E+yX5NvEyQufkZVk= | ||||||
| coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ= | 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= | 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 h1:lzkuHA60pER7L4eYL8qQJor4bUWlJe4V0gqAT19tdOA= | ||||||
| github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= | github.com/AlecAivazis/survey/v2 v2.3.1/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= | ||||||
|  | |||||||
| @ -9,12 +9,17 @@ import ( | |||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"path" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
|  | 	"coopcloud.tech/abra/pkg/client" | ||||||
| 	"coopcloud.tech/abra/pkg/config" | 	"coopcloud.tech/abra/pkg/config" | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"coopcloud.tech/abra/pkg/web" | 	"coopcloud.tech/abra/pkg/web" | ||||||
|  | 	"github.com/docker/distribution/reference" | ||||||
|  | 	"github.com/go-git/go-git/v5" | ||||||
|  | 	"github.com/go-git/go-git/v5/plumbing" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @ -56,17 +61,20 @@ type serviceMeta struct { | |||||||
| 	Tag    string `json:"tag"` | 	Tag    string `json:"tag"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // RecipeVersions are the versions associated with a recipe. | ||||||
|  | type RecipeVersions []map[tag]map[service]serviceMeta | ||||||
|  |  | ||||||
| // RecipeMeta represents metadata for a recipe in the abra catalogue. | // RecipeMeta represents metadata for a recipe in the abra catalogue. | ||||||
| type RecipeMeta struct { | type RecipeMeta struct { | ||||||
| 	Category      string                            `json:"category"` | 	Category      string         `json:"category"` | ||||||
| 	DefaultBranch string                            `json:"default_branch"` | 	DefaultBranch string         `json:"default_branch"` | ||||||
| 	Description   string                            `json:"description"` | 	Description   string         `json:"description"` | ||||||
| 	Features      features                          `json:"features"` | 	Features      features       `json:"features"` | ||||||
| 	Icon          string                            `json:"icon"` | 	Icon          string         `json:"icon"` | ||||||
| 	Name          string                            `json:"name"` | 	Name          string         `json:"name"` | ||||||
| 	Repository    string                            `json:"repository"` | 	Repository    string         `json:"repository"` | ||||||
| 	Versions      []map[tag]map[service]serviceMeta `json:"versions"` | 	Versions      RecipeVersions `json:"versions"` | ||||||
| 	Website       string                            `json:"website"` | 	Website       string         `json:"website"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // LatestVersion returns the latest version of a recipe. | // LatestVersion returns the latest version of a recipe. | ||||||
| @ -365,3 +373,130 @@ func ReadReposMetadata() (RepoCatalogue, error) { | |||||||
|  |  | ||||||
| 	return reposMeta, nil | 	return reposMeta, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GetRecipeVersions retrieves all recipe versions. | ||||||
|  | func GetRecipeVersions(recipeName string) (RecipeVersions, error) { | ||||||
|  | 	versions := RecipeVersions{} | ||||||
|  |  | ||||||
|  | 	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 versions, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	worktree, err := repo.Worktree() | ||||||
|  | 	if err != nil { | ||||||
|  | 		logrus.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	gitTags, err := repo.Tags() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return versions, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) { | ||||||
|  | 		tag := strings.TrimPrefix(string(ref.Name()), "refs/tags/") | ||||||
|  |  | ||||||
|  | 		logrus.Debugf("processing '%s' for '%s'", tag, recipeName) | ||||||
|  |  | ||||||
|  | 		checkOutOpts := &git.CheckoutOptions{ | ||||||
|  | 			Create: false, | ||||||
|  | 			Force:  true, | ||||||
|  | 			Keep:   false, | ||||||
|  | 			Branch: plumbing.ReferenceName(ref.Name()), | ||||||
|  | 		} | ||||||
|  | 		if err := worktree.Checkout(checkOutOpts); err != nil { | ||||||
|  | 			logrus.Debugf("failed to check out '%s' in '%s'", tag, recipeDir) | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		logrus.Debugf("successfully checked out '%s' in '%s'", ref.Name(), recipeDir) | ||||||
|  |  | ||||||
|  | 		recipe, err := recipe.Get(recipeName) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		versionMeta := make(map[string]serviceMeta) | ||||||
|  | 		for _, service := range recipe.Config.Services { | ||||||
|  |  | ||||||
|  | 			img, err := reference.ParseNormalizedNamed(service.Image) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			path := reference.Path(img) | ||||||
|  | 			if strings.Contains(path, "library") { | ||||||
|  | 				path = strings.Split(path, "/")[1] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			digest, err := client.GetTagDigest(img) | ||||||
|  | 			if err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			versionMeta[service.Name] = serviceMeta{ | ||||||
|  | 				Digest: digest, | ||||||
|  | 				Image:  path, | ||||||
|  | 				Tag:    img.(reference.NamedTagged).Tag(), | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			logrus.Debugf("collecting digest: '%s', image: '%s', tag: '%s'", digest, path, tag) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		versions = append(versions, map[string]map[string]serviceMeta{tag: versionMeta}) | ||||||
|  |  | ||||||
|  | 		return nil | ||||||
|  | 	}); err != nil { | ||||||
|  | 		return versions, 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'", recipeDir) | ||||||
|  | 			logrus.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		branch = "main" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	refName := fmt.Sprintf("refs/heads/%s", branch) | ||||||
|  | 	checkOutOpts := &git.CheckoutOptions{ | ||||||
|  | 		Create: false, | ||||||
|  | 		Force:  true, | ||||||
|  | 		Keep:   false, | ||||||
|  | 		Branch: plumbing.ReferenceName(refName), | ||||||
|  | 	} | ||||||
|  | 	if err := worktree.Checkout(checkOutOpts); err != nil { | ||||||
|  | 		logrus.Debugf("failed to check out '%s' in '%s'", branch, recipeDir) | ||||||
|  | 		logrus.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	logrus.Debugf("switched back to '%s' in '%s'", branch, recipeDir) | ||||||
|  | 	logrus.Debugf("collected '%s' for '%s'", versions, recipeName) | ||||||
|  |  | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  | |||||||
| @ -124,7 +124,7 @@ func UpdateLabel(pattern, serviceName, label, recipeName string) error { | |||||||
| 					return err | 					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) | 				replacedBytes := strings.Replace(string(bytes), old, label, -1) | ||||||
|  |  | ||||||
| 				logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename) | 				logrus.Debugf("updating '%s' to '%s' in '%s'", old, label, compose.Filename) | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ import ( | |||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"os" | 	"os" | ||||||
| 	"path" | 	"path" | ||||||
| 	"path/filepath" |  | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/formatter" | 	"coopcloud.tech/abra/cli/formatter" | ||||||
| @ -46,7 +45,12 @@ type App struct { | |||||||
|  |  | ||||||
| // StackName gets what the docker safe stack name is for the app | // StackName gets what the docker safe stack name is for the app | ||||||
| func (a App) StackName() string { | 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 | // SORTING TYPES | ||||||
| @ -139,7 +143,7 @@ func LoadAppFiles(servers ...string) (AppFiles, error) { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	logrus.Debugf("collecting metadata from '%v' servers: '%s'", len(servers), servers) | 	logrus.Debugf("collecting metadata from '%v' servers: '%s'", len(servers), strings.Join(servers, ", ")) | ||||||
|  |  | ||||||
| 	for _, server := range servers { | 	for _, server := range servers { | ||||||
| 		serverDir := path.Join(ABRA_SERVER_FOLDER, server) | 		serverDir := path.Join(ABRA_SERVER_FOLDER, server) | ||||||
| @ -276,8 +280,8 @@ func SanitiseAppName(name string) string { | |||||||
| } | } | ||||||
|  |  | ||||||
| // GetAppStatuses queries servers to check the deployment status of given apps | // GetAppStatuses queries servers to check the deployment status of given apps | ||||||
| func GetAppStatuses(appFiles AppFiles) (map[string]string, error) { | func GetAppStatuses(appFiles AppFiles) (map[string]map[string]string, error) { | ||||||
| 	statuses := map[string]string{} | 	statuses := make(map[string]map[string]string) | ||||||
|  |  | ||||||
| 	var unique []string | 	var unique []string | ||||||
| 	servers := make(map[string]struct{}) | 	servers := make(map[string]struct{}) | ||||||
| @ -300,10 +304,24 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) { | |||||||
| 	for range servers { | 	for range servers { | ||||||
| 		status := <-ch | 		status := <-ch | ||||||
| 		for _, service := range status.Services { | 		for _, service := range status.Services { | ||||||
|  | 			result := make(map[string]string) | ||||||
| 			name := service.Spec.Labels[convert.LabelNamespace] | 			name := service.Spec.Labels[convert.LabelNamespace] | ||||||
|  |  | ||||||
| 			if _, ok := statuses[name]; !ok { | 			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 | // 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. | // merged into a composetypes.Config while respecting the COMPOSE_FILE env var. | ||||||
| func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) { | func GetAppComposeFiles(recipe string, appEnv AppEnv) ([]string, error) { | ||||||
|  | 	var composeFiles []string | ||||||
|  |  | ||||||
| 	if _, ok := appEnv["COMPOSE_FILE"]; !ok { | 	if _, ok := appEnv["COMPOSE_FILE"]; !ok { | ||||||
| 		logrus.Debug("no COMPOSE_FILE detected, loading all compose files") | 		logrus.Debug("no COMPOSE_FILE detected, loading compose.yml") | ||||||
| 		pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe) | 		path := fmt.Sprintf("%s/%s/compose.yml", APPS_DIR, recipe) | ||||||
| 		composeFiles, err := filepath.Glob(pattern) | 		composeFiles = append(composeFiles, path) | ||||||
| 		if err != nil { |  | ||||||
| 			return composeFiles, err |  | ||||||
| 		} |  | ||||||
| 		return composeFiles, nil | 		return composeFiles, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var composeFiles []string |  | ||||||
| 	composeFileEnvVar := appEnv["COMPOSE_FILE"] | 	composeFileEnvVar := appEnv["COMPOSE_FILE"] | ||||||
| 	envVars := strings.Split(composeFileEnvVar, ":") | 	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, ":") { | 	for _, file := range strings.Split(composeFileEnvVar, ":") { | ||||||
| 		path := fmt.Sprintf("%s/%s/%s", APPS_DIR, recipe, file) | 		path := fmt.Sprintf("%s/%s/%s", APPS_DIR, recipe, file) | ||||||
| 		composeFiles = append(composeFiles, path) | 		composeFiles = append(composeFiles, path) | ||||||
|  | |||||||
| @ -41,6 +41,33 @@ func (r Recipe) UpdateTag(image, tag string) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Tags list the recipe tags | ||||||
|  | func (r Recipe) Tags() ([]string, error) { | ||||||
|  | 	var tags []string | ||||||
|  |  | ||||||
|  | 	recipeDir := path.Join(config.ABRA_DIR, "apps", r.Name) | ||||||
|  | 	repo, err := git.PlainOpen(recipeDir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tags, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	gitTags, err := repo.Tags() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return tags, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) { | ||||||
|  | 		tags = append(tags, strings.TrimPrefix(string(ref.Name()), "refs/tags/")) | ||||||
|  | 		return nil | ||||||
|  | 	}); err != nil { | ||||||
|  | 		return tags, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	logrus.Debugf("detected '%s' as tags for recipe '%s'", strings.Join(tags, ", "), r.Name) | ||||||
|  |  | ||||||
|  | 	return tags, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // Get retrieves a recipe. | // Get retrieves a recipe. | ||||||
| func Get(recipeName string) (Recipe, error) { | func Get(recipeName string) (Recipe, error) { | ||||||
| 	if err := EnsureExists(recipeName); err != nil { | 	if err := EnsureExists(recipeName); err != nil { | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| #!/usr/bin/env bash | #!/usr/bin/env bash | ||||||
|  |  | ||||||
| ABRA_VERSION="0.1.6-alpha" | ABRA_VERSION="0.1.8-alpha" | ||||||
| ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" | ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" | ||||||
|  |  | ||||||
| function show_banner { | function show_banner { | ||||||
| @ -38,7 +38,7 @@ function install_abra_release { | |||||||
|   release_url=$(curl -s "$ABRA_RELEASE_URL" | |   release_url=$(curl -s "$ABRA_RELEASE_URL" | | ||||||
|     python3 -c "import sys, json; \ |     python3 -c "import sys, json; \ | ||||||
|                 payload = json.load(sys.stdin); \ |                 payload = json.load(sys.stdin); \ | ||||||
|                 url = [a['browser_download_url'] for a in payload['assets'] if 'x86_64' in a['name']][0]; \ |                 url = [a['browser_download_url'] for a in payload['assets'] if 'linux_x86_64' in a['name']][0]; \ | ||||||
|                 print(url)") |                 print(url)") | ||||||
|  |  | ||||||
|   echo "downloading $ABRA_VERSION x86_64 binary release for abra..." |   echo "downloading $ABRA_VERSION x86_64 binary release for abra..." | ||||||
|  | |||||||
| @ -3,5 +3,5 @@ STACK := abra_installer_script | |||||||
| default: deploy | default: deploy | ||||||
|  |  | ||||||
| deploy: | deploy: | ||||||
| 	@docker stack rm $(STACK) && \ | 	@DOCKER_CONTEXT=swarm.autonomic.zone docker stack rm $(STACK) && \ | ||||||
| 		docker stack deploy -c compose.yml $(STACK) | 		DOCKER_CONTEXT=swarm.autonomic.zone docker stack deploy -c compose.yml $(STACK) | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user