From ed11634abf681f791d4af8fbf0110de43f91e601 Mon Sep 17 00:00:00 2001 From: decentral1se Date: Wed, 25 Aug 2021 13:06:49 +0200 Subject: [PATCH] WIP abra app version implementation --- cli/app/app.go | 1 + cli/app/version.go | 73 +++++++++++++++++++++++++++++++++++++++++++ client/stack/stack.go | 24 ++++++++++++++ config/app.go | 3 ++ 4 files changed, 101 insertions(+) create mode 100644 cli/app/version.go diff --git a/cli/app/app.go b/cli/app/app.go index db075ab4..b629c7c9 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -30,5 +30,6 @@ scaling apps up and spinning them down. appRollbackCommand, appSecretCommand, appVolumeCommand, + appVersionCommand, }, } diff --git a/cli/app/version.go b/cli/app/version.go new file mode 100644 index 00000000..6af5b204 --- /dev/null +++ b/cli/app/version.go @@ -0,0 +1,73 @@ +package app + +import ( + "errors" + "fmt" + "strings" + + abraFormatter "coopcloud.tech/abra/cli/formatter" + "coopcloud.tech/abra/cli/internal" + "coopcloud.tech/abra/client/stack" + "coopcloud.tech/abra/config" + "github.com/docker/distribution/reference" + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +// 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{ + Name: "version", + Usage: "show version of all services in app", + Action: func(c *cli.Context) error { + appName := c.Args().First() + if appName == "" { + internal.ShowSubcommandHelpAndError(c, errors.New("no app name provided")) + } + appFiles, err := config.LoadAppFiles("") + if err != nil { + logrus.Fatal(err) + } + + appEnv, err := config.GetApp(appFiles, appName) + if err != nil { + logrus.Fatal(err) + } + + app := appFiles[appName] + + compose, err := config.GetAppComposeFiles(appEnv.Type) + if err != nil { + logrus.Fatal(err) + } + + tableCol := []string{"Name", "Image", "Version", "Digest"} + table := abraFormatter.CreateTable(tableCol) + + for _, service := range compose.Services { + label := fmt.Sprintf("coop-cloud.%s.%s.version", appEnv.StackName(), service.Name) + status := stack.GetDeployedServicesByLabel(app.Server, label) + for _, serviceStatus := range status.Services { + version, digest := parseVersionLabel(serviceStatus.Spec.Labels[label]) + img, err := reference.ParseNormalizedNamed(service.Image) + if err != nil { + logrus.Fatal(err) + } + image := reference.Path(img) + if strings.Contains(image, "library") { + image = strings.Split(image, "/")[1] + } + serviceName := fmt.Sprintf("%s_%s", appEnv.StackName(), service.Name) + table.Append([]string{serviceName, image, version, digest}) + } + } + + table.Render() + return nil + }, +} diff --git a/client/stack/stack.go b/client/stack/stack.go index 776ac3e5..c959b40b 100644 --- a/client/stack/stack.go +++ b/client/stack/stack.go @@ -47,6 +47,28 @@ func getStackServices(ctx context.Context, apiclient client.APIClient, namespace return apiclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackServiceFilter(namespace)}) } +// GetDeployedServicesByLabel filters services by label +func GetDeployedServicesByLabel(contextName string, label string) StackStatus { + cl, err := abraClient.NewClientWithContext(contextName) + if err != nil { + if strings.Contains(err.Error(), "does not exist") { + // No local context found, bail out gracefully + return StackStatus{[]swarm.Service{}, nil} + } + return StackStatus{[]swarm.Service{}, err} + } + + ctx := context.Background() + filters := filters.NewArgs() + filters.Add("label", label) + services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: filters}) + if err != nil { + return StackStatus{[]swarm.Service{}, err} + } + + return StackStatus{services, nil} +} + func GetAllDeployedServices(contextName string) StackStatus { cl, err := abraClient.NewClientWithContext(contextName) if err != nil { @@ -56,11 +78,13 @@ func GetAllDeployedServices(contextName string) StackStatus { } return StackStatus{[]swarm.Service{}, err} } + ctx := context.Background() services, err := cl.ServiceList(ctx, types.ServiceListOptions{Filters: getAllStacksFilter()}) if err != nil { return StackStatus{[]swarm.Service{}, err} } + return StackStatus{services, nil} } diff --git a/config/app.go b/config/app.go index 87a75df9..8646e84e 100644 --- a/config/app.go +++ b/config/app.go @@ -228,6 +228,9 @@ func GetAppStatuses(appFiles AppFiles) (map[string]string, error) { return statuses, nil } +// GetAppComposeFiles retrieves a compose specification for a recipe. This +// specification is the result of a merge of all the compose.**.yml files in +// the recipe repository. func GetAppComposeFiles(recipe string) (*composetypes.Config, error) { pattern := fmt.Sprintf("%s/%s/compose**yml", APPS_DIR, recipe) composeFiles, err := filepath.Glob(pattern)