github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/googlecompute/driver_gce.go (about)

     1  package googlecompute
     2  
     3  import (
     4  	"crypto/rand"
     5  	"crypto/rsa"
     6  	"crypto/sha1"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"log"
    12  	"net/http"
    13  	"net/url"
    14  	"runtime"
    15  	"strings"
    16  	"time"
    17  
    18  	"google.golang.org/api/compute/v1"
    19  
    20  	"github.com/hashicorp/packer/common"
    21  	"github.com/hashicorp/packer/packer"
    22  	"github.com/hashicorp/packer/version"
    23  
    24  	"golang.org/x/oauth2"
    25  	"golang.org/x/oauth2/google"
    26  	"golang.org/x/oauth2/jwt"
    27  )
    28  
    29  // driverGCE is a Driver implementation that actually talks to GCE.
    30  // Create an instance using NewDriverGCE.
    31  type driverGCE struct {
    32  	projectId string
    33  	service   *compute.Service
    34  	ui        packer.Ui
    35  }
    36  
    37  var DriverScopes = []string{"https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/devstorage.full_control"}
    38  
    39  func NewDriverGCE(ui packer.Ui, p string, a *AccountFile) (Driver, error) {
    40  	var err error
    41  
    42  	var client *http.Client
    43  
    44  	// Auth with AccountFile first if provided
    45  	if a.PrivateKey != "" {
    46  		log.Printf("[INFO] Requesting Google token via AccountFile...")
    47  		log.Printf("[INFO]   -- Email: %s", a.ClientEmail)
    48  		log.Printf("[INFO]   -- Scopes: %s", DriverScopes)
    49  		log.Printf("[INFO]   -- Private Key Length: %d", len(a.PrivateKey))
    50  
    51  		conf := jwt.Config{
    52  			Email:      a.ClientEmail,
    53  			PrivateKey: []byte(a.PrivateKey),
    54  			Scopes:     DriverScopes,
    55  			TokenURL:   "https://accounts.google.com/o/oauth2/token",
    56  		}
    57  
    58  		// Initiate an http.Client. The following GET request will be
    59  		// authorized and authenticated on the behalf of
    60  		// your service account.
    61  		client = conf.Client(oauth2.NoContext)
    62  	} else {
    63  		log.Printf("[INFO] Requesting Google token via GCE API Default Client Token Source...")
    64  		client, err = google.DefaultClient(oauth2.NoContext, DriverScopes...)
    65  		// The DefaultClient uses the DefaultTokenSource of the google lib.
    66  		// The DefaultTokenSource uses the "Application Default Credentials"
    67  		// It looks for credentials in the following places, preferring the first location found:
    68  		// 1. A JSON file whose path is specified by the
    69  		//    GOOGLE_APPLICATION_CREDENTIALS environment variable.
    70  		// 2. A JSON file in a location known to the gcloud command-line tool.
    71  		//    On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
    72  		//    On other systems, $HOME/.config/gcloud/application_default_credentials.json.
    73  		// 3. On Google App Engine it uses the appengine.AccessToken function.
    74  		// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
    75  		//    credentials from the metadata server.
    76  		//    (In this final case any provided scopes are ignored.)
    77  	}
    78  
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	log.Printf("[INFO] Instantiating GCE client...")
    84  	service, err := compute.New(client)
    85  	// Set UserAgent
    86  	versionString := version.FormattedVersion()
    87  	service.UserAgent = fmt.Sprintf(
    88  		"(%s %s) Packer/%s", runtime.GOOS, runtime.GOARCH, versionString)
    89  
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	return &driverGCE{
    95  		projectId: p,
    96  		service:   service,
    97  		ui:        ui,
    98  	}, nil
    99  }
   100  
   101  func (d *driverGCE) CreateImage(name, description, family, zone, disk string, image_labels map[string]string) (<-chan *Image, <-chan error) {
   102  	gce_image := &compute.Image{
   103  		Description: description,
   104  		Name:        name,
   105  		Family:      family,
   106  		Labels:      image_labels,
   107  		SourceDisk:  fmt.Sprintf("%s%s/zones/%s/disks/%s", d.service.BasePath, d.projectId, zone, disk),
   108  		SourceType:  "RAW",
   109  	}
   110  
   111  	imageCh := make(chan *Image, 1)
   112  	errCh := make(chan error, 1)
   113  	op, err := d.service.Images.Insert(d.projectId, gce_image).Do()
   114  	if err != nil {
   115  		errCh <- err
   116  	} else {
   117  		go func() {
   118  			err = waitForState(errCh, "DONE", d.refreshGlobalOp(op))
   119  			if err != nil {
   120  				close(imageCh)
   121  				errCh <- err
   122  				return
   123  			}
   124  			var image *Image
   125  			image, err = d.GetImageFromProject(d.projectId, name, false)
   126  			if err != nil {
   127  				close(imageCh)
   128  				errCh <- err
   129  				return
   130  			}
   131  			imageCh <- image
   132  			close(imageCh)
   133  		}()
   134  	}
   135  
   136  	return imageCh, errCh
   137  }
   138  
   139  func (d *driverGCE) DeleteImage(name string) <-chan error {
   140  	errCh := make(chan error, 1)
   141  	op, err := d.service.Images.Delete(d.projectId, name).Do()
   142  	if err != nil {
   143  		errCh <- err
   144  	} else {
   145  		go waitForState(errCh, "DONE", d.refreshGlobalOp(op))
   146  	}
   147  
   148  	return errCh
   149  }
   150  
   151  func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) {
   152  	op, err := d.service.Instances.Delete(d.projectId, zone, name).Do()
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	errCh := make(chan error, 1)
   158  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op))
   159  	return errCh, nil
   160  }
   161  
   162  func (d *driverGCE) DeleteDisk(zone, name string) (<-chan error, error) {
   163  	op, err := d.service.Disks.Delete(d.projectId, zone, name).Do()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	errCh := make(chan error, 1)
   169  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op))
   170  	return errCh, nil
   171  }
   172  
   173  func (d *driverGCE) GetImage(name string, fromFamily bool) (*Image, error) {
   174  	projects := []string{d.projectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud", "gce-nvme"}
   175  	var errs error
   176  	for _, project := range projects {
   177  		image, err := d.GetImageFromProject(project, name, fromFamily)
   178  		if err != nil {
   179  			errs = packer.MultiErrorAppend(errs, err)
   180  		}
   181  		if image != nil {
   182  			return image, nil
   183  		}
   184  	}
   185  
   186  	return nil, fmt.Errorf(
   187  		"Could not find image, %s, in projects, %s: %s", name,
   188  		projects, errs)
   189  }
   190  
   191  func (d *driverGCE) GetImageFromProject(project, name string, fromFamily bool) (*Image, error) {
   192  	var (
   193  		image *compute.Image
   194  		err   error
   195  	)
   196  
   197  	if fromFamily {
   198  		image, err = d.service.Images.GetFromFamily(project, name).Do()
   199  	} else {
   200  		image, err = d.service.Images.Get(project, name).Do()
   201  	}
   202  
   203  	if err != nil {
   204  		return nil, err
   205  	} else if image == nil || image.SelfLink == "" {
   206  		return nil, fmt.Errorf("Image, %s, could not be found in project: %s", name, project)
   207  	} else {
   208  		return &Image{
   209  			Licenses:  image.Licenses,
   210  			Name:      image.Name,
   211  			ProjectId: project,
   212  			SelfLink:  image.SelfLink,
   213  			SizeGb:    image.DiskSizeGb,
   214  		}, nil
   215  	}
   216  }
   217  
   218  func (d *driverGCE) GetInstanceMetadata(zone, name, key string) (string, error) {
   219  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   220  	if err != nil {
   221  		return "", err
   222  	}
   223  
   224  	for _, item := range instance.Metadata.Items {
   225  		if item.Key == key {
   226  			return *item.Value, nil
   227  		}
   228  	}
   229  
   230  	return "", fmt.Errorf("Instance metadata key, %s, not found.", key)
   231  }
   232  
   233  func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
   234  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   235  	if err != nil {
   236  		return "", err
   237  	}
   238  
   239  	for _, ni := range instance.NetworkInterfaces {
   240  		if ni.AccessConfigs == nil {
   241  			continue
   242  		}
   243  		for _, ac := range ni.AccessConfigs {
   244  			if ac.NatIP != "" {
   245  				return ac.NatIP, nil
   246  			}
   247  		}
   248  	}
   249  
   250  	return "", nil
   251  }
   252  
   253  func (d *driverGCE) GetInternalIP(zone, name string) (string, error) {
   254  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   255  	if err != nil {
   256  		return "", err
   257  	}
   258  
   259  	for _, ni := range instance.NetworkInterfaces {
   260  		if ni.NetworkIP == "" {
   261  			continue
   262  		}
   263  		return ni.NetworkIP, nil
   264  	}
   265  
   266  	return "", nil
   267  }
   268  
   269  func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) {
   270  	output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, name).Do()
   271  	if err != nil {
   272  		return "", err
   273  	}
   274  
   275  	return output.Contents, nil
   276  }
   277  
   278  func (d *driverGCE) ImageExists(name string) bool {
   279  	_, err := d.GetImageFromProject(d.projectId, name, false)
   280  	// The API may return an error for reasons other than the image not
   281  	// existing, but this heuristic is sufficient for now.
   282  	return err == nil
   283  }
   284  
   285  func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
   286  	// Get the zone
   287  	d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
   288  	zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do()
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	// Get the machine type
   294  	d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
   295  	machineType, err := d.service.MachineTypes.Get(
   296  		d.projectId, zone.Name, c.MachineType).Do()
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  	// TODO(mitchellh): deprecation warnings
   301  
   302  	networkSelfLink := ""
   303  	subnetworkSelfLink := ""
   304  
   305  	if u, err := url.Parse(c.Network); err == nil && (u.Scheme == "https" || u.Scheme == "http") {
   306  		// Network is a full server URL
   307  		// Parse out Network and NetworkProjectId from URL
   308  		// https://www.googleapis.com/compute/v1/projects/<ProjectId>/global/networks/<Network>
   309  		networkSelfLink = c.Network
   310  		parts := strings.Split(u.String(), "/")
   311  		if len(parts) >= 10 {
   312  			c.NetworkProjectId = parts[6]
   313  			c.Network = parts[9]
   314  		}
   315  	}
   316  	if u, err := url.Parse(c.Subnetwork); err == nil && (u.Scheme == "https" || u.Scheme == "http") {
   317  		// Subnetwork is a full server URL
   318  		subnetworkSelfLink = c.Subnetwork
   319  	}
   320  
   321  	// If subnetwork is ID's and not full service URL's look them up.
   322  	if subnetworkSelfLink == "" {
   323  
   324  		// Get the network
   325  		if c.NetworkProjectId == "" {
   326  			c.NetworkProjectId = d.projectId
   327  		}
   328  		d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network))
   329  		network, err := d.service.Networks.Get(c.NetworkProjectId, c.Network).Do()
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  		networkSelfLink = network.SelfLink
   334  
   335  		// Subnetwork
   336  		// Validate Subnetwork config now that we have some info about the network
   337  		if !network.AutoCreateSubnetworks && len(network.Subnetworks) > 0 {
   338  			// Network appears to be in "custom" mode, so a subnetwork is required
   339  			if c.Subnetwork == "" {
   340  				return nil, fmt.Errorf("a subnetwork must be specified")
   341  			}
   342  		}
   343  		// Get the subnetwork
   344  		if c.Subnetwork != "" {
   345  			d.ui.Message(fmt.Sprintf("Loading subnetwork: %s for region: %s", c.Subnetwork, c.Region))
   346  			subnetwork, err := d.service.Subnetworks.Get(c.NetworkProjectId, c.Region, c.Subnetwork).Do()
   347  			if err != nil {
   348  				return nil, err
   349  			}
   350  			subnetworkSelfLink = subnetwork.SelfLink
   351  		}
   352  	}
   353  
   354  	var accessconfig *compute.AccessConfig
   355  	// Use external IP if OmitExternalIP isn't set
   356  	if !c.OmitExternalIP {
   357  		accessconfig = &compute.AccessConfig{
   358  			Name: "AccessConfig created by Packer",
   359  			Type: "ONE_TO_ONE_NAT",
   360  		}
   361  
   362  		// If given a static IP, use it
   363  		if c.Address != "" {
   364  			region_url := strings.Split(zone.Region, "/")
   365  			region := region_url[len(region_url)-1]
   366  			address, err := d.service.Addresses.Get(d.projectId, region, c.Address).Do()
   367  			if err != nil {
   368  				return nil, err
   369  			}
   370  			accessconfig.NatIP = address.Address
   371  		}
   372  	}
   373  
   374  	// Build up the metadata
   375  	metadata := make([]*compute.MetadataItems, len(c.Metadata))
   376  	for k, v := range c.Metadata {
   377  		vCopy := v
   378  		metadata = append(metadata, &compute.MetadataItems{
   379  			Key:   k,
   380  			Value: &vCopy,
   381  		})
   382  	}
   383  
   384  	var guestAccelerators []*compute.AcceleratorConfig
   385  	if c.AcceleratorCount > 0 {
   386  		ac := &compute.AcceleratorConfig{
   387  			AcceleratorCount: c.AcceleratorCount,
   388  			AcceleratorType:  c.AcceleratorType,
   389  		}
   390  		guestAccelerators = append(guestAccelerators, ac)
   391  	}
   392  
   393  	// Create the instance information
   394  	instance := compute.Instance{
   395  		Description: c.Description,
   396  		Disks: []*compute.AttachedDisk{
   397  			{
   398  				Type:       "PERSISTENT",
   399  				Mode:       "READ_WRITE",
   400  				Kind:       "compute#attachedDisk",
   401  				Boot:       true,
   402  				AutoDelete: false,
   403  				InitializeParams: &compute.AttachedDiskInitializeParams{
   404  					SourceImage: c.Image.SelfLink,
   405  					DiskSizeGb:  c.DiskSizeGb,
   406  					DiskType:    fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType),
   407  				},
   408  			},
   409  		},
   410  		GuestAccelerators: guestAccelerators,
   411  		Labels:            c.Labels,
   412  		MachineType:       machineType.SelfLink,
   413  		Metadata: &compute.Metadata{
   414  			Items: metadata,
   415  		},
   416  		Name: c.Name,
   417  		NetworkInterfaces: []*compute.NetworkInterface{
   418  			{
   419  				AccessConfigs: []*compute.AccessConfig{accessconfig},
   420  				Network:       networkSelfLink,
   421  				Subnetwork:    subnetworkSelfLink,
   422  			},
   423  		},
   424  		Scheduling: &compute.Scheduling{
   425  			OnHostMaintenance: c.OnHostMaintenance,
   426  			Preemptible:       c.Preemptible,
   427  		},
   428  		ServiceAccounts: []*compute.ServiceAccount{
   429  			{
   430  				Email:  "default",
   431  				Scopes: c.Scopes,
   432  			},
   433  		},
   434  		Tags: &compute.Tags{
   435  			Items: c.Tags,
   436  		},
   437  	}
   438  
   439  	d.ui.Message("Requesting instance creation...")
   440  	op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
   441  	if err != nil {
   442  		return nil, err
   443  	}
   444  
   445  	errCh := make(chan error, 1)
   446  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op))
   447  	return errCh, nil
   448  }
   449  
   450  func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
   451  
   452  	errCh := make(chan error, 1)
   453  	go d.createWindowsPassword(errCh, instance, zone, c)
   454  
   455  	return errCh, nil
   456  }
   457  
   458  func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) {
   459  
   460  	data, err := json.Marshal(c)
   461  
   462  	if err != nil {
   463  		errCh <- err
   464  		return
   465  	}
   466  	dCopy := string(data)
   467  
   468  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   469  	instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy})
   470  
   471  	op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{
   472  		Fingerprint: instance.Metadata.Fingerprint,
   473  		Items:       instance.Metadata.Items,
   474  	}).Do()
   475  
   476  	if err != nil {
   477  		errCh <- err
   478  		return
   479  	}
   480  
   481  	newErrCh := make(chan error, 1)
   482  	go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op))
   483  
   484  	select {
   485  	case err = <-newErrCh:
   486  	case <-time.After(time.Second * 30):
   487  		err = errors.New("time out while waiting for instance to create")
   488  	}
   489  
   490  	if err != nil {
   491  		errCh <- err
   492  		return
   493  	}
   494  
   495  	timeout := time.Now().Add(time.Minute * 3)
   496  	hash := sha1.New()
   497  	random := rand.Reader
   498  
   499  	for time.Now().Before(timeout) {
   500  		if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil {
   501  			for _, response := range passwordResponses {
   502  				if response.Modulus == c.Modulus {
   503  
   504  					decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword)
   505  
   506  					if err != nil {
   507  						errCh <- err
   508  						return
   509  					}
   510  					password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil)
   511  
   512  					if err != nil {
   513  						errCh <- err
   514  						return
   515  					}
   516  
   517  					c.password = string(password)
   518  					errCh <- nil
   519  					return
   520  				}
   521  			}
   522  		}
   523  
   524  		time.Sleep(2 * time.Second)
   525  	}
   526  	err = errors.New("Could not retrieve password. Timed out.")
   527  
   528  	errCh <- err
   529  	return
   530  
   531  }
   532  
   533  func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) {
   534  	output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do()
   535  
   536  	if err != nil {
   537  		return nil, err
   538  	}
   539  
   540  	responses := strings.Split(output.Contents, "\n")
   541  
   542  	passwordResponses := make([]windowsPasswordResponse, 0, len(responses))
   543  
   544  	for _, response := range responses {
   545  		var passwordResponse windowsPasswordResponse
   546  		if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil {
   547  			continue
   548  		}
   549  
   550  		passwordResponses = append(passwordResponses, passwordResponse)
   551  	}
   552  
   553  	return passwordResponses, nil
   554  }
   555  
   556  func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
   557  	errCh := make(chan error, 1)
   558  	go waitForState(errCh, state, d.refreshInstanceState(zone, name))
   559  	return errCh
   560  }
   561  
   562  func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
   563  	return func() (string, error) {
   564  		instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   565  		if err != nil {
   566  			return "", err
   567  		}
   568  		return instance.Status, nil
   569  	}
   570  }
   571  
   572  func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
   573  	return func() (string, error) {
   574  		newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do()
   575  		if err != nil {
   576  			return "", err
   577  		}
   578  
   579  		// If the op is done, check for errors
   580  		err = nil
   581  		if newOp.Status == "DONE" {
   582  			if newOp.Error != nil {
   583  				for _, e := range newOp.Error.Errors {
   584  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   585  				}
   586  			}
   587  		}
   588  
   589  		return newOp.Status, err
   590  	}
   591  }
   592  
   593  func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
   594  	return func() (string, error) {
   595  		newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
   596  		if err != nil {
   597  			return "", err
   598  		}
   599  
   600  		// If the op is done, check for errors
   601  		err = nil
   602  		if newOp.Status == "DONE" {
   603  			if newOp.Error != nil {
   604  				for _, e := range newOp.Error.Errors {
   605  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   606  				}
   607  			}
   608  		}
   609  
   610  		return newOp.Status, err
   611  	}
   612  }
   613  
   614  // stateRefreshFunc is used to refresh the state of a thing and is
   615  // used in conjunction with waitForState.
   616  type stateRefreshFunc func() (string, error)
   617  
   618  // waitForState will spin in a loop forever waiting for state to
   619  // reach a certain target.
   620  func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
   621  	err := common.Retry(2, 2, 0, func(_ uint) (bool, error) {
   622  		state, err := refresh()
   623  		if err != nil {
   624  			return false, err
   625  		} else if state == target {
   626  			return true, nil
   627  		}
   628  		return false, nil
   629  	})
   630  	errCh <- err
   631  	return err
   632  }