github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/googlecompute/driver_gce.go (about) 1 package googlecompute 2 3 import ( 4 "crypto/rand" 5 "crypto/rsa" 6 "crypto/sha1" 7 "encoding/base64" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "log" 12 "net/http" 13 "net/url" 14 "runtime" 15 "strings" 16 "time" 17 18 "google.golang.org/api/compute/v1" 19 20 "github.com/hashicorp/packer/common" 21 "github.com/hashicorp/packer/packer" 22 "github.com/hashicorp/packer/version" 23 24 "golang.org/x/oauth2" 25 "golang.org/x/oauth2/google" 26 "golang.org/x/oauth2/jwt" 27 ) 28 29 // driverGCE is a Driver implementation that actually talks to GCE. 30 // Create an instance using NewDriverGCE. 31 type driverGCE struct { 32 projectId string 33 service *compute.Service 34 ui packer.Ui 35 } 36 37 var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"} 38 39 func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) { 40 var err error 41 42 var client *http.Client 43 44 // Auth with AccountFile first if provided 45 if a.PrivateKey != "" { 46 log.Printf("[INFO] Requesting Google token via AccountFile...") 47 log.Printf("[INFO] -- Email: %s", a.ClientEmail) 48 log.Printf("[INFO] -- Scopes: %s", DriverScopes) 49 log.Printf("[INFO] -- Private Key Length: %d", len(a.PrivateKey)) 50 51 conf := jwt.Config{ 52 Email: a.ClientEmail, 53 PrivateKey: []byte(a.PrivateKey), 54 Scopes: DriverScopes, 55 TokenURL: "https://accounts.google.com/o/oauth2/token", 56 } 57 58 // Initiate an http.Client. The following GET request will be 59 // authorized and authenticated on the behalf of 60 // your service account. 61 client = conf.Client(oauth2.NoContext) 62 } else { 63 log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...") 64 client, err = google.DefaultClient(oauth2.NoContext, DriverScopes...) 65 // The DefaultClient uses the DefaultTokenSource of the google lib. 66 // The DefaultTokenSource uses the "Application Default Credentials" 67 // It looks for credentials in the following places, preferring the first location found: 68 // 1. A JSON file whose path is specified by the 69 // GOOGLE_APPLICATION_CREDENTIALS environment variable. 70 // 2. A JSON file in a location known to the gcloud command-line tool. 71 // On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. 72 // On other systems, $HOME/.config/gcloud/application_default_credentials.json. 73 // 3. On Google App Engine it uses the appengine.AccessToken function. 74 // 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches 75 // credentials from the metadata server. 76 // (In this final case any provided scopes are ignored.) 77 } 78 79 if err != nil { 80 return nil, err 81 } 82 83 log.Printf("[INFO] Instantiating GCE client...") 84 service, err := compute.New(client) 85 // Set UserAgent 86 versionString := version.FormattedVersion() 87 service.UserAgent = fmt.Sprintf( 88 "(%s %s) Packer/%s", runtime.GOOS, runtime.GOARCH, versionString) 89 90 if err != nil { 91 return nil, err 92 } 93 94 return &driverGCE{ 95 projectId: p, 96 service: service, 97 ui: ui, 98 }, nil 99 } 100 101 func (d *driverGCE) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) { 102 gce_image := &compute.Image{ 103 Description: description, 104 Name: name, 105 Family: family, 106 Labels: image_labels, 107 SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk), 108 SourceType: "RAW", 109 } 110 111 imageCh := make(chan *Image, 1) 112 errCh := make(chan error, 1) 113 op, err := d.service.Images.Insert(d.projectId, gce_image).Do() 114 if err != nil { 115 errCh <- err 116 } else { 117 go func() { 118 err = waitForState(errCh, "DONE", d.refreshGlobalOp(op)) 119 if err != nil { 120 close(imageCh) 121 errCh <- err 122 return 123 } 124 var image *Image 125 image, err = d.GetImageFromProject(d.projectId, name, false) 126 if err != nil { 127 close(imageCh) 128 errCh <- err 129 return 130 } 131 imageCh <- image 132 close(imageCh) 133 }() 134 } 135 136 return imageCh, errCh 137 } 138 139 func (d *driverGCE) DeleteImage(name string) <-chan error { 140 errCh := make(chan error, 1) 141 op, err := d.service.Images.Delete(d.projectId, name).Do() 142 if err != nil { 143 errCh <- err 144 } else { 145 go waitForState(errCh, "DONE", d.refreshGlobalOp(op)) 146 } 147 148 return errCh 149 } 150 151 func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) { 152 op, err := d.service.Instances.Delete(d.projectId, zone, name).Do() 153 if err != nil { 154 return nil, err 155 } 156 157 errCh := make(chan error, 1) 158 go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op)) 159 return errCh, nil 160 } 161 162 func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) { 163 op, err := d.service.Disks.Delete(d.projectId, zone, name).Do() 164 if err != nil { 165 return nil, err 166 } 167 168 errCh := make(chan error, 1) 169 go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op)) 170 return errCh, nil 171 } 172 173 func (d *driverGCE) GetImage(name string, fromFamily bool) (*Image, error) { 174 projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud", "gce-nvme"} 175 var errs error 176 for _, project := range projects { 177 image, err := d.GetImageFromProject(project, name, fromFamily) 178 if err != nil { 179 errs = packer.MultiErrorAppend(errs, err) 180 } 181 if image != nil { 182 return image, nil 183 } 184 } 185 186 return nil, fmt.Errorf( 187 "Could not find image, %s, in projects, %s: %s", name, 188 projects, errs) 189 } 190 191 func (d *driverGCE) GetImageFromProject(project, name string, fromFamily bool) (*Image, error) { 192 var ( 193 image *compute.Image 194 err error 195 ) 196 197 if fromFamily { 198 image, err = d.service.Images.GetFromFamily(project, name).Do() 199 } else { 200 image, err = d.service.Images.Get(project, name).Do() 201 } 202 203 if err != nil { 204 return nil, err 205 } else if image == nil || image.SelfLink == "" { 206 return nil, fmt.Errorf("Image, %s, could not be found in project: %s", name, project) 207 } else { 208 return &Image{ 209 Licenses: image.Licenses, 210 Name: image.Name, 211 ProjectId: project, 212 SelfLink: image.SelfLink, 213 SizeGb: image.DiskSizeGb, 214 }, nil 215 } 216 } 217 218 func (d *driverGCE) GetInstanceMetadata(zone, name, key string) (string, error) { 219 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 220 if err != nil { 221 return "", err 222 } 223 224 for _, item := range instance.Metadata.Items { 225 if item.Key == key { 226 return *item.Value, nil 227 } 228 } 229 230 return "", fmt.Errorf("Instance metadata key, %s, not found.", key) 231 } 232 233 func (d *driverGCE) GetNatIP(zone, name string) (string, error) { 234 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 235 if err != nil { 236 return "", err 237 } 238 239 for _, ni := range instance.NetworkInterfaces { 240 if ni.AccessConfigs == nil { 241 continue 242 } 243 for _, ac := range ni.AccessConfigs { 244 if ac.NatIP != "" { 245 return ac.NatIP, nil 246 } 247 } 248 } 249 250 return "", nil 251 } 252 253 func (d *driverGCE) GetInternalIP(zone, name string) (string, error) { 254 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 255 if err != nil { 256 return "", err 257 } 258 259 for _, ni := range instance.NetworkInterfaces { 260 if ni.NetworkIP == "" { 261 continue 262 } 263 return ni.NetworkIP, nil 264 } 265 266 return "", nil 267 } 268 269 func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) { 270 output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, name).Do() 271 if err != nil { 272 return "", err 273 } 274 275 return output.Contents, nil 276 } 277 278 func (d *driverGCE) ImageExists(name string) bool { 279 _, err := d.GetImageFromProject(d.projectId, name, false) 280 // The API may return an error for reasons other than the image not 281 // existing, but this heuristic is sufficient for now. 282 return err == nil 283 } 284 285 func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { 286 // Get the zone 287 d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone)) 288 zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do() 289 if err != nil { 290 return nil, err 291 } 292 293 // Get the machine type 294 d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType)) 295 machineType, err := d.service.MachineTypes.Get( 296 d.projectId, zone.Name, c.MachineType).Do() 297 if err != nil { 298 return nil, err 299 } 300 // TODO(mitchellh): deprecation warnings 301 302 networkSelfLink := "" 303 subnetworkSelfLink := "" 304 305 if u, err := url.Parse(c.Network); err == nil && (u.Scheme == "https" || u.Scheme == "http") { 306 // Network is a full server URL 307 // Parse out Network and NetworkProjectId from URL 308 // https://www.googleapis.com/compute/v1/projects/<ProjectId>/global/networks/<Network> 309 networkSelfLink = c.Network 310 parts := strings.Split(u.String(), "/") 311 if len(parts) >= 10 { 312 c.NetworkProjectId = parts[6] 313 c.Network = parts[9] 314 } 315 } 316 if u, err := url.Parse(c.Subnetwork); err == nil && (u.Scheme == "https" || u.Scheme == "http") { 317 // Subnetwork is a full server URL 318 subnetworkSelfLink = c.Subnetwork 319 } 320 321 // If subnetwork is ID's and not full service URL's look them up. 322 if subnetworkSelfLink == "" { 323 324 // Get the network 325 if c.NetworkProjectId == "" { 326 c.NetworkProjectId = d.projectId 327 } 328 d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network)) 329 network, err := d.service.Networks.Get(c.NetworkProjectId, c.Network).Do() 330 if err != nil { 331 return nil, err 332 } 333 networkSelfLink = network.SelfLink 334 335 // Subnetwork 336 // Validate Subnetwork config now that we have some info about the network 337 if !network.AutoCreateSubnetworks && len(network.Subnetworks) > 0 { 338 // Network appears to be in "custom" mode, so a subnetwork is required 339 if c.Subnetwork == "" { 340 return nil, fmt.Errorf("a subnetwork must be specified") 341 } 342 } 343 // Get the subnetwork 344 if c.Subnetwork != "" { 345 d.ui.Message(fmt.Sprintf("Loading subnetwork: %s for region: %s", c.Subnetwork, c.Region)) 346 subnetwork, err := d.service.Subnetworks.Get(c.NetworkProjectId, c.Region, c.Subnetwork).Do() 347 if err != nil { 348 return nil, err 349 } 350 subnetworkSelfLink = subnetwork.SelfLink 351 } 352 } 353 354 var accessconfig *compute.AccessConfig 355 // Use external IP if OmitExternalIP isn't set 356 if !c.OmitExternalIP { 357 accessconfig = &compute.AccessConfig{ 358 Name: "AccessConfig created by Packer", 359 Type: "ONE_TO_ONE_NAT", 360 } 361 362 // If given a static IP, use it 363 if c.Address != "" { 364 region_url := strings.Split(zone.Region, "/") 365 region := region_url[len(region_url)-1] 366 address, err := d.service.Addresses.Get(d.projectId, region, c.Address).Do() 367 if err != nil { 368 return nil, err 369 } 370 accessconfig.NatIP = address.Address 371 } 372 } 373 374 // Build up the metadata 375 metadata := make([]*compute.MetadataItems, len(c.Metadata)) 376 for k, v := range c.Metadata { 377 vCopy := v 378 metadata = append(metadata, &compute.MetadataItems{ 379 Key: k, 380 Value: &vCopy, 381 }) 382 } 383 384 var guestAccelerators []*compute.AcceleratorConfig 385 if c.AcceleratorCount > 0 { 386 ac := &compute.AcceleratorConfig{ 387 AcceleratorCount: c.AcceleratorCount, 388 AcceleratorType: c.AcceleratorType, 389 } 390 guestAccelerators = append(guestAccelerators, ac) 391 } 392 393 // Create the instance information 394 instance := compute.Instance{ 395 Description: c.Description, 396 Disks: []*compute.AttachedDisk{ 397 { 398 Type: "PERSISTENT", 399 Mode: "READ_WRITE", 400 Kind: "compute#attachedDisk", 401 Boot: true, 402 AutoDelete: false, 403 InitializeParams: &compute.AttachedDiskInitializeParams{ 404 SourceImage: c.Image.SelfLink, 405 DiskSizeGb: c.DiskSizeGb, 406 DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType), 407 }, 408 }, 409 }, 410 GuestAccelerators: guestAccelerators, 411 Labels: c.Labels, 412 MachineType: machineType.SelfLink, 413 Metadata: &compute.Metadata{ 414 Items: metadata, 415 }, 416 Name: c.Name, 417 NetworkInterfaces: []*compute.NetworkInterface{ 418 { 419 AccessConfigs: []*compute.AccessConfig{accessconfig}, 420 Network: networkSelfLink, 421 Subnetwork: subnetworkSelfLink, 422 }, 423 }, 424 Scheduling: &compute.Scheduling{ 425 OnHostMaintenance: c.OnHostMaintenance, 426 Preemptible: c.Preemptible, 427 }, 428 ServiceAccounts: []*compute.ServiceAccount{ 429 { 430 Email: "default", 431 Scopes: c.Scopes, 432 }, 433 }, 434 Tags: &compute.Tags{ 435 Items: c.Tags, 436 }, 437 } 438 439 d.ui.Message("Requesting instance creation...") 440 op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do() 441 if err != nil { 442 return nil, err 443 } 444 445 errCh := make(chan error, 1) 446 go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op)) 447 return errCh, nil 448 } 449 450 func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) { 451 452 errCh := make(chan error, 1) 453 go d.createWindowsPassword(errCh, instance, zone, c) 454 455 return errCh, nil 456 } 457 458 func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) { 459 460 data, err := json.Marshal(c) 461 462 if err != nil { 463 errCh <- err 464 return 465 } 466 dCopy := string(data) 467 468 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 469 instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy}) 470 471 op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{ 472 Fingerprint: instance.Metadata.Fingerprint, 473 Items: instance.Metadata.Items, 474 }).Do() 475 476 if err != nil { 477 errCh <- err 478 return 479 } 480 481 newErrCh := make(chan error, 1) 482 go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op)) 483 484 select { 485 case err = <-newErrCh: 486 case <-time.After(time.Second * 30): 487 err = errors.New("time out while waiting for instance to create") 488 } 489 490 if err != nil { 491 errCh <- err 492 return 493 } 494 495 timeout := time.Now().Add(time.Minute * 3) 496 hash := sha1.New() 497 random := rand.Reader 498 499 for time.Now().Before(timeout) { 500 if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil { 501 for _, response := range passwordResponses { 502 if response.Modulus == c.Modulus { 503 504 decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword) 505 506 if err != nil { 507 errCh <- err 508 return 509 } 510 password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil) 511 512 if err != nil { 513 errCh <- err 514 return 515 } 516 517 c.password = string(password) 518 errCh <- nil 519 return 520 } 521 } 522 } 523 524 time.Sleep(2 * time.Second) 525 } 526 err = errors.New("Could not retrieve password. Timed out.") 527 528 errCh <- err 529 return 530 531 } 532 533 func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) { 534 output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do() 535 536 if err != nil { 537 return nil, err 538 } 539 540 responses := strings.Split(output.Contents, "\n") 541 542 passwordResponses := make([]windowsPasswordResponse, 0, len(responses)) 543 544 for _, response := range responses { 545 var passwordResponse windowsPasswordResponse 546 if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil { 547 continue 548 } 549 550 passwordResponses = append(passwordResponses, passwordResponse) 551 } 552 553 return passwordResponses, nil 554 } 555 556 func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { 557 errCh := make(chan error, 1) 558 go waitForState(errCh, state, d.refreshInstanceState(zone, name)) 559 return errCh 560 } 561 562 func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc { 563 return func() (string, error) { 564 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 565 if err != nil { 566 return "", err 567 } 568 return instance.Status, nil 569 } 570 } 571 572 func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc { 573 return func() (string, error) { 574 newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do() 575 if err != nil { 576 return "", err 577 } 578 579 // If the op is done, check for errors 580 err = nil 581 if newOp.Status == "DONE" { 582 if newOp.Error != nil { 583 for _, e := range newOp.Error.Errors { 584 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 585 } 586 } 587 } 588 589 return newOp.Status, err 590 } 591 } 592 593 func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc { 594 return func() (string, error) { 595 newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do() 596 if err != nil { 597 return "", err 598 } 599 600 // If the op is done, check for errors 601 err = nil 602 if newOp.Status == "DONE" { 603 if newOp.Error != nil { 604 for _, e := range newOp.Error.Errors { 605 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 606 } 607 } 608 } 609 610 return newOp.Status, err 611 } 612 } 613 614 // stateRefreshFunc is used to refresh the state of a thing and is 615 // used in conjunction with waitForState. 616 type stateRefreshFunc func() (string, error) 617 618 // waitForState will spin in a loop forever waiting for state to 619 // reach a certain target. 620 func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { 621 err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { 622 state, err := refresh() 623 if err != nil { 624 return false, err 625 } else if state == target { 626 return true, nil 627 } 628 return false, nil 629 }) 630 errCh <- err 631 return err 632 }