github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/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  		Scheduling: &compute.Scheduling{
   259  			Preemptible: c.Preemptible,
   260  		},
   261  		ServiceAccounts: []*compute.ServiceAccount{
   262  			&compute.ServiceAccount{
   263  				Email: "default",
   264  				Scopes: []string{
   265  					"https://www.googleapis.com/auth/userinfo.email",
   266  					"https://www.googleapis.com/auth/compute",
   267  					"https://www.googleapis.com/auth/devstorage.full_control",
   268  				},
   269  			},
   270  		},
   271  		Tags: &compute.Tags{
   272  			Items: c.Tags,
   273  		},
   274  	}
   275  
   276  	d.ui.Message("Requesting instance creation...")
   277  	op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	errCh := make(chan error, 1)
   283  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op))
   284  	return errCh, nil
   285  }
   286  
   287  func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
   288  	errCh := make(chan error, 1)
   289  	go waitForState(errCh, state, d.refreshInstanceState(zone, name))
   290  	return errCh
   291  }
   292  
   293  func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) {
   294  	projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "ubuntu-os-cloud", "windows-cloud"}
   295  	for _, project := range projects {
   296  		image, err = d.service.Images.Get(project, img.Name).Do()
   297  		if err == nil && image != nil && image.SelfLink != "" {
   298  			return
   299  		}
   300  		image = nil
   301  	}
   302  
   303  	err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects)
   304  	return
   305  }
   306  
   307  func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
   308  	return func() (string, error) {
   309  		instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   310  		if err != nil {
   311  			return "", err
   312  		}
   313  		return instance.Status, nil
   314  	}
   315  }
   316  
   317  func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
   318  	return func() (string, error) {
   319  		newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do()
   320  		if err != nil {
   321  			return "", err
   322  		}
   323  
   324  		// If the op is done, check for errors
   325  		err = nil
   326  		if newOp.Status == "DONE" {
   327  			if newOp.Error != nil {
   328  				for _, e := range newOp.Error.Errors {
   329  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   330  				}
   331  			}
   332  		}
   333  
   334  		return newOp.Status, err
   335  	}
   336  }
   337  
   338  func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
   339  	return func() (string, error) {
   340  		newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
   341  		if err != nil {
   342  			return "", err
   343  		}
   344  
   345  		// If the op is done, check for errors
   346  		err = nil
   347  		if newOp.Status == "DONE" {
   348  			if newOp.Error != nil {
   349  				for _, e := range newOp.Error.Errors {
   350  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   351  				}
   352  			}
   353  		}
   354  
   355  		return newOp.Status, err
   356  	}
   357  }
   358  
   359  // stateRefreshFunc is used to refresh the state of a thing and is
   360  // used in conjunction with waitForState.
   361  type stateRefreshFunc func() (string, error)
   362  
   363  // waitForState will spin in a loop forever waiting for state to
   364  // reach a certain target.
   365  func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
   366  	for {
   367  		state, err := refresh()
   368  		if err != nil {
   369  			errCh <- err
   370  			return
   371  		}
   372  		if state == target {
   373  			errCh <- nil
   374  			return
   375  		}
   376  
   377  		time.Sleep(2 * time.Second)
   378  	}
   379  }