Skip to content

Commit 6b541a9

Browse files
authored
Merge pull request #92 from MZC-CSC/develop
feat: enhance Docker infrastructure commands with improved service management
2 parents b14088d + 0768fed commit 6b541a9

File tree

6 files changed

+184
-15
lines changed

6 files changed

+184
-15
lines changed

cmd/docker/info.go

Lines changed: 139 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ package docker
33
import (
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
296322
func 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
386520
func 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",

cmd/docker/remove.go

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ var removeCmd = &cobra.Command{
3232
removeOptions = ""
3333
}
3434

35-
// 삭제 대상 정보 표시
35+
// Display removal target information
3636
fmt.Println("Removal Target:")
3737

38-
// 서비스 대상 표시
38+
// Display service target
3939
if ServiceName == "" {
4040
fmt.Println(" Services: All services")
4141
} else {
4242
fmt.Printf(" Services: %s\n", ServiceName)
4343
}
4444

45-
// 삭제 범위 표시
45+
// Display removal scope
4646
if allFlag {
4747
fmt.Println(" Scope: Containers + Images + Volumes (all)")
4848
} else if volFlag && imgFlag {
@@ -56,7 +56,7 @@ var removeCmd = &cobra.Command{
5656
}
5757
fmt.Println()
5858

59-
// 추가 옵션 안내
59+
// Display additional options information
6060
if !imgFlag && !volFlag && !allFlag {
6161
fmt.Println("Additional Options:")
6262
fmt.Println(" -i, --images : Also remove images")
@@ -68,7 +68,7 @@ var removeCmd = &cobra.Command{
6868
fmt.Println()
6969
}
7070

71-
// 사용자 확인 요청
71+
// Request user confirmation
7272
fmt.Print("Do you want to proceed with the removal? (y/N): ")
7373
reader := bufio.NewReader(os.Stdin)
7474
response, _ := reader.ReadString('\n')
@@ -80,7 +80,41 @@ var removeCmd = &cobra.Command{
8080
}
8181

8282
convertedServiceName := convertServiceNameForDockerCompose(ServiceName)
83-
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s down %s %s", ProjectName, DockerFilePath, removeOptions, convertedServiceName)
83+
84+
if ServiceName == "" {
85+
// Remove entire system
86+
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s down %s", ProjectName, DockerFilePath, removeOptions)
87+
} else {
88+
// Remove specific service only
89+
// 1. First stop the service
90+
stopCmdStr := fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s stop %s", ProjectName, DockerFilePath, convertedServiceName)
91+
common.SysCall(stopCmdStr)
92+
93+
// 2. Remove service (apply image/volume options)
94+
if volFlag && imgFlag || allFlag {
95+
// Remove volumes and images together
96+
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s rm -f -v %s", ProjectName, DockerFilePath, convertedServiceName)
97+
common.SysCall(cmdStr)
98+
// Image removal (direct removal using docker images command)
99+
// Note: Removing images for specific services is complex,
100+
// so it's safer to guide users to manual removal
101+
fmt.Printf("⚠️ Note: Image removal for specific services is complex.\n")
102+
fmt.Printf(" To remove images manually, use: docker images | grep %s\n", convertedServiceName)
103+
} else if volFlag {
104+
// Remove volumes only
105+
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s rm -f -v %s", ProjectName, DockerFilePath, convertedServiceName)
106+
} else if imgFlag {
107+
// Remove images only
108+
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s rm -f %s", ProjectName, DockerFilePath, convertedServiceName)
109+
common.SysCall(cmdStr)
110+
// Image removal guidance
111+
fmt.Printf("⚠️ Note: Image removal for specific services is complex.\n")
112+
fmt.Printf(" To remove images manually, use: docker images | grep %s\n", convertedServiceName)
113+
} else {
114+
// Remove containers only
115+
cmdStr = fmt.Sprintf("COMPOSE_PROJECT_NAME=%s docker compose -f %s rm -f %s", ProjectName, DockerFilePath, convertedServiceName)
116+
}
117+
}
84118

85119
//fmt.Println(cmdStr)
86120
common.SysCall(cmdStr)

conf/docker/tool/mayfly

13.1 KB
Binary file not shown.

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ module github.com/cm-mayfly/cm-mayfly
33
go 1.19
44

55
require (
6+
github.com/go-sql-driver/mysql v1.8.1
67
github.com/mitchellh/go-homedir v1.1.0
78
github.com/spf13/cobra v1.8.0
89
github.com/spf13/viper v1.10.1
9-
golang.org/x/term v0.13.0
10+
github.com/tidwall/gjson v1.17.1
11+
golang.org/x/term v0.18.0
1012
)
1113

1214
require (
1315
filippo.io/edwards25519 v1.1.0 // indirect
1416
github.com/fsnotify/fsnotify v1.5.1 // indirect
15-
github.com/go-sql-driver/mysql v1.8.1 // indirect
1617
github.com/hashicorp/hcl v1.0.0 // indirect
1718
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1819
github.com/kr/pretty v0.3.1 // indirect
@@ -24,7 +25,6 @@ require (
2425
github.com/spf13/pflag v1.0.5 // indirect
2526
github.com/stretchr/testify v1.9.0 // indirect
2627
github.com/subosito/gotenv v1.2.0 // indirect
27-
github.com/tidwall/gjson v1.17.1 // indirect
2828
github.com/tidwall/match v1.1.1 // indirect
2929
github.com/tidwall/pretty v1.2.1 // indirect
3030
golang.org/x/net v0.23.0 // indirect

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,8 +348,9 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
348348
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
349349
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
350350
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
351-
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
352351
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
352+
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
353+
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
353354
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
354355
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
355356
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

mayfly

6.38 KB
Binary file not shown.

0 commit comments

Comments
 (0)