github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/googlecompute/driver_gce.go (about)

     1  package googlecompute
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/http"
     7  	"time"
     8  
     9  	"code.google.com/p/goauth2/oauth"
    10  	"code.google.com/p/goauth2/oauth/jwt"
    11  	"code.google.com/p/google-api-go-client/compute/v1"
    12  	"github.com/mitchellh/packer/packer"
    13  )
    14  
    15  // driverGCE is a Driver implementation that actually talks to GCE.
    16  // Create an instance using NewDriverGCE.
    17  type driverGCE struct {
    18  	projectId string
    19  	service   *compute.Service
    20  	ui        packer.Ui
    21  }
    22  
    23  const DriverScopes string = "https://www.googleapis.com/auth/compute " +
    24  	"https://www.googleapis.com/auth/devstorage.full_control"
    25  
    26  func NewDriverGCE(ui packer.Ui, p string, a *accountFile, c *clientSecretsFile) (Driver, error) {
    27  	// Get the token for use in our requests
    28  	log.Printf("[INFO] Requesting Google token...")
    29  	log.Printf("[INFO]   -- Email: %s", a.ClientEmail)
    30  	log.Printf("[INFO]   -- Scopes: %s", DriverScopes)
    31  	log.Printf("[INFO]   -- Private Key Length: %d", len(a.PrivateKey))
    32  	log.Printf("[INFO]   -- Token URL: %s", c.Web.TokenURI)
    33  	jwtTok := jwt.NewToken(
    34  		a.ClientEmail,
    35  		DriverScopes,
    36  		[]byte(a.PrivateKey))
    37  	jwtTok.ClaimSet.Aud = c.Web.TokenURI
    38  	token, err := jwtTok.Assert(new(http.Client))
    39  	if err != nil {
    40  		return nil, fmt.Errorf("Error retrieving auth token: %s", err)
    41  	}
    42  
    43  	// Instantiate the transport to communicate to Google
    44  	transport := &oauth.Transport{
    45  		Config: &oauth.Config{
    46  			ClientId: a.ClientId,
    47  			Scope:    DriverScopes,
    48  			TokenURL: c.Web.TokenURI,
    49  			AuthURL:  c.Web.AuthURI,
    50  		},
    51  		Token: token,
    52  	}
    53  
    54  	log.Printf("[INFO] Instantiating GCE client...")
    55  	service, err := compute.New(transport.Client())
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return &driverGCE{
    61  		projectId: p,
    62  		service:   service,
    63  		ui:        ui,
    64  	}, nil
    65  }
    66  
    67  func (d *driverGCE) CreateImage(name, description, url string) <-chan error {
    68  	image := &compute.Image{
    69  		Description: description,
    70  		Name:        name,
    71  		RawDisk: &compute.ImageRawDisk{
    72  			ContainerType: "TAR",
    73  			Source:        url,
    74  		},
    75  		SourceType: "RAW",
    76  	}
    77  
    78  	errCh := make(chan error, 1)
    79  	op, err := d.service.Images.Insert(d.projectId, image).Do()
    80  	if err != nil {
    81  		errCh <- err
    82  	} else {
    83  		go waitForState(errCh, "DONE", d.refreshGlobalOp(op))
    84  	}
    85  
    86  	return errCh
    87  }
    88  
    89  func (d *driverGCE) DeleteImage(name string) <-chan error {
    90  	errCh := make(chan error, 1)
    91  	op, err := d.service.Images.Delete(d.projectId, name).Do()
    92  	if err != nil {
    93  		errCh <- err
    94  	} else {
    95  		go waitForState(errCh, "DONE", d.refreshGlobalOp(op))
    96  	}
    97  
    98  	return errCh
    99  }
   100  
   101  func (d *driverGCE) DeleteInstance(zone, name string) (<-chan error, error) {
   102  	op, err := d.service.Instances.Delete(d.projectId, zone, name).Do()
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	errCh := make(chan error, 1)
   108  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone, op))
   109  	return errCh, nil
   110  }
   111  
   112  func (d *driverGCE) GetNatIP(zone, name string) (string, error) {
   113  	instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   114  	if err != nil {
   115  		return "", err
   116  	}
   117  
   118  	for _, ni := range instance.NetworkInterfaces {
   119  		if ni.AccessConfigs == nil {
   120  			continue
   121  		}
   122  
   123  		for _, ac := range ni.AccessConfigs {
   124  			if ac.NatIP != "" {
   125  				return ac.NatIP, nil
   126  			}
   127  		}
   128  	}
   129  
   130  	return "", nil
   131  }
   132  
   133  func (d *driverGCE) RunInstance(c *InstanceConfig) (<-chan error, error) {
   134  	// Get the zone
   135  	d.ui.Message(fmt.Sprintf("Loading zone: %s", c.Zone))
   136  	zone, err := d.service.Zones.Get(d.projectId, c.Zone).Do()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Get the image
   142  	d.ui.Message(fmt.Sprintf("Loading image: %s in project %s", c.Image.Name, c.Image.ProjectId))
   143  	image, err := d.getImage(c.Image)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  
   148  	// Get the machine type
   149  	d.ui.Message(fmt.Sprintf("Loading machine type: %s", c.MachineType))
   150  	machineType, err := d.service.MachineTypes.Get(
   151  		d.projectId, zone.Name, c.MachineType).Do()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	// TODO(mitchellh): deprecation warnings
   156  
   157  	// Get the network
   158  	d.ui.Message(fmt.Sprintf("Loading network: %s", c.Network))
   159  	network, err := d.service.Networks.Get(d.projectId, c.Network).Do()
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// Build up the metadata
   165  	metadata := make([]*compute.MetadataItems, len(c.Metadata))
   166  	for k, v := range c.Metadata {
   167  		metadata = append(metadata, &compute.MetadataItems{
   168  			Key:   k,
   169  			Value: v,
   170  		})
   171  	}
   172  
   173  	// Create the instance information
   174  	instance := compute.Instance{
   175  		Description: c.Description,
   176  		Disks: []*compute.AttachedDisk{
   177  			&compute.AttachedDisk{
   178  				Type:       "PERSISTENT",
   179  				Mode:       "READ_WRITE",
   180  				Kind:       "compute#attachedDisk",
   181  				Boot:       true,
   182  				AutoDelete: true,
   183  				InitializeParams: &compute.AttachedDiskInitializeParams{
   184  					SourceImage: image.SelfLink,
   185  					DiskSizeGb:  c.DiskSizeGb,
   186  				},
   187  			},
   188  		},
   189  		MachineType: machineType.SelfLink,
   190  		Metadata: &compute.Metadata{
   191  			Items: metadata,
   192  		},
   193  		Name: c.Name,
   194  		NetworkInterfaces: []*compute.NetworkInterface{
   195  			&compute.NetworkInterface{
   196  				AccessConfigs: []*compute.AccessConfig{
   197  					&compute.AccessConfig{
   198  						Name: "AccessConfig created by Packer",
   199  						Type: "ONE_TO_ONE_NAT",
   200  					},
   201  				},
   202  				Network: network.SelfLink,
   203  			},
   204  		},
   205  		ServiceAccounts: []*compute.ServiceAccount{
   206  			&compute.ServiceAccount{
   207  				Email: "default",
   208  				Scopes: []string{
   209  					"https://www.googleapis.com/auth/userinfo.email",
   210  					"https://www.googleapis.com/auth/compute",
   211  					"https://www.googleapis.com/auth/devstorage.full_control",
   212  				},
   213  			},
   214  		},
   215  		Tags: &compute.Tags{
   216  			Items: c.Tags,
   217  		},
   218  	}
   219  
   220  	d.ui.Message("Requesting instance creation...")
   221  	op, err := d.service.Instances.Insert(d.projectId, zone.Name, &instance).Do()
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	errCh := make(chan error, 1)
   227  	go waitForState(errCh, "DONE", d.refreshZoneOp(zone.Name, op))
   228  	return errCh, nil
   229  }
   230  
   231  func (d *driverGCE) WaitForInstance(state, zone, name string) <-chan error {
   232  	errCh := make(chan error, 1)
   233  	go waitForState(errCh, state, d.refreshInstanceState(zone, name))
   234  	return errCh
   235  }
   236  
   237  func (d *driverGCE) getImage(img Image) (image *compute.Image, err error) {
   238  	projects := []string{img.ProjectId, "centos-cloud", "coreos-cloud", "debian-cloud", "google-containers", "opensuse-cloud", "rhel-cloud", "suse-cloud", "windows-cloud"}
   239  	for _, project := range projects {
   240  		image, err = d.service.Images.Get(project, img.Name).Do()
   241  		if err == nil && image != nil && image.SelfLink != "" {
   242  			return
   243  		}
   244  		image = nil
   245  	}
   246  
   247  	err = fmt.Errorf("Image %s could not be found in any of these projects: %s", img.Name, projects)
   248  	return
   249  }
   250  
   251  func (d *driverGCE) refreshInstanceState(zone, name string) stateRefreshFunc {
   252  	return func() (string, error) {
   253  		instance, err := d.service.Instances.Get(d.projectId, zone, name).Do()
   254  		if err != nil {
   255  			return "", err
   256  		}
   257  		return instance.Status, nil
   258  	}
   259  }
   260  
   261  func (d *driverGCE) refreshGlobalOp(op *compute.Operation) stateRefreshFunc {
   262  	return func() (string, error) {
   263  		newOp, err := d.service.GlobalOperations.Get(d.projectId, op.Name).Do()
   264  		if err != nil {
   265  			return "", err
   266  		}
   267  
   268  		// If the op is done, check for errors
   269  		err = nil
   270  		if newOp.Status == "DONE" {
   271  			if newOp.Error != nil {
   272  				for _, e := range newOp.Error.Errors {
   273  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   274  				}
   275  			}
   276  		}
   277  
   278  		return newOp.Status, err
   279  	}
   280  }
   281  
   282  func (d *driverGCE) refreshZoneOp(zone string, op *compute.Operation) stateRefreshFunc {
   283  	return func() (string, error) {
   284  		newOp, err := d.service.ZoneOperations.Get(d.projectId, zone, op.Name).Do()
   285  		if err != nil {
   286  			return "", err
   287  		}
   288  
   289  		// If the op is done, check for errors
   290  		err = nil
   291  		if newOp.Status == "DONE" {
   292  			if newOp.Error != nil {
   293  				for _, e := range newOp.Error.Errors {
   294  					err = packer.MultiErrorAppend(err, fmt.Errorf(e.Message))
   295  				}
   296  			}
   297  		}
   298  
   299  		return newOp.Status, err
   300  	}
   301  }
   302  
   303  // stateRefreshFunc is used to refresh the state of a thing and is
   304  // used in conjunction with waitForState.
   305  type stateRefreshFunc func() (string, error)
   306  
   307  // waitForState will spin in a loop forever waiting for state to
   308  // reach a certain target.
   309  func waitForState(errCh chan<- error, target string, refresh stateRefreshFunc) {
   310  	for {
   311  		state, err := refresh()
   312  		if err != nil {
   313  			errCh <- err
   314  			return
   315  		}
   316  		if state == target {
   317  			errCh <- nil
   318  			return
   319  		}
   320  
   321  		time.Sleep(2 * time.Second)
   322  	}
   323  }