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