forked from toolshed/abra
		
	Compare commits
	
		
			21 Commits
		
	
	
		
			0.4.0-alph
			...
			0.4.0-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 69a7d37fb7 | |||
| 87649cbbd0 | |||
| 4b7ec6384c | |||
| b22b63c2ba | |||
| d9f3a11265 | |||
| d7cf11b876 | |||
| d7e1b2947a | |||
| 1b37d2d5f5 | |||
| 74dfb12fd6 | |||
| 49ccf2d204 | |||
| 76adc45431 | |||
| e38a0078f3 | |||
| 25b44dc54e | |||
| 0c2f6fb676 | |||
| 10e4a8b97f | |||
| eed2756784 | |||
| b61b8f0d2a | |||
| 763e7b5bff | |||
| d5ab9aedbf | |||
| 2ebb00c9d4 | |||
| 6d76b3646a | 
| @ -30,10 +30,10 @@ is failing to deploy or having issues, it could be a lot of things. | |||||||
|  |  | ||||||
| This command currently takes into account: | This command currently takes into account: | ||||||
|  |  | ||||||
| 		Is the service deployed? |     Is the service deployed? | ||||||
|     Is the service killed by an OOM error? |     Is the service killed by an OOM error? | ||||||
| 		Is the service reporting an error (like in "ps --no-trunc" output) |     Is the service reporting an error (like in "ps --no-trunc" output) | ||||||
| 		Is the service healthcheck failing? what are the healthcheck logs? |     Is the service healthcheck failing? what are the healthcheck logs? | ||||||
|  |  | ||||||
| Got any more ideas? Please let us know: | Got any more ideas? Please let us know: | ||||||
|  |  | ||||||
|  | |||||||
| @ -39,13 +39,13 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 		if !internal.Force { | 		if !internal.Force { | ||||||
| 			response := false | 			response := false | ||||||
| 			prompt := &survey.Confirm{ | 			prompt := &survey.Confirm{ | ||||||
| 				Message: fmt.Sprintf("about to delete %s, are you sure?", app.Name), | 				Message: fmt.Sprintf("about to remove %s, are you sure?", app.Name), | ||||||
| 			} | 			} | ||||||
| 			if err := survey.AskOne(prompt, &response); err != nil { | 			if err := survey.AskOne(prompt, &response); err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			if !response { | 			if !response { | ||||||
| 				logrus.Fatal("user aborted app removal") | 				logrus.Fatal("aborting as requested") | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| @ -54,18 +54,16 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if !internal.Force { | 		isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) | ||||||
| 			isDeployed, _, err := stack.IsDeployed(c.Context, cl, app.StackName()) | 		if err != nil { | ||||||
| 			if err != nil { | 			logrus.Fatal(err) | ||||||
| 				logrus.Fatal(err) | 		} | ||||||
| 			} | 		if isDeployed { | ||||||
| 			if isDeployed { | 			logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name) | ||||||
| 				logrus.Fatalf("%s is still deployed. Run \"abra app undeploy %s \" or pass --force", app.Name, app.Name) |  | ||||||
| 			} |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		fs := filters.NewArgs() | 		fs := filters.NewArgs() | ||||||
| 		fs.Add("name", app.Name) | 		fs.Add("name", app.StackName()) | ||||||
| 		secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: fs}) | 		secretList, err := cl.SecretList(c.Context, types.SecretListOptions{Filters: fs}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			logrus.Fatal(err) | 			logrus.Fatal(err) | ||||||
| @ -81,6 +79,7 @@ var appRemoveCommand = &cli.Command{ | |||||||
|  |  | ||||||
| 		if len(secrets) > 0 { | 		if len(secrets) > 0 { | ||||||
| 			var secretNamesToRemove []string | 			var secretNamesToRemove []string | ||||||
|  |  | ||||||
| 			if !internal.Force { | 			if !internal.Force { | ||||||
| 				secretsPrompt := &survey.MultiSelect{ | 				secretsPrompt := &survey.MultiSelect{ | ||||||
| 					Message: "which secrets do you want to remove?", | 					Message: "which secrets do you want to remove?", | ||||||
| @ -142,7 +141,9 @@ var appRemoveCommand = &cli.Command{ | |||||||
| 				logrus.Info("no volumes were removed") | 				logrus.Info("no volumes were removed") | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			logrus.Info("no volumes to remove") | 			if Volumes { | ||||||
|  | 				logrus.Info("no volumes to remove") | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		err = os.Remove(app.Path) | 		err = os.Remove(app.Path) | ||||||
|  | |||||||
| @ -20,18 +20,19 @@ import ( | |||||||
| var allSecrets bool | var allSecrets bool | ||||||
| var allSecretsFlag = &cli.BoolFlag{ | var allSecretsFlag = &cli.BoolFlag{ | ||||||
| 	Name:        "all", | 	Name:        "all", | ||||||
| 	Aliases:     []string{"A"}, | 	Aliases:     []string{"a"}, | ||||||
| 	Value:       false, | 	Value:       false, | ||||||
| 	Destination: &allSecrets, | 	Destination: &allSecrets, | ||||||
| 	Usage:       "Generate all secrets", | 	Usage:       "Generate all secrets", | ||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretGenerateCommand = &cli.Command{ | var appSecretGenerateCommand = &cli.Command{ | ||||||
| 	Name:      "generate", | 	Name:         "generate", | ||||||
| 	Aliases:   []string{"g"}, | 	Aliases:      []string{"g"}, | ||||||
| 	Usage:     "Generate secrets", | 	Usage:        "Generate secrets", | ||||||
| 	ArgsUsage: "<secret> <version>", | 	ArgsUsage:    "<secret> <version>", | ||||||
| 	Flags:     []cli.Flag{allSecretsFlag, internal.PassFlag}, | 	Flags:        []cli.Flag{allSecretsFlag, internal.PassFlag}, | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Action: func(c *cli.Context) error { | 	Action: func(c *cli.Context) error { | ||||||
| 		app := internal.ValidateApp(c) | 		app := internal.ValidateApp(c) | ||||||
|  |  | ||||||
| @ -95,11 +96,12 @@ var appSecretGenerateCommand = &cli.Command{ | |||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretInsertCommand = &cli.Command{ | var appSecretInsertCommand = &cli.Command{ | ||||||
| 	Name:      "insert", | 	Name:         "insert", | ||||||
| 	Aliases:   []string{"i"}, | 	Aliases:      []string{"i"}, | ||||||
| 	Usage:     "Insert secret", | 	Usage:        "Insert secret", | ||||||
| 	Flags:     []cli.Flag{internal.PassFlag}, | 	Flags:        []cli.Flag{internal.PassFlag}, | ||||||
| 	ArgsUsage: "<app> <secret-name> <version> <data>", | 	ArgsUsage:    "<app> <secret-name> <version> <data>", | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command inserts a secret into an app environment. | This command inserts a secret into an app environment. | ||||||
|  |  | ||||||
| @ -139,11 +141,12 @@ Example: | |||||||
| } | } | ||||||
|  |  | ||||||
| var appSecretRmCommand = &cli.Command{ | var appSecretRmCommand = &cli.Command{ | ||||||
| 	Name:      "remove", | 	Name:         "remove", | ||||||
| 	Usage:     "Remove a secret", | 	Usage:        "Remove a secret", | ||||||
| 	Aliases:   []string{"rm"}, | 	Aliases:      []string{"rm"}, | ||||||
| 	Flags:     []cli.Flag{allSecretsFlag, internal.PassFlag}, | 	Flags:        []cli.Flag{allSecretsFlag, internal.PassFlag}, | ||||||
| 	ArgsUsage: "<app> <secret-name>", | 	ArgsUsage:    "<app> <secret-name>", | ||||||
|  | 	BashComplete: autocomplete.AppNameComplete, | ||||||
| 	Description: ` | 	Description: ` | ||||||
| This command removes a secret from an app environment. | This command removes a secret from an app environment. | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,8 +1,6 @@ | |||||||
| package app | package app | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/cli/internal" | 	"coopcloud.tech/abra/cli/internal" | ||||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | 	"coopcloud.tech/abra/pkg/autocomplete" | ||||||
| 	"coopcloud.tech/abra/pkg/client" | 	"coopcloud.tech/abra/pkg/client" | ||||||
| @ -22,9 +20,8 @@ func getImagePath(image string) (string, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	path := reference.Path(img) | 	path := reference.Path(img) | ||||||
| 	if strings.Contains(path, "library") { |  | ||||||
| 		path = strings.Split(path, "/")[1] | 	path = recipe.StripTagMeta(path) | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	logrus.Debugf("parsed %s from %s", path, image) | 	logrus.Debugf("parsed %s from %s", path, image) | ||||||
|  |  | ||||||
|  | |||||||
| @ -24,8 +24,10 @@ import ( | |||||||
| func DeployAction(c *cli.Context) error { | func DeployAction(c *cli.Context) error { | ||||||
| 	app := ValidateApp(c) | 	app := ValidateApp(c) | ||||||
|  |  | ||||||
| 	if err := recipe.EnsureUpToDate(app.Type); err != nil { | 	if !Chaos { | ||||||
| 		logrus.Fatal(err) | 		if err := recipe.EnsureUpToDate(app.Type); err != nil { | ||||||
|  | 			logrus.Fatal(err) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	r, err := recipe.Get(app.Type) | 	r, err := recipe.Get(app.Type) | ||||||
|  | |||||||
| @ -163,9 +163,9 @@ func NewAction(c *cli.Context) error { | |||||||
| 		NewAppServer = "local" | 		NewAppServer = "local" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tableCol := []string{"Name", "Domain", "Type", "Server"} | 	tableCol := []string{"server", "type", "domain", "app name"} | ||||||
| 	table := formatter.CreateTable(tableCol) | 	table := formatter.CreateTable(tableCol) | ||||||
| 	table.Append([]string{sanitisedAppName, Domain, recipe.Name, NewAppServer}) | 	table.Append([]string{NewAppServer, recipe.Name, Domain, NewAppName}) | ||||||
|  |  | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) | 	fmt.Println(fmt.Sprintf("A new %s app has been created! Here is an overview:", recipe.Name)) | ||||||
| @ -173,10 +173,10 @@ func NewAction(c *cli.Context) error { | |||||||
| 	table.Render() | 	table.Render() | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println("You can configure this app by running the following:") | 	fmt.Println("You can configure this app by running the following:") | ||||||
| 	fmt.Println(fmt.Sprintf("\n    abra app config %s", sanitisedAppName)) | 	fmt.Println(fmt.Sprintf("\n    abra app config %s", NewAppName)) | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
| 	fmt.Println("You can deploy this app by running the following:") | 	fmt.Println("You can deploy this app by running the following:") | ||||||
| 	fmt.Println(fmt.Sprintf("\n    abra app deploy %s", sanitisedAppName)) | 	fmt.Println(fmt.Sprintf("\n    abra app deploy %s", NewAppName)) | ||||||
| 	fmt.Println("") | 	fmt.Println("") | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
|  | |||||||
| @ -2,9 +2,9 @@ package internal | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"coopcloud.tech/abra/pkg/recipe" | 	"coopcloud.tech/abra/pkg/recipe" | ||||||
|  | 	recipePkg "coopcloud.tech/abra/pkg/recipe" | ||||||
| 	"github.com/AlecAivazis/survey/v2" | 	"github.com/AlecAivazis/survey/v2" | ||||||
| 	"github.com/docker/distribution/reference" | 	"github.com/docker/distribution/reference" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| @ -94,9 +94,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			path = reference.Path(img) | 			path = reference.Path(img) | ||||||
| 			if strings.Contains(path, "library") { | 			path = recipePkg.StripTagMeta(path) | ||||||
| 				path = strings.Split(path, "/")[1] |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			return path, nil | 			return path, nil | ||||||
| 		} | 		} | ||||||
|  | |||||||
| @ -127,6 +127,7 @@ your SSH keys configured on your account. | |||||||
| func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | ||||||
| 	var services = make(map[string]string) | 	var services = make(map[string]string) | ||||||
|  |  | ||||||
|  | 	missingTag := false | ||||||
| 	for _, service := range recipe.Config.Services { | 	for _, service := range recipe.Config.Services { | ||||||
| 		if service.Image == "" { | 		if service.Image == "" { | ||||||
| 			continue | 			continue | ||||||
| @ -138,21 +139,27 @@ func getImageVersions(recipe recipe.Recipe) (map[string]string, error) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		path := reference.Path(img) | 		path := reference.Path(img) | ||||||
| 		if strings.Contains(path, "library") { |  | ||||||
| 			path = strings.Split(path, "/")[1] | 		path = recipePkg.StripTagMeta(path) | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var tag string | 		var tag string | ||||||
| 		switch img.(type) { | 		switch img.(type) { | ||||||
| 		case reference.NamedTagged: | 		case reference.NamedTagged: | ||||||
| 			tag = img.(reference.NamedTagged).Tag() | 			tag = img.(reference.NamedTagged).Tag() | ||||||
| 		case reference.Named: | 		case reference.Named: | ||||||
| 			return services, fmt.Errorf("%s service is missing image tag?", path) | 			if service.Name == "app" { | ||||||
|  | 				missingTag = true | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		services[path] = tag | 		services[path] = tag | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if missingTag { | ||||||
|  | 		return services, fmt.Errorf("app service is missing image tag?") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return services, nil | 	return services, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | |||||||
| @ -115,23 +115,26 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 			} | 			} | ||||||
| 			logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) | 			logrus.Debugf("retrieved %s from remote registry for %s", regVersions, image) | ||||||
|  |  | ||||||
| 			if strings.Contains(image, "library") { | 			image = recipePkg.StripTagMeta(image) | ||||||
| 				// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>, |  | ||||||
| 				// postgres:<tag>, i.e. images which do not have a username in the | 			switch img.(type) { | ||||||
| 				// first position of the string | 			case reference.NamedTagged: | ||||||
| 				image = strings.Split(image, "/")[1] | 				if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { | ||||||
| 			} | 					logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag()) | ||||||
| 			semverLikeTag := true | 				} | ||||||
| 			if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) { | 			default: | ||||||
| 				logrus.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag()) | 				logrus.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name) | ||||||
| 				semverLikeTag = false | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag()) | 			tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag()) | ||||||
| 			if err != nil && semverLikeTag { | 			if err != nil { | ||||||
| 				logrus.Fatal(err) | 				logrus.Warnf("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name) | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			logrus.Debugf("parsed %s for %s", tag, service.Name) | 			logrus.Debugf("parsed %s for %s", tag, service.Name) | ||||||
|  |  | ||||||
| 			var compatible []tagcmp.Tag | 			var compatible []tagcmp.Tag | ||||||
| 			for _, regVersion := range regVersions { | 			for _, regVersion := range regVersions { | ||||||
| 				other, err := tagcmp.Parse(regVersion.Name) | 				other, err := tagcmp.Parse(regVersion.Name) | ||||||
| @ -148,7 +151,7 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
|  |  | ||||||
| 			sort.Sort(tagcmp.ByTagDesc(compatible)) | 			sort.Sort(tagcmp.ByTagDesc(compatible)) | ||||||
|  |  | ||||||
| 			if len(compatible) == 0 && semverLikeTag { | 			if len(compatible) == 0 { | ||||||
| 				logrus.Info(fmt.Sprintf("no new versions available for %s, %s is the latest", image, tag)) | 				logrus.Info(fmt.Sprintf("no new versions available for %s, %s is the latest", image, tag)) | ||||||
| 				continue // skip on to the next tag and don't update any compose files | 				continue // skip on to the next tag and don't update any compose files | ||||||
| 			} | 			} | ||||||
| @ -188,13 +191,13 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 					if contains { | 					if contains { | ||||||
| 						logrus.Infof("Upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString) | 						logrus.Infof("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString) | ||||||
| 					} else { | 					} else { | ||||||
| 						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | 						logrus.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString) | ||||||
| 						continue | 						continue | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					logrus.Fatalf("Service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String()) | 					logrus.Fatalf("service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String()) | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 			} else { | 			} else { | ||||||
| @ -211,7 +214,7 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 					if upgradeTag == "" { | 					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) | 						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 | 						continue | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| @ -220,7 +223,7 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 						tag := img.(reference.NamedTagged).Tag() | 						tag := img.(reference.NamedTagged).Tag() | ||||||
| 						logrus.Warning(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", 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) | 						msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag) | ||||||
| 						compatibleStrings = []string{} | 						compatibleStrings = []string{"skip"} | ||||||
| 						for _, regVersion := range regVersions { | 						for _, regVersion := range regVersions { | ||||||
| 							compatibleStrings = append(compatibleStrings, regVersion.Name) | 							compatibleStrings = append(compatibleStrings, regVersion.Name) | ||||||
| 						} | 						} | ||||||
| @ -238,10 +241,13 @@ You may invoke this command in "wizard" mode and be prompted for input: | |||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			if upgradeTag != "skip" { | 			if upgradeTag != "skip" { | ||||||
| 				if err := recipe.UpdateTag(image, upgradeTag); err != nil { | 				ok, err := recipe.UpdateTag(image, upgradeTag) | ||||||
|  | 				if err != nil { | ||||||
| 					logrus.Fatal(err) | 					logrus.Fatal(err) | ||||||
| 				} | 				} | ||||||
| 				logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image) | 				if ok { | ||||||
|  | 					logrus.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image) | ||||||
|  | 				} | ||||||
| 			} else { | 			} else { | ||||||
| 				logrus.Warnf("not upgrading %s, skipping as requested", image) | 				logrus.Warnf("not upgrading %s, skipping as requested", image) | ||||||
| 			} | 			} | ||||||
|  | |||||||
| @ -16,10 +16,10 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| // UpdateTag updates an image tag in-place on file system local compose files. | // UpdateTag updates an image tag in-place on file system local compose files. | ||||||
| func UpdateTag(pattern, image, tag, recipeName string) error { | func UpdateTag(pattern, image, tag, recipeName string) (bool, error) { | ||||||
| 	composeFiles, err := filepath.Glob(pattern) | 	composeFiles, err := filepath.Glob(pattern) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return false, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")) | 	logrus.Debugf("considering %s config(s) for tag update", strings.Join(composeFiles, ", ")) | ||||||
| @ -30,12 +30,12 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
| 		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") | 		envSamplePath := path.Join(config.RECIPES_DIR, recipeName, ".env.sample") | ||||||
| 		sampleEnv, err := config.ReadEnv(envSamplePath) | 		sampleEnv, err := config.ReadEnv(envSamplePath) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		compose, err := loader.LoadComposefile(opts, sampleEnv) | 		compose, err := loader.LoadComposefile(opts, sampleEnv) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return false, err | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		for _, service := range compose.Services { | 		for _, service := range compose.Services { | ||||||
| @ -45,24 +45,26 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
|  |  | ||||||
| 			img, _ := reference.ParseNormalizedNamed(service.Image) | 			img, _ := reference.ParseNormalizedNamed(service.Image) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return false, err | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			var composeTag string | ||||||
|  | 			switch img.(type) { | ||||||
|  | 			case reference.NamedTagged: | ||||||
|  | 				composeTag = img.(reference.NamedTagged).Tag() | ||||||
|  | 			default: | ||||||
|  | 				// unable to parse, typically image missing tag | ||||||
|  | 				return false, nil | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			composeImage := reference.Path(img) | 			composeImage := reference.Path(img) | ||||||
| 			if strings.Contains(composeImage, "library") { |  | ||||||
| 				// ParseNormalizedNamed prepends 'library' to images like nginx:<tag>, |  | ||||||
| 				// postgres:<tag>, i.e. images which do not have a username in the |  | ||||||
| 				// first position of the string |  | ||||||
| 				composeImage = strings.Split(composeImage, "/")[1] |  | ||||||
| 			} |  | ||||||
| 			composeTag := img.(reference.NamedTagged).Tag() |  | ||||||
|  |  | ||||||
| 			logrus.Debugf("parsed %s from %s", composeTag, service.Image) | 			logrus.Debugf("parsed %s from %s", composeTag, service.Image) | ||||||
|  |  | ||||||
| 			if image == composeImage { | 			if image == composeImage { | ||||||
| 				bytes, err := ioutil.ReadFile(composeFile) | 				bytes, err := ioutil.ReadFile(composeFile) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					return err | 					return false, err | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				old := fmt.Sprintf("%s:%s", composeImage, composeTag) | 				old := fmt.Sprintf("%s:%s", composeImage, composeTag) | ||||||
| @ -72,13 +74,13 @@ func UpdateTag(pattern, image, tag, recipeName string) error { | |||||||
| 				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename) | 				logrus.Debugf("updating %s to %s in %s", old, new, compose.Filename) | ||||||
|  |  | ||||||
| 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | 				if err := ioutil.WriteFile(compose.Filename, []byte(replacedBytes), 0764); err != nil { | ||||||
| 					return err | 					return true, err | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return false, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // UpdateLabel updates a label in-place on file system local compose files. | // UpdateLabel updates a label in-place on file system local compose files. | ||||||
|  | |||||||
| @ -163,12 +163,17 @@ func (r Recipe) UpdateLabel(pattern, serviceName, label string) error { | |||||||
| } | } | ||||||
|  |  | ||||||
| // UpdateTag updates a recipe tag | // UpdateTag updates a recipe tag | ||||||
| func (r Recipe) UpdateTag(image, tag string) error { | func (r Recipe) UpdateTag(image, tag string) (bool, error) { | ||||||
| 	pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name) | 	pattern := fmt.Sprintf("%s/%s/compose**yml", config.RECIPES_DIR, r.Name) | ||||||
| 	if err := compose.UpdateTag(pattern, image, tag, r.Name); err != nil { |  | ||||||
| 		return err | 	image = StripTagMeta(image) | ||||||
|  |  | ||||||
|  | 	ok, err := compose.UpdateTag(pattern, image, tag, r.Name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return false, err | ||||||
| 	} | 	} | ||||||
| 	return nil |  | ||||||
|  | 	return ok, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Tags list the recipe tags | // Tags list the recipe tags | ||||||
| @ -973,9 +978,8 @@ func GetRecipeVersions(recipeName, registryUsername, registryPassword string) (R | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			path := reference.Path(img) | 			path := reference.Path(img) | ||||||
| 			if strings.Contains(path, "library") { |  | ||||||
| 				path = strings.Split(path, "/")[1] | 			path = StripTagMeta(path) | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			var tag string | 			var tag string | ||||||
| 			switch img.(type) { | 			switch img.(type) { | ||||||
| @ -1041,3 +1045,22 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri | |||||||
|  |  | ||||||
| 	return versions, nil | 	return versions, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // StripTagMeta strips front-matter image tag data that we don't need for parsing. | ||||||
|  | func StripTagMeta(image string) string { | ||||||
|  | 	originalImage := image | ||||||
|  |  | ||||||
|  | 	if strings.Contains(image, "docker.io") { | ||||||
|  | 		image = strings.Split(image, "/")[1] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if strings.Contains(image, "library") { | ||||||
|  | 		image = strings.Split(image, "/")[1] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if originalImage != image { | ||||||
|  | 		logrus.Debugf("stripped %s to %s for parsing", originalImage, image) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return image | ||||||
|  | } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|  |  | ||||||
| ABRA_VERSION="0.3.0-alpha" | ABRA_VERSION="0.3.0-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" | ||||||
| RC_VERSION="0.4.0-alpha-rc2" | RC_VERSION="0.4.0-alpha-rc4" | ||||||
| RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" | RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION" | ||||||
|  |  | ||||||
| for arg in "$@"; do | for arg in "$@"; do | ||||||
|  | |||||||
| @ -17,6 +17,8 @@ wire up for testing in an automated way. | |||||||
| ## deploy, upgrade, rollback | ## deploy, upgrade, rollback | ||||||
|  |  | ||||||
| - `abra app deploy <app>` | - `abra app deploy <app>` | ||||||
|  | - `abra app deploy --force <app>` | ||||||
|  | - `abra app deploy --chaos <app>` | ||||||
| - `abra app upgrade <app>` | - `abra app upgrade <app>` | ||||||
| - `abra app rollback <app>` | - `abra app rollback <app>` | ||||||
|  |  | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user