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.
24 "golang.org/x/oauth2/google"
25 compute "google.golang.org/api/compute/v1"
29 proj = flag.String("project", "symbolic-datum-552", "name of Project")
30 zone = flag.String("zone", "us-central1-a", "GCE zone")
31 mach = flag.String("machinetype", "n1-standard-1", "Machine type")
32 instName = flag.String("instance_name", "http2-demo", "Name of VM instance.")
33 sshPub = flag.String("ssh_public_key", "", "ssh public key file to authorize. Can modify later in Google's web UI anyway.")
34 staticIP = flag.String("static_ip", "130.211.116.44", "Static IP to use. If empty, automatic.")
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 publicObject = flag.Bool("write_object_is_public", false, "Whether the object created by --write_object should be public.")
40 func readFile(v string) string {
41 slurp, err := ioutil.ReadFile(v)
43 log.Fatalf("Error reading %s: %v", v, err)
45 return strings.TrimSpace(string(slurp))
48 var config = &oauth2.Config{
49 // The client-id and secret should be for an "Installed Application" when using
50 // the CLI. Later we'll use a web application with a callback.
51 ClientID: readFile("client-id.dat"),
52 ClientSecret: readFile("client-secret.dat"),
53 Endpoint: google.Endpoint,
55 compute.DevstorageFullControlScope,
57 "https://www.googleapis.com/auth/sqlservice",
58 "https://www.googleapis.com/auth/sqlservice.admin",
60 RedirectURL: "urn:ietf:wg:oauth:2.0:oob",
63 const baseConfig = `#cloud-config
66 - name: h2demo.service
70 Description=HTTP2 Demo
73 ExecStartPre=/bin/bash -c 'mkdir -p /opt/bin && curl -s -o /opt/bin/h2demo http://storage.googleapis.com/http2-demo-server-tls/h2demo && chmod +x /opt/bin/h2demo'
74 ExecStart=/opt/bin/h2demo --prod
80 WantedBy=multi-user.target
86 log.Fatalf("Missing --project flag")
88 prefix := "https://www.googleapis.com/compute/v1/projects/" + *proj
89 machType := prefix + "/zones/" + *zone + "/machineTypes/" + *mach
91 const tokenFileName = "token.dat"
92 tokenFile := tokenCacheFile(tokenFileName)
93 tokenSource := oauth2.ReuseTokenSource(nil, tokenFile)
94 token, err := tokenSource.Token()
96 if *writeObject != "" {
97 log.Fatalf("Can't use --write_object without a valid token.dat file already cached.")
99 log.Printf("Error getting token from %s: %v", tokenFileName, err)
100 log.Printf("Get auth code from %v", config.AuthCodeURL("my-state"))
101 fmt.Print("\nEnter auth code: ")
102 sc := bufio.NewScanner(os.Stdin)
104 authCode := strings.TrimSpace(sc.Text())
105 token, err = config.Exchange(oauth2.NoContext, authCode)
107 log.Fatalf("Error exchanging auth code for a token: %v", err)
109 if err := tokenFile.WriteToken(token); err != nil {
110 log.Fatalf("Error writing to %s: %v", tokenFileName, err)
112 tokenSource = oauth2.ReuseTokenSource(token, nil)
115 oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
117 if *writeObject != "" {
118 writeCloudStorageObject(oauthClient)
122 computeService, _ := compute.New(oauthClient)
126 // Try to find it by name.
127 aggAddrList, err := computeService.Addresses.AggregatedList(*proj).Do()
131 // http://godoc.org/code.google.com/p/google-api-go-client/compute/v1#AddressAggregatedList
133 for _, asl := range aggAddrList.Items {
134 for _, addr := range asl.Addresses {
135 if addr.Name == *instName+"-ip" && addr.Status == "RESERVED" {
143 cloudConfig := baseConfig
145 key := strings.TrimSpace(readFile(*sshPub))
146 cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", key)
148 if os.Getenv("USER") == "bradfitz" {
149 cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwks9dwWKlRC+73gRbvYtVg0vdCwDSuIlyt4z6xa/YU/jTDynM4R4W10hm2tPjy8iR1k8XhDv4/qdxe6m07NjG/By1tkmGpm1mGwho4Pr5kbAAy/Qg+NLCSdAYnnE00FQEcFOC15GFVMOW2AzDGKisReohwH9eIzHPzdYQNPRWXE= bradfitz@papag.bradfitz.com")
151 const maxCloudConfig = 32 << 10 // per compute API docs
152 if len(cloudConfig) > maxCloudConfig {
153 log.Fatalf("cloud config length of %d bytes is over %d byte limit", len(cloudConfig), maxCloudConfig)
156 instance := &compute.Instance{
158 Description: "Go Builder",
159 MachineType: machType,
160 Disks: []*compute.AttachedDisk{instanceDisk(computeService)},
162 Items: []string{"http-server", "https-server"},
164 Metadata: &compute.Metadata{
165 Items: []*compute.MetadataItems{
172 NetworkInterfaces: []*compute.NetworkInterface{
174 AccessConfigs: []*compute.AccessConfig{
176 Type: "ONE_TO_ONE_NAT",
177 Name: "External NAT",
181 Network: prefix + "/global/networks/default",
184 ServiceAccounts: []*compute.ServiceAccount{
188 compute.DevstorageFullControlScope,
189 compute.ComputeScope,
195 log.Printf("Creating instance...")
196 op, err := computeService.Instances.Insert(*proj, *zone, instance).Do()
198 log.Fatalf("Failed to create instance: %v", err)
201 log.Printf("Created. Waiting on operation %v", opName)
204 time.Sleep(2 * time.Second)
205 op, err := computeService.ZoneOperations.Get(*proj, *zone, opName).Do()
207 log.Fatalf("Failed to get op %s: %v", opName, err)
210 case "PENDING", "RUNNING":
211 log.Printf("Waiting on operation %v", opName)
215 for _, operr := range op.Error.Errors {
216 log.Printf("Error: %+v", operr)
218 log.Fatalf("Failed to start.")
220 log.Printf("Success. %+v", op)
223 log.Fatalf("Unknown status %q: %+v", op.Status, op)
227 inst, err := computeService.Instances.Get(*proj, *zone, *instName).Do()
229 log.Fatalf("Error getting instance after creation: %v", err)
231 ij, _ := json.MarshalIndent(inst, "", " ")
232 log.Printf("Instance: %s", ij)
235 func instanceDisk(svc *compute.Service) *compute.AttachedDisk {
236 const imageURL = "https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-stable-444-5-0-v20141016"
237 diskName := *instName + "-disk"
239 return &compute.AttachedDisk{
243 InitializeParams: &compute.AttachedDiskInitializeParams{
245 SourceImage: imageURL,
251 func writeCloudStorageObject(httpClient *http.Client) {
253 const maxSlurp = 1 << 20
255 n, err := io.CopyN(&buf, content, maxSlurp)
256 if err != nil && err != io.EOF {
257 log.Fatalf("Error reading from stdin: %v, %v", n, err)
259 contentType := http.DetectContentType(buf.Bytes())
261 req, err := http.NewRequest("PUT", "https://storage.googleapis.com/"+*writeObject, io.MultiReader(&buf, content))
265 req.Header.Set("x-goog-api-version", "2")
267 req.Header.Set("x-goog-acl", "public-read")
269 req.Header.Set("Content-Type", contentType)
270 res, err := httpClient.Do(req)
274 if res.StatusCode != 200 {
276 log.Fatalf("Failed.")
278 log.Printf("Success.")
282 type tokenCacheFile string
284 func (f tokenCacheFile) Token() (*oauth2.Token, error) {
285 slurp, err := ioutil.ReadFile(string(f))
289 t := new(oauth2.Token)
290 if err := json.Unmarshal(slurp, t); err != nil {
296 func (f tokenCacheFile) WriteToken(t *oauth2.Token) error {
297 jt, err := json.Marshal(t)
301 return ioutil.WriteFile(string(f), jt, 0600)