github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/coordinator/buildongce/create.go (about) 1 // Copyright 2014 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 // +build ignore 6 7 package main // import "golang.org/x/tools/dashboard/coordinator/buildongce" 8 9 import ( 10 "bufio" 11 "bytes" 12 "encoding/json" 13 "flag" 14 "fmt" 15 "io" 16 "io/ioutil" 17 "log" 18 "net/http" 19 "os" 20 "strings" 21 "time" 22 23 "code.google.com/p/goauth2/oauth" 24 compute "google.golang.org/api/compute/v1" 25 ) 26 27 var ( 28 proj = flag.String("project", "symbolic-datum-552", "name of Project") 29 zone = flag.String("zone", "us-central1-a", "GCE zone") 30 mach = flag.String("machinetype", "n1-standard-16", "Machine type") 31 instName = flag.String("instance_name", "go-builder-1", "Name of VM instance.") 32 sshPub = flag.String("ssh_public_key", "", "ssh public key file to authorize. Can modify later in Google's web UI anyway.") 33 staticIP = flag.String("static_ip", "", "Static IP to use. If empty, automatic.") 34 reuseDisk = flag.Bool("reuse_disk", true, "Whether disk images should be reused between shutdowns/restarts.") 35 36 writeObject = flag.String("write_object", "", "If non-empty, a VM isn't created and the flag value is Google Cloud Storage bucket/object to write. The contents from stdin.") 37 ) 38 39 func readFile(v string) string { 40 slurp, err := ioutil.ReadFile(v) 41 if err != nil { 42 log.Fatalf("Error reading %s: %v", v, err) 43 } 44 return strings.TrimSpace(string(slurp)) 45 } 46 47 var config = &oauth.Config{ 48 // The client-id and secret should be for an "Installed Application" when using 49 // the CLI. Later we'll use a web application with a callback. 50 ClientId: readFile("client-id.dat"), 51 ClientSecret: readFile("client-secret.dat"), 52 Scope: strings.Join([]string{ 53 compute.DevstorageFull_controlScope, 54 compute.ComputeScope, 55 "https://www.googleapis.com/auth/sqlservice", 56 "https://www.googleapis.com/auth/sqlservice.admin", 57 }, " "), 58 AuthURL: "https://accounts.google.com/o/oauth2/auth", 59 TokenURL: "https://accounts.google.com/o/oauth2/token", 60 RedirectURL: "urn:ietf:wg:oauth:2.0:oob", 61 } 62 63 const baseConfig = `#cloud-config 64 coreos: 65 units: 66 - name: gobuild.service 67 command: start 68 content: | 69 [Unit] 70 Description=Go Builders 71 After=docker.service 72 Requires=docker.service 73 74 [Service] 75 ExecStartPre=/bin/bash -c 'mkdir -p /opt/bin && curl -s -o /opt/bin/coordinator http://storage.googleapis.com/go-builder-data/coordinator && chmod +x /opt/bin/coordinator' 76 ExecStart=/opt/bin/coordinator 77 RestartSec=10s 78 Restart=always 79 Type=simple 80 81 [Install] 82 WantedBy=multi-user.target 83 ` 84 85 func main() { 86 flag.Parse() 87 if *proj == "" { 88 log.Fatalf("Missing --project flag") 89 } 90 prefix := "https://www.googleapis.com/compute/v1/projects/" + *proj 91 machType := prefix + "/zones/" + *zone + "/machineTypes/" + *mach 92 93 tr := &oauth.Transport{ 94 Config: config, 95 } 96 97 tokenCache := oauth.CacheFile("token.dat") 98 token, err := tokenCache.Token() 99 if err != nil { 100 if *writeObject != "" { 101 log.Fatalf("Can't use --write_object without a valid token.dat file already cached.") 102 } 103 log.Printf("Error getting token from %s: %v", string(tokenCache), err) 104 log.Printf("Get auth code from %v", config.AuthCodeURL("my-state")) 105 fmt.Print("\nEnter auth code: ") 106 sc := bufio.NewScanner(os.Stdin) 107 sc.Scan() 108 authCode := strings.TrimSpace(sc.Text()) 109 token, err = tr.Exchange(authCode) 110 if err != nil { 111 log.Fatalf("Error exchanging auth code for a token: %v", err) 112 } 113 tokenCache.PutToken(token) 114 } 115 116 tr.Token = token 117 oauthClient := &http.Client{Transport: tr} 118 if *writeObject != "" { 119 writeCloudStorageObject(oauthClient) 120 return 121 } 122 123 computeService, _ := compute.New(oauthClient) 124 125 natIP := *staticIP 126 if natIP == "" { 127 // Try to find it by name. 128 aggAddrList, err := computeService.Addresses.AggregatedList(*proj).Do() 129 if err != nil { 130 log.Fatal(err) 131 } 132 // https://godoc.org/google.golang.org/api/compute/v1#AddressAggregatedList 133 IPLoop: 134 for _, asl := range aggAddrList.Items { 135 for _, addr := range asl.Addresses { 136 if addr.Name == *instName+"-ip" && addr.Status == "RESERVED" { 137 natIP = addr.Address 138 break IPLoop 139 } 140 } 141 } 142 } 143 144 cloudConfig := baseConfig 145 if *sshPub != "" { 146 key := strings.TrimSpace(readFile(*sshPub)) 147 cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", key) 148 } 149 if os.Getenv("USER") == "bradfitz" { 150 cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwks9dwWKlRC+73gRbvYtVg0vdCwDSuIlyt4z6xa/YU/jTDynM4R4W10hm2tPjy8iR1k8XhDv4/qdxe6m07NjG/By1tkmGpm1mGwho4Pr5kbAAy/Qg+NLCSdAYnnE00FQEcFOC15GFVMOW2AzDGKisReohwH9eIzHPzdYQNPRWXE= bradfitz@papag.bradfitz.com") 151 } 152 const maxCloudConfig = 32 << 10 // per compute API docs 153 if len(cloudConfig) > maxCloudConfig { 154 log.Fatalf("cloud config length of %d bytes is over %d byte limit", len(cloudConfig), maxCloudConfig) 155 } 156 157 instance := &compute.Instance{ 158 Name: *instName, 159 Description: "Go Builder", 160 MachineType: machType, 161 Disks: []*compute.AttachedDisk{instanceDisk(computeService)}, 162 Tags: &compute.Tags{ 163 Items: []string{"http-server", "https-server"}, 164 }, 165 Metadata: &compute.Metadata{ 166 Items: []*compute.MetadataItems{ 167 { 168 Key: "user-data", 169 Value: cloudConfig, 170 }, 171 }, 172 }, 173 NetworkInterfaces: []*compute.NetworkInterface{ 174 &compute.NetworkInterface{ 175 AccessConfigs: []*compute.AccessConfig{ 176 &compute.AccessConfig{ 177 Type: "ONE_TO_ONE_NAT", 178 Name: "External NAT", 179 NatIP: natIP, 180 }, 181 }, 182 Network: prefix + "/global/networks/default", 183 }, 184 }, 185 ServiceAccounts: []*compute.ServiceAccount{ 186 { 187 Email: "default", 188 Scopes: []string{ 189 compute.DevstorageFull_controlScope, 190 compute.ComputeScope, 191 }, 192 }, 193 }, 194 } 195 196 log.Printf("Creating instance...") 197 op, err := computeService.Instances.Insert(*proj, *zone, instance).Do() 198 if err != nil { 199 log.Fatalf("Failed to create instance: %v", err) 200 } 201 opName := op.Name 202 log.Printf("Created. Waiting on operation %v", opName) 203 OpLoop: 204 for { 205 time.Sleep(2 * time.Second) 206 op, err := computeService.ZoneOperations.Get(*proj, *zone, opName).Do() 207 if err != nil { 208 log.Fatalf("Failed to get op %s: %v", opName, err) 209 } 210 switch op.Status { 211 case "PENDING", "RUNNING": 212 log.Printf("Waiting on operation %v", opName) 213 continue 214 case "DONE": 215 if op.Error != nil { 216 for _, operr := range op.Error.Errors { 217 log.Printf("Error: %+v", operr) 218 } 219 log.Fatalf("Failed to start.") 220 } 221 log.Printf("Success. %+v", op) 222 break OpLoop 223 default: 224 log.Fatalf("Unknown status %q: %+v", op.Status, op) 225 } 226 } 227 228 inst, err := computeService.Instances.Get(*proj, *zone, *instName).Do() 229 if err != nil { 230 log.Fatalf("Error getting instance after creation: %v", err) 231 } 232 ij, _ := json.MarshalIndent(inst, "", " ") 233 log.Printf("Instance: %s", ij) 234 } 235 236 func instanceDisk(svc *compute.Service) *compute.AttachedDisk { 237 const imageURL = "https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-alpha-402-2-0-v20140807" 238 diskName := *instName + "-coreos-stateless-pd" 239 240 if *reuseDisk { 241 dl, err := svc.Disks.List(*proj, *zone).Do() 242 if err != nil { 243 log.Fatalf("Error listing disks: %v", err) 244 } 245 for _, disk := range dl.Items { 246 if disk.Name != diskName { 247 continue 248 } 249 return &compute.AttachedDisk{ 250 AutoDelete: false, 251 Boot: true, 252 DeviceName: diskName, 253 Type: "PERSISTENT", 254 Source: disk.SelfLink, 255 Mode: "READ_WRITE", 256 257 // The GCP web UI's "Show REST API" link includes a 258 // "zone" parameter, but it's not in the API 259 // description. But it wants this form (disk.Zone, a 260 // full zone URL, not *zone): 261 // Zone: disk.Zone, 262 // ... but it seems to work without it. Keep this 263 // comment here until I file a bug with the GCP 264 // people. 265 } 266 } 267 } 268 269 return &compute.AttachedDisk{ 270 AutoDelete: !*reuseDisk, 271 Boot: true, 272 Type: "PERSISTENT", 273 InitializeParams: &compute.AttachedDiskInitializeParams{ 274 DiskName: diskName, 275 SourceImage: imageURL, 276 DiskSizeGb: 50, 277 }, 278 } 279 } 280 281 func writeCloudStorageObject(httpClient *http.Client) { 282 content := os.Stdin 283 const maxSlurp = 1 << 20 284 var buf bytes.Buffer 285 n, err := io.CopyN(&buf, content, maxSlurp) 286 if err != nil && err != io.EOF { 287 log.Fatalf("Error reading from stdin: %v, %v", n, err) 288 } 289 contentType := http.DetectContentType(buf.Bytes()) 290 291 req, err := http.NewRequest("PUT", "https://storage.googleapis.com/"+*writeObject, io.MultiReader(&buf, content)) 292 if err != nil { 293 log.Fatal(err) 294 } 295 req.Header.Set("x-goog-api-version", "2") 296 req.Header.Set("x-goog-acl", "public-read") 297 req.Header.Set("Content-Type", contentType) 298 res, err := httpClient.Do(req) 299 if err != nil { 300 log.Fatal(err) 301 } 302 if res.StatusCode != 200 { 303 res.Write(os.Stderr) 304 log.Fatalf("Failed.") 305 } 306 log.Printf("Success.") 307 os.Exit(0) 308 }