Skip to content

Commit

Permalink
dashboard, env, cmd/buildlet/testssh: fix gomote ssh for a number of …
Browse files Browse the repository at this point in the history
…buidlers

And add a testssh tool to validate that SSH is working.

Updates golang/go#32430

Change-Id: I5182419fa4db31b598f7a02412fb4ecc4060a796
Reviewed-on: https://go-review.googlesource.com/c/build/+/207459
Reviewed-by: Bryan C. Mills <[email protected]>
  • Loading branch information
bradfitz committed Nov 15, 2019
1 parent f0d0eff commit c7f36d0
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 30 deletions.
49 changes: 38 additions & 11 deletions cmd/buildlet/buildlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -1337,14 +1337,27 @@ func handleConnectSSH(w http.ResponseWriter, r *http.Request) {
}
}

sshConn, err := net.Dial("tcp", "localhost:"+sshPort())
if err != nil {
sshServerOnce.Do(startSSHServer)
sshServerOnce.Do(startSSHServer)

var sshConn net.Conn
var err error

// In theory we shouldn't need retries here at all, but the
// startSSHServerLinux's use of sshd -D is kinda sketchy and
// restarts the process whenever we connect to it, so in case
// it's just down between restarts, try a few times. 5 tries
// and 5 seconds seems plenty.
const maxTries = 5
for try := 1; try <= maxTries; try++ {
sshConn, err = net.Dial("tcp", "localhost:"+sshPort())
if err != nil {
if err == nil {
break
}
if try == maxTries {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
time.Sleep(time.Second)
}
defer sshConn.Close()
hj, ok := w.(http.Hijacker)
Expand Down Expand Up @@ -1450,13 +1463,27 @@ func startSSHServerLinux() {
}
}

cmd := exec.Command("/usr/sbin/sshd", "-D", "-p", sshPort())
err := cmd.Start()
if err != nil {
log.Printf("starting sshd: %v", err)
return
}
log.Printf("sshd started.")
go func() {
for {
// TODO: using sshd -D isn't great as it only
// handles a single connection and exits.
// Maybe run in sshd -i (inetd) mode instead,
// and hook that up to the buildlet directly?
t0 := time.Now()
cmd := exec.Command("/usr/sbin/sshd", "-D", "-p", sshPort(), "-d", "-d")
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
log.Printf("starting sshd: %v", err)
return
}
log.Printf("sshd started.")
log.Printf("sshd exited: %v; restarting", cmd.Wait())
if d := time.Since(t0); d < time.Second {
time.Sleep(time.Second - d)
}
}
}()
waitLocalSSH()
}

Expand Down
6 changes: 4 additions & 2 deletions cmd/buildlet/stage0/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

FROM golang:1.12 AS build
FROM golang:1.13 AS build
LABEL maintainer "[email protected]"

ENV GO111MODULE=on
Expand All @@ -24,6 +24,8 @@ COPY . /go/src/golang.org/x/build/

# Install binary to /go/bin/stage0
RUN go install golang.org/x/build/cmd/buildlet/stage0
RUN CGO_ENABLED=0 go build -o /go/bin/stage0.static golang.org/x/build/cmd/buildlet/stage0

FROM golang:1.12
FROM golang:1.13
COPY --from=build /go/bin/stage0 /go/bin/stage0
COPY --from=build /go/bin/stage0.static /go/bin/stage0.static
165 changes: 165 additions & 0 deletions cmd/buildlet/testssh/testssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 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"
"io/ioutil"
"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 := ioutil.ReadFile(pubKeyPath)
if err != nil {
log.Fatal(err)
}
return strings.TrimSpace(string(slurp)), privateKeyPath
}

