Skip to content

Commit 27f1398

Browse files
authored
Merge pull request #37 from akinoriakatsuka/feature/gopose-status-command
`gopose status` コマンドを実装しました
2 parents 33b06c7 + 09cfcf0 commit 27f1398

File tree

2 files changed

+812
-18
lines changed

2 files changed

+812
-18
lines changed

cmd/status.go

Lines changed: 193 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
package cmd
22

33
import (
4+
"context"
5+
"encoding/json"
46
"fmt"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
"sort"
11+
"text/tabwriter"
512

13+
"github.com/harakeishi/gopose/internal/logger"
14+
"github.com/harakeishi/gopose/internal/parser"
15+
"github.com/harakeishi/gopose/pkg/types"
616
"github.com/spf13/cobra"
717
)
818

919
var (
10-
outputFormat string
11-
detailed bool
20+
statusOutputFormat string
21+
statusFilePath string
1222
)
1323

24+
// PortInfo はポート情報を表します。
25+
type PortInfo struct {
26+
Service string `json:"service"`
27+
HostPort int `json:"host_port"`
28+
ContainerPort int `json:"container_port"`
29+
Protocol string `json:"protocol"`
30+
HostIP string `json:"host_ip,omitempty"`
31+
Overridden bool `json:"overridden"`
32+
OriginalPort int `json:"original_port,omitempty"`
33+
}
34+
1435
// statusCmd はstatusコマンドを表します。
1536
var statusCmd = &cobra.Command{
1637
Use: "status",
@@ -20,32 +41,186 @@ var statusCmd = &cobra.Command{
2041
Example: ` # 基本的な状態確認
2142
gopose status
2243
23-
# 詳細情報を表示
44+
# 詳細情報を表示(未実装)
2445
gopose status --detailed
2546
2647
# JSON形式で出力
2748
gopose status --output json`,
28-
RunE: func(cmd *cobra.Command, args []string) error {
29-
ctx := cmd.Context()
30-
cfg := getConfig()
49+
RunE: runStatus,
50+
}
51+
52+
func init() {
53+
statusCmd.Flags().StringVarP(&statusFilePath, "file", "f", "", "Docker Composeファイルのパス")
54+
statusCmd.Flags().StringVarP(&statusOutputFormat, "output", "o", "table", "出力形式 (table, json)")
55+
}
56+
57+
func runStatus(cmd *cobra.Command, args []string) error {
58+
ctx := cmd.Context()
59+
cfg := getConfig()
60+
61+
logger, err := getLogger(cfg)
62+
if err != nil {
63+
return fmt.Errorf("ロガーの初期化に失敗しました: %w", err)
64+
}
3165

32-
logger, err := getLogger(cfg)
66+
// Docker Composeファイルの自動検出
67+
composeFilePath := statusFilePath
68+
if composeFilePath == "" {
69+
wd, err := os.Getwd()
3370
if err != nil {
34-
return fmt.Errorf("ロガーの初期化に失敗しました: %w", err)
71+
return fmt.Errorf("作業ディレクトリの取得に失敗: %w", err)
3572
}
3673

37-
logger.Info(ctx, "gopose status コマンドを開始しています")
74+
detector := parser.NewComposeFileDetectorImpl(logger)
75+
detectedFile, err := detector.GetDefaultComposeFile(ctx, wd)
76+
if err != nil {
77+
return fmt.Errorf("docker composeファイルの自動検出に失敗: %w", err)
78+
}
79+
composeFilePath = detectedFile
80+
}
3881

39-
// TODO: 実際の実装をここに追加
40-
fmt.Println("現在の状態を確認中...")
41-
fmt.Println("現在は実装中です。")
82+
// Docker Composeファイルの解析
83+
yamlParser := parser.NewYamlComposeParser(logger)
84+
config, err := yamlParser.ParseComposeFile(ctx, composeFilePath)
85+
if err != nil {
86+
return fmt.Errorf("docker composeファイルの解析に失敗: %w", err)
87+
}
4288

43-
return nil
44-
},
89+
// override.ymlの読み込み(存在する場合)
90+
overrideConfig, err := loadOverrideConfig(ctx, composeFilePath, logger)
91+
if err != nil {
92+
logger.Debug(ctx, "override.ymlの読み込みをスキップ", types.Field{Key: "reason", Value: err.Error()})
93+
}
94+
95+
// ポート情報の収集
96+
portInfos := collectPortInfos(config, overrideConfig)
97+
98+
// 出力
99+
switch statusOutputFormat {
100+
case "json":
101+
return outputJSON(os.Stdout, portInfos)
102+
default:
103+
return outputTable(os.Stdout, portInfos)
104+
}
45105
}
46106

47-
func init() {
48-
// statusコマンド固有のフラグを定義
49-
statusCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "出力形式 (text, json, yaml)")
50-
statusCmd.Flags().BoolVar(&detailed, "detailed", false, "詳細情報を表示")
107+
// loadOverrideConfig はoverride.ymlを読み込みます。
108+
// Docker Composeのoverride.ymlはComposeConfigと同じ形式なので、
109+
// YamlComposeParserを使用して解析し、OverrideConfigに変換します。
110+
func loadOverrideConfig(ctx context.Context, composeFilePath string, logger logger.Logger) (*types.OverrideConfig, error) {
111+
dir := filepath.Dir(composeFilePath)
112+
113+
// 複数のoverride.ymlファイル名をチェック
114+
overrideFiles := []string{
115+
"compose.override.yml",
116+
"compose.override.yaml",
117+
"docker-compose.override.yml",
118+
"docker-compose.override.yaml",
119+
}
120+
121+
for _, overrideFile := range overrideFiles {
122+
overridePath := filepath.Join(dir, overrideFile)
123+
if _, err := os.Stat(overridePath); err == nil {
124+
// YamlComposeParserを使用して解析
125+
yamlParser := parser.NewYamlComposeParser(logger)
126+
composeConfig, err := yamlParser.ParseComposeFile(ctx, overridePath)
127+
if err != nil {
128+
return nil, fmt.Errorf("override.ymlの解析に失敗: %w", err)
129+
}
130+
131+
// ComposeConfigをOverrideConfigに変換
132+
override := &types.OverrideConfig{
133+
Services: make(map[string]types.ServiceOverride),
134+
}
135+
136+
for serviceName, service := range composeConfig.Services {
137+
override.Services[serviceName] = types.ServiceOverride{
138+
Ports: service.Ports,
139+
}
140+
}
141+
142+
return override, nil
143+
}
144+
}
145+
146+
return nil, fmt.Errorf("override.ymlが見つかりません")
147+
}
148+
149+
// collectPortInfos はすべてのサービスのポート情報を収集します。
150+
func collectPortInfos(config *types.ComposeConfig, override *types.OverrideConfig) []PortInfo {
151+
var portInfos []PortInfo
152+
153+
// サービス名でソート
154+
serviceNames := make([]string, 0, len(config.Services))
155+
for name := range config.Services {
156+
serviceNames = append(serviceNames, name)
157+
}
158+
sort.Strings(serviceNames)
159+
160+
for _, serviceName := range serviceNames {
161+
service := config.Services[serviceName]
162+
163+
for _, port := range service.Ports {
164+
info := PortInfo{
165+
Service: serviceName,
166+
HostPort: port.Host,
167+
ContainerPort: port.Container,
168+
Protocol: port.Protocol,
169+
HostIP: port.HostIP,
170+
Overridden: false,
171+
}
172+
173+
// オーバーライドをチェック
174+
if override != nil {
175+
if serviceOverride, exists := override.Services[serviceName]; exists {
176+
for _, overridePort := range serviceOverride.Ports {
177+
if overridePort.Container == port.Container {
178+
if overridePort.Host != port.Host {
179+
info.OriginalPort = port.Host
180+
info.HostPort = overridePort.Host
181+
info.Overridden = true
182+
}
183+
break
184+
}
185+
}
186+
}
187+
}
188+
189+
portInfos = append(portInfos, info)
190+
}
191+
}
192+
193+
return portInfos
194+
}
195+
196+
// outputTable はテーブル形式で出力します。
197+
func outputTable(out io.Writer, portInfos []PortInfo) error {
198+
w := tabwriter.NewWriter(out, 0, 0, 4, ' ', 0)
199+
defer w.Flush()
200+
201+
// ヘッダー
202+
fmt.Fprintln(w, "SERVICE\tHOST PORT\tCONTAINER PORT\tSTATUS")
203+
204+
for _, info := range portInfos {
205+
status := "-"
206+
if info.Overridden {
207+
status = fmt.Sprintf("overridden (%d)", info.OriginalPort)
208+
}
209+
210+
fmt.Fprintf(w, "%s\t%d\t%d\t%s\n",
211+
info.Service,
212+
info.HostPort,
213+
info.ContainerPort,
214+
status,
215+
)
216+
}
217+
218+
return nil
219+
}
220+
221+
// outputJSON はJSON形式で出力します。
222+
func outputJSON(out io.Writer, portInfos []PortInfo) error {
223+
encoder := json.NewEncoder(out)
224+
encoder.SetIndent("", " ")
225+
return encoder.Encode(portInfos)
51226
}

0 commit comments

Comments
 (0)