@@ -3,9 +3,13 @@ package docker
33import (
44 "encoding/json"
55 "fmt"
6+ "os"
67 "os/exec"
8+ "path/filepath"
79 "strings"
810
11+ "regexp"
12+
913 "github.com/cm-mayfly/cm-mayfly/common"
1014 "github.com/spf13/cobra"
1115)
@@ -143,7 +147,8 @@ func showHumanReadableInfo() {
143147 info .Healthy = "-"
144148 info .InternalPort = "-"
145149 info .ExternalPort = "-"
146- info .Version = "-"
150+ // For non-running services, get version from docker-compose.yaml
151+ info .Version = getVersionFromComposeFile (service )
147152 }
148153
149154 // Get image size - prioritize running container's image
@@ -177,6 +182,7 @@ type ContainerInfo struct {
177182 InternalPort string
178183 ExternalPort string
179184 Version string
185+ Image string
180186}
181187
182188// ImageInfo represents image information
@@ -242,6 +248,7 @@ func getContainerInfo(showAll bool) map[string]ContainerInfo {
242248 Status string `json:"Status"`
243249 Health string `json:"Health"`
244250 Ports string `json:"Ports"`
251+ Image string `json:"Image"`
245252 }
246253
247254 if err := json .Unmarshal ([]byte (line ), & container ); err != nil {
@@ -271,8 +278,11 @@ func getContainerInfo(showAll bool) map[string]ContainerInfo {
271278 }
272279 }
273280
274- // Extract version from image name if possible
275- version := getVersionFromService (container .Service )
281+ // Extract version from image tag if available, otherwise use fallback
282+ version := extractVersionFromImage (container .Image )
283+ if version == "" {
284+ version = getVersionFromService (container .Service )
285+ }
276286
277287 // Normalize status display
278288 status := container .State
@@ -286,12 +296,28 @@ func getContainerInfo(showAll bool) map[string]ContainerInfo {
286296 InternalPort : internalPort ,
287297 ExternalPort : externalPort ,
288298 Version : version ,
299+ Image : container .Image ,
289300 }
290301 }
291302
292303 return containers
293304}
294305
306+ // extractVersionFromImage extracts version/tag from image name
307+ func extractVersionFromImage (imageName string ) string {
308+ if imageName == "" {
309+ return ""
310+ }
311+
312+ // Split by colon to get tag part
313+ parts := strings .Split (imageName , ":" )
314+ if len (parts ) > 1 {
315+ return parts [len (parts )- 1 ]
316+ }
317+
318+ return ""
319+ }
320+
295321// getImageInfo gets image information using docker images
296322func getImageInfo () map [string ]ImageInfo {
297323 images := make (map [string ]ImageInfo )
@@ -382,6 +408,114 @@ func getServiceImageMapping() map[string]string {
382408 }
383409}
384410
411+ // getVersionFromComposeFile reads docker-compose.yaml and returns version for non-running services
412+ func getVersionFromComposeFile (serviceName string ) string {
413+ // Read docker-compose.yaml file
414+ composeFile := fmt .Sprintf ("%s/docker-compose.yaml" , filepath .Dir (DockerFilePath ))
415+ content , err := os .ReadFile (composeFile )
416+ if err != nil {
417+ return "-"
418+ }
419+
420+ // Use regex to find the service and its image
421+ // Pattern: serviceName: at the beginning of a line (with minimal indentation)
422+ servicePattern := fmt .Sprintf (`^\s*%s:\s*$` , serviceName )
423+ imagePattern := `^\s*image:\s*(.+)$`
424+
425+ serviceRegex , err := regexp .Compile (servicePattern )
426+ if err != nil {
427+ return "-"
428+ }
429+
430+ imageRegex , err := regexp .Compile (imagePattern )
431+ if err != nil {
432+ return "-"
433+ }
434+
435+ lines := strings .Split (string (content ), "\n " )
436+ inService := false
437+ indentLevel := 0
438+
439+ for i , line := range lines {
440+ // Check if we're entering the service section
441+ if serviceRegex .MatchString (line ) {
442+ // Check if this is a top-level service (not inside depends_on or other sections)
443+ // by checking if the line starts with minimal indentation and is not inside depends_on
444+ trimmedLine := strings .TrimLeft (line , " \t " )
445+ if strings .HasPrefix (trimmedLine , serviceName + ":" ) {
446+ // Check if we're not inside depends_on section by looking at previous lines
447+ isInDependsOn := false
448+ for j := i - 1 ; j >= 0 && j >= i - 10 ; j -- {
449+ prevLine := strings .TrimSpace (lines [j ])
450+ if prevLine == "depends_on:" {
451+ isInDependsOn = true
452+ break
453+ }
454+ if prevLine != "" && ! strings .HasPrefix (lines [j ], " " ) && ! strings .HasPrefix (lines [j ], "\t " ) {
455+ break
456+ }
457+ }
458+
459+ if ! isInDependsOn {
460+ inService = true
461+ // Calculate the indentation level of the service
462+ indentLevel = len (line ) - len (strings .TrimLeft (line , " \t " ))
463+ continue
464+ }
465+ }
466+ }
467+
468+ // If we're in the service section, look for image field
469+ if inService {
470+ currentIndent := len (line ) - len (strings .TrimLeft (line , " \t " ))
471+
472+ // Check if we've moved to another service or section (same or less indentation)
473+ if strings .TrimSpace (line ) != "" && currentIndent <= indentLevel && ! strings .HasPrefix (line , " " ) && ! strings .HasPrefix (line , "\t " ) {
474+ // We've moved to another service or section
475+ break
476+ }
477+
478+ // Look for image field
479+ if matches := imageRegex .FindStringSubmatch (line ); matches != nil {
480+ image := strings .TrimSpace (matches [1 ])
481+
482+ // Extract version from image
483+ version := extractVersionFromImage (image )
484+ if version == "" {
485+ return "-"
486+ }
487+
488+ // Check if image exists locally
489+ if ! checkImageExists (image ) {
490+ return version + " (Not Downloaded)"
491+ }
492+
493+ return version
494+ }
495+ }
496+ }
497+
498+ return "-"
499+ }
500+
501+ // checkImageExists checks if the specified image exists locally
502+ func checkImageExists (imageName string ) bool {
503+ cmd := exec .Command ("docker" , "images" , "--format" , "{{.Repository}}:{{.Tag}}" , imageName )
504+ output , err := cmd .Output ()
505+ if err != nil {
506+ return false
507+ }
508+
509+ // Check if the image exists in the output
510+ lines := strings .Split (strings .TrimSpace (string (output )), "\n " )
511+ for _ , line := range lines {
512+ if strings .TrimSpace (line ) == imageName {
513+ return true
514+ }
515+ }
516+ return false
517+ }
518+
385519// getVersionFromService gets version information from docker-compose.yaml for a specific service
386520func getVersionFromService (serviceName string ) string {
387521 // Service to version mapping based on docker-compose.yaml
@@ -397,10 +531,10 @@ func getVersionFromService(serviceName string) string {
397531 "cm-butterfly-db" : "14-alpine" ,
398532 "cm-honeybee" : "0.3.6" ,
399533 "cm-damselfly" : "0.3.6" ,
400- "cm-cicada" : "0.3.5" ,
534+ "cm-cicada" : "0.3.6" , // Updated to match actual running version
401535 "airflow-redis" : "7.2-alpine" ,
402536 "airflow-mysql" : "8.0-debian" ,
403- "airflow-server" : "0.3.5" ,
537+ "airflow-server" : "0.3.6" , // Updated to match actual running version
404538 "cm-grasshopper" : "0.3.5" ,
405539 "cm-ant" : "0.4.0" ,
406540 "ant-postgres" : "latest-pg16" ,
0 commit comments