1
1
package main
2
2
3
3
import (
4
+ "context"
5
+ "errors"
4
6
"fmt"
5
7
"os"
6
8
"path/filepath"
9
+ "time"
7
10
8
11
"github.com/klothoplatform/klotho/pkg/engine/debug"
12
+ "github.com/klothoplatform/klotho/pkg/k2/language_host"
13
+ pb "github.com/klothoplatform/klotho/pkg/k2/language_host/go"
9
14
"github.com/klothoplatform/klotho/pkg/k2/model"
10
15
"github.com/klothoplatform/klotho/pkg/k2/orchestration"
11
16
"github.com/klothoplatform/klotho/pkg/k2/stack"
17
+ "github.com/klothoplatform/klotho/pkg/logging"
12
18
"github.com/spf13/afero"
13
19
"github.com/spf13/cobra"
20
+ "go.uber.org/zap"
21
+ "google.golang.org/grpc"
22
+ "google.golang.org/grpc/credentials/insecure"
14
23
)
15
24
16
25
var downConfig struct {
17
26
outputPath string
27
+ debugMode string
28
+ debugPort int
18
29
}
19
30
20
31
func newDownCmd () * cobra.Command {
@@ -25,10 +36,86 @@ func newDownCmd() *cobra.Command {
25
36
}
26
37
flags := downCommand .Flags ()
27
38
flags .StringVarP (& downConfig .outputPath , "output" , "o" , "" , "Output directory" )
39
+ flags .StringVarP (& upConfig .debugMode , "debug" , "d" , "" , "Debug mode" )
40
+ flags .IntVarP (& upConfig .debugPort , "debug-port" , "p" , 5678 , "Language Host Debug port" )
28
41
return downCommand
29
42
30
43
}
31
44
45
+ func getProjectPath (ctx context.Context , inputPath string ) (string , error ) {
46
+ langHost , addr , err := language_host .StartPythonClient (ctx , language_host.DebugConfig {
47
+ Enabled : upConfig .debugMode != "" ,
48
+ Port : upConfig .debugPort ,
49
+ Mode : upConfig .debugMode ,
50
+ }, filepath .Dir (inputPath ))
51
+ if err != nil {
52
+ return "" , err
53
+ }
54
+
55
+ defer func () {
56
+ if err := langHost .Process .Kill (); err != nil {
57
+ zap .L ().Warn ("failed to kill Python client" , zap .Error (err ))
58
+ }
59
+ }()
60
+
61
+ log := logging .GetLogger (ctx ).Sugar ()
62
+
63
+ log .Debug ("Waiting for Python server to start" )
64
+ if upConfig .debugMode != "" {
65
+ // Don't add a timeout in case there are breakpoints in the language host before an address is printed
66
+ <- addr .HasAddr
67
+ } else {
68
+ select {
69
+ case <- addr .HasAddr :
70
+ case <- time .After (30 * time .Second ):
71
+ return "" , errors .New ("timeout waiting for Python server to start" )
72
+ }
73
+ }
74
+ conn , err := grpc .NewClient (addr .Address , grpc .WithTransportCredentials (insecure .NewCredentials ()))
75
+ if err != nil {
76
+ return "" , fmt .Errorf ("failed to connect to Python server: %w" , err )
77
+ }
78
+
79
+ defer func (conn * grpc.ClientConn ) {
80
+ err = conn .Close ()
81
+ if err != nil {
82
+ zap .L ().Error ("failed to close connection" , zap .Error (err ))
83
+ }
84
+ }(conn )
85
+
86
+ client := pb .NewKlothoServiceClient (conn )
87
+
88
+ // make sure the ctx used later doesn't have the timeout (which is only for the IR request)
89
+ irCtx := ctx
90
+ if upConfig .debugMode == "" {
91
+ var cancel context.CancelFunc
92
+ irCtx , cancel = context .WithTimeout (irCtx , time .Second * 10 )
93
+ defer cancel ()
94
+ }
95
+
96
+ req := & pb.IRRequest {Filename : inputPath }
97
+ res , err := client .SendIR (irCtx , req )
98
+ if err != nil {
99
+ return "" , fmt .Errorf ("error sending IR request: %w" , err )
100
+ }
101
+
102
+ ir , err := model .ParseIRFile ([]byte (res .GetYamlPayload ()))
103
+ if err != nil {
104
+ return "" , fmt .Errorf ("error parsing IR file: %w" , err )
105
+ }
106
+
107
+ appUrn , err := model .ParseURN (ir .AppURN )
108
+ if err != nil {
109
+ return "" , fmt .Errorf ("error parsing app URN: %w" , err )
110
+ }
111
+
112
+ appUrnPath , err := model .UrnPath (* appUrn )
113
+ if err != nil {
114
+ return "" , fmt .Errorf ("error getting URN path: %w" , err )
115
+ }
116
+ return appUrnPath , nil
117
+ }
118
+
32
119
func down (cmd * cobra.Command , args []string ) error {
33
120
filePath := args [0 ]
34
121
if _ , err := os .Stat (filePath ); os .IsNotExist (err ) {
@@ -38,9 +125,24 @@ func down(cmd *cobra.Command, args []string) error {
38
125
if err != nil {
39
126
return err
40
127
}
41
- project := args [1 ]
42
- app := args [2 ]
43
- env := args [3 ]
128
+
129
+ var projectPath string
130
+ switch len (args ) {
131
+ case 1 :
132
+ projectPath , err = getProjectPath (cmd .Context (), absolutePath )
133
+ if err != nil {
134
+ return fmt .Errorf ("error getting project path: %w" , err )
135
+ }
136
+
137
+ case 4 :
138
+ project := args [1 ]
139
+ app := args [2 ]
140
+ env := args [3 ]
141
+ projectPath = filepath .Join (project , app , env )
142
+
143
+ default :
144
+ return fmt .Errorf ("invalid number of arguments (%d) expected 4" , len (args ))
145
+ }
44
146
45
147
if downConfig .outputPath == "" {
46
148
downConfig .outputPath = filepath .Join (filepath .Dir (absolutePath ), ".k2" )
@@ -52,8 +154,7 @@ func down(cmd *cobra.Command, args []string) error {
52
154
cmd .SetContext (debug .WithDebugDir (cmd .Context (), debugDir ))
53
155
}
54
156
55
- projectPath := filepath .Join (downConfig .outputPath , project , app , env )
56
- stateFile := filepath .Join (projectPath , "state.yaml" )
157
+ stateFile := filepath .Join (downConfig .outputPath , projectPath , "state.yaml" )
57
158
sm := model .NewStateManager (afero .NewOsFs (), stateFile )
58
159
59
160
if ! sm .CheckStateFileExists () {
@@ -67,7 +168,7 @@ func down(cmd *cobra.Command, args []string) error {
67
168
68
169
var stackReferences []stack.Reference
69
170
for name , construct := range sm .GetAllConstructs () {
70
- constructPath := filepath .Join (projectPath , name )
171
+ constructPath := filepath .Join (downConfig . outputPath , projectPath , name )
71
172
stackReference := stack.Reference {
72
173
ConstructURN : * construct .URN ,
73
174
Name : name ,
0 commit comments