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