golang.org/x/build@v0.0.0-20240506185731-218518f32b70/buildlet/buildlet.go (about) 1 // Copyright 2020 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 package buildlet 6 7 import ( 8 "context" 9 "crypto/tls" 10 "fmt" 11 "io" 12 "log" 13 "net/http" 14 "time" 15 16 "golang.org/x/build/internal/cloud" 17 "google.golang.org/api/compute/v1" 18 ) 19 20 // VMOpts control how new VMs are started. 21 type VMOpts struct { 22 // Zone is the GCE zone to create the VM in. 23 // Optional; defaults to provided build environment's zone. 24 Zone string 25 26 // ProjectID is the GCE project ID (e.g. "foo-bar-123", not 27 // the numeric ID). 28 // Optional; defaults to provided build environment's project ID ("name"). 29 ProjectID string 30 31 // TLS optionally specifies the TLS keypair to use. 32 // If zero, http without auth is used. 33 TLS KeyPair 34 35 // Optional description of the VM. 36 Description string 37 38 // Optional metadata to put on the instance. 39 Meta map[string]string 40 41 // DeleteIn optionally specifies a duration at which 42 // to delete the VM. 43 // If zero, a short default is used (not enough for longtest builders). 44 // Negative means no deletion timeout. 45 DeleteIn time.Duration 46 47 // OnInstanceRequested optionally specifies a hook to run synchronously 48 // after the computeService.Instances.Insert call, but before 49 // waiting for its operation to proceed. 50 OnInstanceRequested func() 51 52 // OnInstanceCreated optionally specifies a hook to run synchronously 53 // after the instance operation succeeds. 54 OnInstanceCreated func() 55 56 // OnInstanceCreated optionally specifies a hook to run synchronously 57 // after the computeService.Instances.Get call. 58 // Only valid for GCE resources. 59 OnGotInstanceInfo func(*compute.Instance) 60 61 // OnInstanceCreated optionally specifies a hook to run synchronously 62 // after the EC2 instance information is retrieved. 63 // Only valid for EC2 resources. 64 OnGotEC2InstanceInfo func(*cloud.Instance) 65 66 // OnBeginBuildletProbe optionally specifies a hook to run synchronously 67 // before StartNewVM tries to hit buildletURL to see if it's up yet. 68 OnBeginBuildletProbe func(buildletURL string) 69 70 // OnEndBuildletProbe optionally specifies a hook to run synchronously 71 // after StartNewVM tries to hit the buildlet's URL to see if it's up. 72 // The hook parameters are the return values from http.Get. 73 OnEndBuildletProbe func(*http.Response, error) 74 75 // SkipEndpointVerification does not verify that the builder is listening 76 // on port 80 or 443 before creating a buildlet client. 77 SkipEndpointVerification bool 78 79 // UseIAPTunnel uses an IAP tunnel to connect to buildlets on GCP. 80 UseIAPTunnel bool 81 82 // DiskSizeGB specifies the size of the boot disk in base-2 GB. The default 83 // disk size is used if unset. 84 // Only valid for GCE resources. 85 DiskSizeGB int64 86 } 87 88 // buildletClient returns a buildlet client configured to speak to a VM via the buildlet 89 // URL. The communication will use TLS if one is provided in the vmopts. This will wait until 90 // it can connect with the endpoint before returning. The buildletURL is in the form of: 91 // "https://<ip>". The ipPort field is in the form of "<ip>:<port>". The function 92 // will attempt to connect to the buildlet for the lesser of: the default timeout period 93 // (10 minutes) or the timeout set in the passed in context. 94 func buildletClient(ctx context.Context, buildletURL, ipPort string, opts *VMOpts) (Client, error) { 95 ctx, cancel := context.WithTimeout(ctx, 10*time.Minute) 96 defer cancel() 97 try := 0 98 for !opts.SkipEndpointVerification { 99 try++ 100 if ctx.Err() != nil { 101 return nil, fmt.Errorf("unable to probe buildet at %s after %d attempts", buildletURL, try) 102 } 103 err := probeBuildlet(ctx, buildletURL, opts) 104 if err == nil { 105 break 106 } 107 log.Printf("probing buildlet at %s with attempt %d failed: %s", buildletURL, try, err) 108 time.Sleep(3 * time.Second) 109 } 110 return NewClient(ipPort, opts.TLS), nil 111 } 112 113 // probeBuildlet attempts to the connect to a buildlet at the provided URL. An error 114 // is returned if it unable to connect to the buildlet. Each request is limited by either 115 // a five second limit or the timeout set in the context. 116 func probeBuildlet(ctx context.Context, buildletURL string, opts *VMOpts) error { 117 cl := &http.Client{ 118 Transport: &http.Transport{ 119 Dial: defaultDialer(), 120 DisableKeepAlives: true, 121 TLSClientConfig: &tls.Config{ 122 InsecureSkipVerify: true, 123 }, 124 }, 125 } 126 if fn := opts.OnBeginBuildletProbe; fn != nil { 127 fn(buildletURL) 128 } 129 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 130 defer cancel() 131 req, err := http.NewRequestWithContext(ctx, http.MethodGet, buildletURL, nil) 132 if err != nil { 133 return fmt.Errorf("error creating buildlet probe request: %w", err) 134 } 135 res, err := cl.Do(req) 136 if fn := opts.OnEndBuildletProbe; fn != nil { 137 fn(res, err) 138 } 139 if err != nil { 140 return fmt.Errorf("error probe buildlet %s: %w", buildletURL, err) 141 } 142 io.ReadAll(res.Body) 143 res.Body.Close() 144 if res.StatusCode != http.StatusOK { 145 return fmt.Errorf("buildlet returned HTTP status code %d for %s", res.StatusCode, buildletURL) 146 } 147 return nil 148 }