You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
8.5 KiB

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