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