From c578afc62cc1b5a735459677247cff229bcf2992 Mon Sep 17 00:00:00 2001 From: Megan Marsh Date: Wed, 29 Apr 2020 12:13:22 -0700 Subject: [PATCH] working on windows --- builder/googlecompute/config.go | 11 ++- builder/googlecompute/step_start_tunnel.go | 30 +++++--- .../googlecompute/tunnel_driver_windows.go | 68 ++++++++++++++----- 3 files changed, 81 insertions(+), 28 deletions(-) diff --git a/builder/googlecompute/config.go b/builder/googlecompute/config.go index 5b5c7c564..3c26b6f4b 100644 --- a/builder/googlecompute/config.go +++ b/builder/googlecompute/config.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "regexp" + "runtime" "time" "github.com/hashicorp/packer/common" @@ -329,10 +330,16 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) { // set defaults for IAP if c.IAPConfig.IAPHashBang == "" { - c.IAPConfig.IAPHashBang = "/bin/sh" + if runtime.GOOS == "windows" { + c.IAPConfig.IAPHashBang = "" + } else { + c.IAPConfig.IAPHashBang = "/bin/sh" + } } if c.IAPConfig.IAPExt == "" { - c.IAPConfig.IAPExt = ".sh" + if runtime.GOOS == "windows" { + c.IAPConfig.IAPExt = ".cmd" + } } // Configure IAP: Update SSH config to use localhost proxy instead diff --git a/builder/googlecompute/step_start_tunnel.go b/builder/googlecompute/step_start_tunnel.go index b085cbd95..59393c49a 100644 --- a/builder/googlecompute/step_start_tunnel.go +++ b/builder/googlecompute/step_start_tunnel.go @@ -105,12 +105,15 @@ func (s *StepStartTunnel) createTempGcloudScript(args []string) (string, error) // Write our contents to it writer := bufio.NewWriter(tf) - s.IAPConf.IAPHashBang = fmt.Sprintf("#!%s\n", s.IAPConf.IAPHashBang) - log.Printf("[INFO] (google): Prepending inline gcloud setup script with %s", - s.IAPConf.IAPHashBang) - _, err = writer.WriteString(s.IAPConf.IAPHashBang) - if err != nil { - return "", fmt.Errorf("Error preparing inline hashbang: %s", err) + if s.IAPConf.IAPHashBang != "" { + s.IAPConf.IAPHashBang = fmt.Sprintf("#!%s\n", s.IAPConf.IAPHashBang) + log.Printf("[INFO] (google): Prepending inline gcloud setup script with %s", + s.IAPConf.IAPHashBang) + _, err = writer.WriteString(s.IAPConf.IAPHashBang) + if err != nil { + return "", fmt.Errorf("Error preparing inline hashbang: %s", err) + } + } // authenticate to gcloud @@ -130,7 +133,8 @@ func (s *StepStartTunnel) createTempGcloudScript(args []string) (string, error) if err := writer.Flush(); err != nil { return "", fmt.Errorf("Error preparing shell script: %s", err) } - + // Have to close temp file before renaming it or Windows will complain. + tf.Close() err = os.Chmod(tf.Name(), 0700) if err != nil { log.Printf("[ERROR] (google): error modifying permissions of temp script file: %s", err.Error()) @@ -181,8 +185,7 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul // TODO make setting LocalHostPort optional s.CommConf.SSHPort = s.IAPConf.IAPLocalhostPort - log.Printf("Calling tunnel launch with args %#v", args) - + log.Printf("Creating tunnel launch script with args %#v", args) // Create temp file that contains both gcloud authentication, and gcloud // proxy setup call. tempScriptFileName, err := s.createTempGcloudScript(args) @@ -211,6 +214,11 @@ func (s *StepStartTunnel) Run(ctx context.Context, state multistep.StateBag) mul err := s.tunnelDriver.StartTunnel(ctx, tempScriptFileName) return err }) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } return multistep.ActionContinue } @@ -221,5 +229,7 @@ func (s *StepStartTunnel) Cleanup(state multistep.StateBag) { log.Printf("Skipping cleanup of IAP tunnel; \"iap\" is false.") return } - s.tunnelDriver.StopTunnel() + if s.tunnelDriver != nil { + s.tunnelDriver.StopTunnel() + } } diff --git a/builder/googlecompute/tunnel_driver_windows.go b/builder/googlecompute/tunnel_driver_windows.go index 73d739b5a..16c10d8c9 100644 --- a/builder/googlecompute/tunnel_driver_windows.go +++ b/builder/googlecompute/tunnel_driver_windows.go @@ -6,6 +6,7 @@ import ( "bytes" "context" "fmt" + "io" "log" "os/exec" "strings" @@ -13,6 +14,7 @@ import ( ) func NewTunnelDriver() TunnelDriver { + log.Printf("Megan created driver for windows") return &TunnelDriverWindows{} } @@ -21,37 +23,71 @@ 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 - cmd := exec.CommandContext(cancelCtx, tempScriptFileName) + args := []string{"/C", "call", tempScriptFileName} + + cmd := exec.CommandContext(cancelCtx, "cmd", args...) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Start() - log.Printf("Waiting 30s for tunnel to create...") if err != nil { + log.Printf("Megan: error calling start.") err := fmt.Errorf("Error calling gcloud sdk to launch IAP tunnel: %s", err) return err } - // Wait for tunnel to launch and gather response. TODO: do this without - // a sleep. - time.Sleep(30 * time.Second) - // Track stdout. - sout := stdout.String() - if sout != "" { - log.Printf("[start-iap-tunnel] stdout is:") + // 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("[start-iap-tunnel] stderr is:") - serr := stderr.String() - log.Println(serr) - if strings.Contains(serr, "ERROR") { - errIdx := strings.Index(serr, "ERROR:") - return fmt.Errorf("ERROR: %s", serr[errIdx+7:]) - } + 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