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