Compare commits

...

39 Commits

Author SHA1 Message Date
75fb9a2774 chore: publish new version
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 13:31:18 +02:00
0d500b636d feat: more info on version changing deployments
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 13:30:33 +02:00
5dd97cace0 docs: expand deploy/upgrade/downgrade docs
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 12:26:07 +02:00
ae32b1eed2 fix: standardise checkout options
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 12:17:58 +02:00
113bdf9e86 feat: add stats to app list
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 12:02:12 +02:00
d4d4da19b7 feat: first steps towards watchable ps output
See coop-cloud/organising#178.
2021-10-14 11:51:40 +02:00
454ee696d6 fix: make ps a bit more useful and less verbose 2021-10-14 11:36:03 +02:00
ca16c002ba docs: add more description for versions command 2021-10-14 11:32:32 +02:00
91cc8b00b3 fix: avoid alias conflict 2021-10-14 11:32:25 +02:00
d0828c4d8d fix: teach app version command to read new versions
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-14 11:29:57 +02:00
b69aed3bcf feat: add rollback command
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#127.
2021-10-14 01:52:55 +02:00
875255fd8c feat: add upgrade command
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2021-10-14 01:23:04 +02:00
2dca602c0b fix: error handling in deploy 2021-10-14 01:22:54 +02:00
1dca8a1067 chore: set 1.16 as requirement now
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#201.
2021-10-13 16:55:58 +02:00
37022bf0c8 feat: make deploy only deploy
All checks were successful
continuous-integration/drone/push Build is passing
See coop-cloud/organising#127.
2021-10-13 16:51:04 +02:00
eb5b35d47f build: change sed flags in installer for mac os compatibility
Some checks are pending
continuous-integration/drone/push Build is running
continuous-integration/drone/pr Build is passing
2021-10-13 16:36:07 +02:00
ece1130797 build: add automatic os and architecture detection to installer script
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-10-13 15:51:19 +02:00
c266316f7e build: remove python3 dependency from installer
All checks were successful
continuous-integration/drone/pr Build is passing
2021-10-13 15:08:00 +02:00
d804276cf2 feat: add pre-deploy overview
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-10-12 13:25:23 +02:00
4235e06943 chore: new 0.1.8-alpha release
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-12 11:19:26 +02:00
a9af0b3627 fix: let gofmt do its magic
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-12 10:34:10 +02:00
3wc
a0b4886eba WIP: default to compose.yml instead of all of 'em
Some checks failed
continuous-integration/drone/push Build is failing
2021-10-12 10:25:37 +02:00
84489495dc fix: load STACK_NAME if not present
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-12 09:03:48 +02:00
a8683dc38a refactor: better formatting 2021-10-12 08:59:14 +02:00
e2128ea5b6 fix: check key existance correctly
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-12 08:55:42 +02:00
ca3c5fef0f refactor: better wording [ci skip] 2021-10-12 08:49:38 +02:00
4a01e411be refactor: handle STACK_NAME override in one place
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-12 01:14:14 +02:00
777d49ac1d fix: handle STACK_NAME for the ps command 2021-10-12 01:11:34 +02:00
deb7d21158 fix: dont loop over dead tags
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#195.
2021-10-12 00:56:52 +02:00
6db1fdcfba refactor!: recipe upgrade: use new tagcmp version
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-11 14:43:06 +00:00
44dc0edf7b refactor: use ; trick for inline checking [ci skip] 2021-10-11 13:48:25 +02:00
36ff50312c fix!: use annotated tags with recipe release
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2021-10-11 10:45:00 +02:00
ff4b978876 fix: only list new versions
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#192.
2021-10-11 01:17:52 +02:00
b68547b2c2 fix: dont overwrite generated catalogue
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#190.
2021-10-11 01:06:51 +02:00
0140f96ca1 fix: make sure to clone recipe
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#193.
2021-10-11 00:34:23 +02:00
3wc
1cb45113db fix: default linux binary in installer, add context
All checks were successful
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#184
2021-10-09 21:45:28 +02:00
c764243f3a fix: manage multiple version showing edge cases
All checks were successful
continuous-integration/drone/push Build is passing
2021-10-08 10:50:48 +02:00
dde8afcd43 feat: support version/upgrade listing
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Closes coop-cloud/organising#130.
2021-10-08 09:51:47 +02:00
98ffc210e1 fix: show descending orders on releases [ci skip] 2021-10-06 09:13:07 +02:00
25 changed files with 820 additions and 273 deletions

View File

@ -28,7 +28,7 @@ checksum:
snapshot: snapshot:
name_template: "{{ incpatch .Version }}-next" name_template: "{{ incpatch .Version }}-next"
changelog: changelog:
sort: asc sort: desc
filters: filters:
exclude: exclude:
- "^WIP:" - "^WIP:"

View File

@ -19,6 +19,7 @@ to scaling apps up and spinning them down.
appNewCommand, appNewCommand,
appConfigCommand, appConfigCommand,
appDeployCommand, appDeployCommand,
appUpgradeCommand,
appUndeployCommand, appUndeployCommand,
appBackupCommand, appBackupCommand,
appRestoreCommand, appRestoreCommand,

View File

@ -2,11 +2,16 @@ package app
import ( import (
"fmt" "fmt"
"strings"
abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
stack "coopcloud.tech/abra/pkg/client/stack" stack "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -15,14 +20,63 @@ var appDeployCommand = &cli.Command{
Name: "deploy", Name: "deploy",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Deploy an app", Usage: "Deploy an app",
Flags: []cli.Flag{
internal.ForceFlag,
},
Description: `
This command deploys a new instance of an app. It does not support changing the
version of an existing deployed app, for this you need to look at the "abra app
upgrade <app>" command.
You may pass "--force" to re-deploy the same version again. This can be useful
if the container runtime has gotten into a weird state or your doing some live
hacking.
`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Debugf("checking whether '%s' is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
if err != nil {
logrus.Fatal(err)
}
if isDeployed {
if internal.Force {
logrus.Infof("continuing with deployment due to '--force/-f' being set")
} else {
logrus.Fatalf("'%s' is already deployed", stackName)
}
}
version := deployedVersion
if version == "" {
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
if err != nil {
logrus.Fatal(err)
}
if len(versions) > 0 {
version = versions[len(versions)-1]
logrus.Infof("choosing '%s' as version to deploy", version)
if err := recipe.EnsureVersion(app.Type, version); err != nil {
logrus.Fatal(err)
}
} else {
version = "latest commit"
logrus.Warning("no versions detected, using latest commit")
if err := recipe.EnsureLatest(app.Type); err != nil {
logrus.Fatal(err)
}
}
}
abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_DIR, app.Type, "abra.sh") abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_DIR, app.Type, "abra.sh")
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath) abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
if err != nil { if err != nil {
@ -32,17 +86,13 @@ var appDeployCommand = &cli.Command{
app.Env[k] = v app.Env[k] = v
} }
if _, exists := app.Env["STACK_NAME"]; !exists {
app.Env["STACK_NAME"] = app.StackName()
}
composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
deployOpts := stack.Deploy{ deployOpts := stack.Deploy{
Composefiles: composeFiles, Composefiles: composeFiles,
Namespace: app.Env["STACK_NAME"], Namespace: stackName,
Prune: false, Prune: false,
ResolveImage: stack.ResolveImageAlways, ResolveImage: stack.ResolveImageAlways,
} }
@ -51,6 +101,10 @@ var appDeployCommand = &cli.Command{
logrus.Fatal(err) logrus.Fatal(err)
} }
if err := DeployOverview(app, version); err != nil {
logrus.Fatal(err)
}
if err := stack.RunDeploy(cl, deployOpts, compose); err != nil { if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
@ -70,3 +124,61 @@ var appDeployCommand = &cli.Command{
} }
}, },
} }
// DeployOverview shows a deployment overview
func DeployOverview(app config.App, version string) error {
tableCol := []string{"server", "compose", "domain", "stack", "version"}
table := abraFormatter.CreateTable(tableCol)
deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
}
table.Append([]string{app.Server, deployConfig, app.Domain, app.StackName(), version})
table.Render()
response := false
prompt := &survey.Confirm{
Message: "continue with deployment?",
}
if err := survey.AskOne(prompt, &response); err != nil {
return err
}
if !response {
logrus.Fatal("exiting as requested")
}
return nil
}
// NewVersionOverview shows an upgrade or downgrade overview
func NewVersionOverview(app config.App, currentVersion, newVersion string) error {
tableCol := []string{"server", "compose", "domain", "stack", "current version", "to be deployed"}
table := abraFormatter.CreateTable(tableCol)
deployConfig := "compose.yml"
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
deployConfig = strings.Join(strings.Split(composeFiles, ":"), "\n")
}
table.Append([]string{app.Server, deployConfig, app.Domain, app.StackName(), currentVersion, newVersion})
table.Render()
response := false
prompt := &survey.Confirm{
Message: "continue with deployment?",
}
if err := survey.AskOne(prompt, &response); err != nil {
return err
}
if !response {
logrus.Fatal("exiting as requested")
}
return nil
}

View File

@ -1,10 +1,14 @@
package app package app
import ( import (
"fmt"
"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 +69,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)
@ -78,22 +82,90 @@ can take some time.
table := abraFormatter.CreateTable(tableCol) table := abraFormatter.CreateTable(tableCol)
table.SetAutoMergeCellsByColumnIndex([]int{0}) table.SetAutoMergeCellsByColumnIndex([]int{0})
var (
versionedAppsCount int
unversionedAppsCount int
onLatestCount int
canUpgradeCount int
)
for _, app := range apps { for _, app := range apps {
var tableRow []string var tableRow []string
if app.Type == appType || appType == "" { if app.Type == appType || appType == "" {
// 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)
versionedAppsCount++
} else { } else {
tableRow = append(tableRow, "unknown") tableRow = append(tableRow, status, version)
unversionedAppsCount++
}
var newUpdates []string
if version != "unknown" {
updates, err := catalogue.GetRecipeCatalogueVersions(app.Type)
if err != nil {
logrus.Fatal(err)
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
logrus.Fatal(err)
}
for _, update := range updates {
parsedUpdate, err := tagcmp.Parse(update)
if err != nil {
logrus.Fatal(err)
}
if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
newUpdates = append(newUpdates, update)
}
}
}
if len(newUpdates) == 0 {
if version == "unknown" {
tableRow = append(tableRow, "unknown")
} else {
tableRow = append(tableRow, "on latest")
onLatestCount++
}
} else {
// FIXME: jeezus golang why do you not have a list reverse function
for i, j := 0, len(newUpdates)-1; i < j; i, j = i+1, j-1 {
newUpdates[i], newUpdates[j] = newUpdates[j], newUpdates[i]
}
tableRow = append(tableRow, strings.Join(newUpdates, "\n"))
canUpgradeCount++
} }
} }
} }
table.Append(tableRow) table.Append(tableRow)
} }
stats := fmt.Sprintf(
"Total apps: %v | Versioned: %v | Unversioned: %v | On latest: %v | Can upgrade: %v",
len(apps),
versionedAppsCount,
unversionedAppsCount,
onLatestCount,
canUpgradeCount,
)
table.SetCaption(true, stats)
table.Render() table.Render()
return nil return nil

