github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/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{d.projectId, "centos-cloud", "coreos-cloud", "cos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud", "gce-nvme", "windows-sql-cloud", "rhel-sap-cloud"} 172 var errs error 173 for _, project := range projects { 174 image, err := d.GetImageFromProject(project, name, fromFamily) 175 if err != nil { 176 errs = packer.MultiErrorAppend(errs, err) 177 } 178 if image != nil { 179 return image, nil 180 } 181 } 182 183 return nil, fmt.Errorf( 184 "Could not find image, %s, in projects, %s: %s", name, 185 projects, errs) 186 } 187 188 func (d *driverGCE) GetImageFromProject(project, name string, fromFamily bool) (*Image, error) { 189 var ( 190 image *compute.Image 191 err error 192 ) 193 194 if fromFamily { 195 image, err = d.service.Images.GetFromFamily(project, name).Do() 196 } else { 197 image, err = d.service.Images.Get(project, name).Do() 198 } 199 200 if err != nil { 201 return nil, err 202 } else if image == nil || image.SelfLink == "" { 203 return nil, fmt.Errorf("Image, %s, could not be found in project: %s", name, project) 204 } else { 205 return &Image{ 206 Licenses: image.Licenses, 207 Name: image.Name, 208 ProjectId: project, 209 SelfLink: image.SelfLink, 210 SizeGb: image.DiskSizeGb, 211 }, nil 212 } 213 } 214 215 func (d *driverGCE) GetInstanceMetadata(zone, name, key string) (string, error) { 216 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 217 if err != nil { 218 return "", err 219 } 220 221 for _, item := range instance.Metadata.Items { 222 if item.Key == key { 223 return *item.Value, nil 224 } 225 } 226 227 return "", fmt.Errorf("Instance metadata key, %s, not found.", key) 228 } 229 230 func (d *driverGCE) GetNatIP(zone, name string) (string, error) { 231 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 232 if err != nil { 233 return "", err 234 } 235 236 for _, ni := range instance.NetworkInterfaces { 237 if ni.AccessConfigs == nil { 238 continue 239 } 240 for _, ac := range ni.AccessConfigs { 241 if ac.NatIP != "" { 242 return ac.NatIP, nil 243 } 244 } 245 } 246 247 return "", nil 248 } 249 250 func (d *driverGCE) GetInternalIP(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.NetworkIP == "" { 258 continue 259 } 260 return ni.NetworkIP, nil 261 } 262 263 return "", nil 264 } 265 266 func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) { 267 output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, name).Do() 268 if err != nil { 269 return "", err 270 } 271 272 return output.Contents, nil 273 } 274 275 func (d *driverGCE) ImageExists(name string) bool { 276 _, err := d.GetImageFromProject(d.projectId, name, false) 277 // The API may return an error for reasons other than the image not 278 // existing, but this heuristic is sufficient for now. 279 return err == nil 280 } 281 282 func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { 283 // Get the zone 284 d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone)) 285 zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do() 286 if err != nil { 287 return nil, err 288 } 289 290 // Get the machine type 291 d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType)) 292 machineType, err := d.service.MachineTypes.Get( 293 d.projectId, zone.Name, c.MachineType).Do() 294 if err != nil { 295 return nil, err 296 } 297 // TODO(mitchellh): deprecation warnings 298 299 networkId, subnetworkId, err := getNetworking(c) 300 if err != nil { 301 return nil, err 302 } 303 304 var accessconfig *compute.AccessConfig 305 // Use external IP if OmitExternalIP isn't set 306 if !c.OmitExternalIP { 307 accessconfig = &compute.AccessConfig{ 308 Name: "AccessConfig created by Packer", 309 Type: "ONE_TO_ONE_NAT", 310 } 311 312 // If given a static IP, use it 313 if c.Address != "" { 314 region_url := strings.Split(zone.Region, "/") 315 region := region_url[len(region_url)-1] 316 address, err := d.service.Addresses.Get(d.projectId, region, c.Address).Do() 317 if err != nil { 318 return nil, err 319 } 320 accessconfig.NatIP = address.Address 321 } 322 } 323 324 // Build up the metadata 325 metadata := make([]*compute.MetadataItems, len(c.Metadata)) 326 for k, v := range c.Metadata { 327 vCopy := v 328 metadata = append(metadata, &compute.MetadataItems{ 329 Key: k, 330 Value: &vCopy, 331 }) 332 } 333 334 var guestAccelerators []*compute.AcceleratorConfig 335 if c.AcceleratorCount > 0 { 336 ac := &compute.AcceleratorConfig{ 337 AcceleratorCount: c.AcceleratorCount, 338 AcceleratorType: c.AcceleratorType, 339 } 340 guestAccelerators = append(guestAccelerators, ac) 341 } 342 343 // Configure the instance's service account. If the user has set 344 // disable_default_service_account, then the default service account 345 // will not be used. If they also do not set service_account_email, then 346 // the instance will be created with no service account or scopes. 347 serviceAccount := &compute.ServiceAccount{} 348 if !c.DisableDefaultServiceAccount { 349 serviceAccount.Email = "default" 350 serviceAccount.Scopes = c.Scopes 351 } 352 if c.ServiceAccountEmail != "" { 353 serviceAccount.Email = c.ServiceAccountEmail 354 serviceAccount.Scopes = c.Scopes 355 } 356 357 // Create the instance information 358 instance := compute.Instance{ 359 Description: c.Description, 360 Disks: []*compute.AttachedDisk{ 361 { 362 Type: "PERSISTENT", 363 Mode: "READ_WRITE", 364 Kind: "compute#attachedDisk", 365 Boot: true, 366 AutoDelete: false, 367 InitializeParams: &compute.AttachedDiskInitializeParams{ 368 SourceImage: c.Image.SelfLink, 369 DiskSizeGb: c.DiskSizeGb, 370 DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType), 371 }, 372 }, 373 }, 374 GuestAccelerators: guestAccelerators, 375 Labels: c.Labels, 376 MachineType: machineType.SelfLink, 377 Metadata: &compute.Metadata{ 378 Items: metadata, 379 }, 380 Name: c.Name, 381 NetworkInterfaces: []*compute.NetworkInterface{ 382 { 383 AccessConfigs: []*compute.AccessConfig{accessconfig}, 384 Network: networkId, 385 Subnetwork: subnetworkId, 386 }, 387 }, 388 Scheduling: &compute.Scheduling{ 389 OnHostMaintenance: c.OnHostMaintenance, 390 Preemptible: c.Preemptible, 391 }, 392 ServiceAccounts: []*compute.ServiceAccount{ 393 serviceAccount, 394 }, 395 Tags: &compute.Tags{ 396 Items: c.Tags, 397 }, 398 } 399 400 d.ui.Message("Requesting instance creation...") 401 op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do() 402 if err != nil { 403 return nil, err 404 } 405 406 errCh := make(chan error, 1) 407 go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op)) 408 return errCh, nil 409 } 410 411 func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) { 412 413 errCh := make(chan error, 1) 414 go d.createWindowsPassword(errCh, instance, zone, c) 415 416 return errCh, nil 417 } 418 419 func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) { 420 421 data, err := json.Marshal(c) 422 423 if err != nil { 424 errCh <- err 425 return 426 } 427 dCopy := string(data) 428 429 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 430 instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy}) 431 432 op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{ 433 Fingerprint: instance.Metadata.Fingerprint, 434 Items: instance.Metadata.Items, 435 }).Do() 436 437 if err != nil { 438 errCh <- err 439 return 440 } 441 442 newErrCh := make(chan error, 1) 443 go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op)) 444 445 select { 446 case err = <-newErrCh: 447 case <-time.After(time.Second * 30): 448 err = errors.New("time out while waiting for instance to create") 449 } 450 451 if err != nil { 452 errCh <- err 453 return 454 } 455 456 timeout := time.Now().Add(time.Minute * 3) 457 hash := sha1.New() 458 random := rand.Reader 459 460 for time.Now().Before(timeout) { 461 if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil { 462 for _, response := range passwordResponses { 463 if response.Modulus == c.Modulus { 464 465 decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword) 466 467 if err != nil { 468 errCh <- err 469 return 470 } 471 password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil) 472 473 if err != nil { 474 errCh <- err 475 return 476 } 477 478 c.password = string(password) 479 errCh <- nil 480 return 481 } 482 } 483 } 484 485 time.Sleep(2 * time.Second) 486 } 487 err = errors.New("Could not retrieve password. Timed out.") 488 489 errCh <- err 490 return 491 492 } 493 494 func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) { 495 output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do() 496 497 if err != nil { 498 return nil, err 499 } 500 501 responses := strings.Split(output.Contents, "\n") 502 503 passwordResponses := make([]windowsPasswordResponse, 0, len(responses)) 504 505 for _, response := range responses { 506 var passwordResponse windowsPasswordResponse 507 if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil { 508 continue 509 } 510 511 passwordResponses = append(passwordResponses, passwordResponse) 512 } 513 514 return passwordResponses, nil 515 } 516 517 func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { 518 errCh := make(chan error, 1) 519 go waitForState(errCh, state, d.refreshInstanceState(zone, name)) 520 return errCh 521 } 522 523 func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc { 524 return func() (string, error) { 525 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 526 if err != nil { 527 return "", err 528 } 529 return instance.Status, nil 530 } 531 } 532 533 func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc { 534 return func() (string, error) { 535 newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do() 536 if err != nil { 537 return "", err 538 } 539 540 // If the op is done, check for errors 541 err = nil 542 if newOp.Status == "DONE" { 543 if newOp.Error != nil { 544 for _, e := range newOp.Error.Errors { 545 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 546 } 547 } 548 } 549 550 return newOp.Status, err 551 } 552 } 553 554 func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc { 555 return func() (string, error) { 556 newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, 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 // used in conjunction with waitForState. 576 type stateRefreshFunc func() (string, error) 577 578 // waitForState will spin in a loop forever waiting for state to 579 // reach a certain target. 580 func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error { 581 err := common.Retry(2, 2, 0, func(_ uint) (bool, error) { 582 state, err := refresh() 583 if err != nil { 584 return false, err 585 } else if state == target { 586 return true, nil 587 } 588 return false, nil 589 }) 590 errCh <- err 591 return err 592 }