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