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