deduplicate excess code

This commit is contained in:
Megan Marsh 2020-05-06 14:49:36 -07:00
parent 54b33ad8d1
commit fda55fe928
4 changed files with 71 additions and 128 deletions

View File

@ -8,8 +8,10 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"io"
"log" "log"
"os" "os"
"os/exec"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -56,6 +58,68 @@ type TunnelDriver interface {
StopTunnel() 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 { type RetryableTunnelError struct {
s string s string
} }

View File

@ -1,12 +1,17 @@
package googlecompute package googlecompute
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"runtime" "runtime"
"strings"
"testing" "testing"
"time"
"github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/communicator"
) )

View File

@ -3,15 +3,10 @@
package googlecompute package googlecompute
import ( import (
"bytes"
"context" "context"
"fmt"
"io"
"log" "log"
"os/exec" "os/exec"
"strings"
"syscall" "syscall"
"time"
) )
func NewTunnelDriver() TunnelDriver { func NewTunnelDriver() TunnelDriver {
@ -23,70 +18,14 @@ type TunnelDriverLinux struct {
} }
func (t *TunnelDriverLinux) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error { 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 := exec.CommandContext(cancelCtx, tempScriptFileName)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err := cmd.Start() err := RunTunnelCommand(cmd)
if err != nil { if err != nil {
err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s",
err)
return 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 // Store successful command on step so we can access it to cancel it
// later. // later.
t.cmd = cmd t.cmd = cmd

View File

@ -3,18 +3,12 @@
package googlecompute package googlecompute
import ( import (
"bytes"
"context" "context"
"fmt"
"io"
"log" "log"
"os/exec" "os/exec"
"strings"
"time"
) )
func NewTunnelDriver() TunnelDriver { func NewTunnelDriver() TunnelDriver {
log.Printf("Megan created driver for windows")
return &TunnelDriverWindows{} return &TunnelDriverWindows{}
} }
@ -23,71 +17,12 @@ type TunnelDriverWindows struct {
} }
func (t *TunnelDriverWindows) StartTunnel(cancelCtx context.Context, tempScriptFileName string) error { 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} args := []string{"/C", "call", tempScriptFileName}
cmd := exec.CommandContext(cancelCtx, "cmd", args...) cmd := exec.CommandContext(cancelCtx, "cmd", args...)
cmd.Stdout = &stdout err := RunTunnelCommand(cmd)
cmd.Stderr = &stderr
err := cmd.Start()
if err != nil { if err != nil {
log.Printf("Megan: error calling start.")
err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s",
err)
return 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 // Store successful command on step so we can access it to cancel it
// later. // later.
t.cmd = cmd t.cmd = cmd