Show image differences in pre-deploy overview
This commit is contained in:
		@ -3,7 +3,6 @@ package app
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"coopcloud.tech/abra/cli/internal"
 | 
			
		||||
@ -206,10 +205,9 @@ checkout as-is. Recipe commit hashes are also supported as values for
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Gather images
 | 
			
		||||
 | 
			
		||||
		var imageInfo []string
 | 
			
		||||
		for _, service := range compose.Services {
 | 
			
		||||
			imageInfo = append(imageInfo, fmt.Sprintf("%s: %s", service.Name, service.Image))
 | 
			
		||||
		imageInfo, err := deploy.GatherImagesForDeploy(cl, app, compose)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Show deploy overview
 | 
			
		||||
 | 
			
		||||
@ -39,10 +39,11 @@ func RemoveConfigs(cl *client.Client, ctx context.Context, configNames []string,
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetConfigNameAndVersion(fullName string, stackName string) (string, string) {
 | 
			
		||||
func GetConfigNameAndVersion(fullName string, stackName string) (string, string, error) {
 | 
			
		||||
	name := strings.TrimPrefix(fullName, stackName + "_")
 | 
			
		||||
	if lastUnderscore := strings.LastIndex(name, "_"); lastUnderscore != -1 {
 | 
			
		||||
		return name[0:lastUnderscore], name[lastUnderscore+1:]
 | 
			
		||||
		return name[0:lastUnderscore], name[lastUnderscore+1:], nil
 | 
			
		||||
	} else {
 | 
			
		||||
			return "", "", errors.New(i18n.G("can't parse version from config '%s'", fullName))
 | 
			
		||||
	}
 | 
			
		||||
	return name, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,9 @@ package deploy
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@ -16,8 +18,8 @@ import (
 | 
			
		||||
	dockerClient "github.com/docker/docker/client"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetConfigNamesForStack retrieves all Docker configs attached to services in a given stack.
 | 
			
		||||
func GetConfigNamesForStack(cl *dockerClient.Client, app appPkg.App) ([]string, error) {
 | 
			
		||||
// GetConfigsForStack retrieves all Docker configs attached to services in a given stack.
 | 
			
		||||
func GetConfigsForStack(cl *dockerClient.Client, app appPkg.App) (map[string]string, error) {
 | 
			
		||||
	filters, err := app.Filters(false, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@ -31,25 +33,92 @@ func GetConfigNamesForStack(cl *dockerClient.Client, app appPkg.App) ([]string,
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect unique config names from all services
 | 
			
		||||
	configNames := make(map[string]bool)
 | 
			
		||||
	// Collect unique config names with versions
 | 
			
		||||
	configs := make(map[string]string)
 | 
			
		||||
	for _, service := range services {
 | 
			
		||||
		if service.Spec.TaskTemplate.ContainerSpec != nil {
 | 
			
		||||
			for _, configRef := range service.Spec.TaskTemplate.ContainerSpec.Configs {
 | 
			
		||||
				if configRef.ConfigName != "" {
 | 
			
		||||
					configNames[configRef.ConfigName] = true
 | 
			
		||||
				configName := configRef.ConfigName
 | 
			
		||||
				if configName == "" {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				configBaseName, configVersion, err := client.GetConfigNameAndVersion(configName, app.StackName())
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Warn(err)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				existingConfigVersion, ok := configs[configBaseName]
 | 
			
		||||
				if !ok {
 | 
			
		||||
					// First time seeing this, add to map
 | 
			
		||||
					configs[configBaseName] = configVersion
 | 
			
		||||
				} else {
 | 
			
		||||
					// Just make sure the versions are the same..
 | 
			
		||||
					if existingConfigVersion != configVersion {
 | 
			
		||||
						log.Warnf("different versions for config '%s', '%s' and %s'", configBaseName, existingConfigVersion, configVersion)
 | 
			
		||||
					}	
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert map to slice
 | 
			
		||||
	result := make([]string, 0, len(configNames))
 | 
			
		||||
	for name := range configNames {
 | 
			
		||||
		result = append(result, name)
 | 
			
		||||
	return configs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetImageNameAndTag(imageName string) (string, string, error) {
 | 
			
		||||
		imageParts := regexp.MustCompile("^([^:]*):([^@]*)@?").FindSubmatch([]byte(imageName))
 | 
			
		||||
 | 
			
		||||
		if len(imageParts) == 0 {
 | 
			
		||||
			return "", "", errors.New("can't determine image version for image '%s'")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		imageBaseName := string(imageParts[1])
 | 
			
		||||
		imageTag := string(imageParts[2])
 | 
			
		||||
 | 
			
		||||
	return imageBaseName, imageTag, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetImagesForStack retrieves all Docker images for services in a given stack.
 | 
			
		||||
func GetImagesForStack(cl *dockerClient.Client, app appPkg.App) (map[string]string, error) {
 | 
			
		||||
	filters, err := app.Filters(false, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result, nil
 | 
			
		||||
	// List all services in the stack
 | 
			
		||||
	services, err := cl.ServiceList(context.Background(), swarm.ServiceListOptions{
 | 
			
		||||
		Filters: filters,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect unique image names with versions
 | 
			
		||||
	images := make(map[string]string)
 | 
			
		||||
	for _, service := range services {
 | 
			
		||||
		if service.Spec.TaskTemplate.ContainerSpec != nil {
 | 
			
		||||
			imageName := service.Spec.TaskTemplate.ContainerSpec.Image
 | 
			
		||||
 | 
			
		||||
			imageBaseName, imageTag, err := GetImageNameAndTag(imageName)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Warn(err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			existingImageVersion, ok := images[imageBaseName]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				// First time seeing this, add to map
 | 
			
		||||
				images[imageBaseName] = imageTag
 | 
			
		||||
			} else {
 | 
			
		||||
				// Just make sure the versions are the same..
 | 
			
		||||
				if existingImageVersion != imageTag {
 | 
			
		||||
					log.Warnf("different versions for image '%s', '%s' and %s'", imageBaseName, existingImageVersion, imageTag)
 | 
			
		||||
				}	
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return images, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GatherSecretsForDeploy(cl *dockerClient.Client, app appPkg.App) ([]string, error) {
 | 
			
		||||
@ -73,21 +142,12 @@ func GatherSecretsForDeploy(cl *dockerClient.Client, app appPkg.App) ([]string,
 | 
			
		||||
 | 
			
		||||
func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config, abraShEnv map[string]string) ([]string, error) {
 | 
			
		||||
		// Get current configs from existing deployment
 | 
			
		||||
		currentConfigNames, err := GetConfigNamesForStack(cl, app)
 | 
			
		||||
		currentConfigs, err := GetConfigsForStack(cl, app)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Infof("Config names: %v", currentConfigNames)
 | 
			
		||||
 | 
			
		||||
		// Create map of current config base names to versions
 | 
			
		||||
		currentConfigs := make(map[string]string)
 | 
			
		||||
		for _, configName := range currentConfigNames {
 | 
			
		||||
			baseName, version := client.GetConfigNameAndVersion(configName, app.StackName())
 | 
			
		||||
			currentConfigs[baseName] = version
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Infof("Configs: %v", currentConfigs)
 | 
			
		||||
		log.Debugf("Deployed config names: %v", currentConfigs)
 | 
			
		||||
 | 
			
		||||
		// Get new configs from the compose specification
 | 
			
		||||
		newConfigs := compose.Configs
 | 
			
		||||
@ -116,3 +176,51 @@ func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *co
 | 
			
		||||
 | 
			
		||||
	return configInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GatherImagesForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config) ([]string, error){
 | 
			
		||||
 | 
			
		||||
	// Get current images from existing deployment
 | 
			
		||||
	currentImages, err := GetImagesForStack(cl, app)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Infof("Deployed images: %v", currentImages)
 | 
			
		||||
 | 
			
		||||
	// Proposed new images from the compose files
 | 
			
		||||
	newImages := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	for _, service := range compose.Services {
 | 
			
		||||
		imageBaseName, imageTag, err := GetImageNameAndTag(service.Image)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Warn(err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		existingImageVersion, ok := newImages[imageBaseName]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			// First time seeing this, add to map
 | 
			
		||||
			newImages[imageBaseName] = imageTag
 | 
			
		||||
		} else {
 | 
			
		||||
			// Just make sure the versions are the same..
 | 
			
		||||
			if existingImageVersion != imageTag {
 | 
			
		||||
				log.Warnf("different versions for image '%s', '%s' and %s'", imageBaseName, existingImageVersion, imageTag)
 | 
			
		||||
			}	
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	log.Infof("Proposed images: %v", newImages)
 | 
			
		||||
 | 
			
		||||
	var imageInfo []string
 | 
			
		||||
	for newImageName, newImageVersion := range newImages {
 | 
			
		||||
		if currentVersion, exists := currentImages[newImageName]; exists {
 | 
			
		||||
			if currentVersion == newImageVersion {
 | 
			
		||||
				imageInfo = append(imageInfo, fmt.Sprintf("%s: %s (unchanged)", newImageName, newImageVersion))
 | 
			
		||||
			} else {
 | 
			
		||||
				imageInfo = append(imageInfo, fmt.Sprintf("%s: %s → %s", newImageName, currentVersion, newImageVersion))
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			imageInfo = append(imageInfo, fmt.Sprintf("%s: %s (new)", newImageName, newImageVersion))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return imageInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user