package app import ( "errors" "fmt" "sort" "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" ) // getImagePath returns the image name func getImagePath(image string) (string, error) { img, err := reference.ParseNormalizedNamed(image) if err != nil { return "", err } path := reference.Path(img) if strings.Contains(path, "library") { path = strings.Split(path, "/")[1] } 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{ 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] composeFiles, err := config.GetAppComposeFiles(appEnv.Type, appEnv.Env) if err != nil { logrus.Fatal(err) } opts := stack.Deploy{Composefiles: composeFiles} compose, err := config.GetAppComposeConfig(appEnv.Type, opts, appEnv.Env) if err != nil { logrus.Fatal(err) } ch := make(chan stack.StackStatus, len(compose.Services)) for _, service := range compose.Services { label := fmt.Sprintf("coop-cloud.%s.%s.version", appEnv.StackName(), service.Name) go func(s string, l string) { ch <- stack.GetDeployedServicesByLabel(s, l) }(app.Server, label) } tableCol := []string{"Name", "Image", "Version", "Digest"} table := abraFormatter.CreateTable(tableCol) statuses := make(map[string]stack.StackStatus) for range compose.Services { status := <-ch if len(status.Services) > 0 { serviceName := 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", appEnv.StackName(), service.Name) version, digest := 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() return nil }, }