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