golang.org/x/build@v0.0.0-20240506185731-218518f32b70/buildenv/envs.go (about) 1 // Copyright 2011 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 buildenv contains definitions for the 6 // environments the Go build system can run in. 7 package buildenv 8 9 import ( 10 "context" 11 "flag" 12 "fmt" 13 "log" 14 "math/rand" 15 "os" 16 "path/filepath" 17 "sync" 18 19 "golang.org/x/oauth2" 20 "golang.org/x/oauth2/google" 21 compute "google.golang.org/api/compute/v1" 22 oauth2api "google.golang.org/api/oauth2/v2" 23 ) 24 25 const ( 26 prefix = "https://www.googleapis.com/compute/v1/projects/" 27 ) 28 29 // KubeConfig describes the configuration of a Kubernetes cluster. 30 type KubeConfig struct { 31 // The zone of the cluster. Autopilot clusters have no single zone. 32 Zone string 33 34 // The region of the cluster. 35 Region string 36 37 // Name is the name of the Kubernetes cluster that will be used. 38 Name string 39 40 // Namespace is the Kubernetes namespace to use within the cluster. 41 Namespace string 42 } 43 44 // Location returns the zone or if unset, the region of the cluster. 45 // This is the string to use as the "zone" of the cluster when connecting to it 46 // with kubectl. 47 func (kc KubeConfig) Location() string { 48 if kc.Zone != "" { 49 return kc.Zone 50 } 51 if kc.Region != "" { 52 return kc.Region 53 } 54 panic(fmt.Sprintf("KubeConfig has neither zone nor region: %#v", kc)) 55 } 56 57 // Environment describes the configuration of the infrastructure for a 58 // coordinator and its buildlet resources running on Google Cloud Platform. 59 // Staging and Production are the two common build environments. 60 type Environment struct { 61 // The GCP project name that the build infrastructure will be provisioned in. 62 // This field may be overridden as necessary without impacting other fields. 63 ProjectName string 64 65 // ProjectNumber is the GCP build infrastructure project's number, as visible 66 // in the admin console. This is used for things such as constructing the 67 // "email" of the default service account. 68 ProjectNumber int64 69 70 // The GCP project name for the Go project, where build status is stored. 71 // This field may be overridden as necessary without impacting other fields. 72 GoProjectName string 73 74 // The IsProd flag indicates whether production functionality should be 75 // enabled. When true, GCE and Kubernetes builders are enabled and the 76 // coordinator serves on 443. Otherwise, GCE and Kubernetes builders are 77 // disabled and the coordinator serves on 8119. 78 IsProd bool 79 80 // VMRegion is the region we deploy build VMs to. 81 VMRegion string 82 83 // VMZones are the GCE zones that the VMs will be deployed to. These 84 // GCE zones will be periodically cleaned by deleting old VMs. The zones 85 // should all exist within VMRegion. 86 VMZones []string 87 88 // StaticIP is the public, static IP address that will be attached to the 89 // coordinator instance. The zero value means the address will be looked 90 // up by name. This field is optional. 91 StaticIP string 92 93 // KubeServices is the cluster that runs the coordinator and other services. 94 KubeServices KubeConfig 95 96 // DashURL is the base URL of the build dashboard, ending in a slash. 97 DashURL string 98 99 // PerfDataURL is the base URL of the benchmark storage server. 100 PerfDataURL string 101 102 // CoordinatorName is the hostname of the coordinator instance. 103 CoordinatorName string 104 105 // BuildletBucket is the GCS bucket that stores buildlet binaries. 106 // TODO: rename. this is not just for buildlets; also for bootstrap. 107 BuildletBucket string 108 109 // LogBucket is the GCS bucket to which logs are written. 110 LogBucket string 111 112 // SnapBucket is the GCS bucket to which snapshots of 113 // completed builds (after make.bash, before tests) are 114 // written. 115 SnapBucket string 116 117 // MaxBuilds is the maximum number of concurrent builds that 118 // can run. Zero means unlimited. This is typically only used 119 // in a development or staging environment. 120 MaxBuilds int 121 122 // COSServiceAccount (Container Optimized OS) is the service 123 // account that will be assigned to a VM instance that hosts 124 // a container when the instance is created. 125 COSServiceAccount string 126 127 // AWSSecurityGroup is the security group name that any VM instance 128 // created on EC2 should contain. These security groups are 129 // collections of firewall rules to be applied to the VM. 130 AWSSecurityGroup string 131 132 // AWSRegion is the region where AWS resources are deployed. 133 AWSRegion string 134 135 // iapServiceIDs is a map of service-backends to service IDs for the backend 136 // services used by IAP enabled HTTP paths. 137 // map[backend-service-name]service_id 138 iapServiceIDs map[string]string 139 140 // GomoteTransferBucket is the bucket used by the gomote GRPC service 141 // to transfer files between gomote clients and the gomote instances. 142 GomoteTransferBucket string 143 } 144 145 // ComputePrefix returns the URI prefix for Compute Engine resources in a project. 146 func (e Environment) ComputePrefix() string { 147 return prefix + e.ProjectName 148 } 149 150 // RandomVMZone returns a randomly selected zone from the zones in VMZones. 151 func (e Environment) RandomVMZone() string { 152 return e.VMZones[rand.Intn(len(e.VMZones))] 153 } 154 155 // SnapshotURL returns the absolute URL of the .tar.gz containing a 156 // built Go tree for the builderType and Go rev (40 character Git 157 // commit hash). The tarball is suitable for passing to 158 // (buildlet.Client).PutTarFromURL. 159 func (e Environment) SnapshotURL(builderType, rev string) string { 160 return fmt.Sprintf("https://storage.googleapis.com/%s/go/%s/%s.tar.gz", e.SnapBucket, builderType, rev) 161 } 162 163 // DashBase returns the base URL of the build dashboard, ending in a slash. 164 func (e Environment) DashBase() string { 165 // TODO(quentin): Should we really default to production? That's what the old code did. 166 if e.DashURL != "" { 167 return e.DashURL 168 } 169 return Production.DashURL 170 } 171 172 // Credentials returns the credentials required to access the GCP environment 173 // with the necessary scopes. 174 func (e Environment) Credentials(ctx context.Context) (*google.Credentials, error) { 175 // TODO: this method used to do much more. maybe remove it 176 // when TODO below is addressed, pushing scopes to caller? Or 177 // add a Scopes func/method somewhere instead? 178 scopes := []string{ 179 // Cloud Platform should include all others, but the 180 // old code duplicated compute and the storage full 181 // control scopes, so I leave them here for now. They 182 // predated the all-encompassing "cloud platform" 183 // scope anyway. 184 // TODO: remove compute and DevstorageFullControlScope once verified to work 185 // without. 186 compute.CloudPlatformScope, 187 compute.ComputeScope, 188 compute.DevstorageFullControlScope, 189 190 // The coordinator needed the userinfo email scope for 191 // reporting to the perf dashboard running on App 192 // Engine at one point. The perf dashboard is down at 193 // the moment, but when it's back up we'll need this, 194 // and if we do other authenticated requests to App 195 // Engine apps, this would be useful. 196 oauth2api.UserinfoEmailScope, 197 } 198 creds, err := google.FindDefaultCredentials(ctx, scopes...) 199 if err != nil { 200 CheckUserCredentials() 201 return nil, err 202 } 203 creds.TokenSource = diagnoseFailureTokenSource{creds.TokenSource} 204 return creds, nil 205 } 206 207 // IAPServiceID returns the service id for the backend service. If a path does not exist for a 208 // backend, the service id will be an empty string. 209 func (e Environment) IAPServiceID(backendServiceName string) string { 210 if v, ok := e.iapServiceIDs[backendServiceName]; ok { 211 return v 212 } 213 return "" 214 } 215 216 // ByProjectID returns an Environment for the specified 217 // project ID. It is currently limited to the symbolic-datum-552 218 // and go-dashboard-dev projects. 219 // ByProjectID will panic if the project ID is not known. 220 func ByProjectID(projectID string) *Environment { 221 var envKeys []string 222 223 for k := range possibleEnvs { 224 envKeys = append(envKeys, k) 225 } 226 227 var env *Environment 228 env, ok := possibleEnvs[projectID] 229 if !ok { 230 panic(fmt.Sprintf("Can't get buildenv for unknown project %q. Possible envs are %s", projectID, envKeys)) 231 } 232 233 return env 234 } 235 236 // Staging defines the environment that the coordinator and build 237 // infrastructure is deployed to before it is released to production. 238 // For local dev, override the project with the program's flag to set 239 // a custom project. 240 var Staging = &Environment{ 241 ProjectName: "go-dashboard-dev", 242 ProjectNumber: 302018677728, 243 GoProjectName: "go-dashboard-dev", 244 IsProd: true, 245 VMRegion: "us-central1", 246 VMZones: []string{"us-central1-a", "us-central1-b", "us-central1-c", "us-central1-f"}, 247 StaticIP: "104.154.113.235", 248 KubeServices: KubeConfig{ 249 Zone: "us-central1-f", 250 Region: "us-central1", 251 Name: "go", 252 Namespace: "default", 253 }, 254 DashURL: "https://build-staging.golang.org/", 255 PerfDataURL: "https://perfdata.golang.org", 256 CoordinatorName: "farmer", 257 BuildletBucket: "dev-go-builder-data", 258 LogBucket: "dev-go-build-log", 259 SnapBucket: "dev-go-build-snap", 260 COSServiceAccount: "linux-cos-builders@go-dashboard-dev.iam.gserviceaccount.com", 261 AWSSecurityGroup: "staging-go-builders", 262 AWSRegion: "us-east-1", 263 iapServiceIDs: map[string]string{}, 264 } 265 266 // Production defines the environment that the coordinator and build 267 // infrastructure is deployed to for production usage at build.golang.org. 268 var Production = &Environment{ 269 ProjectName: "symbolic-datum-552", 270 ProjectNumber: 872405196845, 271 GoProjectName: "golang-org", 272 IsProd: true, 273 VMRegion: "us-central1", 274 VMZones: []string{"us-central1-a", "us-central1-b", "us-central1-f"}, 275 StaticIP: "107.178.219.46", 276 KubeServices: KubeConfig{ 277 Region: "us-central1", 278 Name: "services", 279 Namespace: "prod", 280 }, 281 DashURL: "https://build.golang.org/", 282 PerfDataURL: "https://perfdata.golang.org", 283 CoordinatorName: "farmer", 284 BuildletBucket: "go-builder-data", 285 LogBucket: "go-build-log", 286 SnapBucket: "go-build-snap", 287 COSServiceAccount: "linux-cos-builders@symbolic-datum-552.iam.gserviceaccount.com", 288 AWSSecurityGroup: "go-builders", 289 AWSRegion: "us-east-2", 290 iapServiceIDs: map[string]string{ 291 "coordinator-internal-iap": "7963570695201399464", 292 "relui-internal": "155577380958854618", 293 }, 294 GomoteTransferBucket: "gomote-transfer", 295 } 296 297 var LUCIProduction = &Environment{ 298 ProjectName: "golang-ci-luci", 299 ProjectNumber: 257595674695, 300 IsProd: true, 301 GomoteTransferBucket: "gomote-luci-transfer", 302 } 303 304 var Development = &Environment{ 305 GoProjectName: "golang-org", 306 IsProd: false, 307 StaticIP: "127.0.0.1", 308 PerfDataURL: "http://localhost:8081", 309 } 310 311 // possibleEnvs enumerate the known buildenv.Environment definitions. 312 var possibleEnvs = map[string]*Environment{ 313 "dev": Development, 314 "symbolic-datum-552": Production, 315 "go-dashboard-dev": Staging, 316 "golang-ci-luci": LUCIProduction, 317 } 318 319 var ( 320 stagingFlag bool 321 localDevFlag bool 322 registeredFlags bool 323 ) 324 325 // RegisterFlags registers the "staging" and "localdev" flags. 326 func RegisterFlags() { 327 if registeredFlags { 328 panic("duplicate call to RegisterFlags or RegisterStagingFlag") 329 } 330 flag.BoolVar(&localDevFlag, "localdev", false, "use the localhost in-development coordinator") 331 RegisterStagingFlag() 332 registeredFlags = true 333 } 334 335 // RegisterStagingFlag registers the "staging" flag. 336 func RegisterStagingFlag() { 337 if registeredFlags { 338 panic("duplicate call to RegisterFlags or RegisterStagingFlag") 339 } 340 flag.BoolVar(&stagingFlag, "staging", false, "use the staging build coordinator and buildlets") 341 registeredFlags = true 342 } 343 344 // FromFlags returns the build environment specified from flags, 345 // as registered by RegisterFlags or RegisterStagingFlag. 346 // By default it returns the production environment. 347 func FromFlags() *Environment { 348 if !registeredFlags { 349 panic("FromFlags called without RegisterFlags") 350 } 351 if localDevFlag { 352 return Development 353 } 354 if stagingFlag { 355 return Staging 356 } 357 return Production 358 } 359 360 // warnCredsOnce guards CheckUserCredentials spamming stderr. Once is enough. 361 var warnCredsOnce sync.Once 362 363 // CheckUserCredentials warns if the gcloud Application Default Credentials file doesn't exist 364 // and says how to log in properly. 365 func CheckUserCredentials() { 366 adcJSON := filepath.Join(os.Getenv("HOME"), ".config/gcloud/application_default_credentials.json") 367 if _, err := os.Stat(adcJSON); os.IsNotExist(err) { 368 warnCredsOnce.Do(func() { 369 log.Printf("warning: file %s does not exist; did you run 'gcloud auth application-default login' ? (The 'application-default' part matters, confusingly.)", adcJSON) 370 }) 371 } 372 } 373 374 // diagnoseFailureTokenSource is an oauth2.TokenSource wrapper that, 375 // upon failure, diagnoses why the token acquistion might've failed. 376 type diagnoseFailureTokenSource struct { 377 ts oauth2.TokenSource 378 } 379 380 func (ts diagnoseFailureTokenSource) Token() (*oauth2.Token, error) { 381 t, err := ts.ts.Token() 382 if err != nil { 383 CheckUserCredentials() 384 return nil, err 385 } 386 return t, nil 387 }