View File

@ -3,6 +3,7 @@ package app
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
abraFormatter "coopcloud.tech/abra/cli/formatter" abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -15,44 +16,33 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
var watch bool
var watchFlag = &cli.BoolFlag{
Name: "watch",
Aliases: []string{"w"},
Value: false,
Usage: "Watch status by polling repeatedly",
Destination: &watch,
}
var appPsCommand = &cli.Command{ var appPsCommand = &cli.Command{
Name: "ps", Name: "ps",
Usage: "Check app status", Usage: "Check app status",
Aliases: []string{"p"}, Aliases: []string{"p"},
Flags: []cli.Flag{
watchFlag,
},
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) if !watch {
showPSOutput(c)
cl, err := client.New(app.Server) return nil
if err != nil {
logrus.Fatal(err)
} }
filters := filters.NewArgs() // TODO: how do we make this update in-place in an x-platform way?
filters.Add("name", app.StackName()) for {
showPSOutput(c)
containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters}) time.Sleep(2 * time.Second)
if err != nil {
logrus.Fatal(err)
} }
tableCol := []string{"id", "image", "command", "created", "status", "ports", "names"}
table := abraFormatter.CreateTable(tableCol)
for _, container := range containers {
tableRow := []string{
abraFormatter.ShortenID(container.ID),
abraFormatter.RemoveSha(container.Image),
abraFormatter.Truncate(container.Command),
abraFormatter.HumanDuration(container.Created),
container.Status,
formatter.DisplayablePorts(container.Ports),
strings.Join(container.Names, ", "),
}
table.Append(tableRow)
}
table.Render()
return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
appNames, err := config.GetAppNames() appNames, err := config.GetAppNames()
@ -67,3 +57,43 @@ var appPsCommand = &cli.Command{
} }
}, },
} }
// showPSOutput renders ps output.
func showPSOutput(c *cli.Context) {
app := internal.ValidateApp(c)
cl, err := client.New(app.Server)
if err != nil {
logrus.Fatal(err)
}
filters := filters.NewArgs()
filters.Add("name", app.StackName())
containers, err := cl.ContainerList(c.Context, types.ContainerListOptions{Filters: filters})
if err != nil {
logrus.Fatal(err)
}
tableCol := []string{"image", "created", "status", "ports", "names"}
table := abraFormatter.CreateTable(tableCol)
for _, container := range containers {
var containerNames []string
for _, containerName := range container.Names {
trimmed := strings.TrimPrefix(containerName, "/")
containerNames = append(containerNames, trimmed)
}
tableRow := []string{
abraFormatter.RemoveSha(container.Image),
abraFormatter.HumanDuration(container.Created),
container.Status,
formatter.DisplayablePorts(container.Ports),
strings.Join(containerNames, "\n"),
}
table.Append(tableRow)
}
table.Render()
}

View File

@ -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)
} }
} }

View File

