/
envs.go
383 lines (334 loc) · 12.7 KB
/
envs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// Copyright 2011 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.
// Package buildenv contains definitions for the
// environments the Go build system can run in.
package buildenv
import (
"context"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"golang.org/x/oauth2/google"
compute "google.golang.org/api/compute/v1"
oauth2api "google.golang.org/api/oauth2/v2"
)
const (
prefix = "https://www.googleapis.com/compute/v1/projects/"
)
// KubeConfig describes the configuration of a Kubernetes cluster.
type KubeConfig struct {
// MinNodes is the minimum number of nodes in the Kubernetes cluster.
// The autoscaler will ensure that at least this many nodes is always
// running despite any scale-down decision.
MinNodes int64
// MaxNodes is the maximum number of nodes that the autoscaler can
// provision in the Kubernetes cluster.
// If MaxNodes is 0, Kubernetes is not used.
MaxNodes int64
// MachineType is the GCE machine type to use for the Kubernetes cluster nodes.
MachineType string
// Name is the name of the Kubernetes cluster that will be created.
Name string
}
// Environment describes the configuration of the infrastructure for a
// coordinator and its buildlet resources running on Google Cloud Platform.
// Staging and Production are the two common build environments.
type Environment struct {
// The GCP project name that the build infrastructure will be provisioned in.
// This field may be overridden as necessary without impacting other fields.
ProjectName string
// ProjectNumber is the GCP project's number, as visible in the admin console.
// This is used for things such as constructing the "email" of the default
// service account.
ProjectNumber int64
// The IsProd flag indicates whether production functionality should be
// enabled. When true, GCE and Kubernetes builders are enabled and the
// coordinator serves on 443. Otherwise, GCE and Kubernetes builders are
// disabled and the coordinator serves on 8119.
IsProd bool
// Zone is the GCE zone that the coordinator instance and Kubernetes cluster
// will run in. This field may be overridden as necessary without impacting
// other fields.
Zone string
// ZonesToClean are the GCE zones that will be periodically cleaned by
// deleting old VMs. The zero value means that no cleaning will occur.
// This field is optional.
ZonesToClean []string
// StaticIP is the public, static IP address that will be attached to the
// coordinator instance. The zero value means the address will be looked
// up by name. This field is optional.
StaticIP string
// MachineType is the GCE machine type to use for the coordinator.
MachineType string
// KubeBuild is the Kubernetes config for the buildlet cluster.
KubeBuild KubeConfig
// KubeTools is the Kubernetes config for the tools cluster.
KubeTools KubeConfig
// PreferContainersOnCOS controls whether we do most builds on
// Google's Container-Optimized OS Linux image running on a VM
// rather than using Kubernetes for builds. This does not
// affect cross-compiled builds just running make.bash. Those
// still use Kubernetes for now.
// See https://golang.org/issue/25108.
PreferContainersOnCOS bool
// DashURL is the base URL of the build dashboard, ending in a slash.
DashURL string
// PerfDataURL is the base URL of the benchmark storage server.
PerfDataURL string
// CoordinatorURL is the location from which the coordinator
// binary will be downloaded.
// This is only used by cmd/coordinator/buildongce/create.go when
// creating the coordinator VM from scratch.
CoordinatorURL string
// CoordinatorName is the hostname of the coordinator instance.
CoordinatorName string
// BuildletBucket is the GCS bucket that stores buildlet binaries.
// TODO: rename. this is not just for buildlets; also for bootstrap.
BuildletBucket string
// LogBucket is the GCS bucket to which logs are written.
LogBucket string
// SnapBucket is the GCS bucket to which snapshots of
// completed builds (after make.bash, before tests) are
// written.
SnapBucket string
// MaxBuilds is the maximum number of concurrent builds that
// can run. Zero means unlimit. This is typically only used
// in a development or staging environment.
MaxBuilds int
// AutoCertCacheBucket is the GCS bucket to use for the
// golang.org/x/crypto/acme/autocert (LetsEncrypt) cache.
// If empty, LetsEncrypt isn't used.
AutoCertCacheBucket string
}
// MachineTypeURI returns the URI for the environment's Machine Type.
func (e Environment) MachineTypeURI() string {
return e.ComputePrefix() + "/zones/" + e.Zone + "/machineTypes/" + e.MachineType
}
// ComputePrefix returns the URI prefix for Compute Engine resources in a project.
func (e Environment) ComputePrefix() string {
return prefix + e.ProjectName
}
// Region returns the GCE region, derived from its zone.
func (e Environment) Region() string {
return e.Zone[:strings.LastIndex(e.Zone, "-")]
}
// SnapshotURL returns the absolute URL of the .tar.gz containing a
// built Go tree for the builderType and Go rev (40 character Git
// commit hash). The tarball is suitable for passing to
// (*buildlet.Client).PutTarFromURL.
func (e Environment) SnapshotURL(builderType, rev string) string {
return fmt.Sprintf("https://storage.googleapis.com/%s/go/%s/%s.tar.gz", e.SnapBucket, builderType, rev)
}
// DashBase returns the base URL of the build dashboard, ending in a slash.
func (e Environment) DashBase() string {
// TODO(quentin): Should we really default to production? That's what the old code did.
if e.DashURL != "" {
return e.DashURL
}
return Production.DashURL
}
// Credentials returns the credentials required to access the GCP environment.
//
// It tries to use, in order:
//
// - the file $HOME/keys/$PROJECT_NAME.key.json
// - the file $HOME/.config/gcloud/legacy_credentials/$ANYTHING@google.com/adc.json
// - the Application Default Credentials (i.e. GCE metadata service, etc)
func (e Environment) Credentials(ctx context.Context) (*google.Credentials, error) {
scopes := []string{
// Cloud Platform should include all others, but the
// old code duplicated compute and the storage full
// control scopes, so I leave them here for now. They
// predated the all-encompassing "cloud platform"
// scope anyway.
// TODO: remove compute and DevstorageFullControlScope once verified to work
// without.
compute.CloudPlatformScope,
compute.ComputeScope,
compute.DevstorageFullControlScope,
// The coordinator needed the userinfo email scope for
// reporting to the perf dashboard running on App
// Engine at one point. The perf dashboard is down at
// the moment, but when it's back up we'll need this,
// and if we do other authenticated requests to App
// Engine apps, this would be useful.
oauth2api.UserinfoEmailScope,
}
// Prefer any "$HOME/keys/$PROJECT.key.json" file first.
keyFile := filepath.Join(os.Getenv("HOME"), "keys", e.ProjectName+".key.json")
if _, err := os.Stat(keyFile); err == nil {
log.Printf("Using credentials from %s", keyFile)
jcred, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
return google.CredentialsFromJSON(ctx, jcred, scopes...)
}
// Then prefer a gcloud @google.com user.
if n := findGoogUserJSON(); n != "" {
jsonData, err := ioutil.ReadFile(n)
if err != nil {
return nil, err
}
creds, err := google.CredentialsFromJSON(ctx, jsonData, scopes...)
if err != nil {
log.Printf("gobuild.NewClient: error loading google user credentials from %s: %v", n, err)
return nil, err
}
log.Printf("Using credentials from %s", n)
return creds, nil
}
creds, err := google.FindDefaultCredentials(ctx, scopes...)
if err != nil {
return nil, err
}
log.Printf("Using Application Default Credentials")
return creds, nil
}
// findGoogUserJSON walks the gcloud config directory and returns the filename of
// $HOME/.config/gcloud/legacy_credentials/USER@google.com/adc.json
// for the first USER found with such a file. On miss it returns the empty string.
func findGoogUserJSON() (jsonFile string) {
gcloud := filepath.Join(os.Getenv("HOME"), ".config/gcloud")
filepath.Walk(gcloud, func(path string, fi os.FileInfo, err error) error {
if jsonFile == "" && err == nil && fi.Mode().IsRegular() && strings.HasSuffix(path, "@google.com/adc.json") &&
strings.HasPrefix(path, filepath.Join(gcloud, "legacy_credentials")+"/") {
jsonFile = path
}
return nil
})
return
}
// ByProjectID returns an Environment for the specified
// project ID. It is currently limited to the symbolic-datum-552
// and go-dashboard-dev projects.
// ByProjectID will panic if the project ID is not known.
func ByProjectID(projectID string) *Environment {
var envKeys []string
for k := range possibleEnvs {
envKeys = append(envKeys, k)
}
var env *Environment
env, ok := possibleEnvs[projectID]
if !ok {
panic(fmt.Sprintf("Can't get buildenv for unknown project %q. Possible envs are %s", projectID, envKeys))
}
return env
}
// Staging defines the environment that the coordinator and build
// infrastructure is deployed to before it is released to production.
// For local dev, override the project with the program's flag to set
// a custom project.
var Staging = &Environment{
ProjectName: "go-dashboard-dev",
ProjectNumber: 302018677728,
IsProd: true,
Zone: "us-central1-f",
ZonesToClean: []string{"us-central1-a", "us-central1-b", "us-central1-f"},
StaticIP: "104.154.113.235",
MachineType: "n1-standard-1",
PreferContainersOnCOS: true,
KubeBuild: KubeConfig{
MinNodes: 1,
MaxNodes: 1, // auto-scaling disabled
Name: "buildlets",
MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
},
KubeTools: KubeConfig{
MinNodes: 3,
MaxNodes: 3,
Name: "go",
MachineType: "n1-standard-4",
},
DashURL: "https://go-dashboard-dev.appspot.com/",
PerfDataURL: "https://perfdata.golang.org",
CoordinatorURL: "https://storage.googleapis.com/dev-go-builder-data/coordinator",
CoordinatorName: "farmer",
BuildletBucket: "dev-go-builder-data",
LogBucket: "dev-go-build-log",
SnapBucket: "dev-go-build-snap",
}
// Production defines the environment that the coordinator and build
// infrastructure is deployed to for production usage at build.golang.org.
var Production = &Environment{
ProjectName: "symbolic-datum-552",
ProjectNumber: 872405196845,
IsProd: true,
Zone: "us-central1-f",
ZonesToClean: []string{"us-central1-f"},
StaticIP: "107.178.219.46",
MachineType: "n1-standard-4",
PreferContainersOnCOS: true,
KubeBuild: KubeConfig{
MinNodes: 2,
MaxNodes: 2, // auto-scaling disabled
Name: "buildlets",
MachineType: "n1-standard-4", // only used for make.bash due to PreferContainersOnCOS
},
KubeTools: KubeConfig{
MinNodes: 4,
MaxNodes: 4,
Name: "go",
MachineType: "n1-standard-4",
},
DashURL: "https://build.golang.org/",
PerfDataURL: "https://perfdata.golang.org",
CoordinatorURL: "https://storage.googleapis.com/go-builder-data/coordinator",
CoordinatorName: "farmer",
BuildletBucket: "go-builder-data",
LogBucket: "go-build-log",
SnapBucket: "go-build-snap",
AutoCertCacheBucket: "farmer-golang-org-autocert-cache",
}
var Development = &Environment{
IsProd: false,
StaticIP: "127.0.0.1",
}
// possibleEnvs enumerate the known buildenv.Environment definitions.
var possibleEnvs = map[string]*Environment{
"dev": Development,
"symbolic-datum-552": Production,
"go-dashboard-dev": Staging,
}
var (
stagingFlag bool
localDevFlag bool
registeredFlags bool
)
// RegisterFlags registers the "staging" and "localdev" flags.
func RegisterFlags() {
if registeredFlags {
panic("duplicate call to RegisterFlags or RegisterStagingFlag")
}
flag.BoolVar(&localDevFlag, "localdev", false, "use the localhost in-development coordinator")
RegisterStagingFlag()
registeredFlags = true
}
// RegisterStagingFlag registers the "staging" flag.
func RegisterStagingFlag() {
if registeredFlags {
panic("duplicate call to RegisterFlags or RegisterStagingFlag")
}
flag.BoolVar(&stagingFlag, "staging", false, "use the staging build coordinator and buildlets")
registeredFlags = true
}
// FromFlags returns the build environment specified from flags,
// as registered by RegisterFlags or RegisterStagingFlag.
// By default it returns the production environment.
func FromFlags() *Environment {
if !registeredFlags {
panic("FromFlags called without RegisterFlags")
}
if localDevFlag {
return Development
}
if stagingFlag {
return Staging
}
return Production
}