diff --git a/builtin/providers/rancher/provider.go b/builtin/providers/rancher/provider.go index 13a6b7ce4..3025aac8c 100644 --- a/builtin/providers/rancher/provider.go +++ b/builtin/providers/rancher/provider.go @@ -50,6 +50,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "rancher_environment": resourceRancherEnvironment(), + "rancher_host": resourceRancherHost(), "rancher_registration_token": resourceRancherRegistrationToken(), "rancher_registry": resourceRancherRegistry(), "rancher_registry_credential": resourceRancherRegistryCredential(), diff --git a/builtin/providers/rancher/resource_rancher_host.go b/builtin/providers/rancher/resource_rancher_host.go new file mode 100644 index 000000000..326423d40 --- /dev/null +++ b/builtin/providers/rancher/resource_rancher_host.go @@ -0,0 +1,200 @@ +package rancher + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + rancher "github.com/rancher/go-rancher/client" +) + +// ro_labels are used internally by Rancher +// They are not documented and should not be set in Terraform +var ro_labels = []string{ + "io.rancher.host.agent_image", + "io.rancher.host.docker_version", + "io.rancher.host.kvm", + "io.rancher.host.linux_kernel_version", +} + +func resourceRancherHost() *schema.Resource { + return &schema.Resource{ + Create: resourceRancherHostCreate, + Read: resourceRancherHostRead, + Update: resourceRancherHostUpdate, + Delete: resourceRancherHostDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "environment_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "hostname": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + }, + }, + } +} + +func resourceRancherHostCreate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO][rancher] Creating Host: %s", d.Id()) + client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string)) + if err != nil { + return err + } + + hosts, _ := client.Host.List(NewListOpts()) + hostname := d.Get("hostname").(string) + var host rancher.Host + + for _, h := range hosts.Data { + if h.Hostname == hostname { + host = h + break + } + } + + if host.Hostname == "" { + return fmt.Errorf("Failed to find host %s", hostname) + } + + d.SetId(host.Id) + + return resourceRancherHostUpdate(d, meta) +} + +func resourceRancherHostRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Refreshing Host: %s", d.Id()) + client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string)) + if err != nil { + return err + } + + host, err := client.Host.ById(d.Id()) + if err != nil { + return err + } + + log.Printf("[INFO] Host Name: %s", host.Name) + + d.Set("description", host.Description) + d.Set("name", host.Name) + d.Set("hostname", host.Hostname) + + labels := host.Labels + // Remove read-only labels + for _, lbl := range ro_labels { + delete(labels, lbl) + } + d.Set("labels", host.Labels) + + return nil +} + +func resourceRancherHostUpdate(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Updating Host: %s", d.Id()) + client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string)) + if err != nil { + return err + } + + name := d.Get("name").(string) + description := d.Get("description").(string) + + // Process labels: merge ro_labels into new labels + labels := d.Get("labels").(map[string]interface{}) + host, err := client.Host.ById(d.Id()) + if err != nil { + return err + } + for _, lbl := range ro_labels { + labels[lbl] = host.Labels[lbl] + } + + data := map[string]interface{}{ + "name": &name, + "description": &description, + "labels": &labels, + } + + var newHost rancher.Host + if err := client.Update("host", &host.Resource, data, &newHost); err != nil { + return err + } + + return resourceRancherHostRead(d, meta) +} + +func resourceRancherHostDelete(d *schema.ResourceData, meta interface{}) error { + log.Printf("[INFO] Deleting Host: %s", d.Id()) + id := d.Id() + client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string)) + if err != nil { + return err + } + + host, err := client.Host.ById(id) + if err != nil { + return err + } + + if err := client.Host.Delete(host); err != nil { + return fmt.Errorf("Error deleting Host: %s", err) + } + + log.Printf("[DEBUG] Waiting for host (%s) to be removed", id) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"active", "removed", "removing"}, + Target: []string{"removed"}, + Refresh: HostStateRefreshFunc(client, id), + Timeout: 10 * time.Minute, + Delay: 1 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, waitErr := stateConf.WaitForState() + if waitErr != nil { + return fmt.Errorf( + "Error waiting for host (%s) to be removed: %s", id, waitErr) + } + + d.SetId("") + return nil +} + +// HostStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch +// a Rancher Host. +func HostStateRefreshFunc(client *rancher.RancherClient, hostID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + host, err := client.Host.ById(hostID) + + if err != nil { + return nil, "", err + } + + return host, host.State, nil + } +} diff --git a/builtin/providers/rancher/util.go b/builtin/providers/rancher/util.go index 60317bf57..efa4a4c3f 100644 --- a/builtin/providers/rancher/util.go +++ b/builtin/providers/rancher/util.go @@ -37,3 +37,8 @@ func splitID(id string) (envID, resourceID string) { } return "", id } + +// NewListOpts wraps around client.NewListOpts() +func NewListOpts() *client.ListOpts { + return client.NewListOpts() +}