forked from coop-cloud/abra
WIP: add first run at app rollback command
See coop-cloud/organising#146.
This commit is contained in:
parent
855e9ea26d
commit
3c3d8dc0e7
|
@ -92,25 +92,6 @@ var appNewCommand = &cli.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRecipeMeta retrieves the recipe metadata from the recipe catalogue.
|
|
||||||
func getRecipeMeta(recipeName string) (catalogue.RecipeMeta, error) {
|
|
||||||
catl, err := catalogue.ReadRecipeCatalogue()
|
|
||||||
if err != nil {
|
|
||||||
return catalogue.RecipeMeta{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeMeta, ok := catl[recipeName]
|
|
||||||
if !ok {
|
|
||||||
err := fmt.Errorf("recipe '%s' does not exist?", recipeName)
|
|
||||||
return catalogue.RecipeMeta{}, err
|
|
||||||
}
|
|
||||||
if err := recipePkg.EnsureExists(recipeName); err != nil {
|
|
||||||
return catalogue.RecipeMeta{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipeMeta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
// ensureDomainFlag checks if the domain flag was used. if not, asks the user for it/
|
||||||
func ensureDomainFlag() error {
|
func ensureDomainFlag() error {
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
|
@ -190,7 +171,7 @@ func action(c *cli.Context) error {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeMeta, err := getRecipeMeta(recipe.Name)
|
recipeMeta, err := catalogue.GetRecipeMeta(recipe.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,27 @@ package app
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
|
"coopcloud.tech/abra/pkg/catalogue"
|
||||||
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
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{"b"},
|
Aliases: []string{"r"},
|
||||||
ArgsUsage: "[<version>]",
|
ArgsUsage: "[<version>]",
|
||||||
BashComplete: func(c *cli.Context) {
|
BashComplete: func(c *cli.Context) {
|
||||||
appNames, err := config.GetAppNames()
|
appNames, err := config.GetAppNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
logrus.Warn(err)
|
||||||
}
|
}
|
||||||
if c.NArg() > 0 {
|
if c.NArg() > 0 {
|
||||||
return
|
return
|
||||||
|
@ -24,4 +32,49 @@ var appRollbackCommand = &cli.Command{
|
||||||
fmt.Println(a)
|
fmt.Println(a)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
app := internal.ValidateApp(c)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
cl, err := client.New(app.Server)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
recipeMeta, err := catalogue.GetRecipeMeta(app.Type)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(recipeMeta.Versions) == 0 {
|
||||||
|
logrus.Fatalf("no catalogue versions available for '%s'", app.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployedVersions, isDeployed, err := appPkg.DeployedVersions(ctx, cl, app)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
if !isDeployed {
|
||||||
|
logrus.Fatalf("'%s' is not deployed?", app.Name)
|
||||||
|
}
|
||||||
|
if _, exists := deployedVersions["app"]; !exists {
|
||||||
|
logrus.Fatalf("no versioned 'app' service for '%s', cannot determine version", app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
version := c.Args().Get(1)
|
||||||
|
if version == "" {
|
||||||
|
// TODO:
|
||||||
|
// using deployedVersions["app"], get index+1 version from catalogue
|
||||||
|
// otherwise bail out saying there is nothing to rollback to
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
// ensure this version is listed in the catalogue
|
||||||
|
// ensure this version is "older" (lower down in the list)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// display table of existing state and expected state and prompt
|
||||||
|
// run the deployment with this target version!
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
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/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"
|
||||||
|
@ -27,19 +28,6 @@ func getImagePath(image string) (string, error) {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseVersionLabel parses a $STACK_NAME_$SERVICE_NAME service label
|
|
||||||
func parseServiceName(label string) string {
|
|
||||||
idx := strings.LastIndex(label, "_")
|
|
||||||
return label[idx+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseVersionLabel parses a $VERSION-$DIGEST service label
|
|
||||||
func parseVersionLabel(label string) (string, string) {
|
|
||||||
// versions may look like v4.2-abcd or v4.2-alpine-abcd
|
|
||||||
idx := strings.LastIndex(label, "-")
|
|
||||||
return label[:idx], label[idx+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
var appVersionCommand = &cli.Command{
|
var appVersionCommand = &cli.Command{
|
||||||
Name: "version",
|
Name: "version",
|
||||||
Aliases: []string{"v"},
|
Aliases: []string{"v"},
|
||||||
|
@ -72,7 +60,7 @@ var appVersionCommand = &cli.Command{
|
||||||
for range compose.Services {
|
for range compose.Services {
|
||||||
status := <-ch
|
status := <-ch
|
||||||
if len(status.Services) > 0 {
|
if len(status.Services) > 0 {
|
||||||
serviceName := parseServiceName(status.Services[0].Spec.Name)
|
serviceName := appPkg.ParseServiceName(status.Services[0].Spec.Name)
|
||||||
statuses[serviceName] = status
|
statuses[serviceName] = status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +73,7 @@ var appVersionCommand = &cli.Command{
|
||||||
if status, ok := statuses[service.Name]; ok {
|
if status, ok := statuses[service.Name]; ok {
|
||||||
statusService := status.Services[0]
|
statusService := status.Services[0]
|
||||||
label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name)
|
label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), service.Name)
|
||||||
version, digest := parseVersionLabel(statusService.Spec.Labels[label])
|
version, digest := appPkg.ParseVersionLabel(statusService.Spec.Labels[label])
|
||||||
image, err := getImagePath(statusService.Spec.Labels["com.docker.stack.image"])
|
image, err := getImagePath(statusService.Spec.Labels["com.docker.stack.image"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
|
|
|
@ -2,7 +2,6 @@ package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
@ -22,7 +21,6 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
|
||||||
recipe, err := recipe.Get(recipeName)
|
recipe, err := recipe.Get(recipeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return recipe
|
return recipe
|
||||||
|
@ -39,7 +37,6 @@ func ValidateApp(c *cli.Context) config.App {
|
||||||
app, err := app.Get(appName)
|
app, err := app.Get(appName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/pkg/client/stack"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
apiclient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Get retrieves an app
|
// Get retrieves an app
|
||||||
|
@ -18,3 +24,45 @@ func Get(appName string) (config.App, error) {
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deployedServiceSpec represents a deployed service of an app.
|
||||||
|
type deployedServiceSpec struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionSpec represents a deployed app and associated metadata.
|
||||||
|
type VersionSpec map[string]deployedServiceSpec
|
||||||
|
|
||||||
|
// DeployedVersions lists metadata (e.g. versions) for deployed
|
||||||
|
func DeployedVersions(ctx context.Context, cl *apiclient.Client, app config.App) (VersionSpec, bool, error) {
|
||||||
|
services, err := stack.GetStackServices(ctx, cl, app.StackName())
|
||||||
|
if err != nil {
|
||||||
|
return VersionSpec{}, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appSpec := make(VersionSpec)
|
||||||
|
for _, service := range services {
|
||||||
|
serviceName := ParseServiceName(service.Spec.Name)
|
||||||
|
label := fmt.Sprintf("coop-cloud.%s.%s.version", app.StackName(), serviceName)
|
||||||
|
if deployLabel, ok := service.Spec.Labels[label]; ok {
|
||||||
|
version, _ := ParseVersionLabel(deployLabel)
|
||||||
|
appSpec[serviceName] = deployedServiceSpec{Name: serviceName, Version: version}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return appSpec, len(services) > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVersionLabel parses a $VERSION-$DIGEST app service label.
|
||||||
|
func ParseVersionLabel(label string) (string, string) {
|
||||||
|
// versions may look like v4.2-abcd or v4.2-alpine-abcd
|
||||||
|
idx := strings.LastIndex(label, "-")
|
||||||
|
return label[:idx], label[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseVersionName parses a $STACK_NAME_$SERVICE_NAME service label.
|
||||||
|
func ParseServiceName(label string) string {
|
||||||
|
idx := strings.LastIndex(label, "_")
|
||||||
|
return label[idx+1:]
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/abra/pkg/web"
|
"coopcloud.tech/abra/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -212,3 +213,22 @@ func VersionsOfService(recipe, serviceName string) ([]string, error) {
|
||||||
|
|
||||||
return versions, nil
|
return versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRecipeMeta retrieves the recipe metadata from the recipe catalogue.
|
||||||
|
func GetRecipeMeta(recipeName string) (RecipeMeta, error) {
|
||||||
|
catl, err := ReadRecipeCatalogue()
|
||||||
|
if err != nil {
|
||||||
|
return RecipeMeta{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
recipeMeta, ok := catl[recipeName]
|
||||||
|
if !ok {
|
||||||
|
err := fmt.Errorf("recipe '%s' does not exist?", recipeName)
|
||||||
|
return RecipeMeta{}, err
|
||||||
|
}
|
||||||
|
if err := recipe.EnsureExists(recipeName); err != nil {
|
||||||
|
return RecipeMeta{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipeMeta, nil
|
||||||
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
func RunRemove(ctx context.Context, client *apiclient.Client, opts Remove) error {
|
func RunRemove(ctx context.Context, client *apiclient.Client, opts Remove) error {
|
||||||
var errs []string
|
var errs []string
|
||||||
for _, namespace := range opts.Namespaces {
|
for _, namespace := range opts.Namespaces {
|
||||||
services, err := getStackServices(ctx, client, namespace)
|
services, err := GetStackServices(ctx, client, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func getAllStacksFilter() filters.Args {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStackServices(ctx context.Context, dockerclient client.APIClient, namespace string) ([]swarm.Service, error) {
|
func GetStackServices(ctx context.Context, dockerclient client.APIClient, namespace string) ([]swarm.Service, error) {
|
||||||
return dockerclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackServiceFilter(namespace)})
|
return dockerclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackServiceFilter(namespace)})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ func GetAllDeployedServices(contextName string) StackStatus {
|
||||||
|
|
||||||
// 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())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Infof("Failed to list services: %s\n", err)
|
logrus.Infof("Failed to list services: %s\n", err)
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,7 @@ func deployCompose(ctx context.Context, cl *dockerclient.Client, opts Deploy, co
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return deployServices(ctx, cl, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
return deployServices(ctx, cl, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -290,7 +291,7 @@ func deployServices(
|
||||||
namespace convert.Namespace,
|
namespace convert.Namespace,
|
||||||
sendAuth bool,
|
sendAuth bool,
|
||||||
resolveImage string) error {
|
resolveImage string) error {
|
||||||
existingServices, err := getStackServices(ctx, cl, namespace.Name())
|
existingServices, err := GetStackServices(ctx, cl, namespace.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue