github.com/marksheahan/packer@v0.10.2-0.20160613200515-1acb2d6645a0/builder/googlecompute/driver_gce.go (about) 1 package googlecompute 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "runtime" 8 "time" 9 10 "github.com/mitchellh/packer/packer" 11 "github.com/mitchellh/packer/version" 12 13 "golang.org/x/oauth2" 14 "golang.org/x/oauth2/google" 15 "golang.org/x/oauth2/jwt" 16 "google.golang.org/api/compute/v1" 17 "strings" 18 ) 19 20 // driverGCE is a Driver implementation that actually talks to GCE. 21 // Create an instance using NewDriverGCE. 22 type driverGCE struct { 23 projectId string 24 service *compute.Service 25 ui packer.Ui 26 } 27 28 var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"} 29 30 func NewDriverGCE(ui packer.Ui, p string, a *accountFile) (Driver, error) { 31 var err error 32 33 var client *http.Client 34 35 // Auth with AccountFile first if provided 36 if a.PrivateKey != "" { 37 log.Printf("[INFO] Requesting Google token via AccountFile...") 38 log.Printf("[INFO] -- Email: %s", a.ClientEmail) 39 log.Printf("[INFO] -- Scopes: %s", DriverScopes) 40 log.Printf("[INFO] -- Private Key Length: %d", len(a.PrivateKey)) 41 42 conf := jwt.Config{ 43 Email: a.ClientEmail, 44 PrivateKey: []byte(a.PrivateKey), 45 Scopes: DriverScopes, 46 TokenURL: "https://accounts.google.com/o/oauth2/token", 47 } 48 49 // Initiate an http.Client. The following GET request will be 50 // authorized and authenticated on the behalf of 51 // your service account. 52 client = conf.Client(oauth2.NoContext) 53 } else { 54 log.Printf("[INFO] Requesting Google token via GCE Service Role...") 55 client = &http.Client{ 56 Transport: &oauth2.Transport{ 57 // Fetch from Google Compute Engine's metadata server to retrieve 58 // an access token for the provided account. 59 // If no account is specified, "default" is used. 60 Source: google.ComputeTokenSource(""), 61 }, 62 } 63 } 64 65 if err != nil { 66 return nil, err 67 } 68 69 log.Printf("[INFO] Instantiating GCE client...") 70 service, err := compute.New(client) 71 // Set UserAgent 72 versionString := version.FormattedVersion() 73 service.UserAgent = fmt.Sprintf( 74 "(%s %s) Packer/%s", runtime.GOOS, runtime.GOARCH, versionString) 75 76 if err != nil { 77 return nil, err 78 } 79 80 return &driverGCE{ 81 projectId: p, 82 service: service, 83 ui: ui, 84 }, nil 85 } 86 87 func (d *driverGCE) ImageExists(name string) bool { 88 _, err := d.service.Images.Get(d.projectId, name).Do() 89 // The API may return an error for reasons other than the image not 90 // existing, but this heuristic is sufficient for now. 91 return err == nil 92 } 93 94 func (d *driverGCE) CreateImage(name, description, family, zone, disk string) <-chan error { 95 image := &compute.Image{ 96 Description: description, 97 Name: name, 98 Family: family, 99 SourceDisk: fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk), 100 SourceType: "RAW", 101 } 102 103 errCh := make(chan error, 1) 104 op, err := d.service.Images.Insert(d.projectId, image).Do() 105 if err != nil { 106 errCh <- err 107 } else { 108 go waitForState(errCh, "DONE", d.refreshGlobalOp(op)) 109 } 110 111 return errCh 112 } 113 114 func (d *driverGCE) DeleteImage(name string) <-chan error { 115 errCh := make(chan error, 1) 116 op, err := d.service.Images.Delete(d.projectId, name).Do() 117 if err != nil { 118 errCh <- err 119 } else { 120 go waitForState(errCh, "DONE", d.refreshGlobalOp(op)) 121 } 122 123 return errCh 124 } 125 126 func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) { 127 op, err := d.service.Instances.Delete(d.projectId, zone, name).Do() 128 if err != nil { 129 return nil, err 130 } 131 132 errCh := make(chan error, 1) 133 go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op)) 134 return errCh, nil 135 } 136 137 func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) { 138 op, err := d.service.Disks.Delete(d.projectId, zone, name).Do() 139 if err != nil { 140 return nil, err 141 } 142 143 errCh := make(chan error, 1) 144 go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op)) 145 return errCh, nil 146 } 147 148 func (d *driverGCE) GetNatIP(zone, name string) (string, error) { 149 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 150 if err != nil { 151 return "", err 152 } 153 154 for _, ni := range instance.NetworkInterfaces { 155 if ni.AccessConfigs == nil { 156 continue 157 } 158 for _, ac := range ni.AccessConfigs { 159 if ac.NatIP != "" { 160 return ac.NatIP, nil 161 } 162 } 163 } 164 165 return "", nil 166 } 167 168 func (d *driverGCE) GetInternalIP(zone, name string) (string, error) { 169 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 170 if err != nil { 171 return "", err 172 } 173 174 for _, ni := range instance.NetworkInterfaces { 175 if ni.NetworkIP == "" { 176 continue 177 } 178 return ni.NetworkIP, nil 179 } 180 181 return "", nil 182 } 183 184 func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { 185 // Get the zone 186 d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone)) 187 zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do() 188 if err != nil { 189 return nil, err 190 } 191 192 // Get the image 193 d.ui.Message(fmt.Sprintf("Loading image: %s in project %s", c.Image.Name, c.Image.ProjectId)) 194 image, err := d.getImage(c.Image) 195 if err != nil { 196 return nil, err 197 } 198 199 // Get the machine type 200 d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType)) 201 machineType, err := d.service.MachineTypes.Get( 202 d.projectId, zone.Name, c.MachineType).Do() 203 if err != nil { 204 return nil, err 205 } 206 // TODO(mitchellh): deprecation warnings 207 208 // Get the network 209 d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network)) 210 network, err := d.service.Networks.Get(d.projectId, c.Network).Do() 211 if err != nil { 212 return nil, err 213 } 214 215 // Subnetwork 216 // Validate Subnetwork config now that we have some info about the network 217 if !network.AutoCreateSubnetworks && len(network.Subnetworks) > 0 { 218 // Network appears to be in "custom" mode, so a subnetwork is required 219 if c.Subnetwork == "" { 220 return nil, fmt.Errorf("a subnetwork must be specified") 221 } 222 } 223 // Get the subnetwork 224 subnetworkSelfLink := "" 225 if c.Subnetwork != "" { 226 d.ui.Message(fmt.Sprintf("Loading subnetwork: %s for region: %s", c.Subnetwork, c.Region)) 227 subnetwork, err := d.service.Subnetworks.Get(d.projectId, c.Region, c.Subnetwork).Do() 228 if err != nil { 229 return nil, err 230 } 231 subnetworkSelfLink = subnetwork.SelfLink 232 } 233 234 // If given a regional ip, get it 235 accessconfig := compute.AccessConfig{ 236 Name: "AccessConfig created by Packer", 237 Type: "ONE_TO_ONE_NAT", 238 } 239 240 if c.Address != "" { 241 d.ui.Message(fmt.Sprintf("Looking up address: %s", c.Address)) 242 region_url := strings.Split(zone.Region, "/") 243 region := region_url[len(region_url)-1] 244 address, err := d.service.Addresses.Get(d.projectId, region, c.Address).Do() 245 if err != nil { 246 return nil, err 247 } 248 accessconfig.NatIP = address.Address 249 } 250 251 // Build up the metadata 252 metadata := make([]*compute.MetadataItems, len(c.Metadata)) 253 for k, v := range c.Metadata { 254 vCopy := v 255 metadata = append(metadata, &compute.MetadataItems{ 256 Key: k, 257 Value: &vCopy, 258 }) 259 } 260 261 // Create the instance information 262 instance := compute.Instance{ 263 Description: c.Description, 264 Disks: []*compute.AttachedDisk{ 265 &compute.AttachedDisk{ 266 Type: "PERSISTENT", 267 Mode: "READ_WRITE", 268 Kind: "compute#attachedDisk", 269 Boot: true, 270 AutoDelete: false, 271 InitializeParams: &compute.AttachedDiskInitializeParams{ 272 SourceImage: image.SelfLink, 273 DiskSizeGb: c.DiskSizeGb, 274 DiskType: fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType), 275 }, 276 }, 277 }, 278 MachineType: machineType.SelfLink, 279 Metadata: &compute.Metadata{ 280 Items: metadata, 281 }, 282 Name: c.Name, 283 NetworkInterfaces: []*compute.NetworkInterface{ 284 &compute.NetworkInterface{ 285 AccessConfigs: []*compute.AccessConfig{ 286 &accessconfig, 287 }, 288 Network: network.SelfLink, 289 Subnetwork: subnetworkSelfLink, 290 }, 291 }, 292 Scheduling: &compute.Scheduling{ 293 Preemptible: c.Preemptible, 294 }, 295 ServiceAccounts: []*compute.ServiceAccount{ 296 &compute.ServiceAccount{ 297 Email: "default", 298 Scopes: []string{ 299 "https://www.googleapis.com/auth/userinfo.email", 300 "https://www.googleapis.com/auth/compute", 301 "https://www.googleapis.com/auth/devstorage.full_control", 302 }, 303 }, 304 }, 305 Tags: &compute.Tags{ 306 Items: c.Tags, 307 }, 308 } 309 310 d.ui.Message("Requesting instance creation...") 311 op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do() 312 if err != nil { 313 return nil, err 314 } 315 316 errCh := make(chan error, 1) 317 go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op)) 318 return errCh, nil 319 } 320 321 func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { 322 errCh := make(chan error, 1) 323 go waitForState(errCh, state, d.refreshInstanceState(zone, name)) 324 return errCh 325 } 326 327 func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) { 328 projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud"} 329 for _, project := range projects { 330 image, err = d.service.Images.Get(project, img.Name).Do() 331 if err == nil && image != nil && image.SelfLink != "" { 332 return 333 } 334 image = nil 335 } 336 337 err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects) 338 return 339 } 340 341 func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc { 342 return func() (string, error) { 343 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 344 if err != nil { 345 return "", err 346 } 347 return instance.Status, nil 348 } 349 } 350 351 func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc { 352 return func() (string, error) { 353 newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do() 354 if err != nil { 355 return "", err 356 } 357 358 // If the op is done, check for errors 359 err = nil 360 if newOp.Status == "DONE" { 361 if newOp.Error != nil { 362 for _, e := range newOp.Error.Errors { 363 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 364 } 365 } 366 } 367 368 return newOp.Status, err 369 } 370 } 371 372 func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc { 373 return func() (string, error) { 374 newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do() 375 if err != nil { 376 return "", err 377 } 378 379 // If the op is done, check for errors 380 err = nil 381 if newOp.Status == "DONE" { 382 if newOp.Error != nil { 383 for _, e := range newOp.Error.Errors { 384 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 385 } 386 } 387 } 388 389 return newOp.Status, err 390 } 391 } 392 393 // stateRefreshFunc is used to refresh the state of a thing and is 394 // used in conjunction with waitForState. 395 type stateRefreshFunc func() (string, error) 396 397 // waitForState will spin in a loop forever waiting for state to 398 // reach a certain target. 399 func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) { 400 for { 401 state, err := refresh() 402 if err != nil { 403 errCh <- err 404 return 405 } 406 if state == target { 407 errCh <- nil 408 return 409 } 410 411 time.Sleep(2 * time.Second) 412 } 413 }