diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go index 3e99bbaf41..7f85b90819 100644 --- a/bootstrap/bootstrap.go +++ b/bootstrap/bootstrap.go @@ -18,7 +18,7 @@ package bootstrap import ( "context" "fmt" - "net" + "io/ioutil" "os" "os/exec" "os/signal" @@ -129,16 +129,30 @@ Follow these instructions to create a token (we don't store any tokens): // Create ngrok tunnel. colorstring.Println("[white]=> creating secure tunnel") s.Start() - // Check if there is already an ngrok running by seeing if there's already - // something bound to its API port. - conn, err := net.Dial("tcp", ngrokAPIURL) - // We expect an error. - if err == nil { - conn.Close() // nolint: errcheck - return errors.New("unable to start ngrok because there is already something bound to its API port: " + ngrokAPIURL) + + // We use a config file so we can set ngrok's API port (web_addr). We use + // the API to get the public URL and if there's already ngrok running, it + // will just choose a random API port and we won't be able to get the right + // url. + ngrokConfig := fmt.Sprintf(` +web_addr: %s +tunnels: + atlantis: + addr: %d + bind_tls: true + proto: http +`, ngrokAPIURL, atlantisPort) + + ngrokConfigFile, err := ioutil.TempFile("", "") + if err != nil { + return errors.Wrap(err, "creating ngrok config file") + } + err = ioutil.WriteFile(ngrokConfigFile.Name(), []byte(ngrokConfig), 0600) + if err != nil { + return errors.Wrap(err, "writing ngrok config file") } - ngrokCmd, err := executeCmd("/tmp/ngrok", []string{"http", "4141"}) + ngrokCmd, err := executeCmd("/tmp/ngrok", []string{"start", "atlantis", "--config", ngrokConfigFile.Name()}) if err != nil { return errors.Wrapf(err, "creating ngrok tunnel") } diff --git a/bootstrap/utils.go b/bootstrap/utils.go index 7b0ff570e7..e9bc42792a 100644 --- a/bootstrap/utils.go +++ b/bootstrap/utils.go @@ -24,13 +24,15 @@ import ( "path/filepath" "syscall" + "github.com/pkg/errors" "golang.org/x/crypto/ssh/terminal" ) -var hashicorpReleasesURL = "https://releases.hashicorp.com" -var terraformVersion = "0.10.8" -var ngrokDownloadURL = "https://bin.equinox.io/c/4VmDzA7iaHb" -var ngrokAPIURL = "localhost:4040" +const hashicorpReleasesURL = "https://releases.hashicorp.com" +const terraformVersion = "0.10.8" +const ngrokDownloadURL = "https://bin.equinox.io/c/4VmDzA7iaHb" +const ngrokAPIURL = "localhost:41414" // We hope this isn't used. +const atlantisPort = 4141 func readPassword() (string, error) { password, err := terminal.ReadPassword(syscall.Stdin) @@ -90,35 +92,38 @@ func unzip(archive, target string) error { } func getTunnelAddr() (string, error) { - response, err := http.Get(fmt.Sprintf("http://%s/api/tunnels", ngrokAPIURL)) + tunAPI := fmt.Sprintf("http://%s/api/tunnels", ngrokAPIURL) + response, err := http.Get(tunAPI) if err != nil { return "", err } defer response.Body.Close() // nolint: errcheck - type tunnel struct { - Name string `json:"name"` - URI string `json:"uri"` - PublicURL string `json:"public_url"` - Proto string `json:"http"` - } - type tunnels struct { - Tunnels []tunnel + Tunnels []struct { + PublicURL string `json:"public_url"` + Proto string `json:"proto"` + Config struct { + Addr string `json:"addr"` + } `json:"config"` + } `json:"tunnels"` } var t tunnels - err = json.NewDecoder(response.Body).Decode(&t) - if err != nil { - return "", err + if err = json.NewDecoder(response.Body).Decode(&t); err != nil { + return "", errors.Wrapf(err, "parsing ngrok api at %s", tunAPI) } - if len(t.Tunnels) != 2 { - return "", fmt.Errorf("didn't find tunnels that were expected to be created") + // Find the tunnel we just created. + expAtlantisURL := fmt.Sprintf("localhost:%d", atlantisPort) + for _, tun := range t.Tunnels { + if tun.Proto == "https" && tun.Config.Addr == expAtlantisURL { + return tun.PublicURL, nil + } } - return t.Tunnels[1].PublicURL, nil + return "", fmt.Errorf("did not find ngrok tunnel with proto 'https' and config.addr '%s' in list of tunnels at %s", expAtlantisURL, tunAPI) } // nolint: unparam