func getIPPort() string {
if *startImage != "" {
buildlet := "buildlet.linux-amd64"
if strings.Contains(*startImage, "linux-x86-alpine") {
buildlet = "buildlet.linux-amd64-static"
}
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 ""
}
1 change: 1 addition & 0 deletions cmd/xb/xb.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ func runDocker() {
for _, layer := range layers {
if strings.HasPrefix(layer, "golang:") ||
strings.HasPrefix(layer, "debian:") ||
strings.HasPrefix(layer, "alpine:") ||
strings.HasPrefix(layer, "fedora:") {
continue
}
Expand Down
7 changes: 6 additions & 1 deletion dashboard/builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ var Hosts = map[string]*HostConfig{
ContainerImage: "js-wasm:latest",
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64",
env: []string{"GOROOT_BOOTSTRAP=/go1.4"},
SSHUsername: "root",
},
"host-s390x-cross-kube": &HostConfig{
Notes: "Container with s390x cross-compiler.",
Expand All @@ -177,26 +178,30 @@ var Hosts = map[string]*HostConfig{
"host-linux-x86-alpine": &HostConfig{
Notes: "Alpine container",
ContainerImage: "linux-x86-alpine:latest",
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64-static",
buildletURLTmpl: "https://storage.googleapis.com/$BUCKET/buildlet.linux-amd64-static",
env: []string{"GOROOT_BOOTSTRAP=/usr/lib/go"},
SSHUsername: "root",
},
"host-linux-clang": &HostConfig{
Notes: "Container with clang.",
ContainerImage: "linux-x86-clang:latest",
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64",
env: []string{"GOROOT_BOOTSTRAP=/go1.4"},
SSHUsername: "root",
},
"host-linux-sid": &HostConfig{
Notes: "Debian sid, updated occasionally.",
ContainerImage: "linux-x86-sid:latest",
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64",
env: []string{"GOROOT_BOOTSTRAP=/go1.4"},
SSHUsername: "root",
},
"host-linux-fedora": &HostConfig{
Notes: "Fedora 30",
ContainerImage: "linux-x86-fedora:latest",
buildletURLTmpl: "http://storage.googleapis.com/$BUCKET/buildlet.linux-amd64",
env: []string{"GOROOT_BOOTSTRAP=/goboot"},
SSHUsername: "root",
},
"host-linux-arm-scaleway": &HostConfig{
IsReverse: true,
Expand Down
9 changes: 7 additions & 2 deletions env/linux-x86-alpine/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

FROM golang/buildlet-stage0 AS stage0

FROM alpine:3.5
FROM alpine:3.10
MAINTAINER golang-dev <[email protected]>

RUN apk add --no-cache \
Expand All @@ -24,10 +24,15 @@ RUN apk add --no-cache \
go \
libc-dev \
lsof \
openssh \
procps \
strace

COPY --from=stage0 /go/bin/stage0 /usr/local/bin/stage0
RUN ssh-keygen -A
RUN bash -c "(echo ChallengeResponseAuthentication no; echo PasswordAuthentication no; echo PermitRootLogin yes) > /etc/ssh/sshd_config"
RUN passwd -u root

COPY --from=stage0 /go/bin/stage0.static /usr/local/bin/stage0

ENV GOROOT_BOOTSTRAP=/usr/lib/go

Expand Down
18 changes: 7 additions & 11 deletions env/linux-x86-clang/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,35 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.

# Linux builder VM with clang instead of gccc.
# Linux builder VM with clang instead of gcc.
# Docker tag gobuilders/linux-x86-clang

FROM golang/buildlet-stage0 AS stage0

FROM debian:jessie
FROM debian:buster
MAINTAINER golang-dev <[email protected]>

ENV DEBIAN_FRONTEND noninteractive

COPY sources/clang-deps.list /etc/apt/sources.list.d/
COPY llvm-snapshot.gpg.key /tmp/

RUN apt-key add /tmp/llvm-snapshot.gpg.key

# strace: optionally used by some net/http tests
# libc6-dev-i386 gcc-multilib: for 32-bit builds
# procps lsof psmisc: misc basic tools
RUN apt-get update && apt-get install -y \
--no-install-recommends \
ca-certificates \
curl \
clang-3.9 \
clang \
strace \
libc6-dev-i386 \
gcc-multilib \
procps \
lsof \
psmisc \
openssh-server \
&& rm -rf /var/lib/apt/lists/* \
&& rm -f /usr/bin/gcc \
&& ln -snf /usr/bin/clang-3.9 /usr/bin/clang \
&& ln -snf /usr/bin/clang++-3.9 /usr/bin/clang++
&& rm -f /usr/bin/gcc

RUN mkdir --mode=0700 /run/sshd

RUN mkdir -p /go1.4-amd64 \
&& ( \
Expand Down
5 changes: 4 additions & 1 deletion env/linux-x86-fedora/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ RUN yum -y update && yum -y install \
patch \
strace \
which \
openssh-server \
&& true

RUN ssh-keygen -A

RUN mkdir -p /goboot-amd64 \
&& ( \
curl --silent https://storage.googleapis.com/golang/go1.12.5.linux-amd64.tar.gz | tar -C /goboot-amd64 -zxv \
curl --silent https://storage.googleapis.com/golang/go1.13.4.linux-amd64.tar.gz | tar -C /goboot-amd64 -zxv \
) \
&& mv /goboot-amd64/go /goboot \
&& rm -rf /goboot-amd64 \
Expand Down
Loading

0 comments on commit c7f36d0

Please sign in to comment.