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