forked from toolshed/abra
Compare commits
11 Commits
0.4.0-alph
...
0.4.0-alph
Author | SHA1 | Date | |
---|---|---|---|
69a7d37fb7
|
|||
87649cbbd0
|
|||
4b7ec6384c
|
|||
b22b63c2ba
|
|||
d9f3a11265
|
|||
d7cf11b876
|
|||
d7e1b2947a
|
|||
1b37d2d5f5
|
|||
74dfb12fd6
|
|||
49ccf2d204
|
|||
76adc45431
|
@ -141,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)
|
||||||
|
@ -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)
|
||||||
|
@ -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-rc3"
|
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