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  }