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