Skip to content

Commit 1082f61

Browse files
committed
Add Pod and Cluster resources to CloudStack Terraform provider
This commit adds support for managing CloudStack Pods and Clusters through Terraform. It implements the following new resources and data sources: Resources: - cloudstack_pod: Allows creation and management of CloudStack Pods - cloudstack_cluster: Allows creation and management of CloudStack Clusters Data Sources: - cloudstack_cluster: Provides lookup capabilities for existing Clusters Implementation details: - Added resource_cloudstack_pod.go with CRUD operations for Pods - Added resource_cloudstack_cluster.go with CRUD operations for Clusters - Added data_source_cloudstack_cluster.go for Cluster data source - Created corresponding test files for all resources and data sources - Added documentation for all new resources and data sources - Updated provider.go to register the new resources and data sources - Added import functionality to both resources
1 parent 4eb6d4b commit 1082f61

10 files changed

+1423
-0
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+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
"testing"
24+
25+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
26+
)
27+
28+
func TestAccClusterDataSource_basic(t *testing.T) {
29+
resource.Test(t, resource.TestCase{
30+
PreCheck: func() { testAccPreCheck(t) },
31+
Providers: testAccProviders,
32+
Steps: []resource.TestStep{
33+
{
34+
Config: testClusterDataSourceConfig_basic,
35+
Check: resource.ComposeTestCheckFunc(
36+
resource.TestCheckResourceAttr("data.cloudstack_cluster.test", "name", "terraform-test-cluster"),
37+
),
38+
},
39+
},
40+
})
41+
}
42+
43+
const testClusterDataSourceConfig_basic = `
44+
data "cloudstack_zone" "zone" {
45+
filter {
46+
name = "name"
47+
value = "Sandbox-simulator"
48+
}
49+
}
50+
51+
data "cloudstack_pod" "pod" {
52+
filter {
53+
name = "name"
54+
value = "POD0"
55+
}
56+
}
57+
58+
# Create a cluster first
59+
resource "cloudstack_cluster" "test_cluster" {
60+
name = "terraform-test-cluster"
61+
cluster_type = "CloudManaged"
62+
hypervisor = "KVM"
63+
pod_id = data.cloudstack_pod.pod.id
64+
zone_id = data.cloudstack_zone.zone.id
65+
arch = "x86_64"
66+
}
67+
68+
# Then query it with the data source
69+
data "cloudstack_cluster" "test" {
70+
filter {
71+
name = "name"
72+
value = "terraform-test-cluster"
73+
}
74+
depends_on = [cloudstack_cluster.test_cluster]
75+
}
76+
`

cloudstack/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func Provider() *schema.Provider {
9090
"cloudstack_user": dataSourceCloudstackUser(),
9191
"cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(),
9292
"cloudstack_pod": dataSourceCloudstackPod(),
93+
"cloudstack_cluster": dataSourceCloudstackCluster(),
9394
},
9495

9596
ResourcesMap: map[string]*schema.Resource{
@@ -110,6 +111,8 @@ func Provider() *schema.Provider {
110111
"cloudstack_network_acl": resourceCloudStackNetworkACL(),
111112
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
112113
"cloudstack_nic": resourceCloudStackNIC(),
114+
"cloudstack_pod": resourceCloudStackPod(),
115+
"cloudstack_cluster": resourceCloudStackCluster(),
113116
"cloudstack_port_forward": resourceCloudStackPortForward(),
114117
"cloudstack_private_gateway": resourceCloudStackPrivateGateway(),
115118
"cloudstack_secondary_ipaddress": resourceCloudStackSecondaryIPAddress(),

0 commit comments

Comments
 (0)