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