From fda55fe928e8d2c7032a329f424c9774ad0693d4 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 6 May 2020 14:49:36 -0700 Subject: [PATCH] deduplicate excess code --- builder/googlecompute/step_start_tunnel.go | 64 ++++++++++++++++++ .../googlecompute/step_start_tunnel_test.go | 5 ++ builder/googlecompute/tunnel_driver.go | 63 +---------------- .../googlecompute/tunnel_driver_windows.go | 67 +------------------ 4 files changed, 71 insertions(+), 128 deletions(-) diff --git a/builder/googlecompute/step_start_tunnel.go b/builder/googlecompute/step_start_tunnel.go index 83447a6c9..6b774a57e 100644 --- a/builder/googlecompute/step_start_tunnel.go +++ b/builder/googlecompute/step_start_tunnel.go @@ -8,8 +8,10 @@ import ( "bytes" "context" "fmt" + "io" "log" "os" + "os/exec" "runtime" "strconv" "strings" @@ -56,6 +58,68 @@ type TunnelDriver interface { StopTunnel() } +func RunTunnelCommand(cmd *exec.Cmd) error { + // set stdout and stderr so we can read what's going on. + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + err := cmd.Start() + if err != nil { + err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s", + err) + return err + } + + // Give tunnel 30 seconds to either launch, or return an error. + // Unfortunately, the SDK doesn't provide any official acknowledgment that + // the tunnel is launched when it's not being run through a TTY so we + // are just trusting here that 30s is enough to know whether the tunnel + // launch was going to fail. Yep, feels icky to me too. But I spent an + // afternoon trying to figure out how to get the SDK to actually send + // the "Listening on port [n]" line I see when I run it manually, and I + // can't justify spending more time than that on aesthetics. + for i := 0; i < 30; i++ { + time.Sleep(1 * time.Second) + + lineStderr, err := stderr.ReadString('\n') + if err != nil && err != io.EOF { + log.Printf("Err from scanning stderr is %s", err) + return fmt.Errorf("Error reading stderr from tunnel launch: %s", err) + } + if lineStderr != "" { + log.Printf("stderr: %s", lineStderr) + } + + lineStdout, err := stdout.ReadString('\n') + if err != nil && err != io.EOF { + log.Printf("Err from scanning stdout is %s", err) + return fmt.Errorf("Error reading stdout from tunnel launch: %s", err) + } + if lineStdout != "" { + log.Printf("stdout: %s", lineStdout) + } + + if strings.Contains(lineStderr, "ERROR") { + // 4033: Either you don't have permission to access the instance, + // the instance doesn't exist, or the instance is stopped. + // The two sub-errors we may see while the permissions settle are + // "not authorized" and "failed to connect to backend," but after + // about a minute of retries this goes away and we're able to + // connect. + // 4003: "failed to connect to backend". Network blip. + if strings.Contains(lineStderr, "4033") || strings.Contains(lineStderr, "4003") { + return RetryableTunnelError{lineStderr} + } else { + log.Printf("NOT RETRYABLE: %s", lineStderr) + return fmt.Errorf("Non-retryable tunnel error: %s", lineStderr) + } + } + } + + log.Printf("No error detected after tunnel launch; continuing...") + return nil +} + type RetryableTunnelError struct { s string } diff --git a/builder/googlecompute/step_start_tunnel_test.go b/builder/googlecompute/step_start_tunnel_test.go index cf8a8b56f..176b14ac4 100644 --- a/builder/googlecompute/step_start_tunnel_test.go +++ b/builder/googlecompute/step_start_tunnel_test.go @@ -1,12 +1,17 @@ package googlecompute import ( + "bytes" "context" "fmt" + "io" "io/ioutil" "os" + "os/exec" "runtime" + "strings" "testing" + "time" "github.com/hashicorp/packer/helper/communicator" ) diff --git a/builder/googlecompute/tunnel_driver.go b/builder/googlecompute/tunnel_driver.go index cd067b1ea..8bd5e0a56 100644 --- a/builder/googlecompute/tunnel_driver.go +++ b/builder/googlecompute/tunnel_driver.go @@ -3,15 +3,10 @@ package googlecompute import ( - "bytes" "context" - "fmt" - "io" "log" "os/exec" - "strings" "syscall" - "time" ) func NewTunnelDriver() TunnelDriver { @@ -23,70 +18,14 @@ type TunnelDriverLinux struct { } func (t *TunnelDriverLinux) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error { - // set stdout and stderr so we can read what's going on. - var stdout, stderr bytes.Buffer - cmd := exec.CommandContext(cancelCtx, tempScriptFileName) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} - err := cmd.Start() + err := RunTunnelCommand(cmd) if err != nil { - err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s", - err) return err } - // Give tunnel 30 seconds to either launch, or return an error. - // Unfortunately, the SDK doesn't provide any official acknowledgment that - // the tunnel is launched when it's not being run through a TTY so we - // are just trusting here that 30s is enough to know whether the tunnel - // launch was going to fail. Yep, feels icky to me too. But I spent an - // afternoon trying to figure out how to get the SDK to actually send - // the "Listening on port [n]" line I see when I run it manually, and I - // can't justify spending more time than that on aesthetics. - for i := 0; i < 30; i++ { - time.Sleep(1 * time.Second) - - lineStderr, err := stderr.ReadString('\n') - if err != nil && err != io.EOF { - log.Printf("Err from scanning stderr is %s", err) - return fmt.Errorf("Error reading stderr from tunnel launch: %s", err) - } - if lineStderr != "" { - log.Printf("stderr: %s", lineStderr) - } - - lineStdout, err := stdout.ReadString('\n') - if err != nil && err != io.EOF { - log.Printf("Err from scanning stdout is %s", err) - return fmt.Errorf("Error reading stdout from tunnel launch: %s", err) - } - if lineStdout != "" { - log.Printf("stdout: %s", lineStdout) - } - - if strings.Contains(lineStderr, "ERROR") { - // 4033: Either you don't have permission to access the instance, - // the instance doesn't exist, or the instance is stopped. - // The two sub-errors we may see while the permissions settle are - // "not authorized" and "failed to connect to backend," but after - // about a minute of retries this goes away and we're able to - // connect. - // 4003: "failed to connect to backend". Network blip. - if strings.Contains(lineStderr, "4033") || strings.Contains(lineStderr, "4003") { - return RetryableTunnelError{lineStderr} - } else { - log.Printf("NOT RETRYABLE: %s", lineStderr) - return fmt.Errorf("Non-retryable tunnel error: %s", lineStderr) - } - } - } - - log.Printf("No error detected after tunnel launch; continuing...") - // Store successful command on step so we can access it to cancel it // later. t.cmd = cmd diff --git a/builder/googlecompute/tunnel_driver_windows.go b/builder/googlecompute/tunnel_driver_windows.go index 16c10d8c9..110b945a3 100644 --- a/builder/googlecompute/tunnel_driver_windows.go +++ b/builder/googlecompute/tunnel_driver_windows.go @@ -3,18 +3,12 @@ package googlecompute import ( - "bytes" "context" - "fmt" - "io" "log" "os/exec" - "strings" - "time" ) func NewTunnelDriver() TunnelDriver { - log.Printf("Megan created driver for windows") return &TunnelDriverWindows{} } @@ -23,71 +17,12 @@ type TunnelDriverWindows struct { } func (t *TunnelDriverWindows) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error { - log.Printf("Megan inside StartTunnel for windows") - // set stdout and stderr so we can read what's going on. - var stdout, stderr bytes.Buffer - args := []string{"/C", "call", tempScriptFileName} - cmd := exec.CommandContext(cancelCtx, "cmd", args...) - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - err := cmd.Start() + err := RunTunnelCommand(cmd) if err != nil { - log.Printf("Megan: error calling start.") - err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s", - err) return err } - - // Give tunnel 30 seconds to either launch, or return an error. - // Unfortunately, the SDK doesn't provide any official acknowledgment that - // the tunnel is launched when it's not being run through a TTY so we - // are just trusting here that 30s is enough to know whether the tunnel - // launch was going to fail. Yep, feels icky to me too. But I spent an - // afternoon trying to figure out how to get the SDK to actually send - // the "Listening on port [n]" line I see when I run it manually, and I - // can't justify spending more time than that on aesthetics. - for i := 0; i < 30; i++ { - time.Sleep(1 * time.Second) - - lineStderr, err := stderr.ReadString('\n') - if err != nil && err != io.EOF { - log.Printf("Err from scanning stderr is %s", err) - return fmt.Errorf("Error reading stderr from tunnel launch: %s", err) - } - if lineStderr != "" { - log.Printf("stderr: %s", lineStderr) - } - - lineStdout, err := stdout.ReadString('\n') - if err != nil && err != io.EOF { - log.Printf("Err from scanning stdout is %s", err) - return fmt.Errorf("Error reading stdout from tunnel launch: %s", err) - } - if lineStdout != "" { - log.Printf("stdout: %s", lineStdout) - } - - if strings.Contains(lineStderr, "ERROR") { - // 4033: Either you don't have permission to access the instance, - // the instance doesn't exist, or the instance is stopped. - // The two sub-errors we may see while the permissions settle are - // "not authorized" and "failed to connect to backend," but after - // about a minute of retries this goes away and we're able to - // connect. - if strings.Contains(lineStderr, "4033") { - return RetryableTunnelError{lineStderr} - } else { - log.Printf("NOT RETRYABLE: %s", lineStderr) - return fmt.Errorf("Non-retryable tunnel error: %s", lineStderr) - } - } - } - - log.Printf("No error detected after tunnel launch; continuing...") - // Store successful command on step so we can access it to cancel it // later. t.cmd = cmd