github.phpd.cn/hashicorp/packer@v1.3.2/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{
   172  		d.projectId,
   173  		// Public projects, drawn from
   174  		// https://cloud.google.com/compute/docs/images
   175  		"centos-cloud",
   176  		"cos-cloud",
   177  		"coreos-cloud",
   178  		"debian-cloud",
   179  		"rhel-cloud",
   180  		"rhel-sap-cloud",
   181  		"suse-cloud",
   182  		"suse-sap-cloud",
   183  		"ubuntu-os-cloud",
   184  		"windows-cloud",
   185  		"windows-sql-cloud",
   186  		"gce-uefi-images",
   187  		"gce-nvme",
   188  		// misc
   189  		"google-containers",
   190  		"opensuse-cloud",
   191  	}
   192  	var errs error
   193  	for _, project := range projects {
   194  		image, err := d.GetImageFromProject(project, name, fromFamily)
   195  		if err != nil {
   196  			errs = packer.MultiErrorAppend(errs, err)
   197  		}
   198  		if image != nil {
   199  			return image, nil
   200  		}
   201  	}
   202  
   203  	return nil, fmt.Errorf(
   204  		"Could not find image, %s, in projects, %s: %s", name,
   205  		projects, errs)
   206  }
   207  
   208  func (d *driverGCE) GetImageFromProject(project, name string, fromFamily bool) (*Image, error) {
   209  	var (
   210  		image *compute.Image
   211  		err   error
   212  	)
   213  
   214  	if fromFamily {
   215  		image, err = d.service.Images.GetFromFamily(project, name).Do()
   216  	} else {
   217  		image, err = d.service.Images.Get(project, name).Do()
   218  	}
   219  
   220  	if err != nil {
   221  		return nil, err
   222  	} else if image == nil || image.SelfLink == "" {
   223  		return nil, fmt.Errorf("Image, %s, could not be found in project: %s", name, project)
   224  	} else {
   225  		return &Image{
   226  			Licenses:  image.Licenses,
   227  			Name:      image.Name,
   228  			ProjectId: project,
   229  			SelfLink:  image.SelfLink,
   230  			SizeGb:    image.DiskSizeGb,
   231  		}, nil
   232  	}
   233  }
   234  
   235  func (d *driverGCE) GetInstanceMetadata(zone, name, key string) (string, error) {
   236  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   237  	if err != nil {
   238  		return "", err
   239  	}
   240  
   241  	for _, item := range instance.Metadata.Items {
   242  		if item.Key == key {
   243  			return *item.Value, nil
   244  		}
   245  	}
   246  
   247  	return "", fmt.Errorf("Instance metadata key, %s, not found.", key)
   248  }
   249  
   250  func (d *driverGCE) GetNatIP(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.AccessConfigs == nil {
   258  			continue
   259  		}
   260  		for _, ac := range ni.AccessConfigs {
   261  			if ac.NatIP != "" {
   262  				return ac.NatIP, nil
   263  			}
   264  		}
   265  	}
   266  
   267  	return "", nil
   268  }
   269  
   270  func (d *driverGCE) GetInternalIP(zone, name string) (string, error) {
   271  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   272  	if err != nil {
   273  		return "", err
   274  	}
   275  
   276  	for _, ni := range instance.NetworkInterfaces {
   277  		if ni.NetworkIP == "" {
   278  			continue
   279  		}
   280  		return ni.NetworkIP, nil
   281  	}
   282  
   283  	return "", nil
   284  }
   285  
   286  func (d *driverGCE) GetSerialPortOutput(zone, name string) (string, error) {
   287  	output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, name).Do()
   288  	if err != nil {
   289  		return "", err
   290  	}
   291  
   292  	return output.Contents, nil
   293  }
   294  
   295  func (d *driverGCE) ImageExists(name string) bool {
   296  	_, err := d.GetImageFromProject(d.projectId, name, false)
   297  	// The API may return an error for reasons other than the image not
   298  	// existing, but this heuristic is sufficient for now.
   299  	return err == nil
   300  }
   301  
   302  func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
   303  	// Get the zone
   304  	d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
   305  	zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do()
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	// Get the machine type
   311  	d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
   312  	machineType, err := d.service.MachineTypes.Get(
   313  		d.projectId, zone.Name, c.MachineType).Do()
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	// TODO(mitchellh): deprecation warnings
   318  
   319  	networkId, subnetworkId, err := getNetworking(c)
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  
   324  	var accessconfig *compute.AccessConfig
   325  	// Use external IP if OmitExternalIP isn't set
   326  	if !c.OmitExternalIP {
   327  		accessconfig = &compute.AccessConfig{
   328  			Name: "AccessConfig created by Packer",
   329  			Type: "ONE_TO_ONE_NAT",
   330  		}
   331  
   332  		// If given a static IP, use it
   333  		if c.Address != "" {
   334  			region_url := strings.Split(zone.Region, "/")
   335  			region := region_url[len(region_url)-1]
   336  			address, err := d.service.Addresses.Get(d.projectId, region, c.Address).Do()
   337  			if err != nil {
   338  				return nil, err
   339  			}
   340  			accessconfig.NatIP = address.Address
   341  		}
   342  	}
   343  
   344  	// Build up the metadata
   345  	metadata := make([]*compute.MetadataItems, len(c.Metadata))
   346  	for k, v := range c.Metadata {
   347  		vCopy := v
   348  		metadata = append(metadata, &compute.MetadataItems{
   349  			Key:   k,
   350  			Value: &vCopy,
   351  		})
   352  	}
   353  
   354  	var guestAccelerators []*compute.AcceleratorConfig
   355  	if c.AcceleratorCount > 0 {
   356  		ac := &compute.AcceleratorConfig{
   357  			AcceleratorCount: c.AcceleratorCount,
   358  			AcceleratorType:  c.AcceleratorType,
   359  		}
   360  		guestAccelerators = append(guestAccelerators, ac)
   361  	}
   362  
   363  	// Configure the instance's service account. If the user has set
   364  	// disable_default_service_account, then the default service account
   365  	// will not be used. If they also do not set service_account_email, then
   366  	// the instance will be created with no service account or scopes.
   367  	serviceAccount := &compute.ServiceAccount{}
   368  	if !c.DisableDefaultServiceAccount {
   369  		serviceAccount.Email = "default"
   370  		serviceAccount.Scopes = c.Scopes
   371  	}
   372  	if c.ServiceAccountEmail != "" {
   373  		serviceAccount.Email = c.ServiceAccountEmail
   374  		serviceAccount.Scopes = c.Scopes
   375  	}
   376  
   377  	// Create the instance information
   378  	instance := compute.Instance{
   379  		Description: c.Description,
   380  		Disks: []*compute.AttachedDisk{
   381  			{
   382  				Type:       "PERSISTENT",
   383  				Mode:       "READ_WRITE",
   384  				Kind:       "compute#attachedDisk",
   385  				Boot:       true,
   386  				AutoDelete: false,
   387  				InitializeParams: &compute.AttachedDiskInitializeParams{
   388  					SourceImage: c.Image.SelfLink,
   389  					DiskSizeGb:  c.DiskSizeGb,
   390  					DiskType:    fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType),
   391  				},
   392  			},
   393  		},
   394  		GuestAccelerators: guestAccelerators,
   395  		Labels:            c.Labels,
   396  		MachineType:       machineType.SelfLink,
   397  		Metadata: &compute.Metadata{
   398  			Items: metadata,
   399  		},
   400  		MinCpuPlatform: c.MinCpuPlatform,
   401  		Name:           c.Name,
   402  		NetworkInterfaces: []*compute.NetworkInterface{
   403  			{
   404  				AccessConfigs: []*compute.AccessConfig{accessconfig},
   405  				Network:       networkId,
   406  				Subnetwork:    subnetworkId,
   407  			},
   408  		},
   409  		Scheduling: &compute.Scheduling{
   410  			OnHostMaintenance: c.OnHostMaintenance,
   411  			Preemptible:       c.Preemptible,
   412  		},
   413  		ServiceAccounts: []*compute.ServiceAccount{
   414  			serviceAccount,
   415  		},
   416  		Tags: &compute.Tags{
   417  			Items: c.Tags,
   418  		},
   419  	}
   420  
   421  	d.ui.Message("Requesting instance creation...")
   422  	op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
   423  	if err != nil {
   424  		return nil, err
   425  	}
   426  
   427  	errCh := make(chan error, 1)
   428  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op))
   429  	return errCh, nil
   430  }
   431  
   432  func (d *driverGCE) CreateOrResetWindowsPassword(instance, zone string, c *WindowsPasswordConfig) (<-chan error, error) {
   433  
   434  	errCh := make(chan error, 1)
   435  	go d.createWindowsPassword(errCh, instance, zone, c)
   436  
   437  	return errCh, nil
   438  }
   439  
   440  func (d *driverGCE) createWindowsPassword(errCh chan<- error, name, zone string, c *WindowsPasswordConfig) {
   441  
   442  	data, err := json.Marshal(c)
   443  
   444  	if err != nil {
   445  		errCh <- err
   446  		return
   447  	}
   448  	dCopy := string(data)
   449  
   450  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   451  	instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{Key: "windows-keys", Value: &dCopy})
   452  
   453  	op, err := d.service.Instances.SetMetadata(d.projectId, zone, name, &compute.Metadata{
   454  		Fingerprint: instance.Metadata.Fingerprint,
   455  		Items:       instance.Metadata.Items,
   456  	}).Do()
   457  
   458  	if err != nil {
   459  		errCh <- err
   460  		return
   461  	}
   462  
   463  	newErrCh := make(chan error, 1)
   464  	go waitForState(newErrCh, "DONE", d.refreshZoneOp(zone, op))
   465  
   466  	select {
   467  	case err = <-newErrCh:
   468  	case <-time.After(time.Second * 30):
   469  		err = errors.New("time out while waiting for instance to create")
   470  	}
   471  
   472  	if err != nil {
   473  		errCh <- err
   474  		return
   475  	}
   476  
   477  	timeout := time.Now().Add(time.Minute * 3)
   478  	hash := sha1.New()
   479  	random := rand.Reader
   480  
   481  	for time.Now().Before(timeout) {
   482  		if passwordResponses, err := d.getPasswordResponses(zone, name); err == nil {
   483  			for _, response := range passwordResponses {
   484  				if response.Modulus == c.Modulus {
   485  
   486  					decodedPassword, err := base64.StdEncoding.DecodeString(response.EncryptedPassword)
   487  
   488  					if err != nil {
   489  						errCh <- err
   490  						return
   491  					}
   492  					password, err := rsa.DecryptOAEP(hash, random, c.key, decodedPassword, nil)
   493  
   494  					if err != nil {
   495  						errCh <- err
   496  						return
   497  					}
   498  
   499  					c.password = string(password)
   500  					errCh <- nil
   501  					return
   502  				}
   503  			}
   504  		}
   505  
   506  		time.Sleep(2 * time.Second)
   507  	}
   508  	err = errors.New("Could not retrieve password. Timed out.")
   509  
   510  	errCh <- err
   511  	return
   512  
   513  }
   514  
   515  func (d *driverGCE) getPasswordResponses(zone, instance string) ([]windowsPasswordResponse, error) {
   516  	output, err := d.service.Instances.GetSerialPortOutput(d.projectId, zone, instance).Port(4).Do()
   517  
   518  	if err != nil {
   519  		return nil, err
   520  	}
   521  
   522  	responses := strings.Split(output.Contents, "\n")
   523  
   524  	passwordResponses := make([]windowsPasswordResponse, 0, len(responses))
   525  
   526  	for _, response := range responses {
   527  		var passwordResponse windowsPasswordResponse
   528  		if err := json.Unmarshal([]byte(response), &passwordResponse); err != nil {
   529  			continue
   530  		}
   531  
   532  		passwordResponses = append(passwordResponses, passwordResponse)
   533  	}
   534  
   535  	return passwordResponses, nil
   536  }
   537  
   538  func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
   539  	errCh := make(chan error, 1)
   540  	go waitForState(errCh, state, d.refreshInstanceState(zone, name))
   541  	return errCh
   542  }
   543  
   544  func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
   545  	return func() (string, error) {
   546  		instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   547  		if err != nil {
   548  			return "", err
   549  		}
   550  		return instance.Status, nil
   551  	}
   552  }
   553  
   554  func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
   555  	return func() (string, error) {
   556  		newOp, err := d.service.GlobalOperations.Get(d.projectId, 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  func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
   576  	return func() (string, error) {
   577  		newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
   578  		if err != nil {
   579  			return "", err
   580  		}
   581  
   582  		// If the op is done, check for errors
   583  		err = nil
   584  		if newOp.Status == "DONE" {
   585  			if newOp.Error != nil {
   586  				for _, e := range newOp.Error.Errors {
   587  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   588  				}
   589  			}
   590  		}
   591  
   592  		return newOp.Status, err
   593  	}
   594  }
   595  
   596  // used in conjunction with waitForState.
   597  type stateRefreshFunc func() (string, error)
   598  
   599  // waitForState will spin in a loop forever waiting for state to
   600  // reach a certain target.
   601  func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) error {
   602  	err := common.Retry(2, 2, 0, func(_ uint) (bool, error) {
   603  		state, err := refresh()
   604  		if err != nil {
   605  			return false, err
   606  		} else if state == target {
   607  			return true, nil
   608  		}
   609  		return false, nil
   610  	})
   611  	errCh <- err
   612  	return err
   613  }