Skip to content

Commit e7024dc

Browse files
committed
Add cloudstack_pod and cloudstack_cluster resources
- Add cloudstack_pod resource with full CRUD operations - Add cloudstack_cluster resource with full CRUD operations - Add data sources for both resources - Include comprehensive validation and error handling - Add complete test coverage - Add documentation for both resources - Resolve all merge conflicts from original PR apache#217
1 parent 3021ce2 commit e7024dc

File tree

8 files changed

+929
-299
lines changed

8 files changed

+929
-299
lines changed
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package cloudstack
21+
22+
import (
23+
"fmt"
24+
"log"
25+
"reflect"
26+
"regexp"
27+
"strings"
28+
29+
"github.com/apache/cloudstack-go/v2/cloudstack"
30+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
31+
)
32+
33+
func dataSourceCloudstackCluster() *schema.Resource {
34+
return &schema.Resource{
35+
Read: datasourceCloudStackClusterRead,
36+
Schema: map[string]*schema.Schema{
37+
"filter": dataSourceFiltersSchema(),
38+
39+
//Computed values
40+
"id": {
41+
Type: schema.TypeString,
42+
Computed: true,
43+
},
44+
"name": {
45+
Type: schema.TypeString,
46+
Computed: true,
47+
},
48+
"cluster_type": {
49+
Type: schema.TypeString,
50+
Computed: true,
51+
},
52+
"hypervisor": {
53+
Type: schema.TypeString,
54+
Computed: true,
55+
},
56+
"pod_id": {
57+
Type: schema.TypeString,
58+
Computed: true,
59+
},
60+
"pod_name": {
61+
Type: schema.TypeString,
62+
Computed: true,
63+
},
64+
"zone_id": {
65+
Type: schema.TypeString,
66+
Computed: true,
67+
},
68+
"zone_name": {
69+
Type: schema.TypeString,
70+
Computed: true,
71+
},
72+
"allocation_state": {
73+
Type: schema.TypeString,
74+
Computed: true,
75+
},
76+
"managed_state": {
77+
Type: schema.TypeString,
78+
Computed: true,
79+
},
80+
"cpu_overcommit_ratio": {
81+
Type: schema.TypeString,
82+
Computed: true,
83+
},
84+
"memory_overcommit_ratio": {
85+
Type: schema.TypeString,
86+
Computed: true,
87+
},
88+
"arch": {
89+
Type: schema.TypeString,
90+
Computed: true,
91+
},
92+
"ovm3vip": {
93+
Type: schema.TypeString,
94+
Computed: true,
95+
},
96+
"capacity": {
97+
Type: schema.TypeList,
98+
Computed: true,
99+
Elem: &schema.Resource{
100+
Schema: map[string]*schema.Schema{
101+
"capacity_allocated": {
102+
Type: schema.TypeInt,
103+
Computed: true,
104+
},
105+
"capacity_total": {
106+
Type: schema.TypeInt,
107+
Computed: true,
108+
},
109+
"capacity_used": {
110+
Type: schema.TypeInt,
111+
Computed: true,
112+
},
113+
"cluster_id": {
114+
Type: schema.TypeString,
115+
Computed: true,
116+
},
117+
"cluster_name": {
118+
Type: schema.TypeString,
119+
Computed: true,
120+
},
121+
"name": {
122+
Type: schema.TypeString,
123+
Computed: true,
124+
},
125+
"percent_used": {
126+
Type: schema.TypeInt,
127+
Computed: true,
128+
},
129+
"pod_id": {
130+
Type: schema.TypeString,
131+
Computed: true,
132+
},
133+
"pod_name": {
134+
Type: schema.TypeString,
135+
Computed: true,
136+
},
137+
"type": {
138+
Type: schema.TypeString,
139+
Computed: true,
140+
},
141+
"zone_id": {
142+
Type: schema.TypeString,
143+
Computed: true,
144+
},
145+
"zone_name": {
146+
Type: schema.TypeString,
147+
Computed: true,
148+
},
149+
},
150+
},
151+
},
152+
},
153+
}
154+
}
155+
156+
func dsFlattenClusterCapacity(capacity []cloudstack.ClusterCapacity) []map[string]interface{} {
157+
cap := make([]map[string]interface{}, len(capacity))
158+
for i, c := range capacity {
159+
cap[i] = map[string]interface{}{
160+
"capacity_allocated": c.Capacityallocated,
161+
"capacity_total": c.Capacitytotal,
162+
"capacity_used": c.Capacityused,
163+
"cluster_id": c.Clusterid,
164+
"cluster_name": c.Clustername,
165+
"name": c.Name,
166+
"percent_used": c.Percentused,
167+
"pod_id": c.Podid,
168+
"pod_name": c.Podname,
169+
"type": c.Type,
170+
"zone_id": c.Zoneid,
171+
"zone_name": c.Zonename,
172+
}
173+
}
174+
return cap
175+
}
176+
177+
func datasourceCloudStackClusterRead(d *schema.ResourceData, meta interface{}) error {
178+
cs := meta.(*cloudstack.CloudStackClient)
179+
p := cs.Cluster.NewListClustersParams()
180+
181+
csClusters, err := cs.Cluster.ListClusters(p)
182+
if err != nil {
183+
return fmt.Errorf("failed to list clusters: %s", err)
184+
}
185+
186+
filters := d.Get("filter")
187+
188+
for _, cluster := range csClusters.Clusters {
189+
match, err := applyClusterFilters(cluster, filters.(*schema.Set))
190+
if err != nil {
191+
return err
192+
}
193+
if match {
194+
return clusterDescriptionAttributes(d, cluster)
195+
}
196+
}
197+
198+
return fmt.Errorf("no clusters found")
199+
}
200+
201+
func clusterDescriptionAttributes(d *schema.ResourceData, cluster *cloudstack.Cluster) error {
202+
d.SetId(cluster.Id)
203+
204+
fields := map[string]interface{}{
205+
"id": cluster.Id,
206+
"name": cluster.Name,
207+
"cluster_type": cluster.Clustertype,
208+
"hypervisor": cluster.Hypervisortype,
209+
"pod_id": cluster.Podid,
210+
"pod_name": cluster.Podname,
211+
"zone_id": cluster.Zoneid,
212+
"zone_name": cluster.Zonename,
213+
"allocation_state": cluster.Allocationstate,
214+
"managed_state": cluster.Managedstate,
215+
"cpu_overcommit_ratio": cluster.Cpuovercommitratio,
216+
"memory_overcommit_ratio": cluster.Memoryovercommitratio,
217+
"arch": cluster.Arch,
218+
"ovm3vip": cluster.Ovm3vip,
219+
"capacity": dsFlattenClusterCapacity(cluster.Capacity),
220+
}
221+
222+
for k, v := range fields {
223+
if err := d.Set(k, v); err != nil {
224+
log.Printf("[WARN] Error setting %s: %s", k, err)
225+
}
226+
}
227+
228+
return nil
229+
}
230+
231+
func applyClusterFilters(cluster *cloudstack.Cluster, filters *schema.Set) (bool, error) {
232+
val := reflect.ValueOf(cluster).Elem()
233+
234+
for _, f := range filters.List() {
235+
filter := f.(map[string]interface{})
236+
r, err := regexp.Compile(filter["value"].(string))
237+
if err != nil {
238+
return false, fmt.Errorf("invalid regex: %s", err)
239+
}
240+
updatedName := strings.ReplaceAll(filter["name"].(string), "_", "")
241+
clusterField := val.FieldByNameFunc(func(fieldName string) bool {
242+
if strings.EqualFold(fieldName, updatedName) {
243+
updatedName = fieldName
244+
return true
245+
}
246+
return false
247+
}).String()
248+
249+
if r.MatchString(clusterField) {
250+
return true, nil
251+
}
252+
}
253+
254+
return false, nil
255+
}

cloudstack/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func Provider() *schema.Provider {
9393
"cloudstack_domain": dataSourceCloudstackDomain(),
9494
"cloudstack_physical_network": dataSourceCloudStackPhysicalNetwork(),
9595
"cloudstack_role": dataSourceCloudstackRole(),
96+
"cloudstack_cluster": dataSourceCloudstackCluster(),
9697
},
9798

9899
ResourcesMap: map[string]*schema.Resource{

0 commit comments

Comments
 (0)