| // Copyright 2019 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // The testssh binary exists to verify that a buildlet container's | |
| // ssh works, without running the whole coordinator binary in the | |
| // staging environment. | |
| package main | |
| import ( | |
| "bytes" | |
| "flag" | |
| "fmt" | |
| "io" | |
| "log" | |
| "net" | |
| "os" | |
| "os/exec" | |
| "path/filepath" | |
| "strings" | |
| "time" | |
| "golang.org/x/build/buildenv" | |
| "golang.org/x/build/buildlet" | |
| ) | |
| var ( | |
| container = flag.String("container", "", "if non-empty, the ID of a running docker container") | |
| startImage = flag.String("start-image", "", "if non-empty, the Docker image to start a buildlet of locally, and use its container ID for the -container value") | |
| user = flag.String("user", "root", "SSH user") | |
| ) | |
| func main() { | |
| flag.Parse() | |
| ipPort := getIPPort() | |
| defer cleanContainer() | |
| bc := buildlet.NewClient(ipPort, buildlet.NoKeyPair) | |
| for { | |
| c, err := net.Dial("tcp", ipPort) | |
| if err == nil { | |
| c.Close() | |
| break | |
| } | |
| log.Printf("waiting for %v to come up...", ipPort) | |
| time.Sleep(time.Second) | |
| } | |
| pubKey, privPath := genKey() | |
| log.Printf("hitting buildlet's /connect-ssh ...") | |
| buildletConn, err := bc.ConnectSSH(*user, pubKey) | |
| if err != nil { | |
| var out []byte | |
| if *container != "" { | |
| var err error | |
| out, err = exec.Command("docker", "logs", *container).CombinedOutput() | |
| if err != nil { | |
| log.Printf("failed to fetch docker logs: %v", err) | |
| } | |
| } | |
| cleanContainer() | |
| log.Printf("image logs: %s", out) | |
| log.Fatalf("ConnectSSH: %v (logs above)", err) | |
| } | |
| defer buildletConn.Close() | |
| log.Printf("ConnectSSH succeeded; testing connection...") | |
| ln, err := net.Listen("tcp", "localhost:0") | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| go func() { | |
| c, err := ln.Accept() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| go io.Copy(buildletConn, c) | |
| go io.Copy(c, buildletConn) | |
| }() | |
| ip, port, err := net.SplitHostPort(ln.Addr().String()) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| cmd := exec.Command("ssh", | |
| "-v", | |
| "-i", privPath, | |
| "-o", "StrictHostKeyChecking=no", | |
| "-o", "UserKnownHostsFile=/dev/null", | |
| "-o", "LogLevel=ERROR", | |
| "-p", port, | |
| *user+"@"+ip, | |
| "echo", "SSH works") | |
| stdout := new(bytes.Buffer) | |
| stderr := new(bytes.Buffer) | |
| cmd.Stderr = stderr | |
| cmd.Stdout = stdout | |
| if err := cmd.Run(); err != nil { | |
| cleanContainer() | |
| log.Fatalf("ssh client: %v, %s", err, stderr) | |
| } | |
| fmt.Print(stdout.String()) | |
| } | |
| func cleanContainer() { | |
| if *startImage == "" { | |
| return | |
| } | |
| out, err := exec.Command("docker", "rm", "-f", *container).CombinedOutput() | |
| if err != nil { | |
| log.Printf("docker rm: %v, %s", err, out) | |
| } | |
| } | |
| func genKey() (pubKey, privateKeyPath string) { | |
| cache, err := os.UserCacheDir() | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| cache = filepath.Join(cache, "testssh") | |
| os.MkdirAll(cache, 0755) | |
| privateKeyPath = filepath.Join(cache, "testkey") | |
| pubKeyPath := filepath.Join(cache, "testkey.pub") | |
| if _, err := os.Stat(pubKeyPath); err != nil { | |
| out, err := exec.Command("ssh-keygen", "-t", "ed25519", "-f", privateKeyPath, "-N", "").CombinedOutput() | |
| if err != nil { | |
| log.Fatalf("ssh-keygen: %v, %s", err, out) | |
| } | |
| } | |
| slurp, err := os.ReadFile(pubKeyPath) | |
| if err != nil { | |
| log.Fatal(err) | |
| } | |
| return strings.TrimSpace(string(slurp)), privateKeyPath | |
| } | |
| func getIPPort() string { | |
| if *startImage != "" { | |
| buildlet := "buildlet.linux-amd64" | |
| log.Printf("creating container with image %s ...", *startImage) | |
| out, err := exec.Command("docker", "run", "-d", | |
| "--stop-timeout=300", | |
| "-e", "META_BUILDLET_BINARY_URL=https://storage.googleapis.com/"+buildenv.Production.BuildletBucket+"/"+buildlet, | |
| *startImage).CombinedOutput() | |
| if err != nil { | |
| log.Fatalf("docker run: %v, %s", err, out) | |
| } | |
| *container = strings.TrimSpace(string(out)) | |
| log.Printf("created container %s ...", *container) | |
| } | |
| if *container != "" { | |
| out, err := exec.Command("bash", "-c", "docker inspect "+*container+" | jq -r '.[0].NetworkSettings.IPAddress'").CombinedOutput() | |
| if err != nil { | |
| log.Fatalf("%v: %s", err, out) | |
| } | |
| return strings.TrimSpace(string(out)) + ":80" | |
| } | |
| log.Fatalf("no address specified") | |
| return "" | |
| } |