11package cmd
22
33import (
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
919var (
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コマンドを表します。
1536var 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\t HOST PORT\t CONTAINER PORT\t STATUS" )
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