github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/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/goauth2/oauth" 10 "code.google.com/p/goauth2/oauth/jwt" 11 "code.google.com/p/google-api-go-client/compute/v1" 12 "github.com/mitchellh/packer/packer" 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 const DriverScopes string = "https://www.googleapis.com/auth/compute " + 24 "https://www.googleapis.com/auth/devstorage.full_control" 25 26 func NewDriverGCE(ui packer.Ui, p string, a *accountFile, c *clientSecretsFile) (Driver, error) { 27 // Get the token for use in our requests 28 log.Printf("[INFO] Requesting Google token...") 29 log.Printf("[INFO] -- Email: %s", a.ClientEmail) 30 log.Printf("[INFO] -- Scopes: %s", DriverScopes) 31 log.Printf("[INFO] -- Private Key Length: %d", len(a.PrivateKey)) 32 log.Printf("[INFO] -- Token URL: %s", c.Web.TokenURI) 33 jwtTok := jwt.NewToken( 34 a.ClientEmail, 35 DriverScopes, 36 []byte(a.PrivateKey)) 37 jwtTok.ClaimSet.Aud = c.Web.TokenURI 38 token, err := jwtTok.Assert(new(http.Client)) 39 if err != nil { 40 return nil, fmt.Errorf("Error retrieving auth token: %s", err) 41 } 42 43 // Instantiate the transport to communicate to Google 44 transport := &oauth.Transport{ 45 Config: &oauth.Config{ 46 ClientId: a.ClientId, 47 Scope: DriverScopes, 48 TokenURL: c.Web.TokenURI, 49 AuthURL: c.Web.AuthURI, 50 }, 51 Token: token, 52 } 53 54 log.Printf("[INFO] Instantiating GCE client...") 55 service, err := compute.New(transport.Client()) 56 if err != nil { 57 return nil, err 58 } 59 60 return &driverGCE{ 61 projectId: p, 62 service: service, 63 ui: ui, 64 }, nil 65 } 66 67 func (d *driverGCE) CreateImage(name, description, url string) <-chan error { 68 image := &compute.Image{ 69 Description: description, 70 Name: name, 71 RawDisk: &compute.ImageRawDisk{ 72 ContainerType: "TAR", 73 Source: url, 74 }, 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) GetNatIP(zone, name string) (string, error) { 113 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 114 if err != nil { 115 return "", err 116 } 117 118 for _, ni := range instance.NetworkInterfaces { 119 if ni.AccessConfigs == nil { 120 continue 121 } 122 123 for _, ac := range ni.AccessConfigs { 124 if ac.NatIP != "" { 125 return ac.NatIP, nil 126 } 127 } 128 } 129 130 return "", nil 131 } 132 133 func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) { 134 // Get the zone 135 d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone)) 136 zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do() 137 if err != nil { 138 return nil, err 139 } 140 141 // Get the image 142 d.ui.Message(fmt.Sprintf("Loading image: %s in project %s", c.Image.Name, c.Image.ProjectId)) 143 image, err := d.getImage(c.Image) 144 if err != nil { 145 return nil, err 146 } 147 148 // Get the machine type 149 d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType)) 150 machineType, err := d.service.MachineTypes.Get( 151 d.projectId, zone.Name, c.MachineType).Do() 152 if err != nil { 153 return nil, err 154 } 155 // TODO(mitchellh): deprecation warnings 156 157 // Get the network 158 d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network)) 159 network, err := d.service.Networks.Get(d.projectId, c.Network).Do() 160 if err != nil { 161 return nil, err 162 } 163 164 // Build up the metadata 165 metadata := make([]*compute.MetadataItems, len(c.Metadata)) 166 for k, v := range c.Metadata { 167 metadata = append(metadata, &compute.MetadataItems{ 168 Key: k, 169 Value: v, 170 }) 171 } 172 173 // Create the instance information 174 instance := compute.Instance{ 175 Description: c.Description, 176 Disks: []*compute.AttachedDisk{ 177 &compute.AttachedDisk{ 178 Type: "PERSISTENT", 179 Mode: "READ_WRITE", 180 Kind: "compute#attachedDisk", 181 Boot: true, 182 AutoDelete: true, 183 InitializeParams: &compute.AttachedDiskInitializeParams{ 184 SourceImage: image.SelfLink, 185 DiskSizeGb: c.DiskSizeGb, 186 }, 187 }, 188 }, 189 MachineType: machineType.SelfLink, 190 Metadata: &compute.Metadata{ 191 Items: metadata, 192 }, 193 Name: c.Name, 194 NetworkInterfaces: []*compute.NetworkInterface{ 195 &compute.NetworkInterface{ 196 AccessConfigs: []*compute.AccessConfig{ 197 &compute.AccessConfig{ 198 Name: "AccessConfig created by Packer", 199 Type: "ONE_TO_ONE_NAT", 200 }, 201 }, 202 Network: network.SelfLink, 203 }, 204 }, 205 ServiceAccounts: []*compute.ServiceAccount{ 206 &compute.ServiceAccount{ 207 Email: "default", 208 Scopes: []string{ 209 "https://www.googleapis.com/auth/userinfo.email", 210 "https://www.googleapis.com/auth/compute", 211 "https://www.googleapis.com/auth/devstorage.full_control", 212 }, 213 }, 214 }, 215 Tags: &compute.Tags{ 216 Items: c.Tags, 217 }, 218 } 219 220 d.ui.Message("Requesting instance creation...") 221 op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do() 222 if err != nil { 223 return nil, err 224 } 225 226 errCh := make(chan error, 1) 227 go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op)) 228 return errCh, nil 229 } 230 231 func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error { 232 errCh := make(chan error, 1) 233 go waitForState(errCh, state, d.refreshInstanceState(zone, name)) 234 return errCh 235 } 236 237 func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) { 238 projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "windows-cloud"} 239 for _, project := range projects { 240 image, err = d.service.Images.Get(project, img.Name).Do() 241 if err == nil && image != nil && image.SelfLink != "" { 242 return 243 } 244 image = nil 245 } 246 247 err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects) 248 return 249 } 250 251 func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc { 252 return func() (string, error) { 253 instance, err := d.service.Instances.Get(d.projectId, zone, name).Do() 254 if err != nil { 255 return "", err 256 } 257 return instance.Status, nil 258 } 259 } 260 261 func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc { 262 return func() (string, error) { 263 newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do() 264 if err != nil { 265 return "", err 266 } 267 268 // If the op is done, check for errors 269 err = nil 270 if newOp.Status == "DONE" { 271 if newOp.Error != nil { 272 for _, e := range newOp.Error.Errors { 273 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 274 } 275 } 276 } 277 278 return newOp.Status, err 279 } 280 } 281 282 func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc { 283 return func() (string, error) { 284 newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do() 285 if err != nil { 286 return "", err 287 } 288 289 // If the op is done, check for errors 290 err = nil 291 if newOp.Status == "DONE" { 292 if newOp.Error != nil { 293 for _, e := range newOp.Error.Errors { 294 err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message)) 295 } 296 } 297 } 298 299 return newOp.Status, err 300 } 301 } 302 303 // stateRefreshFunc is used to refresh the state of a thing and is 304 // used in conjunction with waitForState. 305 type stateRefreshFunc func() (string, error) 306 307 // waitForState will spin in a loop forever waiting for state to 308 // reach a certain target. 309 func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) { 310 for { 311 state, err := refresh() 312 if err != nil { 313 errCh <- err 314 return 315 } 316 if state == target { 317 errCh <- nil 318 return 319 } 320 321 time.Sleep(2 * time.Second) 322 } 323 }