Add rancher_host resource type (#11545)
* 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 https://github.com/rancher/rancher/issues/8165 * Do not ForceNew on environment_id
This commit is contained in:
parent
4faeabf32e
commit
1917646ec3
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue