cmd/debugnewvm: add EC2 buildlets
This adds the ability to debug the creation of buildlets which run
on EC2.
Updates golang/go#36841
Change-Id: Ib6891bc6ea985716f76e5668ade178541073a344
Reviewed-on: https://go-review.googlesource.com/c/build/+/236577
Run-TryBot: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
diff --git a/cmd/debugnewvm/README.md b/cmd/debugnewvm/README.md
index b320da0..d912328 100644
--- a/cmd/debugnewvm/README.md
+++ b/cmd/debugnewvm/README.md
@@ -4,4 +4,4 @@
# golang.org/x/build/cmd/debugnewvm
-The debugnewvm command creates and destroys a VM-based GCE buildlet with lots of logging for debugging.
+The debugnewvm command creates and destroys a VM-based buildlets with lots of logging for debugging.
diff --git a/cmd/debugnewvm/debugnewvm.go b/cmd/debugnewvm/debugnewvm.go
index 9858c52..932b627 100644
--- a/cmd/debugnewvm/debugnewvm.go
+++ b/cmd/debugnewvm/debugnewvm.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// The debugnewvm command creates and destroys a VM-based GCE buildlet
+// The debugnewvm command creates and destroys a VM-based buildlet
// with lots of logging for debugging. Nothing depends on this.
package main
@@ -17,11 +17,15 @@
"strings"
"time"
+ "cloud.google.com/go/compute/metadata"
"golang.org/x/build/buildenv"
"golang.org/x/build/buildlet"
"golang.org/x/build/dashboard"
"golang.org/x/build/internal/buildgo"
+ "golang.org/x/build/internal/cloud"
+ "golang.org/x/build/internal/secret"
"golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
)
@@ -29,13 +33,17 @@
hostType = flag.String("host", "", "host type to create")
zone = flag.String("zone", "", "if non-empty, force a certain GCP zone")
overrideImage = flag.String("override-image", "", "if non-empty, an alternate GCE VM image or container image to use, depending on the host type")
- serial = flag.Bool("serial", true, "watch serial")
+ serial = flag.Bool("serial", true, "watch serial. Supported for GCE VMs")
pauseAfterUp = flag.Duration("pause-after-up", 0, "pause for this duration before buildlet is destroyed")
sleepSec = flag.Int("sleep-test-secs", 0, "number of seconds to sleep when buildlet comes up, to test time source; OpenBSD only for now")
runBuild = flag.String("run-build", "", "optional builder name to run all.bash or make.bash for")
makeOnly = flag.Bool("make-only", false, "if a --run-build builder name is given, this controls whether make.bash or all.bash is run")
buildRev = flag.String("rev", "master", "if --run-build is specified, the git hash or branch name to build")
+
+ awsKeyID = flag.String("aws-key-id", "", "if the builder runs on aws then key id is required. If executed on GCE, it will be retrieved from secrets.")
+ awsAccessKey = flag.String("aws-access-key", "", "if the builder runs on aws then the access key is required. If executed on GCE, it will be retrieved from secrets.")
+ awsRegion = flag.String("aws-region", "us-east-2", "if the builder runs on aws then it is created in this region.")
)
var (
@@ -73,6 +81,16 @@
if !hconf.IsVM() && !hconf.IsContainer() {
log.Fatalf("host type %q is type %q; want a VM or container host type", *hostType, hconf.PoolName())
}
+ if hconf.IsEC2() && (*awsKeyID == "" || *awsAccessKey == "") {
+ if !metadata.OnGCE() {
+ log.Fatal("missing -aws-key-id and -aws-access-key params are required for builders on AWS")
+ }
+ var err error
+ *awsKeyID, *awsAccessKey, err = awsCredentialsFromSecrets()
+ if err != nil {
+ log.Fatalf("unable to retrieve AWS credentials: %s", err)
+ }
+ }
if img := *overrideImage; img != "" {
if hconf.IsContainer() {
hconf.ContainerImage = img
@@ -90,44 +108,33 @@
}
env = buildenv.FromFlags()
-
ctx := context.Background()
-
- buildenv.CheckUserCredentials()
- creds, err := env.Credentials(ctx)
- if err != nil {
- log.Fatal(err)
- }
- computeSvc, _ = compute.New(oauth2.NewClient(ctx, creds.TokenSource))
-
name := fmt.Sprintf("debug-temp-%d", time.Now().Unix())
log.Printf("Creating %s (with VM image %s)", name, vmImageSummary)
- var zoneSelected string
- bc, err := buildlet.StartNewVM(creds, env, name, *hostType, buildlet.VMOpts{
- Zone: *zone,
- OnInstanceRequested: func() { log.Printf("instance requested") },
- OnInstanceCreated: func() {
- log.Printf("instance created")
- if *serial {
- go watchSerial(zoneSelected, name)
- }
- },
- OnGotInstanceInfo: func(inst *compute.Instance) {
- zoneSelected = inst.Zone
- log.Printf("got instance info; running in %v", zoneSelected)
- },
- OnBeginBuildletProbe: func(buildletURL string) {
- log.Printf("About to hit %s to see if buildlet is up yet...", buildletURL)
- },
- OnEndBuildletProbe: func(res *http.Response, err error) {
- if err != nil {
- log.Printf("client buildlet probe error: %v", err)
- return
- }
- log.Printf("buildlet probe: %s", res.Status)
- },
- })
+ var (
+ bc *buildlet.Client
+ err error
+ )
+ if hconf.IsEC2() {
+ awsC, err := cloud.NewAWSClient(*awsRegion, *awsKeyID, *awsAccessKey)
+ if err != nil {
+ log.Fatalf("unable to create aws cloud client: %s", err)
+ }
+ ec2C := buildlet.NewEC2Client(awsC)
+ if err != nil {
+ log.Fatalf("unable to create ec2 client: %v", err)
+ }
+ bc, err = ec2Buildlet(context.Background(), ec2C, hconf, env, name, *hostType, *zone)
+ } else {
+ buildenv.CheckUserCredentials()
+ creds, err := env.Credentials(ctx)
+ if err != nil {
+ log.Fatal(err)
+ }
+ computeSvc, _ = compute.New(oauth2.NewClient(ctx, creds.TokenSource))
+ bc, err = gceBuildlet(creds, env, name, *hostType, *zone)
+ }
if err != nil {
log.Fatalf("StartNewVM: %v", err)
}
@@ -215,6 +222,7 @@
// gcloud compute connect-to-serial-port --zone=xxx $NAME
// but in Go and works. For some reason, gcloud doesn't work as a
// child process and has weird errors.
+// TODO(golang.org/issue/39485) - investigate if this is possible for EC2 instances
func watchSerial(zone, name string) {
start := int64(0)
indent := strings.Repeat(" ", len("2017/07/25 06:37:14 SERIAL: "))
@@ -235,3 +243,79 @@
}
}
}
+
+// awsCredentialsFromSecrets retrieves AWS credentials from the secret management service.
+// This funciton returns the key ID and the access key.
+func awsCredentialsFromSecrets() (string, string, error) {
+ c, err := secret.NewClient()
+ if err != nil {
+ return "", "", fmt.Errorf("unable to create secret client: %w", err)
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+ keyID, err := c.Retrieve(ctx, secret.NameAWSKeyID)
+ if err != nil {
+ return "", "", fmt.Errorf("unable to retrieve key ID: %w", err)
+ }
+ accessKey, err := c.Retrieve(ctx, secret.NameAWSAccessKey)
+ if err != nil {
+ return "", "", fmt.Errorf("unable to retrueve access key: %w", err)
+ }
+ return keyID, accessKey, nil
+}
+
+func gceBuildlet(creds *google.Credentials, env *buildenv.Environment, name, hostType, zone string) (*buildlet.Client, error) {
+ var zoneSelected string
+ return buildlet.StartNewVM(creds, env, name, hostType, buildlet.VMOpts{
+ Zone: zone,
+ OnInstanceRequested: func() { log.Printf("instance requested") },
+ OnInstanceCreated: func() {
+ log.Printf("instance created")
+ if *serial {
+ go watchSerial(zoneSelected, name)
+ }
+ },
+ OnGotInstanceInfo: func(inst *compute.Instance) {
+ zoneSelected = inst.Zone
+ log.Printf("got instance info; running in %v", zoneSelected)
+ },
+ OnBeginBuildletProbe: func(buildletURL string) {
+ log.Printf("About to hit %s to see if buildlet is up yet...", buildletURL)
+ },
+ OnEndBuildletProbe: func(res *http.Response, err error) {
+ if err != nil {
+ log.Printf("client buildlet probe error: %v", err)
+ return
+ }
+ log.Printf("buildlet probe: %s", res.Status)
+ },
+ })
+}
+
+func ec2Buildlet(ctx context.Context, ec2Client *buildlet.EC2Client, hconf *dashboard.HostConfig, env *buildenv.Environment, name, hostType, zone string) (*buildlet.Client, error) {
+ kp, err := buildlet.NewKeyPair()
+ if err != nil {
+ log.Fatalf("key pair failed: %v", err)
+ }
+ return ec2Client.StartNewVM(ctx, env, hconf, name, hostType, &buildlet.VMOpts{
+ TLS: kp,
+ Zone: zone,
+ OnInstanceRequested: func() { log.Printf("instance requested") },
+ OnInstanceCreated: func() {
+ log.Printf("instance created")
+ },
+ OnGotEC2InstanceInfo: func(inst *cloud.Instance) {
+ log.Printf("got instance info: running in %v", inst.Zone)
+ },
+ OnBeginBuildletProbe: func(buildletURL string) {
+ log.Printf("About to hit %s to see if buildlet is up yet...", buildletURL)
+ },
+ OnEndBuildletProbe: func(res *http.Response, err error) {
+ if err != nil {
+ log.Printf("client buildlet probe error: %v", err)
+ return
+ }
+ log.Printf("buildlet probe: %s", res.Status)
+ },
+ })
+}