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