Skip to content

Commit e0cbf39

Browse files
cagedmantisgopherbot
authored andcommitted
cmd/gomoteserver: create gomote server
This change copies the gomote server functionality from the coordinator into a new server. This will require refactoring after this initial CL. Updates golang/go#61912 For golang/go#61772 Change-Id: I25ef657f4512d034bdc3e98bf2718e5e768be1c4 Reviewed-on: https://go-review.googlesource.com/c/build/+/519475 Run-TryBot: Carlos Amedee <[email protected]> Auto-Submit: Carlos Amedee <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent fdf5df9 commit e0cbf39

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

cmd/gomoteserver/gomoteserver.go

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.20 && (linux || darwin)
6+
7+
package main
8+
9+
import (
10+
"context"
11+
"errors"
12+
"flag"
13+
"fmt"
14+
"log"
15+
"net/http"
16+
"strings"
17+
"time"
18+
19+
"cloud.google.com/go/compute/metadata"
20+
"cloud.google.com/go/storage"
21+
"golang.org/x/build/buildenv"
22+
"golang.org/x/build/internal/access"
23+
"golang.org/x/build/internal/coordinator/pool"
24+
"golang.org/x/build/internal/coordinator/remote"
25+
"golang.org/x/build/internal/coordinator/schedule"
26+
"golang.org/x/build/internal/gomote"
27+
gomotepb "golang.org/x/build/internal/gomote/protos"
28+
"golang.org/x/build/internal/https"
29+
"golang.org/x/build/internal/secret"
30+
"golang.org/x/build/internal/swarmclient"
31+
"google.golang.org/api/option"
32+
"google.golang.org/grpc"
33+
)
34+
35+
var (
36+
sshAddr = flag.String("ssh_addr", ":2222", "Address the gomote SSH server should listen on")
37+
buildEnvName = flag.String("env", "", "The build environment configuration to use. Not required if running in dev mode locally or prod mode on GCE.")
38+
mode = flag.String("mode", "", "Valid modes are 'dev', 'prod', or '' for auto-detect. dev means localhost development, not be confused with staging on go-dashboard-dev, which is still the 'prod' mode.")
39+
)
40+
41+
func main() {
42+
log.Println("starting gomote server")
43+
flag.Parse()
44+
if err := secret.InitFlagSupport(context.Background()); err != nil {
45+
log.Fatalln(err)
46+
}
47+
privateKey := secret.Flag(secret.NameGomoteSSHPrivateKey, "SendGrid API key for workflows involving sending email.")
48+
publicKey := secret.Flag(secret.NameGomoteSSHPublicKey, "SendGrid API key for workflows involving sending email.")
49+
50+
sp := remote.NewSessionPool(context.Background())
51+
sshCA := mustRetrieveSSHCertificateAuthority()
52+
var sched = schedule.NewScheduler()
53+
54+
var gomoteBucket string
55+
var opts []grpc.ServerOption
56+
if *buildEnvName == "" && *mode != "dev" && metadata.OnGCE() {
57+
projectID, err := metadata.ProjectID()
58+
if err != nil {
59+
log.Fatalf("metadata.ProjectID() = %v", err)
60+
}
61+
env := buildenv.ByProjectID(projectID)
62+
gomoteBucket = env.GomoteTransferBucket
63+
var coordinatorBackend, serviceID = "coordinator-internal-iap", ""
64+
if serviceID = env.IAPServiceID(coordinatorBackend); serviceID == "" {
65+
log.Fatalf("unable to retrieve Service ID for backend service=%q", coordinatorBackend)
66+
}
67+
opts = append(opts, grpc.UnaryInterceptor(access.RequireIAPAuthUnaryInterceptor(access.IAPSkipAudienceValidation)))
68+
opts = append(opts, grpc.StreamInterceptor(access.RequireIAPAuthStreamInterceptor(access.IAPSkipAudienceValidation)))
69+
}
70+
grpcServer := grpc.NewServer(opts...)
71+
gomoteServer := gomote.New(sp, sched, sshCA, gomoteBucket, mustStorageClient(), mustLUCIConfigClient())
72+
gomotepb.RegisterGomoteServiceServer(grpcServer, gomoteServer)
73+
74+
mux := http.NewServeMux()
75+
mux.HandleFunc("/reverse", pool.HandleReverse)
76+
mux.HandleFunc("/", grpcHandlerFunc(grpcServer, handleStatus)) // Serve a status page.
77+
78+
configureSSHServer := func() (*remote.SSHServer, error) {
79+
if *privateKey != "" && *publicKey != "" {
80+
return remote.NewSSHServer(*sshAddr, []byte(*privateKey), []byte(*publicKey), sshCA, sp)
81+
}
82+
if *mode != "dev" {
83+
return nil, errors.New("SSH key pair is not configured")
84+
}
85+
priKey, pubKey, err := remote.SSHKeyPair()
86+
if err != nil {
87+
return nil, fmt.Errorf("unable to generate development SSH key pair: %s", err)
88+
}
89+
return remote.NewSSHServer(*sshAddr, priKey, pubKey, sshCA, sp)
90+
}
91+
sshServ, err := configureSSHServer()
92+
if err != nil {
93+
log.Printf("unable to configure SSH server: %s", err)
94+
} else {
95+
go func() {
96+
log.Printf("running SSH server on %s", *sshAddr)
97+
err := sshServ.ListenAndServe()
98+
log.Printf("SSH server ended with error: %v", err)
99+
}()
100+
defer func() {
101+
err := sshServ.Close()
102+
if err != nil {
103+
log.Printf("unable to close SSH server: %s", err)
104+
}
105+
}()
106+
}
107+
log.Fatalln(https.ListenAndServe(context.Background(), mux))
108+
}
109+
110+
func mustLUCIConfigClient() *swarmclient.ConfigClient {
111+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
112+
defer cancel()
113+
c, err := swarmclient.NewConfigClient(ctx)
114+
if err != nil {
115+
log.Fatalf("unable to create LUCI config client: %s", err)
116+
}
117+
return c
118+
}
119+
120+
func mustRetrieveSSHCertificateAuthority() (privateKey []byte) {
121+
privateKey, _, err := remote.SSHKeyPair()
122+
if err != nil {
123+
log.Fatalf("unable to create SSH CA cert: %s", err)
124+
}
125+
return privateKey
126+
}
127+
128+
func mustStorageClient() *storage.Client {
129+
if metadata.OnGCE() {
130+
return pool.NewGCEConfiguration().StorageClient()
131+
}
132+
storageClient, err := storage.NewClient(context.Background(), option.WithoutAuthentication())
133+
if err != nil {
134+
log.Fatalf("unable to create storage client: %s", err)
135+
}
136+
return storageClient
137+
}
138+
139+
func fromSecret(ctx context.Context, sc *secret.Client, secretName string) (string, error) {
140+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
141+
defer cancel()
142+
return sc.Retrieve(ctx, secretName)
143+
}
144+
145+
// grpcHandlerFunc creates handler which intercepts requests intended for a GRPC server and directs the calls to the server.
146+
// All other requests are directed toward the passed in handler.
147+
func grpcHandlerFunc(gs *grpc.Server, h http.HandlerFunc) http.HandlerFunc {
148+
return func(w http.ResponseWriter, r *http.Request) {
149+
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
150+
gs.ServeHTTP(w, r)
151+
return
152+
}
153+
h(w, r)
154+
}
155+
}
156+
157+
func handleStatus(w http.ResponseWriter, r *http.Request) {
158+
if r.URL.Path != "/" {
159+
http.NotFound(w, r)
160+
return
161+
}
162+
w.Header().Set("Content-Type", "text/plain")
163+
fmt.Fprintf(w, "gomote status page placeholder")
164+
}

0 commit comments

Comments
 (0)