@ -3,14 +3,15 @@ package app
import ( import (
"fmt" "fmt"
"context" "coopcloud.tech/abra/pkg/catalogue"
stack "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
@ -18,8 +19,21 @@ import (
var appRollbackCommand = &cli.Command{ var appRollbackCommand = &cli.Command{
Name: "rollback", Name: "rollback",
Usage: "Roll an app back to a previous version", Usage: "Roll an app back to a previous version",
Aliases: []string{"r"}, Aliases: []string{"r", "downgrade"},
ArgsUsage: "[<version>]", ArgsUsage: "<app>",
Flags: []cli.Flag{
internal.ForceFlag,
},
Description: `
This command rolls an app back to a previous version if one exists.
You may pass "--force/-f" to downgrade to the same version again. This can be
useful if the container runtime has gotten into a weird state or your doing
some live hacking.
This action could be destructive, please ensure you have a copy of your app
data beforehand - see "abra app backup <app>" for more.
`,
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
appNames, err := config.GetAppNames() appNames, err := config.GetAppNames()
if err != nil { if err != nil {
@ -34,48 +48,110 @@ var appRollbackCommand = &cli.Command{
}, },
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName()
ctx := context.Background()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
recipeMeta, err := catalogue.GetRecipeMeta(app.Type) logrus.Debugf("checking whether '%s' is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
if len(recipeMeta.Versions) == 0 {
logrus.Fatalf("no catalogue versions available for '%s'", app.Type)
}
deployedVersions, isDeployed, err := appPkg.DeployedVersions(ctx, cl, app) if deployedVersion == "" {
if err != nil { logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
logrus.Fatal(err)
} }
if !isDeployed { if !isDeployed {
logrus.Fatalf("'%s' is not deployed?", app.Name) logrus.Fatalf("'%s' is not deployed?", app.Name)
} }
if _, exists := deployedVersions["app"]; !exists {
logrus.Fatalf("no versioned 'app' service for '%s', cannot determine version", app.Name) versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
if err != nil {
logrus.Fatal(err)
} }
version := c.Args().Get(1) var availableDowngrades []string
if version == "" { for _, version := range versions {
// TODO: parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
// using deployedVersions["app"], get index+1 version from catalogue if err != nil {
// otherwise bail out saying there is nothing to rollback to logrus.Fatal(err)
} else { }
// TODO parsedVersion, err := tagcmp.Parse(version)
// ensure this version is listed in the catalogue if err != nil {
// ensure this version is "older" (lower down in the list) logrus.Fatal(err)
}
if parsedVersion != parsedDeployedVersion && parsedVersion.IsLessThan(parsedDeployedVersion) {
availableDowngrades = append(availableDowngrades, version)
}
} }
// TODO if len(availableDowngrades) == 0 {
// display table of existing state and expected state and prompt logrus.Fatal("no available downgrades, you're on latest")
// run the deployment with this target version! }
logrus.Fatal("command not implemented yet, coming soon TM") // FIXME: jeezus golang why do you not have a list reverse function
for i, j := 0, len(availableDowngrades)-1; i < j; i, j = i+1, j-1 {
availableDowngrades[i], availableDowngrades[j] = availableDowngrades[j], availableDowngrades[i]
}
var chosenDowngrade string
if !internal.Force {
prompt := &survey.Select{
Message: fmt.Sprintf("Please select a downgrade (current version: '%s'):", deployedVersion),
Options: availableDowngrades,
}
if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
return err
}
}
if internal.Force {
chosenDowngrade = availableDowngrades[0]
logrus.Debugf("choosing '%s' as version to downgrade to", chosenDowngrade)
}
if err := recipe.EnsureVersion(app.Type, chosenDowngrade); err != nil {
logrus.Fatal(err)
}
abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_DIR, app.Type, "abra.sh")
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
if err != nil {
logrus.Fatal(err)
}
for k, v := range abraShEnv {
app.Env[k] = v
}
composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
if err != nil {
logrus.Fatal(err)
}
deployOpts := stack.Deploy{
Composefiles: composeFiles,
Namespace: stackName,
Prune: false,
ResolveImage: stack.ResolveImageAlways,
}
compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
if err != nil {
logrus.Fatal(err)
}
if !internal.Force {
if err := NewVersionOverview(app, deployedVersion, chosenDowngrade); err != nil {
logrus.Fatal(err)
}
}
if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
logrus.Fatal(err)
}
return nil return nil
}, },

157
cli/app/upgrade.go Normal file
View File

@ -0,0 +1,157 @@
package app
import (
"fmt"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/client"
stack "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/recipe"
"coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)
var appUpgradeCommand = &cli.Command{
Name: "upgrade",
Aliases: []string{"u"},
Usage: "Upgrade an app",
ArgsUsage: "<app>",
Flags: []cli.Flag{
internal.ForceFlag,
},
Description: `
This command supports upgrading an app. You can use it to choose and roll out a
new upgrade to an existing app.
This command specifically supports changing the version of running apps, as
opposed to "abra app deploy <app>" which will not change the version of a
deployed app.
You may pass "--force/-f" to upgrade to the same version again. This can be
useful if the container runtime has gotten into a weird state or your doing
some live hacking.
This action could be destructive, please ensure you have a copy of your app
data beforehand - see "abra app backup <app>" for more.
`,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
stackName := app.StackName()
cl, err := client.New(app.Server)
if err != nil {
logrus.Fatal(err)
}
logrus.Debugf("checking whether '%s' is already deployed", stackName)
isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
if err != nil {
logrus.Fatal(err)
}
if deployedVersion == "" {
logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
}
if !isDeployed {
logrus.Fatalf("'%s' is not deployed?", app.Name)
}
versions, err := catalogue.GetRecipeCatalogueVersions(app.Type)
if err != nil {
logrus.Fatal(err)
}
var availableUpgrades []string
for _, version := range versions {
parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
if err != nil {
logrus.Fatal(err)
}
parsedVersion, err := tagcmp.Parse(version)
if err != nil {
logrus.Fatal(err)
}
if parsedVersion.IsGreaterThan(parsedDeployedVersion) {
availableUpgrades = append(availableUpgrades, version)
}
}
if len(availableUpgrades) == 0 {
logrus.Fatal("no available upgrades, you're on latest")
}
var chosenUpgrade string
if !internal.Force {
prompt := &survey.Select{
Message: fmt.Sprintf("Please select an upgrade (current version: '%s'):", deployedVersion),
Options: availableUpgrades,
}
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
return err
}
}
if internal.Force {
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
logrus.Debugf("choosing '%s' as version to upgrade to", chosenUpgrade)
}
if err := recipe.EnsureVersion(app.Type, chosenUpgrade); err != nil {
logrus.Fatal(err)
}
abraShPath := fmt.Sprintf("%s/%s/%s", config.APPS_DIR, app.Type, "abra.sh")
abraShEnv, err := config.ReadAbraShEnvVars(abraShPath)
if err != nil {
logrus.Fatal(err)
}
for k, v := range abraShEnv {
app.Env[k] = v
}
composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env)
if err != nil {
logrus.Fatal(err)
}
deployOpts := stack.Deploy{
Composefiles: composeFiles,
Namespace: stackName,
Prune: false,
ResolveImage: stack.ResolveImageAlways,
}
compose, err := config.GetAppComposeConfig(app.Name, deployOpts, app.Env)
if err != nil {
logrus.Fatal(err)
}
if !internal.Force {
if err := NewVersionOverview(app, deployedVersion, chosenUpgrade); err != nil {
logrus.Fatal(err)
}
}
if err := stack.RunDeploy(cl, deployOpts, compose); err != nil {
logrus.Fatal(err)
}
return nil
},
BashComplete: func(c *cli.Context) {
appNames, err := config.GetAppNames()
if err != nil {
logrus.Warn(err)
}
if c.NArg() > 0 {
return
}
for _, a := range appNames {
fmt.Println(a)
}
},
}

