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  }