Skip to content

Commit 67bfc2f

Browse files
committed
Added folder handling for folder-qualified vm names
Added acceptance test for creation in folders Added 'baseName' as computed schema attribute for convenience Added 'base_name' computed attribute for convenience Added new vsphere folder resource Fixed folder behavior Assure test folders are properly removed Avoid creating recreating search index in loop Fix typeo in vsphere.createFolder Updated website documentation Renamed test folders to be unique across tests Fixes based on acc test findings; code cleanup Added combined folder and vm acc test Restored newline; fixed skipped acc tests Marked 'existing_path' as computed only Removed debug logging from tests Changed folder read to return error
1 parent dbd6a1f commit 67bfc2f

7 files changed

Lines changed: 814 additions & 26 deletions

File tree

builtin/providers/vsphere/provider.go

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

3434
ResourcesMap: map[string]*schema.Resource{
35+
"vsphere_folder": resourceVSphereFolder(),
3536
"vsphere_virtual_machine": resourceVSphereVirtualMachine(),
3637
},
3738

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package vsphere
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"path"
7+
"strings"
8+
9+
"github.com/hashicorp/terraform/helper/schema"
10+
"github.com/vmware/govmomi"
11+
"github.com/vmware/govmomi/find"
12+
"github.com/vmware/govmomi/object"
13+
"golang.org/x/net/context"
14+
)
15+
16+
type folder struct {
17+
datacenter string
18+
existingPath string
19+
path string
20+
}
21+
22+
func resourceVSphereFolder() *schema.Resource {
23+
return &schema.Resource{
24+
Create: resourceVSphereFolderCreate,
25+
Read: resourceVSphereFolderRead,
26+
Delete: resourceVSphereFolderDelete,
27+
28+
Schema: map[string]*schema.Schema{
29+
"datacenter": &schema.Schema{
30+
Type: schema.TypeString,
31+
Optional: true,
32+
ForceNew: true,
33+
},
34+
35+
"path": &schema.Schema{
36+
Type: schema.TypeString,
37+
Required: true,
38+
ForceNew: true,
39+
},
40+
41+
"existing_path": &schema.Schema{
42+
Type: schema.TypeString,
43+
Computed: true,
44+
},
45+
},
46+
}
47+
}
48+
49+
func resourceVSphereFolderCreate(d *schema.ResourceData, meta interface{}) error {
50+
51+
client := meta.(*govmomi.Client)
52+
53+
f := folder{
54+
path: strings.TrimRight(d.Get("path").(string), "/"),
55+
}
56+
57+
if v, ok := d.GetOk("datacenter"); ok {
58+
f.datacenter = v.(string)
59+
}
60+
61+
createFolder(client, &f)
62+
63+
d.Set("existing_path", f.existingPath)
64+
d.SetId(fmt.Sprintf("%v/%v", f.datacenter, f.path))
65+
log.Printf("[INFO] Created folder: %s", f.path)
66+
67+
return resourceVSphereFolderRead(d, meta)
68+
}
69+
70+
71+
func createFolder(client *govmomi.Client, f *folder) error {
72+
73+
finder := find.NewFinder(client.Client, true)
74+
75+
dc, err := finder.Datacenter(context.TODO(), f.datacenter)
76+
if err != nil {
77+
return fmt.Errorf("error %s", err)
78+
}
79+
finder = finder.SetDatacenter(dc)
80+
si := object.NewSearchIndex(client.Client)
81+
82+
dcFolders, err := dc.Folders(context.TODO())
83+
if err != nil {
84+
return fmt.Errorf("error %s", err)
85+
}
86+
87+
folder := dcFolders.VmFolder
88+
var workingPath string
89+
90+
pathParts := strings.Split(f.path, "/")
91+
for _, pathPart := range pathParts {
92+
if len(workingPath) > 0 {
93+
workingPath += "/"
94+
}
95+
workingPath += pathPart
96+
subfolder, err := si.FindByInventoryPath(
97+
context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, workingPath))
98+
99+
if err != nil {
100+
return fmt.Errorf("error %s", err)
101+
} else if subfolder == nil {
102+
log.Printf("[DEBUG] folder not found; creating: %s", workingPath)
103+
folder, err = folder.CreateFolder(context.TODO(), pathPart)
104+
if err != nil {
105+
return fmt.Errorf("Failed to create folder at %s; %s", workingPath, err)
106+
}
107+
} else {
108+
log.Printf("[DEBUG] folder already exists: %s", workingPath)
109+
f.existingPath = workingPath
110+
folder = subfolder.(*object.Folder)
111+
}
112+
}
113+
return nil
114+
}
115+
116+
117+
func resourceVSphereFolderRead(d *schema.ResourceData, meta interface{}) error {
118+
119+
log.Printf("[DEBUG] reading folder: %#v", d)
120+
client := meta.(*govmomi.Client)
121+
122+
dc, err := getDatacenter(client, d.Get("datacenter").(string))
123+
if err != nil {
124+
return err
125+
}
126+
127+
finder := find.NewFinder(client.Client, true)
128+
finder = finder.SetDatacenter(dc)
129+
130+
folder, err := object.NewSearchIndex(client.Client).FindByInventoryPath(
131+
context.TODO(), fmt.Sprintf("%v/vm/%v", d.Get("datacenter").(string),
132+
d.Get("path").(string)))
133+
134+
if err != nil {
135+
return err
136+
}
137+
138+
if folder == nil {
139+
d.SetId("")
140+
}
141+
142+
return nil
143+
}
144+
145+
func resourceVSphereFolderDelete(d *schema.ResourceData, meta interface{}) error {
146+
147+
f := folder{
148+
path: strings.TrimRight(d.Get("path").(string), "/"),
149+
existingPath: d.Get("existing_path").(string),
150+
}
151+
152+
if v, ok := d.GetOk("datacenter"); ok {
153+
f.datacenter = v.(string)
154+
}
155+
156+
client := meta.(*govmomi.Client)
157+
158+
deleteFolder(client, &f)
159+
160+
d.SetId("")
161+
return nil
162+
}
163+
164+
func deleteFolder(client *govmomi.Client, f *folder) error {
165+
dc, err := getDatacenter(client, f.datacenter)
166+
if err != nil {
167+
return err
168+
}
169+
var folder *object.Folder
170+
currentPath := f.path
171+
172+
finder := find.NewFinder(client.Client, true)
173+
finder = finder.SetDatacenter(dc)
174+
si := object.NewSearchIndex(client.Client)
175+
176+
folderRef, err := si.FindByInventoryPath(
177+
context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, f.path))
178+
179+
if err != nil {
180+
return fmt.Errorf("[ERROR] Could not locate folder %s: %v", f.path, err)
181+
} else {
182+
folder = folderRef.(*object.Folder)
183+
}
184+
185+
log.Printf("[INFO] Deleting empty sub-folders of existing path: %s", f.existingPath)
186+
for currentPath != f.existingPath {
187+
log.Printf("[INFO] Deleting folder: %s", currentPath)
188+
children, err := folder.Children(context.TODO())
189+
if err != nil {
190+
return err
191+
}
192+
193+
if len(children) > 0 {
194+
return fmt.Errorf("Folder %s is non-empty and will not be deleted", currentPath)
195+
} else {
196+
log.Printf("[DEBUG] current folder: %#v", folder)
197+
currentPath = path.Dir(currentPath)
198+
if currentPath == "." {
199+
currentPath = ""
200+
}
201+
log.Printf("[INFO] parent path of %s is calculated as %s", f.path, currentPath)
202+
task, err := folder.Destroy(context.TODO())
203+
if err != nil {
204+
return err
205+
}
206+
err = task.Wait(context.TODO())
207+
if err != nil {
208+
return err
209+
}
210+
folderRef, err = si.FindByInventoryPath(
211+
context.TODO(), fmt.Sprintf("%v/vm/%v", f.datacenter, currentPath))
212+
213+
if err != nil {
214+
return err
215+
} else if folderRef != nil {
216+
folder = folderRef.(*object.Folder)
217+
}
218+
}
219+
}
220+
return nil
221+
}
222+
223+
// getDatacenter gets datacenter object
224+
func getDatacenter(c *govmomi.Client, dc string) (*object.Datacenter, error) {
225+
finder := find.NewFinder(c.Client, true)
226+
if dc != "" {
227+
d, err := finder.Datacenter(context.TODO(), dc)
228+
return d, err
229+
} else {
230+
d, err := finder.DefaultDatacenter(context.TODO())
231+
return d, err
232+
}
233+
}

0 commit comments

Comments
 (0)