1 package helper
2
3 import (
4 "fmt"
5 "os"
6 "regexp"
7 "time"
8
9 "github.com/ActiveState/termtest/expect"
10 "github.com/onsi/gomega"
11 . "github.com/onsi/gomega"
12 "github.com/onsi/gomega/gexec"
13 )
14
15
16
111
112 type DevSession struct {
113 session *gexec.Session
114 stopped bool
115 console *expect.Console
116 address string
117 StdOut string
118 ErrOut string
119 Endpoints map[string]string
120 APIServerEndpoint string
121 }
122
123 type DevSessionOpts struct {
124 EnvVars []string
125 CmdlineArgs []string
126 RunOnPodman bool
127 TimeoutInSeconds int
128 NoRandomPorts bool
129 NoWatch bool
130 NoCommands bool
131 CustomAddress string
132 StartAPIServer bool
133 APIServerPort int
134 SyncGitDir bool
135 ShowLogs bool
136 VerboseLevel string
137 }
138
139
140
141
142
143 func StartDevMode(options DevSessionOpts) (devSession DevSession, err error) {
144 if options.RunOnPodman {
145 options.CmdlineArgs = append(options.CmdlineArgs, "--platform", "podman")
146 }
147 c, err := expect.NewConsole(expect.WithStdout(os.Stdout))
148 if err != nil {
149 return DevSession{}, err
150 }
151
152 env := append([]string{}, options.EnvVars...)
153 args := []string{"dev"}
154 if options.NoCommands {
155 args = append(args, "--no-commands")
156 }
157 if !options.NoRandomPorts {
158 args = append(args, "--random-ports")
159 }
160 if options.NoWatch {
161 args = append(args, "--no-watch")
162 }
163 if options.CustomAddress != "" {
164 args = append(args, "--address", options.CustomAddress)
165 }
166 if !options.StartAPIServer {
167 args = append(args, "--api-server=false")
168 }
169 if options.APIServerPort != 0 {
170 args = append(args, fmt.Sprintf("--api-server-port=%d", options.APIServerPort))
171 }
172 if options.SyncGitDir {
173 args = append(args, "--sync-git-dir")
174 }
175 if options.ShowLogs {
176 args = append(args, "--logs")
177 }
178 if options.VerboseLevel != "" {
179 args = append(args, "-v", options.VerboseLevel)
180 }
181 args = append(args, options.CmdlineArgs...)
182 cmd := Cmd("odo", args...)
183 cmd.Cmd.Stdin = c.Tty()
184 cmd.Cmd.Stdout = c.Tty()
185 cmd.Cmd.Stderr = c.Tty()
186
187 session := cmd.AddEnv(env...).Runner().session
188 timeoutInSeconds := 420
189 if options.TimeoutInSeconds != 0 {
190 timeoutInSeconds = options.TimeoutInSeconds
191 }
192 WaitForOutputToContain("[Ctrl+c] - Exit", timeoutInSeconds, 10, session)
193 result := DevSession{
194 session: session,
195 console: c,
196 address: options.CustomAddress,
197 }
198 outContents := session.Out.Contents()
199 errContents := session.Err.Contents()
200 err = session.Out.Clear()
201 if err != nil {
202 return DevSession{}, err
203 }
204 err = session.Err.Clear()
205 if err != nil {
206 return DevSession{}, err
207 }
208 result.StdOut = string(outContents)
209 result.ErrOut = string(errContents)
210 result.Endpoints = getPorts(string(outContents), options.CustomAddress)
211 if options.StartAPIServer {
212 result.APIServerEndpoint = getAPIServerPort(string(outContents))
213 }
214 return result, nil
215
216 }
217
218
219 func (o DevSession) Kill() {
220 if o.console != nil {
221 err := o.console.Close()
222 gomega.Expect(err).NotTo(gomega.HaveOccurred())
223 }
224 o.session.Kill()
225 }
226
227 func (o DevSession) PID() int {
228 return o.session.Command.Process.Pid
229 }
230
231
232 func (o *DevSession) Stop() {
233 if o.session == nil {
234 return
235 }
236 if o.console != nil {
237 err := o.console.Close()
238 gomega.Expect(err).NotTo(gomega.HaveOccurred())
239 }
240 if o.stopped {
241 return
242 }
243
244 if o.session.ExitCode() == -1 {
245 err := terminateProc(o.session)
246 gomega.Expect(err).NotTo(gomega.HaveOccurred())
247 }
248 o.stopped = true
249 }
250
251 func (o *DevSession) PressKey(p byte) {
252 if o.console == nil || o.session == nil {
253 return
254 }
255 _, err := o.console.Write([]byte{p})
256 Expect(err).ToNot(HaveOccurred())
257 }
258
259 func (o DevSession) WaitEnd() {
260 if o.session == nil {
261 return
262 }
263 o.session.Wait(3 * time.Minute)
264 }
265
266 func (o DevSession) GetExitCode() int {
267 if o.session == nil {
268 return -1
269 }
270 return o.session.ExitCode()
271 }
272
273
274
275
276
277 func (o *DevSession) WaitSync() error {
278 WaitForOutputToContainOne([]string{"Pushing files...", "Updating Component..."}, 180, 10, o.session)
279 WaitForOutputToContain("Dev mode", 240, 10, o.session)
280 return o.UpdateInfo()
281 }
282
283 func (o *DevSession) WaitRestartPortforward() error {
284 WaitForOutputToContain("Forwarding from", 240, 10, o.session)
285 return o.UpdateInfo()
286 }
287
288
289
290
291 func (o *DevSession) UpdateInfo() error {
292 outContents := o.session.Out.Contents()
293 errContents := o.session.Err.Contents()
294 var err error
295 if !o.session.Out.Closed() {
296 err = o.session.Out.Clear()
297 if err != nil {
298 return err
299 }
300 }
301 if !o.session.Err.Closed() {
302 err = o.session.Err.Clear()
303 if err != nil {
304 return err
305 }
306 }
307 o.StdOut = string(outContents)
308 o.ErrOut = string(errContents)
309 endpoints := getPorts(o.StdOut, o.address)
310 if len(endpoints) != 0 {
311
312 o.Endpoints = endpoints
313 }
314 return nil
315 }
316
317 func (o DevSession) CheckNotSynced(timeout time.Duration) {
318 Consistently(func() string {
319 return string(o.session.Out.Contents())
320 }, timeout).ShouldNot(ContainSubstring("Pushing files..."))
321 }
322
323
324
325
326 func RunDevMode(options DevSessionOpts, inside func(session *gexec.Session, outContents string, errContents string, ports map[string]string)) error {
327
328 session, err := StartDevMode(options)
329 if err != nil {
330 return err
331 }
332 defer func() {
333 session.Stop()
334 session.WaitEnd()
335 }()
336 inside(session.session, session.StdOut, session.ErrOut, session.Endpoints)
337 return nil
338 }
339
340
341
342
343
344
345 func WaitForDevModeToContain(options DevSessionOpts, substring string, stopSessionAfter bool, checkErrOut bool) (DevSession, error) {
346 args := []string{"dev", "--random-ports"}
347 args = append(args, options.CmdlineArgs...)
348 if options.RunOnPodman {
349 args = append(args, "--platform", "podman")
350 }
351 if options.CustomAddress != "" {
352 args = append(args, "--address", options.CustomAddress)
353 }
354 session := Cmd("odo", args...).AddEnv(options.EnvVars...).Runner().session
355 if checkErrOut {
356 WaitForErroutToContain(substring, 360, 10, session)
357 } else {
358 WaitForOutputToContain(substring, 360, 10, session)
359 }
360 result := DevSession{
361 session: session,
362 address: options.CustomAddress,
363 }
364 if stopSessionAfter {
365 defer func() {
366 result.Stop()
367 result.WaitEnd()
368 }()
369 }
370
371 outContents := session.Out.Contents()
372 errContents := session.Err.Contents()
373 err := session.Out.Clear()
374 if err != nil {
375 return DevSession{}, err
376 }
377 err = session.Err.Clear()
378 if err != nil {
379 return DevSession{}, err
380 }
381 result.StdOut = string(outContents)
382 result.ErrOut = string(errContents)
383 result.Endpoints = getPorts(result.StdOut, options.CustomAddress)
384 return result, nil
385 }
386
387
388
389
390 func getPorts(s, address string) map[string]string {
391 if address == "" {
392 address = "127.0.0.1"
393 }
394 result := map[string]string{}
395 re := regexp.MustCompile(fmt.Sprintf("(%s:[0-9]+) -> ([0-9]+)", address))
396 matches := re.FindAllStringSubmatch(s, -1)
397 for _, match := range matches {
398 result[match[2]] = match[1]
399 }
400 return result
401 }
402
403
404 func getAPIServerPort(s string) string {
405 re := regexp.MustCompile(`API Server started at http://(localhost:[0-9]+\/api\/v1)`)
406 matches := re.FindStringSubmatch(s)
407 return matches[1]
408 }
409
View as plain text