Skip to content

Commit 1917646

Browse files
raphinkstack72
authored andcommitted
Add rancher_host resource type (hashicorp#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 rancher/rancher#8165 * Do not ForceNew on environment_id
1 parent 4faeabf commit 1917646

3 files changed

Lines changed: 206 additions & 0 deletions

File tree

builtin/providers/rancher/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ func Provider() terraform.ResourceProvider {
5050

5151
ResourcesMap: map[string]*schema.Resource{
5252
"rancher_environment": resourceRancherEnvironment(),
53+
"rancher_host": resourceRancherHost(),
5354
"rancher_registration_token": resourceRancherRegistrationToken(),
5455
"rancher_registry": resourceRancherRegistry(),
5556
"rancher_registry_credential": resourceRancherRegistryCredential(),
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package rancher
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"time"
7+
8+
"github.com/hashicorp/terraform/helper/resource"
9+
"github.com/hashicorp/terraform/helper/schema"
10+
rancher "github.com/rancher/go-rancher/client"
11+
)
12+
13+
// ro_labels are used internally by Rancher
14+
// They are not documented and should not be set in Terraform
15+
var ro_labels = []string{
16+
"io.rancher.host.agent_image",
17+
"io.rancher.host.docker_version",
18+
"io.rancher.host.kvm",
19+
"io.rancher.host.linux_kernel_version",
20+
}
21+
22+
func resourceRancherHost() *schema.Resource {
23+
return &schema.Resource{
24+
Create: resourceRancherHostCreate,
25+
Read: resourceRancherHostRead,
26+
Update: resourceRancherHostUpdate,
27+
Delete: resourceRancherHostDelete,
28+
Importer: &schema.ResourceImporter{
29+
State: schema.ImportStatePassthrough,
30+
},
31+
32+
Schema: map[string]*schema.Schema{
33+
"id": &schema.Schema{
34+
Type: schema.TypeString,
35+
Computed: true,
36+
},
37+
"name": &schema.Schema{
38+
Type: schema.TypeString,
39+
Required: true,
40+
},
41+
"description": &schema.Schema{
42+
Type: schema.TypeString,
43+
Optional: true,
44+
},
45+
"environment_id": &schema.Schema{
46+
Type: schema.TypeString,
47+
Required: true,
48+
},
49+
"hostname": &schema.Schema{
50+
Type: schema.TypeString,
51+
Required: true,
52+
},
53+
"labels": {
54+
Type: schema.TypeMap,
55+
Optional: true,
56+
},
57+
},
58+
}
59+
}
60+
61+
func resourceRancherHostCreate(d *schema.ResourceData, meta interface{}) error {
62+
log.Printf("[INFO][rancher] Creating Host: %s", d.Id())
63+
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
64+
if err != nil {
65+
return err
66+
}
67+
68+
hosts, _ := client.Host.List(NewListOpts())
69+
hostname := d.Get("hostname").(string)
70+
var host rancher.Host
71+
72+
for _, h := range hosts.Data {
73+
if h.Hostname == hostname {
74+
host = h
75+
break
76+
}
77+
}
78+
79+
if host.Hostname == "" {
80+
return fmt.Errorf("Failed to find host %s", hostname)
81+
}
82+
83+
d.SetId(host.Id)
84+
85+
return resourceRancherHostUpdate(d, meta)
86+
}
87+
88+
func resourceRancherHostRead(d *schema.ResourceData, meta interface{}) error {
89+
log.Printf("[INFO] Refreshing Host: %s", d.Id())
90+
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
91+
if err != nil {
92+
return err
93+
}
94+
95+
host, err := client.Host.ById(d.Id())
96+
if err != nil {
97+
return err
98+
}
99+
100+
log.Printf("[INFO] Host Name: %s", host.Name)
101+
102+
d.Set("description", host.Description)
103+
d.Set("name", host.Name)
104+
d.Set("hostname", host.Hostname)
105+
106+
labels := host.Labels
107+
// Remove read-only labels
108+
for _, lbl := range ro_labels {
109+
delete(labels, lbl)
110+
}
111+
d.Set("labels", host.Labels)
112+
113+
return nil
114+
}
115+
116+
func resourceRancherHostUpdate(d *schema.ResourceData, meta interface{}) error {
117+
log.Printf("[INFO] Updating Host: %s", d.Id())
118+
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
119+
if err != nil {
120+
return err
121+
}
122+
123+
name := d.Get("name").(string)
124+
description := d.Get("description").(string)
125+
126+
// Process labels: merge ro_labels into new labels
127+
labels := d.Get("labels").(map[string]interface{})
128+
host, err := client.Host.ById(d.Id())
129+
if err != nil {
130+
return err
131+
}
132+
for _, lbl := range ro_labels {
133+
labels[lbl] = host.Labels[lbl]
134+
}
135+
136+
data := map[string]interface{}{
137+
"name": &name,
138+
"description": &description,
139+
"labels": &labels,
140+
}
141+
142+
var newHost rancher.Host
143+
if err := client.Update("host", &host.Resource, data, &newHost); err != nil {
144+
return err
145+
}
146+
147+
return resourceRancherHostRead(d, meta)
148+
}
149+
150+
func resourceRancherHostDelete(d *schema.ResourceData, meta interface{}) error {
151+
log.Printf("[INFO] Deleting Host: %s", d.Id())
152+
id := d.Id()
153+
client, err := meta.(*Config).EnvironmentClient(d.Get("environment_id").(string))
154+
if err != nil {
155+
return err
156+
}
157+
158+
host, err := client.Host.ById(id)
159+
if err != nil {
160+
return err
161+
}
162+
163+
if err := client.Host.Delete(host); err != nil {
164+
return fmt.Errorf("Error deleting Host: %s", err)
165+
}
166+
167+
log.Printf("[DEBUG] Waiting for host (%s) to be removed", id)
168+
169+
stateConf := &resource.StateChangeConf{
170+
Pending: []string{"active", "removed", "removing"},
171+
Target: []string{"removed"},
172+
Refresh: HostStateRefreshFunc(client, id),
173+
Timeout: 10 * time.Minute,
174+
Delay: 1 * time.Second,
175+
MinTimeout: 3 * time.Second,
176+
}
177+
178+
_, waitErr := stateConf.WaitForState()
179+
if waitErr != nil {
180+
return fmt.Errorf(
181+
"Error waiting for host (%s) to be removed: %s", id, waitErr)
182+
}
183+
184+
d.SetId("")
185+
return nil
186+
}
187+
188+
// HostStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
189+
// a Rancher Host.
190+
func HostStateRefreshFunc(client *rancher.RancherClient, hostID string) resource.StateRefreshFunc {
191+
return func() (interface{}, string, error) {
192+
host, err := client.Host.ById(hostID)
193+
194+
if err != nil {
195+
return nil, "", err
196+
}
197+
198+
return host, host.State, nil
199+
}
200+
}

builtin/providers/rancher/util.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ func splitID(id string) (envID, resourceID string) {
3737
}
3838
return "", id
3939
}
40+
41+
// NewListOpts wraps around client.NewListOpts()
42+
func NewListOpts() *client.ListOpts {
43+
return client.NewListOpts()
44+
}

0 commit comments

Comments
 (0)