github.com/lamielle/terraform@v0.3.2-0.20141121070651-81f008ba53d5/builtin/providers/google/resource_compute_instance.go (about)

     1  package google
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"code.google.com/p/google-api-go-client/compute/v1"
     9  	"code.google.com/p/google-api-go-client/googleapi"
    10  	"github.com/hashicorp/terraform/helper/hashcode"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  )
    13  
    14  func resourceComputeInstance() *schema.Resource {
    15  	return &schema.Resource{
    16  		Create: resourceComputeInstanceCreate,
    17  		Read:   resourceComputeInstanceRead,
    18  		Update: resourceComputeInstanceUpdate,
    19  		Delete: resourceComputeInstanceDelete,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"name": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Required: true,
    25  				ForceNew: true,
    26  			},
    27  
    28  			"description": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Optional: true,
    31  				ForceNew: true,
    32  			},
    33  
    34  			"machine_type": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Required: true,
    37  				ForceNew: true,
    38  			},
    39  
    40  			"zone": &schema.Schema{
    41  				Type:     schema.TypeString,
    42  				Required: true,
    43  				ForceNew: true,
    44  			},
    45  
    46  			"disk": &schema.Schema{
    47  				Type:     schema.TypeList,
    48  				Required: true,
    49  				ForceNew: true,
    50  				Elem: &schema.Resource{
    51  					Schema: map[string]*schema.Schema{
    52  						// TODO(mitchellh): one of image or disk is required
    53  
    54  						"disk": &schema.Schema{
    55  							Type:     schema.TypeString,
    56  							Optional: true,
    57  						},
    58  
    59  						"image": &schema.Schema{
    60  							Type:     schema.TypeString,
    61  							Optional: true,
    62  						},
    63  
    64  						"type": &schema.Schema{
    65  							Type:     schema.TypeString,
    66  							Optional: true,
    67  							ForceNew: true,
    68  						},
    69  
    70  						"auto_delete": &schema.Schema{
    71  							Type:     schema.TypeBool,
    72  							Optional: true,
    73  						},
    74  					},
    75  				},
    76  			},
    77  
    78  			"network": &schema.Schema{
    79  				Type:     schema.TypeList,
    80  				Required: true,
    81  				ForceNew: true,
    82  				Elem: &schema.Resource{
    83  					Schema: map[string]*schema.Schema{
    84  						"source": &schema.Schema{
    85  							Type:     schema.TypeString,
    86  							Required: true,
    87  						},
    88  
    89  						"address": &schema.Schema{
    90  							Type:     schema.TypeString,
    91  							Optional: true,
    92  						},
    93  
    94  						"name": &schema.Schema{
    95  							Type:     schema.TypeString,
    96  							Computed: true,
    97  						},
    98  
    99  						"internal_address": &schema.Schema{
   100  							Type:     schema.TypeString,
   101  							Computed: true,
   102  						},
   103  
   104  						"external_address": &schema.Schema{
   105  							Type:     schema.TypeString,
   106  							Computed: true,
   107  						},
   108  					},
   109  				},
   110  			},
   111  
   112  			"can_ip_forward": &schema.Schema{
   113  				Type:     schema.TypeBool,
   114  				Optional: true,
   115  				Default:  false,
   116  				ForceNew: true,
   117  			},
   118  
   119  			"metadata": &schema.Schema{
   120  				Type:     schema.TypeList,
   121  				Optional: true,
   122  				Elem: &schema.Schema{
   123  					Type: schema.TypeMap,
   124  				},
   125  			},
   126  
   127  			"tags": &schema.Schema{
   128  				Type:     schema.TypeSet,
   129  				Optional: true,
   130  				Elem:     &schema.Schema{Type: schema.TypeString},
   131  				Set: func(v interface{}) int {
   132  					return hashcode.String(v.(string))
   133  				},
   134  			},
   135  
   136  			"metadata_fingerprint": &schema.Schema{
   137  				Type:     schema.TypeString,
   138  				Computed: true,
   139  			},
   140  
   141  			"tags_fingerprint": &schema.Schema{
   142  				Type:     schema.TypeString,
   143  				Computed: true,
   144  			},
   145  		},
   146  	}
   147  }
   148  
   149  func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   150  	config := meta.(*Config)
   151  
   152  	// Get the zone
   153  	log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string))
   154  	zone, err := config.clientCompute.Zones.Get(
   155  		config.Project, d.Get("zone").(string)).Do()
   156  	if err != nil {
   157  		return fmt.Errorf(
   158  			"Error loading zone '%s': %s", d.Get("zone").(string), err)
   159  	}
   160  
   161  	// Get the machine type
   162  	log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
   163  	machineType, err := config.clientCompute.MachineTypes.Get(
   164  		config.Project, zone.Name, d.Get("machine_type").(string)).Do()
   165  	if err != nil {
   166  		return fmt.Errorf(
   167  			"Error loading machine type: %s",
   168  			err)
   169  	}
   170  
   171  	// Build up the list of disks
   172  	disksCount := d.Get("disk.#").(int)
   173  	disks := make([]*compute.AttachedDisk, 0, disksCount)
   174  	for i := 0; i < disksCount; i++ {
   175  		prefix := fmt.Sprintf("disk.%d", i)
   176  
   177  		// var sourceLink string
   178  
   179  		// Build the disk
   180  		var disk compute.AttachedDisk
   181  		disk.Type = "PERSISTENT"
   182  		disk.Mode = "READ_WRITE"
   183  		disk.Boot = i == 0
   184  		disk.AutoDelete = true
   185  
   186  		if v, ok := d.GetOk(prefix + ".auto_delete"); ok {
   187  			disk.AutoDelete = v.(bool)
   188  		}
   189  
   190  		// Load up the disk for this disk if specified
   191  		if v, ok := d.GetOk(prefix + ".disk"); ok {
   192  			diskName := v.(string)
   193  			diskData, err := config.clientCompute.Disks.Get(
   194  				config.Project, zone.Name, diskName).Do()
   195  			if err != nil {
   196  				return fmt.Errorf(
   197  					"Error loading disk '%s': %s",
   198  					diskName, err)
   199  			}
   200  
   201  			disk.Source = diskData.SelfLink
   202  		}
   203  
   204  		// Load up the image for this disk if specified
   205  		if v, ok := d.GetOk(prefix + ".image"); ok {
   206  			imageName := v.(string)
   207  			image, err := readImage(config, imageName)
   208  			if err != nil {
   209  				return fmt.Errorf(
   210  					"Error loading image '%s': %s",
   211  					imageName, err)
   212  			}
   213  
   214  			disk.InitializeParams = &compute.AttachedDiskInitializeParams{
   215  				SourceImage: image.SelfLink,
   216  			}
   217  		}
   218  
   219  		if v, ok := d.GetOk(prefix + ".type"); ok {
   220  			diskTypeName := v.(string)
   221  			diskType, err := readDiskType(config, zone, diskTypeName)
   222  			if err != nil {
   223  				return fmt.Errorf(
   224  					"Error loading disk type '%s': %s",
   225  					diskTypeName, err)
   226  			}
   227  
   228  			disk.InitializeParams.DiskType = diskType.SelfLink
   229  		}
   230  
   231  		disks = append(disks, &disk)
   232  	}
   233  
   234  	// Build up the list of networks
   235  	networksCount := d.Get("network.#").(int)
   236  	networks := make([]*compute.NetworkInterface, 0, networksCount)
   237  	for i := 0; i < networksCount; i++ {
   238  		prefix := fmt.Sprintf("network.%d", i)
   239  		// Load up the name of this network
   240  		networkName := d.Get(prefix + ".source").(string)
   241  		network, err := config.clientCompute.Networks.Get(
   242  			config.Project, networkName).Do()
   243  		if err != nil {
   244  			return fmt.Errorf(
   245  				"Error loading network '%s': %s",
   246  				networkName, err)
   247  		}
   248  
   249  		// Build the disk
   250  		var iface compute.NetworkInterface
   251  		iface.AccessConfigs = []*compute.AccessConfig{
   252  			&compute.AccessConfig{
   253  				Type:  "ONE_TO_ONE_NAT",
   254  				NatIP: d.Get(prefix + ".address").(string),
   255  			},
   256  		}
   257  		iface.Network = network.SelfLink
   258  
   259  		networks = append(networks, &iface)
   260  	}
   261  
   262  	// Create the instance information
   263  	instance := compute.Instance{
   264  		CanIpForward:      d.Get("can_ip_forward").(bool),
   265  		Description:       d.Get("description").(string),
   266  		Disks:             disks,
   267  		MachineType:       machineType.SelfLink,
   268  		Metadata:          resourceInstanceMetadata(d),
   269  		Name:              d.Get("name").(string),
   270  		NetworkInterfaces: networks,
   271  		Tags:              resourceInstanceTags(d),
   272  		/*
   273  			ServiceAccounts: []*compute.ServiceAccount{
   274  				&compute.ServiceAccount{
   275  					Email: "default",
   276  					Scopes: []string{
   277  						"https://www.googleapis.com/auth/userinfo.email",
   278  						"https://www.googleapis.com/auth/compute",
   279  						"https://www.googleapis.com/auth/devstorage.full_control",
   280  					},
   281  				},
   282  			},
   283  		*/
   284  	}
   285  
   286  	log.Printf("[INFO] Requesting instance creation")
   287  	op, err := config.clientCompute.Instances.Insert(
   288  		config.Project, zone.Name, &instance).Do()
   289  	if err != nil {
   290  		return fmt.Errorf("Error creating instance: %s", err)
   291  	}
   292  
   293  	// Store the ID now
   294  	d.SetId(instance.Name)
   295  
   296  	// Wait for the operation to complete
   297  	w := &OperationWaiter{
   298  		Service: config.clientCompute,
   299  		Op:      op,
   300  		Project: config.Project,
   301  		Zone:    zone.Name,
   302  		Type:    OperationWaitZone,
   303  	}
   304  	state := w.Conf()
   305  	state.Delay = 10 * time.Second
   306  	state.Timeout = 10 * time.Minute
   307  	state.MinTimeout = 2 * time.Second
   308  	opRaw, err := state.WaitForState()
   309  	if err != nil {
   310  		return fmt.Errorf("Error waiting for instance to create: %s", err)
   311  	}
   312  	op = opRaw.(*compute.Operation)
   313  	if op.Error != nil {
   314  		// The resource didn't actually create
   315  		d.SetId("")
   316  
   317  		// Return the error
   318  		return OperationError(*op.Error)
   319  	}
   320  
   321  	return resourceComputeInstanceRead(d, meta)
   322  }
   323  
   324  func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
   325  	config := meta.(*Config)
   326  
   327  	instance, err := config.clientCompute.Instances.Get(
   328  		config.Project, d.Get("zone").(string), d.Id()).Do()
   329  	if err != nil {
   330  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   331  			// The resource doesn't exist anymore
   332  			d.SetId("")
   333  
   334  			return nil
   335  		}
   336  
   337  		return fmt.Errorf("Error reading instance: %s", err)
   338  	}
   339  
   340  	d.Set("can_ip_forward", instance.CanIpForward)
   341  
   342  	// Set the networks
   343  	externalIP := ""
   344  	for i, iface := range instance.NetworkInterfaces {
   345  		prefix := fmt.Sprintf("network.%d", i)
   346  		d.Set(prefix+".name", iface.Name)
   347  
   348  		// Use the first external IP found for the default connection info.
   349  		natIP := resourceInstanceNatIP(iface)
   350  		if externalIP == "" && natIP != "" {
   351  			externalIP = natIP
   352  		}
   353  		d.Set(prefix+".external_address", natIP)
   354  
   355  		d.Set(prefix+".internal_address", iface.NetworkIP)
   356  	}
   357  
   358  	// Initialize the connection info
   359  	d.SetConnInfo(map[string]string{
   360  		"type": "ssh",
   361  		"host": externalIP,
   362  	})
   363  
   364  	// Set the metadata fingerprint if there is one.
   365  	if instance.Metadata != nil {
   366  		d.Set("metadata_fingerprint", instance.Metadata.Fingerprint)
   367  	}
   368  
   369  	// Set the tags fingerprint if there is one.
   370  	if instance.Tags != nil {
   371  		d.Set("tags_fingerprint", instance.Tags.Fingerprint)
   372  	}
   373  
   374  	return nil
   375  }
   376  
   377  func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   378  	config := meta.(*Config)
   379  
   380  	// Enable partial mode for the resource since it is possible
   381  	d.Partial(true)
   382  
   383  	// If the Metadata has changed, then update that.
   384  	if d.HasChange("metadata") {
   385  		metadata := resourceInstanceMetadata(d)
   386  		op, err := config.clientCompute.Instances.SetMetadata(
   387  			config.Project, d.Get("zone").(string), d.Id(), metadata).Do()
   388  		if err != nil {
   389  			return fmt.Errorf("Error updating metadata: %s", err)
   390  		}
   391  
   392  		w := &OperationWaiter{
   393  			Service: config.clientCompute,
   394  			Op:      op,
   395  			Project: config.Project,
   396  			Zone:    d.Get("zone").(string),
   397  			Type:    OperationWaitZone,
   398  		}
   399  		state := w.Conf()
   400  		state.Delay = 1 * time.Second
   401  		state.Timeout = 5 * time.Minute
   402  		state.MinTimeout = 2 * time.Second
   403  		opRaw, err := state.WaitForState()
   404  		if err != nil {
   405  			return fmt.Errorf("Error waiting for metadata to update: %s", err)
   406  		}
   407  		op = opRaw.(*compute.Operation)
   408  		if op.Error != nil {
   409  			// Return the error
   410  			return OperationError(*op.Error)
   411  		}
   412  
   413  		d.SetPartial("metadata")
   414  	}
   415  
   416  	if d.HasChange("tags") {
   417  		tags := resourceInstanceTags(d)
   418  		op, err := config.clientCompute.Instances.SetTags(
   419  			config.Project, d.Get("zone").(string), d.Id(), tags).Do()
   420  		if err != nil {
   421  			return fmt.Errorf("Error updating tags: %s", err)
   422  		}
   423  
   424  		w := &OperationWaiter{
   425  			Service: config.clientCompute,
   426  			Op:      op,
   427  			Project: config.Project,
   428  			Zone:    d.Get("zone").(string),
   429  			Type:    OperationWaitZone,
   430  		}
   431  		state := w.Conf()
   432  		state.Delay = 1 * time.Second
   433  		state.Timeout = 5 * time.Minute
   434  		state.MinTimeout = 2 * time.Second
   435  		opRaw, err := state.WaitForState()
   436  		if err != nil {
   437  			return fmt.Errorf("Error waiting for tags to update: %s", err)
   438  		}
   439  		op = opRaw.(*compute.Operation)
   440  		if op.Error != nil {
   441  			// Return the error
   442  			return OperationError(*op.Error)
   443  		}
   444  
   445  		d.SetPartial("tags")
   446  	}
   447  
   448  	// We made it, disable partial mode
   449  	d.Partial(false)
   450  
   451  	return resourceComputeInstanceRead(d, meta)
   452  }
   453  
   454  func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   455  	config := meta.(*Config)
   456  
   457  	op, err := config.clientCompute.Instances.Delete(
   458  		config.Project, d.Get("zone").(string), d.Id()).Do()
   459  	if err != nil {
   460  		return fmt.Errorf("Error deleting instance: %s", err)
   461  	}
   462  
   463  	// Wait for the operation to complete
   464  	w := &OperationWaiter{
   465  		Service: config.clientCompute,
   466  		Op:      op,
   467  		Project: config.Project,
   468  		Zone:    d.Get("zone").(string),
   469  		Type:    OperationWaitZone,
   470  	}
   471  	state := w.Conf()
   472  	state.Delay = 5 * time.Second
   473  	state.Timeout = 5 * time.Minute
   474  	state.MinTimeout = 2 * time.Second
   475  	opRaw, err := state.WaitForState()
   476  	if err != nil {
   477  		return fmt.Errorf("Error waiting for instance to delete: %s", err)
   478  	}
   479  	op = opRaw.(*compute.Operation)
   480  	if op.Error != nil {
   481  		// Return the error
   482  		return OperationError(*op.Error)
   483  	}
   484  
   485  	d.SetId("")
   486  	return nil
   487  }
   488  
   489  func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata {
   490  	var metadata *compute.Metadata
   491  	if v := d.Get("metadata").([]interface{}); len(v) > 0 {
   492  		m := new(compute.Metadata)
   493  		m.Items = make([]*compute.MetadataItems, 0, len(v))
   494  		for _, v := range v {
   495  			for k, v := range v.(map[string]interface{}) {
   496  				m.Items = append(m.Items, &compute.MetadataItems{
   497  					Key:   k,
   498  					Value: v.(string),
   499  				})
   500  			}
   501  		}
   502  
   503  		// Set the fingerprint. If the metadata has never been set before
   504  		// then this will just be blank.
   505  		m.Fingerprint = d.Get("metadata_fingerprint").(string)
   506  
   507  		metadata = m
   508  	}
   509  
   510  	return metadata
   511  }
   512  
   513  func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
   514  	// Calculate the tags
   515  	var tags *compute.Tags
   516  	if v := d.Get("tags"); v != nil {
   517  		vs := v.(*schema.Set)
   518  		tags = new(compute.Tags)
   519  		tags.Items = make([]string, vs.Len())
   520  		for i, v := range vs.List() {
   521  			tags.Items[i] = v.(string)
   522  		}
   523  
   524  		tags.Fingerprint = d.Get("tags_fingerprint").(string)
   525  	}
   526  
   527  	return tags
   528  }
   529  
   530  // resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type
   531  // in the compute.NetworkInterface's AccessConfigs.
   532  func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) {
   533  	for _, config := range iface.AccessConfigs {
   534  		if config.Type == "ONE_TO_ONE_NAT" {
   535  			natIP = config.NatIP
   536  			break
   537  		}
   538  	}
   539  
   540  	return natIP
   541  }