github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/builtin/providers/triton/resource_machine.go (about)

     1  package triton
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"regexp"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/schema"
    11  	"github.com/joyent/gosdc/cloudapi"
    12  )
    13  
    14  var (
    15  	machineStateRunning = "running"
    16  	machineStateStopped = "stopped"
    17  	machineStateDeleted = "deleted"
    18  
    19  	machineStateChangeTimeout       = 10 * time.Minute
    20  	machineStateChangeCheckInterval = 10 * time.Second
    21  
    22  	resourceMachineMetadataKeys = map[string]string{
    23  		// semantics: "schema_name": "metadata_name"
    24  		"root_authorized_keys": "root_authorized_keys",
    25  		"user_script":          "user-script",
    26  		"user_data":            "user-data",
    27  		"administrator_pw":     "administrator-pw",
    28  	}
    29  )
    30  
    31  func resourceMachine() *schema.Resource {
    32  	return &schema.Resource{
    33  		Create: resourceMachineCreate,
    34  		Exists: resourceMachineExists,
    35  		Read:   resourceMachineRead,
    36  		Update: resourceMachineUpdate,
    37  		Delete: resourceMachineDelete,
    38  
    39  		Schema: map[string]*schema.Schema{
    40  			"name": {
    41  				Description:  "friendly name",
    42  				Type:         schema.TypeString,
    43  				Optional:     true,
    44  				Computed:     true,
    45  				ValidateFunc: resourceMachineValidateName,
    46  			},
    47  			"type": {
    48  				Description: "machine type (smartmachine or virtualmachine)",
    49  				Type:        schema.TypeString,
    50  				Computed:    true,
    51  			},
    52  			"state": {
    53  				Description: "current state of the machine",
    54  				Type:        schema.TypeString,
    55  				Computed:    true,
    56  			},
    57  			"dataset": {
    58  				Description: "dataset URN the machine was provisioned with",
    59  				Type:        schema.TypeString,
    60  				Computed:    true,
    61  			},
    62  			"memory": {
    63  				Description: "amount of memory the machine has (in Mb)",
    64  				Type:        schema.TypeInt,
    65  				Computed:    true,
    66  			},
    67  			"disk": {
    68  				Description: "amount of disk the machine has (in Gb)",
    69  				Type:        schema.TypeInt,
    70  				Computed:    true,
    71  			},
    72  			"ips": {
    73  				Description: "IP addresses the machine has",
    74  				Type:        schema.TypeList,
    75  				Computed:    true,
    76  				Elem: &schema.Schema{
    77  					Type: schema.TypeString,
    78  				},
    79  			},
    80  			"tags": {
    81  				Description: "machine tags",
    82  				Type:        schema.TypeMap,
    83  				Optional:    true,
    84  			},
    85  			"created": {
    86  				Description: "when the machine was created",
    87  				Type:        schema.TypeString,
    88  				Computed:    true,
    89  			},
    90  			"updated": {
    91  				Description: "when the machine was update",
    92  				Type:        schema.TypeString,
    93  				Computed:    true,
    94  			},
    95  			"package": {
    96  				Description: "name of the package to use on provisioning",
    97  				Type:        schema.TypeString,
    98  				Required:    true,
    99  			},
   100  			"image": {
   101  				Description: "image UUID",
   102  				Type:        schema.TypeString,
   103  				Required:    true,
   104  				ForceNew:    true,
   105  				// TODO: validate that the UUID is valid
   106  			},
   107  			"primaryip": {
   108  				Description: "the primary (public) IP address for the machine",
   109  				Type:        schema.TypeString,
   110  				Computed:    true,
   111  			},
   112  			"nic": {
   113  				Description: "network interface",
   114  				Type:        schema.TypeSet,
   115  				Computed:    true,
   116  				Optional:    true,
   117  				Set: func(v interface{}) int {
   118  					m := v.(map[string]interface{})
   119  					return hashcode.String(m["network"].(string))
   120  				},
   121  				Elem: &schema.Resource{
   122  					Schema: map[string]*schema.Schema{
   123  						"ip": {
   124  							Description: "NIC's IPv4 address",
   125  							Computed:    true,
   126  							Type:        schema.TypeString,
   127  						},
   128  						"mac": {
   129  							Description: "NIC's MAC address",
   130  							Computed:    true,
   131  							Type:        schema.TypeString,
   132  						},
   133  						"primary": {
   134  							Description: "Whether this is the machine's primary NIC",
   135  							Computed:    true,
   136  							Type:        schema.TypeBool,
   137  						},
   138  						"netmask": {
   139  							Description: "IPv4 netmask",
   140  							Computed:    true,
   141  							Type:        schema.TypeString,
   142  						},
   143  						"gateway": {
   144  							Description: "IPv4 gateway",
   145  							Computed:    true,
   146  							Type:        schema.TypeString,
   147  						},
   148  						"state": {
   149  							Description: "describes the state of the NIC (e.g. provisioning, running, or stopped)",
   150  							Computed:    true,
   151  							Type:        schema.TypeString,
   152  						},
   153  						"network": {
   154  							Description: "Network ID this NIC is attached to",
   155  							Required:    true,
   156  							Type:        schema.TypeString,
   157  						},
   158  					},
   159  				},
   160  			},
   161  			"firewall_enabled": {
   162  				Description: "enable firewall for this machine",
   163  				Type:        schema.TypeBool,
   164  				Optional:    true,
   165  				Default:     false,
   166  			},
   167  
   168  			// computed resources from metadata
   169  			"root_authorized_keys": {
   170  				Description: "authorized keys for the root user on this machine",
   171  				Type:        schema.TypeString,
   172  				Optional:    true,
   173  				Computed:    true,
   174  			},
   175  			"user_script": {
   176  				Description: "user script to run on boot (every boot on SmartMachines)",
   177  				Type:        schema.TypeString,
   178  				Optional:    true,
   179  				Computed:    true,
   180  			},
   181  			"user_data": {
   182  				Description: "copied to machine on boot",
   183  				Type:        schema.TypeString,
   184  				Optional:    true,
   185  				Computed:    true,
   186  			},
   187  			"administrator_pw": {
   188  				Description: "administrator's initial password (Windows only)",
   189  				Type:        schema.TypeString,
   190  				Optional:    true,
   191  				Computed:    true,
   192  			},
   193  
   194  			// deprecated fields
   195  			"networks": {
   196  				Description: "desired network IDs",
   197  				Type:        schema.TypeList,
   198  				Optional:    true,
   199  				Computed:    true,
   200  				Deprecated:  "Networks is deprecated, please use `nic`",
   201  				Elem: &schema.Schema{
   202  					Type: schema.TypeString,
   203  				},
   204  			},
   205  		},
   206  	}
   207  }
   208  
   209  func resourceMachineCreate(d *schema.ResourceData, meta interface{}) error {
   210  	client := meta.(*cloudapi.Client)
   211  
   212  	var networks []string
   213  	for _, network := range d.Get("networks").([]interface{}) {
   214  		networks = append(networks, network.(string))
   215  	}
   216  	nics := d.Get("nic").(*schema.Set)
   217  	for _, nicI := range nics.List() {
   218  		nic := nicI.(map[string]interface{})
   219  		networks = append(networks, nic["network"].(string))
   220  	}
   221  
   222  	metadata := map[string]string{}
   223  	for schemaName, metadataKey := range resourceMachineMetadataKeys {
   224  		if v, ok := d.GetOk(schemaName); ok {
   225  			metadata[metadataKey] = v.(string)
   226  		}
   227  	}
   228  
   229  	tags := map[string]string{}
   230  	for k, v := range d.Get("tags").(map[string]interface{}) {
   231  		tags[k] = v.(string)
   232  	}
   233  
   234  	machine, err := client.CreateMachine(cloudapi.CreateMachineOpts{
   235  		Name:            d.Get("name").(string),
   236  		Package:         d.Get("package").(string),
   237  		Image:           d.Get("image").(string),
   238  		Networks:        networks,
   239  		Metadata:        metadata,
   240  		Tags:            tags,
   241  		FirewallEnabled: d.Get("firewall_enabled").(bool),
   242  	})
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	err = waitForMachineState(client, machine.Id, machineStateRunning, machineStateChangeTimeout)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	// refresh state after it provisions
   253  	d.SetId(machine.Id)
   254  	err = resourceMachineRead(d, meta)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func resourceMachineExists(d *schema.ResourceData, meta interface{}) (bool, error) {
   263  	client := meta.(*cloudapi.Client)
   264  
   265  	machine, err := client.GetMachine(d.Id())
   266  
   267  	return machine != nil && err == nil, err
   268  }
   269  
   270  func resourceMachineRead(d *schema.ResourceData, meta interface{}) error {
   271  	client := meta.(*cloudapi.Client)
   272  
   273  	machine, err := client.GetMachine(d.Id())
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	nics, err := client.ListNICs(d.Id())
   279  	if err != nil {
   280  		return err
   281  	}
   282  
   283  	d.SetId(machine.Id)
   284  	d.Set("name", machine.Name)
   285  	d.Set("type", machine.Type)
   286  	d.Set("state", machine.State)
   287  	d.Set("dataset", machine.Dataset)
   288  	d.Set("memory", machine.Memory)
   289  	d.Set("disk", machine.Disk)
   290  	d.Set("ips", machine.IPs)
   291  	d.Set("tags", machine.Tags)
   292  	d.Set("created", machine.Created)
   293  	d.Set("updated", machine.Updated)
   294  	d.Set("package", machine.Package)
   295  	d.Set("image", machine.Image)
   296  	d.Set("primaryip", machine.PrimaryIP)
   297  	d.Set("firewall_enabled", machine.FirewallEnabled)
   298  
   299  	// create and update NICs
   300  	var (
   301  		machineNICs []map[string]interface{}
   302  		networks    []string
   303  	)
   304  	for _, nic := range nics {
   305  		machineNICs = append(
   306  			machineNICs,
   307  			map[string]interface{}{
   308  				"ip":      nic.IP,
   309  				"mac":     nic.MAC,
   310  				"primary": nic.Primary,
   311  				"netmask": nic.Netmask,
   312  				"gateway": nic.Gateway,
   313  				"state":   nic.State,
   314  				"network": nic.Network,
   315  			},
   316  		)
   317  		networks = append(networks, nic.Network)
   318  	}
   319  	d.Set("nic", machineNICs)
   320  	d.Set("networks", networks)
   321  
   322  	// computed attributes from metadata
   323  	for schemaName, metadataKey := range resourceMachineMetadataKeys {
   324  		d.Set(schemaName, machine.Metadata[metadataKey])
   325  	}
   326  
   327  	return nil
   328  }
   329  
   330  func resourceMachineUpdate(d *schema.ResourceData, meta interface{}) error {
   331  	client := meta.(*cloudapi.Client)
   332  
   333  	d.Partial(true)
   334  
   335  	if d.HasChange("name") {
   336  		if err := client.RenameMachine(d.Id(), d.Get("name").(string)); err != nil {
   337  			return err
   338  		}
   339  
   340  		err := waitFor(
   341  			func() (bool, error) {
   342  				machine, err := client.GetMachine(d.Id())
   343  				return machine.Name == d.Get("name").(string), err
   344  			},
   345  			machineStateChangeCheckInterval,
   346  			1*time.Minute,
   347  		)
   348  		if err != nil {
   349  			return err
   350  		}
   351  
   352  		d.SetPartial("name")
   353  	}
   354  
   355  	if d.HasChange("tags") {
   356  		tags := map[string]string{}
   357  		for k, v := range d.Get("tags").(map[string]interface{}) {
   358  			tags[k] = v.(string)
   359  		}
   360  
   361  		var err error
   362  		if len(tags) == 0 {
   363  			err = client.DeleteMachineTags(d.Id())
   364  		} else {
   365  			_, err = client.ReplaceMachineTags(d.Id(), tags)
   366  		}
   367  		if err != nil {
   368  			return err
   369  		}
   370  
   371  		err = waitFor(
   372  			func() (bool, error) {
   373  				machine, err := client.GetMachine(d.Id())
   374  				return reflect.DeepEqual(machine.Tags, tags), err
   375  			},
   376  			machineStateChangeCheckInterval,
   377  			1*time.Minute,
   378  		)
   379  		if err != nil {
   380  			return err
   381  		}
   382  
   383  		d.SetPartial("tags")
   384  	}
   385  
   386  	if d.HasChange("package") {
   387  		if err := client.ResizeMachine(d.Id(), d.Get("package").(string)); err != nil {
   388  			return err
   389  		}
   390  
   391  		err := waitFor(
   392  			func() (bool, error) {
   393  				machine, err := client.GetMachine(d.Id())
   394  				return machine.Package == d.Get("package").(string) && machine.State == machineStateRunning, err
   395  			},
   396  			machineStateChangeCheckInterval,
   397  			machineStateChangeTimeout,
   398  		)
   399  		if err != nil {
   400  			return err
   401  		}
   402  
   403  		d.SetPartial("package")
   404  	}
   405  
   406  	if d.HasChange("firewall_enabled") {
   407  		var err error
   408  		if d.Get("firewall_enabled").(bool) {
   409  			err = client.EnableFirewallMachine(d.Id())
   410  		} else {
   411  			err = client.DisableFirewallMachine(d.Id())
   412  		}
   413  		if err != nil {
   414  			return err
   415  		}
   416  
   417  		err = waitFor(
   418  			func() (bool, error) {
   419  				machine, err := client.GetMachine(d.Id())
   420  				return machine.FirewallEnabled == d.Get("firewall_enabled").(bool), err
   421  			},
   422  			machineStateChangeCheckInterval,
   423  			machineStateChangeTimeout,
   424  		)
   425  
   426  		if err != nil {
   427  			return err
   428  		}
   429  
   430  		d.SetPartial("firewall_enabled")
   431  	}
   432  
   433  	if d.HasChange("nic") {
   434  		o, n := d.GetChange("nic")
   435  		if o == nil {
   436  			o = new(schema.Set)
   437  		}
   438  		if n == nil {
   439  			n = new(schema.Set)
   440  		}
   441  
   442  		oldNICs := o.(*schema.Set)
   443  		newNICs := o.(*schema.Set)
   444  
   445  		// add new NICs that are not in old NICs
   446  		for _, nicI := range newNICs.Difference(oldNICs).List() {
   447  			nic := nicI.(map[string]interface{})
   448  			fmt.Printf("adding %+v\n", nic)
   449  			_, err := client.AddNIC(d.Id(), nic["network"].(string))
   450  			if err != nil {
   451  				return err
   452  			}
   453  		}
   454  
   455  		// remove old NICs that are not in new NICs
   456  		for _, nicI := range oldNICs.Difference(newNICs).List() {
   457  			nic := nicI.(map[string]interface{})
   458  			fmt.Printf("removing %+v\n", nic)
   459  			err := client.RemoveNIC(d.Id(), nic["mac"].(string))
   460  			if err != nil {
   461  				return err
   462  			}
   463  		}
   464  
   465  		d.SetPartial("nic")
   466  	}
   467  
   468  	// metadata stuff
   469  	metadata := map[string]string{}
   470  	for schemaName, metadataKey := range resourceMachineMetadataKeys {
   471  		if d.HasChange(schemaName) {
   472  			metadata[metadataKey] = d.Get(schemaName).(string)
   473  		}
   474  	}
   475  	if len(metadata) > 0 {
   476  		_, err := client.UpdateMachineMetadata(d.Id(), metadata)
   477  		if err != nil {
   478  			return err
   479  		}
   480  
   481  		err = waitFor(
   482  			func() (bool, error) {
   483  				machine, err := client.GetMachine(d.Id())
   484  				for k, v := range metadata {
   485  					if provider_v, ok := machine.Metadata[k]; !ok || v != provider_v {
   486  						return false, err
   487  					}
   488  				}
   489  				return true, err
   490  			},
   491  			machineStateChangeCheckInterval,
   492  			1*time.Minute,
   493  		)
   494  		if err != nil {
   495  			return err
   496  		}
   497  
   498  		for schemaName := range resourceMachineMetadataKeys {
   499  			if d.HasChange(schemaName) {
   500  				d.SetPartial(schemaName)
   501  			}
   502  		}
   503  	}
   504  
   505  	d.Partial(false)
   506  
   507  	err := resourceMachineRead(d, meta)
   508  	if err != nil {
   509  		return err
   510  	}
   511  
   512  	return nil
   513  }
   514  
   515  func resourceMachineDelete(d *schema.ResourceData, meta interface{}) error {
   516  	client := meta.(*cloudapi.Client)
   517  
   518  	state, err := readMachineState(client, d.Id())
   519  	if state != machineStateStopped {
   520  		err = client.StopMachine(d.Id())
   521  		if err != nil {
   522  			return err
   523  		}
   524  
   525  		waitForMachineState(client, d.Id(), machineStateStopped, machineStateChangeTimeout)
   526  	}
   527  
   528  	err = client.DeleteMachine(d.Id())
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	waitForMachineState(client, d.Id(), machineStateDeleted, machineStateChangeTimeout)
   534  	return nil
   535  }
   536  
   537  func readMachineState(api *cloudapi.Client, id string) (string, error) {
   538  	machine, err := api.GetMachine(id)
   539  	if err != nil {
   540  		return "", err
   541  	}
   542  
   543  	return machine.State, nil
   544  }
   545  
   546  // waitForMachineState waits for a machine to be in the desired state (waiting
   547  // some seconds between each poll). If it doesn't reach the state within the
   548  // duration specified in `timeout`, it returns ErrMachineStateTimeout.
   549  func waitForMachineState(api *cloudapi.Client, id, state string, timeout time.Duration) error {
   550  	return waitFor(
   551  		func() (bool, error) {
   552  			currentState, err := readMachineState(api, id)
   553  			return currentState == state, err
   554  		},
   555  		machineStateChangeCheckInterval,
   556  		machineStateChangeTimeout,
   557  	)
   558  }
   559  
   560  func resourceMachineValidateName(value interface{}, name string) (warnings []string, errors []error) {
   561  	warnings = []string{}
   562  	errors = []error{}
   563  
   564  	r := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9\_\.\-]*$`)
   565  	if !r.Match([]byte(value.(string))) {
   566  		errors = append(errors, fmt.Errorf(`"%s" is not a valid %s`, value.(string), name))
   567  	}
   568  
   569  	return warnings, errors
   570  }