github.com/chalford/terraform@v0.3.7-0.20150113080010-a78c69a8c81f/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  			"service_account": &schema.Schema{
   128  				Type:     schema.TypeList,
   129  				Optional: true,
   130  				ForceNew: true,
   131  				Elem: &schema.Resource{
   132  					Schema: map[string]*schema.Schema{
   133  						"email": &schema.Schema{
   134  							Type:     schema.TypeString,
   135  							Computed: true,
   136  							ForceNew: true,
   137  						},
   138  
   139  						"scopes": &schema.Schema{
   140  							Type:      schema.TypeList,
   141  							Required:  true,
   142  							ForceNew:  true,
   143  							Elem:      &schema.Schema{
   144  								Type:      schema.TypeString,
   145  								StateFunc: func(v interface{}) string {
   146  									return canonicalizeServiceScope(v.(string))
   147  								},
   148  							},
   149  						},
   150  					},
   151  				},
   152  			},
   153  
   154  			"tags": &schema.Schema{
   155  				Type:     schema.TypeSet,
   156  				Optional: true,
   157  				Elem:     &schema.Schema{Type: schema.TypeString},
   158  				Set: func(v interface{}) int {
   159  					return hashcode.String(v.(string))
   160  				},
   161  			},
   162  
   163  			"metadata_fingerprint": &schema.Schema{
   164  				Type:     schema.TypeString,
   165  				Computed: true,
   166  			},
   167  
   168  			"tags_fingerprint": &schema.Schema{
   169  				Type:     schema.TypeString,
   170  				Computed: true,
   171  			},
   172  		},
   173  	}
   174  }
   175  
   176  func resourceComputeInstanceCreate(d *schema.ResourceData, meta interface{}) error {
   177  	config := meta.(*Config)
   178  
   179  	// Get the zone
   180  	log.Printf("[DEBUG] Loading zone: %s", d.Get("zone").(string))
   181  	zone, err := config.clientCompute.Zones.Get(
   182  		config.Project, d.Get("zone").(string)).Do()
   183  	if err != nil {
   184  		return fmt.Errorf(
   185  			"Error loading zone '%s': %s", d.Get("zone").(string), err)
   186  	}
   187  
   188  	// Get the machine type
   189  	log.Printf("[DEBUG] Loading machine type: %s", d.Get("machine_type").(string))
   190  	machineType, err := config.clientCompute.MachineTypes.Get(
   191  		config.Project, zone.Name, d.Get("machine_type").(string)).Do()
   192  	if err != nil {
   193  		return fmt.Errorf(
   194  			"Error loading machine type: %s",
   195  			err)
   196  	}
   197  
   198  	// Build up the list of disks
   199  	disksCount := d.Get("disk.#").(int)
   200  	disks := make([]*compute.AttachedDisk, 0, disksCount)
   201  	for i := 0; i < disksCount; i++ {
   202  		prefix := fmt.Sprintf("disk.%d", i)
   203  
   204  		// var sourceLink string
   205  
   206  		// Build the disk
   207  		var disk compute.AttachedDisk
   208  		disk.Type = "PERSISTENT"
   209  		disk.Mode = "READ_WRITE"
   210  		disk.Boot = i == 0
   211  		disk.AutoDelete = true
   212  
   213  		if v, ok := d.GetOk(prefix + ".auto_delete"); ok {
   214  			disk.AutoDelete = v.(bool)
   215  		}
   216  
   217  		// Load up the disk for this disk if specified
   218  		if v, ok := d.GetOk(prefix + ".disk"); ok {
   219  			diskName := v.(string)
   220  			diskData, err := config.clientCompute.Disks.Get(
   221  				config.Project, zone.Name, diskName).Do()
   222  			if err != nil {
   223  				return fmt.Errorf(
   224  					"Error loading disk '%s': %s",
   225  					diskName, err)
   226  			}
   227  
   228  			disk.Source = diskData.SelfLink
   229  		}
   230  
   231  		// Load up the image for this disk if specified
   232  		if v, ok := d.GetOk(prefix + ".image"); ok {
   233  			imageName := v.(string)
   234  			image, err := readImage(config, imageName)
   235  			if err != nil {
   236  				return fmt.Errorf(
   237  					"Error loading image '%s': %s",
   238  					imageName, err)
   239  			}
   240  
   241  			disk.InitializeParams = &compute.AttachedDiskInitializeParams{
   242  				SourceImage: image.SelfLink,
   243  			}
   244  		}
   245  
   246  		if v, ok := d.GetOk(prefix + ".type"); ok {
   247  			diskTypeName := v.(string)
   248  			diskType, err := readDiskType(config, zone, diskTypeName)
   249  			if err != nil {
   250  				return fmt.Errorf(
   251  					"Error loading disk type '%s': %s",
   252  					diskTypeName, err)
   253  			}
   254  
   255  			disk.InitializeParams.DiskType = diskType.SelfLink
   256  		}
   257  
   258  		disks = append(disks, &disk)
   259  	}
   260  
   261  	// Build up the list of networks
   262  	networksCount := d.Get("network.#").(int)
   263  	networks := make([]*compute.NetworkInterface, 0, networksCount)
   264  	for i := 0; i < networksCount; i++ {
   265  		prefix := fmt.Sprintf("network.%d", i)
   266  		// Load up the name of this network
   267  		networkName := d.Get(prefix + ".source").(string)
   268  		network, err := config.clientCompute.Networks.Get(
   269  			config.Project, networkName).Do()
   270  		if err != nil {
   271  			return fmt.Errorf(
   272  				"Error loading network '%s': %s",
   273  				networkName, err)
   274  		}
   275  
   276  		// Build the disk
   277  		var iface compute.NetworkInterface
   278  		iface.AccessConfigs = []*compute.AccessConfig{
   279  			&compute.AccessConfig{
   280  				Type:  "ONE_TO_ONE_NAT",
   281  				NatIP: d.Get(prefix + ".address").(string),
   282  			},
   283  		}
   284  		iface.Network = network.SelfLink
   285  
   286  		networks = append(networks, &iface)
   287  	}
   288  
   289  	serviceAccountsCount := d.Get("service_account.#").(int)
   290  	serviceAccounts := make([]*compute.ServiceAccount, 0, serviceAccountsCount)
   291  	for i := 0; i < serviceAccountsCount; i++ {
   292  		prefix := fmt.Sprintf("service_account.%d", i)
   293  
   294  		scopesCount := d.Get(prefix + ".scopes.#").(int)
   295  		scopes := make([]string, 0, scopesCount)
   296  		for j := 0; j < scopesCount; j++ {
   297  			scope := d.Get(fmt.Sprintf(prefix + ".scopes.%d", j)).(string)
   298  			scopes = append(scopes, canonicalizeServiceScope(scope))
   299  		}
   300  
   301  		serviceAccount := &compute.ServiceAccount {
   302  			Email:  "default",
   303  			Scopes: scopes,
   304  		}
   305  
   306  		serviceAccounts = append(serviceAccounts, serviceAccount)
   307  	}
   308  
   309  	// Create the instance information
   310  	instance := compute.Instance{
   311  		CanIpForward:      d.Get("can_ip_forward").(bool),
   312  		Description:       d.Get("description").(string),
   313  		Disks:             disks,
   314  		MachineType:       machineType.SelfLink,
   315  		Metadata:          resourceInstanceMetadata(d),
   316  		Name:              d.Get("name").(string),
   317  		NetworkInterfaces: networks,
   318  		Tags:              resourceInstanceTags(d),
   319  		ServiceAccounts:   serviceAccounts,
   320  	}
   321  
   322  	log.Printf("[INFO] Requesting instance creation")
   323  	op, err := config.clientCompute.Instances.Insert(
   324  		config.Project, zone.Name, &instance).Do()
   325  	if err != nil {
   326  		return fmt.Errorf("Error creating instance: %s", err)
   327  	}
   328  
   329  	// Store the ID now
   330  	d.SetId(instance.Name)
   331  
   332  	// Wait for the operation to complete
   333  	w := &OperationWaiter{
   334  		Service: config.clientCompute,
   335  		Op:      op,
   336  		Project: config.Project,
   337  		Zone:    zone.Name,
   338  		Type:    OperationWaitZone,
   339  	}
   340  	state := w.Conf()
   341  	state.Delay = 10 * time.Second
   342  	state.Timeout = 10 * time.Minute
   343  	state.MinTimeout = 2 * time.Second
   344  	opRaw, err := state.WaitForState()
   345  	if err != nil {
   346  		return fmt.Errorf("Error waiting for instance to create: %s", err)
   347  	}
   348  	op = opRaw.(*compute.Operation)
   349  	if op.Error != nil {
   350  		// The resource didn't actually create
   351  		d.SetId("")
   352  
   353  		// Return the error
   354  		return OperationError(*op.Error)
   355  	}
   356  
   357  	return resourceComputeInstanceRead(d, meta)
   358  }
   359  
   360  func resourceComputeInstanceRead(d *schema.ResourceData, meta interface{}) error {
   361  	config := meta.(*Config)
   362  
   363  	instance, err := config.clientCompute.Instances.Get(
   364  		config.Project, d.Get("zone").(string), d.Id()).Do()
   365  	if err != nil {
   366  		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
   367  			// The resource doesn't exist anymore
   368  			d.SetId("")
   369  
   370  			return nil
   371  		}
   372  
   373  		return fmt.Errorf("Error reading instance: %s", err)
   374  	}
   375  
   376  	d.Set("can_ip_forward", instance.CanIpForward)
   377  
   378  	// Set the service accounts
   379  	for i, serviceAccount := range instance.ServiceAccounts {
   380  		prefix := fmt.Sprintf("service_account.%d", i)
   381  		d.Set(prefix + ".email", serviceAccount.Email)
   382  		d.Set(prefix + ".scopes.#", len(serviceAccount.Scopes))
   383  		for j, scope := range serviceAccount.Scopes {
   384  			d.Set(fmt.Sprintf("%s.scopes.%d", prefix, j), scope)
   385  		}
   386  	}
   387  
   388  	// Set the networks
   389  	externalIP := ""
   390  	for i, iface := range instance.NetworkInterfaces {
   391  		prefix := fmt.Sprintf("network.%d", i)
   392  		d.Set(prefix+".name", iface.Name)
   393  
   394  		// Use the first external IP found for the default connection info.
   395  		natIP := resourceInstanceNatIP(iface)
   396  		if externalIP == "" && natIP != "" {
   397  			externalIP = natIP
   398  		}
   399  		d.Set(prefix+".external_address", natIP)
   400  
   401  		d.Set(prefix+".internal_address", iface.NetworkIP)
   402  	}
   403  
   404  	// Initialize the connection info
   405  	d.SetConnInfo(map[string]string{
   406  		"type": "ssh",
   407  		"host": externalIP,
   408  	})
   409  
   410  	// Set the metadata fingerprint if there is one.
   411  	if instance.Metadata != nil {
   412  		d.Set("metadata_fingerprint", instance.Metadata.Fingerprint)
   413  	}
   414  
   415  	// Set the tags fingerprint if there is one.
   416  	if instance.Tags != nil {
   417  		d.Set("tags_fingerprint", instance.Tags.Fingerprint)
   418  	}
   419  
   420  	return nil
   421  }
   422  
   423  func resourceComputeInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
   424  	config := meta.(*Config)
   425  
   426  	// Enable partial mode for the resource since it is possible
   427  	d.Partial(true)
   428  
   429  	// If the Metadata has changed, then update that.
   430  	if d.HasChange("metadata") {
   431  		metadata := resourceInstanceMetadata(d)
   432  		op, err := config.clientCompute.Instances.SetMetadata(
   433  			config.Project, d.Get("zone").(string), d.Id(), metadata).Do()
   434  		if err != nil {
   435  			return fmt.Errorf("Error updating metadata: %s", err)
   436  		}
   437  
   438  		w := &OperationWaiter{
   439  			Service: config.clientCompute,
   440  			Op:      op,
   441  			Project: config.Project,
   442  			Zone:    d.Get("zone").(string),
   443  			Type:    OperationWaitZone,
   444  		}
   445  		state := w.Conf()
   446  		state.Delay = 1 * time.Second
   447  		state.Timeout = 5 * time.Minute
   448  		state.MinTimeout = 2 * time.Second
   449  		opRaw, err := state.WaitForState()
   450  		if err != nil {
   451  			return fmt.Errorf("Error waiting for metadata to update: %s", err)
   452  		}
   453  		op = opRaw.(*compute.Operation)
   454  		if op.Error != nil {
   455  			// Return the error
   456  			return OperationError(*op.Error)
   457  		}
   458  
   459  		d.SetPartial("metadata")
   460  	}
   461  
   462  	if d.HasChange("tags") {
   463  		tags := resourceInstanceTags(d)
   464  		op, err := config.clientCompute.Instances.SetTags(
   465  			config.Project, d.Get("zone").(string), d.Id(), tags).Do()
   466  		if err != nil {
   467  			return fmt.Errorf("Error updating tags: %s", err)
   468  		}
   469  
   470  		w := &OperationWaiter{
   471  			Service: config.clientCompute,
   472  			Op:      op,
   473  			Project: config.Project,
   474  			Zone:    d.Get("zone").(string),
   475  			Type:    OperationWaitZone,
   476  		}
   477  		state := w.Conf()
   478  		state.Delay = 1 * time.Second
   479  		state.Timeout = 5 * time.Minute
   480  		state.MinTimeout = 2 * time.Second
   481  		opRaw, err := state.WaitForState()
   482  		if err != nil {
   483  			return fmt.Errorf("Error waiting for tags to update: %s", err)
   484  		}
   485  		op = opRaw.(*compute.Operation)
   486  		if op.Error != nil {
   487  			// Return the error
   488  			return OperationError(*op.Error)
   489  		}
   490  
   491  		d.SetPartial("tags")
   492  	}
   493  
   494  	// We made it, disable partial mode
   495  	d.Partial(false)
   496  
   497  	return resourceComputeInstanceRead(d, meta)
   498  }
   499  
   500  func resourceComputeInstanceDelete(d *schema.ResourceData, meta interface{}) error {
   501  	config := meta.(*Config)
   502  
   503  	op, err := config.clientCompute.Instances.Delete(
   504  		config.Project, d.Get("zone").(string), d.Id()).Do()
   505  	if err != nil {
   506  		return fmt.Errorf("Error deleting instance: %s", err)
   507  	}
   508  
   509  	// Wait for the operation to complete
   510  	w := &OperationWaiter{
   511  		Service: config.clientCompute,
   512  		Op:      op,
   513  		Project: config.Project,
   514  		Zone:    d.Get("zone").(string),
   515  		Type:    OperationWaitZone,
   516  	}
   517  	state := w.Conf()
   518  	state.Delay = 5 * time.Second
   519  	state.Timeout = 5 * time.Minute
   520  	state.MinTimeout = 2 * time.Second
   521  	opRaw, err := state.WaitForState()
   522  	if err != nil {
   523  		return fmt.Errorf("Error waiting for instance to delete: %s", err)
   524  	}
   525  	op = opRaw.(*compute.Operation)
   526  	if op.Error != nil {
   527  		// Return the error
   528  		return OperationError(*op.Error)
   529  	}
   530  
   531  	d.SetId("")
   532  	return nil
   533  }
   534  
   535  func resourceInstanceMetadata(d *schema.ResourceData) *compute.Metadata {
   536  	var metadata *compute.Metadata
   537  	if v := d.Get("metadata").([]interface{}); len(v) > 0 {
   538  		m := new(compute.Metadata)
   539  		m.Items = make([]*compute.MetadataItems, 0, len(v))
   540  		for _, v := range v {
   541  			for k, v := range v.(map[string]interface{}) {
   542  				m.Items = append(m.Items, &compute.MetadataItems{
   543  					Key:   k,
   544  					Value: v.(string),
   545  				})
   546  			}
   547  		}
   548  
   549  		// Set the fingerprint. If the metadata has never been set before
   550  		// then this will just be blank.
   551  		m.Fingerprint = d.Get("metadata_fingerprint").(string)
   552  
   553  		metadata = m
   554  	}
   555  
   556  	return metadata
   557  }
   558  
   559  func resourceInstanceTags(d *schema.ResourceData) *compute.Tags {
   560  	// Calculate the tags
   561  	var tags *compute.Tags
   562  	if v := d.Get("tags"); v != nil {
   563  		vs := v.(*schema.Set)
   564  		tags = new(compute.Tags)
   565  		tags.Items = make([]string, vs.Len())
   566  		for i, v := range vs.List() {
   567  			tags.Items[i] = v.(string)
   568  		}
   569  
   570  		tags.Fingerprint = d.Get("tags_fingerprint").(string)
   571  	}
   572  
   573  	return tags
   574  }
   575  
   576  // resourceInstanceNatIP acquires the first NatIP with a "ONE_TO_ONE_NAT" type
   577  // in the compute.NetworkInterface's AccessConfigs.
   578  func resourceInstanceNatIP(iface *compute.NetworkInterface) (natIP string) {
   579  	for _, config := range iface.AccessConfigs {
   580  		if config.Type == "ONE_TO_ONE_NAT" {
   581  			natIP = config.NatIP
   582  			break
   583  		}
   584  	}
   585  
   586  	return natIP
   587  }