feat: show image differences in pre-deploy overview

This commit is contained in:
3wc
2025-09-02 13:48:09 -04:00
parent 8e8f7715a2
commit 14d3f1f669
3 changed files with 137 additions and 30 deletions

View File

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