Skip to content

Commit

Permalink
Add rancher_host resource type (#11545)
Browse files Browse the repository at this point in the history
* Add rancher_host resource type

This adds a rancher_host resource type.
For now, the goal is to detect if the host already exists,
so that it can be purged cleanly when the host is deprovisioned.

The typical use is to create both an instance (e.g. aws_instance)
and a rancher_host resources with the same hostname. The rancher_host
resource will detect when the agent has registered itself.

When removing the host, both the instance and the rancher_host
resources can be removed, ensuring the host is purged from Rancher.

In future versions, this could support creating hosts as well.

* Use ro_labels to avoid removing internal Rancher labels

As reported in rancher/rancher#8165

* Do not ForceNew on environment_id
  • Loading branch information
raphink authored and stack72 committed Mar 14, 2017
1 parent 4faeabf commit 1917646
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 0 deletions.
1 change: 1 addition & 0 deletions builtin/providers/rancher/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
200 changes: 200 additions & 0 deletions builtin/providers/rancher/resource_rancher_host.go
Original file line number Diff line number Diff line change
@@ -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
}
}
5 changes: 5 additions & 0 deletions builtin/providers/rancher/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

0 comments on commit 1917646

Please sign in to comment.