View File

@ -2,12 +2,12 @@ package app
import ( import (
"fmt" "fmt"
"sort"
"strings" "strings"
abraFormatter "coopcloud.tech/abra/cli/formatter" abraFormatter "coopcloud.tech/abra/cli/formatter"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
appPkg "coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/catalogue"
"coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/client/stack" "coopcloud.tech/abra/pkg/client/stack"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -32,65 +32,61 @@ func getImagePath(image string) (string, error) {
var appVersionCommand = &cli.Command{ var appVersionCommand = &cli.Command{
Name: "version", Name: "version",
Aliases: []string{"v"}, Aliases: []string{"v"},
Usage: "Show version of all services in app", Usage: "Show app versions",
Description: `
This command shows all information about versioning related to a deployed app.
This includes the individual image names, tags and digests. But also the Co-op
Cloud recipe version.
`,
Action: func(c *cli.Context) error { Action: func(c *cli.Context) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(c)
stackName := app.StackName()
composeFiles, err := config.GetAppComposeFiles(app.Type, app.Env) cl, err := client.New(app.Server)
if err != nil {
logrus.Fatal(err)
}
opts := stack.Deploy{Composefiles: composeFiles}
compose, err := config.GetAppComposeConfig(app.Type, opts, app.Env)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
ch := make(chan stack.StackStatus, len(compose.Services)) logrus.Debugf("checking whether '%s' is already deployed", stackName)
for _, service := range compose.Services {
label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name) isDeployed, deployedVersion, err := stack.IsDeployed(c.Context, cl, stackName)
go func(s string, l string) { if err != nil {
ch <- stack.GetDeployedServicesByLabel(s, l) logrus.Fatal(err)
}(app.Server, label)
} }
tableCol := []string{"name", "image", "version", "digest"} if deployedVersion == "" {
logrus.Fatalf("failed to determine version of deployed '%s'", app.Name)
}
if !isDeployed {
logrus.Fatalf("'%s' is not deployed?", app.Name)
}
recipeMeta, err := catalogue.GetRecipeMeta(app.Type)
if err != nil {
logrus.Fatal(err)
}
versionsMeta := make(map[string]catalogue.ServiceMeta)
for _, recipeVersion := range recipeMeta.Versions {
if currentVersion, exists := recipeVersion[deployedVersion]; exists {
versionsMeta = currentVersion
}
}
if len(versionsMeta) == 0 {
logrus.Fatalf("PANIC: could not retrieve deployed version ('%s') from recipe catalogue?", deployedVersion)
}
tableCol := []string{"name", "image", "version", "tag", "digest"}
table := abraFormatter.CreateTable(tableCol) table := abraFormatter.CreateTable(tableCol)
statuses := make(map[string]stack.StackStatus) for serviceName, versionMeta := range versionsMeta {
for range compose.Services { table.Append([]string{serviceName, versionMeta.Image, deployedVersion, versionMeta.Tag, versionMeta.Digest})
status := <-ch
if len(status.Services) > 0 {
serviceName := appPkg.ParseServiceName(status.Services[0].Spec.Name)
statuses[serviceName] = status
}
}
sort.SliceStable(compose.Services, func(i, j int) bool {
return compose.Services[i].Name < compose.Services[j].Name
})
for _, service := range compose.Services {
if status, ok := statuses[service.Name]; ok {
statusService := status.Services[0]
label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name)
version, digest := appPkg.ParseVersionLabel(statusService.Spec.Labels[label])
image, err := getImagePath(statusService.Spec.Labels["com.docker.stack.image"])
if err != nil {
logrus.Fatal(err)
}
table.Append([]string{service.Name, image, version, digest})
continue
}
image, err := getImagePath(service.Image)
if err != nil {
logrus.Fatal(err)
}
table.Append([]string{service.Name, image, "?", "?"})
} }
table.Render() table.Render()
return nil return nil
}, },
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {

View File

@ -96,7 +96,7 @@ var appVolumeRemoveCommand = &cli.Command{
var appVolumeCommand = &cli.Command{ var appVolumeCommand = &cli.Command{
Name: "volume", Name: "volume",
Aliases: []string{"v"}, Aliases: []string{"vl"},
Usage: "Manage app volumes", Usage: "Manage app volumes",
ArgsUsage: "<command>", ArgsUsage: "<command>",
Subcommands: []*cli.Command{ Subcommands: []*cli.Command{

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path" "path"
"coopcloud.tech/abra/cli/formatter" "coopcloud.tech/abra/cli/formatter"
@ -143,8 +144,26 @@ 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.Infof("generated new recipe catalogue in '%s'", config.APPS_JSON) logrus.Infof("generated new recipe catalogue in '%s'", config.APPS_JSON)

View File

@ -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

View File

@ -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"
) )
@ -50,6 +50,14 @@ var CommitFlag = &cli.BoolFlag{
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",
@ -84,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)
@ -92,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 {
@ -172,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 {
@ -187,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 {
@ -232,20 +259,17 @@ or a rollback of an app.
newTag.Minor = "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 {

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"errors"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -11,20 +12,21 @@ import (
) )
var recipeSyncCommand = &cli.Command{ var recipeSyncCommand = &cli.Command{
Name: "sync", Name: "sync",
Usage: "Ensure recipe version labels are up-to-date", 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 the main recipe service (i.e. the service This command will generate labels for the main recipe service (i.e. by
named "app", by convention) which corresponds to the following format: convention, typically the service named "app") which corresponds to the
following format:
coop-cloud.${STACK_NAME}.version=${RECIPE_TAG} coop-cloud.${STACK_NAME}.version=<version>
The ${RECIPE_TAG} is determined by the recipe maintainer and is retrieved by The <version> is determined by the recipe maintainer and is specified on the
this command by asking for the list of git tags on the local git repository. command-line. The <recipe> configuration will be updated on the local file
The <recipe> configuration will be updated on the local file system. system.
`, `,
ArgsUsage: "<recipe>",
BashComplete: func(c *cli.Context) { BashComplete: func(c *cli.Context) {
catl, err := catalogue.ReadRecipeCatalogue() catl, err := catalogue.ReadRecipeCatalogue()
if err != nil { if err != nil {
@ -38,10 +40,17 @@ The <recipe> configuration will be updated on the local file system.
} }
}, },
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)
mainService := "app" // TODO: validate with tagcmp when new commits come in
// See https://git.coopcloud.tech/coop-cloud/abra/pulls/109
nextTag := c.Args().Get(1)
mainService := "app"
var services []string var services []string
hasAppService := false hasAppService := false
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
@ -67,24 +76,12 @@ The <recipe> configuration will be updated on the local file system.
logrus.Debugf("selecting '%s' as the service to sync version labels", mainService) logrus.Debugf("selecting '%s' as the service to sync version labels", mainService)
tags, err := recipe.Tags() label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
if err != nil {
logrus.Fatal(err)
}
if len(tags) == 0 {
logrus.Fatalf("no tags detected for '%s'", recipe.Name)
}
latestTag := tags[len(tags)-1]
logrus.Infof("choosing '%s' as latest tag for recipe '%s'", latestTag, recipe.Name)
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", latestTag)
if err := recipe.UpdateLabel(mainService, label); err != nil { if err := recipe.UpdateLabel(mainService, label); err != nil {
logrus.Fatal(err) logrus.Fatal(err)
} }
logrus.Infof("added label '%s' to service '%s'", label, mainService) logrus.Infof("synced label '%s' to service '%s'", label, mainService)
return nil return nil
}, },

View File

@ -120,11 +120,11 @@ is up to the end-user to decide.
var upgradeTag string var upgradeTag string
if bumpType != 0 { if bumpType != 0 {
for _, upTag := range compatible { for _, upTag := range compatible {
upElement, err := tag.UpgradeElement(upTag) upElement, err := tag.UpgradeDelta(upTag)
if err != nil { if err != nil {
return err return err
} }
delta := tagcmp.UpgradeType(upElement) delta := upElement.UpgradeType()
if delta <= bumpType { if delta <= bumpType {
upgradeTag = upTag.String() upgradeTag = upTag.String()
break break

63
go.mod
View File

@ -1,9 +1,9 @@
module coopcloud.tech/abra module coopcloud.tech/abra
go 1.17 go 1.16
require ( require (
coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182 coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb
github.com/AlecAivazis/survey/v2 v2.3.1 github.com/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
@ -25,75 +25,16 @@ require (
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.4.17 // indirect
github.com/Microsoft/hcsshim v0.8.21 // indirect github.com/Microsoft/hcsshim v0.8.21 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/containerd/cgroups v1.0.1 // indirect
github.com/containerd/containerd v1.5.5 // indirect github.com/containerd/containerd v1.5.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/fvbommel/sortorder v1.0.2 // indirect github.com/fvbommel/sortorder v1.0.2 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.0 // indirect github.com/gorilla/mux v1.8.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/miekg/pkcs11 v1.0.3 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/moby/sys/mount v0.2.0 // indirect github.com/moby/sys/mount v0.2.0 // indirect
github.com/moby/sys/mountinfo v0.4.1 // indirect
github.com/morikuni/aec v1.0.0 // indirect github.com/morikuni/aec v1.0.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/opencontainers/runc v1.0.2 // indirect github.com/opencontainers/runc v1.0.2 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/spf13/cobra v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/theupdateframework/notary v0.7.0 // indirect github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xanzy/ssh-agent v0.3.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.22.3 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0 // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
golang.org/x/text v0.3.4 // indirect
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect
google.golang.org/grpc v1.33.2 // indirect
google.golang.org/protobuf v1.26.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )

8
go.sum
View File

@ -21,12 +21,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.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-20211011140827-4f27c74467eb h1:Jf+Dnna2kXcNQvcA5JMp6d2Uyvg2pIVJfip9+X5FrH0=
coopcloud.tech/tagcmp v0.0.0-20210906102006-2a8edd82d75d/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ= coopcloud.tech/tagcmp v0.0.0-20211011140827-4f27c74467eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
coopcloud.tech/tagcmp v0.0.0-20211003074705-03d2daced95c h1:7kCjnhjrOevcJeA/koCyyv20E6AglvqC7biGbLzyrbU=
coopcloud.tech/tagcmp v0.0.0-20211003074705-03d2daced95c/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182 h1:VGFzudsoGXGRaw5eJE3rErHHUDsmuIJpQkdfKJgrNs4=
coopcloud.tech/tagcmp v0.0.0-20211003080922-7b06d1c16182/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
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=

View File

@ -54,15 +54,15 @@ type tag = string
// service represents a service within a recipe. // service represents a service within a recipe.
type service = string type service = string
// serviceMeta represents meta info associated with a service. // ServiceMeta represents meta info associated with a service.
type serviceMeta struct { type ServiceMeta struct {
Digest string `json:"digest"` Digest string `json:"digest"`
Image string `json:"image"` Image string `json:"image"`
Tag string `json:"tag"` Tag string `json:"tag"`
} }
// RecipeVersions are the versions associated with a recipe. // RecipeVersions are the versions associated with a recipe.
type RecipeVersions []map[tag]map[service]serviceMeta type RecipeVersions []map[tag]map[service]ServiceMeta
// RecipeMeta represents metadata for a recipe in the abra catalogue. // RecipeMeta represents metadata for a recipe in the abra catalogue.
type RecipeMeta struct { type RecipeMeta struct {
@ -405,7 +405,6 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
checkOutOpts := &git.CheckoutOptions{ checkOutOpts := &git.CheckoutOptions{
Create: false, Create: false,
Force: true, Force: true,
Keep: false,
Branch: plumbing.ReferenceName(ref.Name()), Branch: plumbing.ReferenceName(ref.Name()),
} }
if err := worktree.Checkout(checkOutOpts); err != nil { if err := worktree.Checkout(checkOutOpts); err != nil {
@ -420,7 +419,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
return err return err
} }
versionMeta := make(map[string]serviceMeta) versionMeta := make(map[string]ServiceMeta)
for _, service := range recipe.Config.Services { for _, service := range recipe.Config.Services {
img, err := reference.ParseNormalizedNamed(service.Image) img, err := reference.ParseNormalizedNamed(service.Image)
@ -438,7 +437,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
return err return err
} }
versionMeta[service.Name] = serviceMeta{ versionMeta[service.Name] = ServiceMeta{
Digest: digest, Digest: digest,
Image: path, Image: path,
Tag: img.(reference.NamedTagged).Tag(), Tag: img.(reference.NamedTagged).Tag(),
@ -447,7 +446,7 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
logrus.Debugf("collecting digest: '%s', image: '%s', tag: '%s'", digest, path, tag) logrus.Debugf("collecting digest: '%s', image: '%s', tag: '%s'", digest, path, tag)
} }
versions = append(versions, map[string]map[string]serviceMeta{tag: versionMeta}) versions = append(versions, map[string]map[string]ServiceMeta{tag: versionMeta})
return nil return nil
}); err != nil { }); err != nil {
@ -467,7 +466,6 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
checkOutOpts := &git.CheckoutOptions{ checkOutOpts := &git.CheckoutOptions{
Create: false, Create: false,
Force: true, Force: true,
Keep: false,
Branch: plumbing.ReferenceName(refName), Branch: plumbing.ReferenceName(refName),
} }
if err := worktree.Checkout(checkOutOpts); err != nil { if err := worktree.Checkout(checkOutOpts); err != nil {
@ -480,3 +478,23 @@ func GetRecipeVersions(recipeName string) (RecipeVersions, error) {
return versions, nil 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
}

View File

@ -2,6 +2,7 @@ package stack
import ( import (
"context" "context"
"fmt"
"strings" "strings"
abraClient "coopcloud.tech/abra/pkg/client" abraClient "coopcloud.tech/abra/pkg/client"
@ -92,6 +93,50 @@ func GetAllDeployedServices(contextName string) StackStatus {
return StackStatus{services, nil} return StackStatus{services, nil}
} }
// GetDeployedServicesByName filters services by name
func GetDeployedServicesByName(ctx context.Context, cl *dockerclient.Client, stackName, serviceName string) StackStatus {
filters := filters.NewArgs()
filters.Add("name", fmt.Sprintf("%s_%s", stackName, serviceName))
services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filters})
if err != nil {
return StackStatus{[]swarm.Service{}, err}
}
return StackStatus{services, nil}
}
// IsDeployed chekcks whether an appp is deployed or not.
func IsDeployed(ctx context.Context, cl *dockerclient.Client, stackName string) (bool, string, error) {
version := ""
isDeployed := false
filter := filters.NewArgs()
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
if err != nil {
return false, version, err
}
if len(services) > 0 {
for _, service := range services {
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
if deployedVersion, ok := service.Spec.Labels[labelKey]; ok {
version = deployedVersion
break
}
}
logrus.Debugf("'%s' has been detected as deployed with version '%s'", stackName, version)
return true, version, nil
}
logrus.Debugf("'%s' has been detected as not deployed", stackName)
return isDeployed, version, nil
}
// pruneServices removes services that are no longer referenced in the source // pruneServices removes services that are no longer referenced in the source
func pruneServices(ctx context.Context, cl *dockerclient.Client, namespace convert.Namespace, services map[string]struct{}) { func pruneServices(ctx context.Context, cl *dockerclient.Client, namespace convert.Namespace, services map[string]struct{}) {
oldServices, err := GetStackServices(ctx, cl, namespace.Name()) oldServices, err := GetStackServices(ctx, cl, namespace.Name())

View File

@ -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)

View File

@ -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
@ -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)

View File

@ -62,7 +62,6 @@ func EnsureUpToDate(dir string) error {
checkOutOpts := &git.CheckoutOptions{ checkOutOpts := &git.CheckoutOptions{
Create: false, Create: false,
Force: true, Force: true,
Keep: false,
Branch: plumbing.ReferenceName(refName), Branch: plumbing.ReferenceName(refName),
} }
if err := worktree.Checkout(checkOutOpts); err != nil { if err := worktree.Checkout(checkOutOpts); err != nil {

View File

@ -119,10 +119,10 @@ func EnsureVersion(recipeName, version string) error {
return nil return nil
} }
logrus.Debugf("read '%s' as tags for recipe '%s'", tags, recipeName) var parsedTags []string
var tagRef plumbing.ReferenceName var tagRef plumbing.ReferenceName
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) { if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
parsedTags = append(parsedTags, ref.Name().Short())
if ref.Name().Short() == version { if ref.Name().Short() == version {
tagRef = ref.Name() tagRef = ref.Name()
} }
@ -131,6 +131,8 @@ func EnsureVersion(recipeName, version string) error {
return err return err
} }
logrus.Debugf("read '%s' as tags for recipe '%s'", strings.Join(parsedTags, ", "), recipeName)
if tagRef.String() == "" { if tagRef.String() == "" {
return fmt.Errorf("%s is not available?", version) return fmt.Errorf("%s is not available?", version)
} }
@ -140,12 +142,56 @@ func EnsureVersion(recipeName, version string) error {
return err return err
} }
opts := &git.CheckoutOptions{Branch: tagRef, Keep: true} opts := &git.CheckoutOptions{
Branch: tagRef,
Create: false,
Force: true,
}
if err := worktree.Checkout(opts); err != nil { if err := worktree.Checkout(opts); err != nil {
return err return err
} }
logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef, recipeDir) logrus.Debugf("successfully checked '%s' out to '%s' in '%s'", recipeName, tagRef.Short(), recipeDir)
return nil
}
// EnsureLatest makes sure the latest commit is checkout on for a local recipe repository.
func EnsureLatest(recipeName string) error {
recipeDir := path.Join(config.ABRA_DIR, "apps", recipeName)
logrus.Debugf("attempting to open git repository in '%s'", recipeDir)
repo, err := git.PlainOpen(recipeDir)
if err != nil {
return err
}
worktree, err := repo.Worktree()
if err != nil {
return err
}
branch := "master"
if _, err := repo.Branch("master"); err != nil {
if _, err := repo.Branch("main"); err != nil {
logrus.Debugf("failed to select branch in '%s'", path.Join(config.APPS_DIR, recipeName))
return err
}
branch = "main"
}
refName := fmt.Sprintf("refs/heads/%s", branch)
checkOutOpts := &git.CheckoutOptions{
Create: false,
Force: true,
Branch: plumbing.ReferenceName(refName),
}
if err := worktree.Checkout(checkOutOpts); err != nil {
logrus.Debugf("failed to check out '%s' in '%s'", branch, recipeDir)
return err
}
return nil return nil
} }

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
ABRA_VERSION="0.1.7-alpha" ABRA_VERSION="0.2.0-alpha"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
function show_banner { function show_banner {
@ -35,13 +35,11 @@ function install_abra_release {
fi fi
# FIXME: support different architectures # FIXME: support different architectures
release_url=$(curl -s "$ABRA_RELEASE_URL" | PLATFORM=$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)
python3 -c "import sys, json; \ sed_command='s/.*"assets":\[\{[^}]*"name":"abra.*_'"$PLATFORM"'"[^}]*"browser_download_url":"([^"]*)".*\].*/\1/p'
payload = json.load(sys.stdin); \ release_url=$(curl -s $ABRA_RELEASE_URL | sed -En $sed_command)
url = [a['browser_download_url'] for a in payload['assets'] if 'x86_64' in a['name']][0]; \
print(url)")
echo "downloading $ABRA_VERSION x86_64 binary release for abra..." echo "downloading $ABRA_VERSION $PLATFORM binary release for abra..."
curl --progress-bar "$release_url" --output "$HOME/.local/bin/abra" curl --progress-bar "$release_url" --output "$HOME/.local/bin/abra"
chmod +x "$HOME/.local/bin/abra" chmod +x "$HOME/.local/bin/abra"

View File

@ -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)