github.com/kikitux/packer@v0.10.1-0.20160322154024-6237df566f9f/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  					DiskType:    fmt.Sprintf("zones/%s/diskTypes/%s", zone.Name, c.DiskType),
   278  				},
   279  			},
   280  		},
   281  		MachineType: machineType.SelfLink,
   282  		Metadata: &compute.Metadata{
   283  			Items: metadata,
   284  		},
   285  		Name: c.Name,
   286  		NetworkInterfaces: []*compute.NetworkInterface{
   287  			&compute.NetworkInterface{
   288  				AccessConfigs: []*compute.AccessConfig{
   289  					&accessconfig,
   290  				},
   291  				Network:    network.SelfLink,
   292  				Subnetwork: subnetworkSelfLink,
   293  			},
   294  		},
   295  		Scheduling: &compute.Scheduling{
   296  			Preemptible: c.Preemptible,
   297  		},
   298  		ServiceAccounts: []*compute.ServiceAccount{
   299  			&compute.ServiceAccount{
   300  				Email: "default",
   301  				Scopes: []string{
   302  					"https://www.googleapis.com/auth/userinfo.email",
   303  					"https://www.googleapis.com/auth/compute",
   304  					"https://www.googleapis.com/auth/devstorage.full_control",
   305  				},
   306  			},
   307  		},
   308  		Tags: &compute.Tags{
   309  			Items: c.Tags,
   310  		},
   311  	}
   312  
   313  	d.ui.Message("Requesting instance creation...")
   314  	op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	errCh := make(chan error, 1)
   320  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op))
   321  	return errCh, nil
   322  }
   323  
   324  func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
   325  	errCh := make(chan error, 1)
   326  	go waitForState(errCh, state, d.refreshInstanceState(zone, name))
   327  	return errCh
   328  }
   329  
   330  func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) {
   331  	projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud"}
   332  	for _, project := range projects {
   333  		image, err = d.service.Images.Get(project, img.Name).Do()
   334  		if err == nil && image != nil && image.SelfLink != "" {
   335  			return
   336  		}
   337  		image = nil
   338  	}
   339  
   340  	err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects)
   341  	return
   342  }
   343  
   344  func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
   345  	return func() (string, error) {
   346  		instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   347  		if err != nil {
   348  			return "", err
   349  		}
   350  		return instance.Status, nil
   351  	}
   352  }
   353  
   354  func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
   355  	return func() (string, error) {
   356  		newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do()
   357  		if err != nil {
   358  			return "", err
   359  		}
   360  
   361  		// If the op is done, check for errors
   362  		err = nil
   363  		if newOp.Status == "DONE" {
   364  			if newOp.Error != nil {
   365  				for _, e := range newOp.Error.Errors {
   366  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   367  				}
   368  			}
   369  		}
   370  
   371  		return newOp.Status, err
   372  	}
   373  }
   374  
   375  func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
   376  	return func() (string, error) {
   377  		newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
   378  		if err != nil {
   379  			return "", err
   380  		}
   381  
   382  		// If the op is done, check for errors
   383  		err = nil
   384  		if newOp.Status == "DONE" {
   385  			if newOp.Error != nil {
   386  				for _, e := range newOp.Error.Errors {
   387  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   388  				}
   389  			}
   390  		}
   391  
   392  		return newOp.Status, err
   393  	}
   394  }
   395  
   396  // stateRefreshFunc is used to refresh the state of a thing and is
   397  // used in conjunction with waitForState.
   398  type stateRefreshFunc func() (string, error)
   399  
   400  // waitForState will spin in a loop forever waiting for state to
   401  // reach a certain target.
   402  func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
   403  	for {
   404  		state, err := refresh()
   405  		if err != nil {
   406  			errCh <- err
   407  			return
   408  		}
   409  		if state == target {
   410  			errCh <- nil
   411  			return
   412  		}
   413  
   414  		time.Sleep(2 * time.Second)
   415  	}
   416  }