Skip to content

Commit 8e5f57f

Browse files
committed
Adding support for Kubernetes Clusters
1 parent 9496d6a commit 8e5f57f

File tree

5 files changed

+365
-3
lines changed

5 files changed

+365
-3
lines changed

cloudstack/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ func Provider() terraform.ResourceProvider {
8989
"cloudstack_firewall": resourceCloudStackFirewall(),
9090
"cloudstack_instance": resourceCloudStackInstance(),
9191
"cloudstack_ipaddress": resourceCloudStackIPAddress(),
92+
"cloudstack_kubernetes_cluster": resourceCloudStackKubernetesCluster(),
9293
"cloudstack_loadbalancer_rule": resourceCloudStackLoadBalancerRule(),
9394
"cloudstack_network": resourceCloudStackNetwork(),
9495
"cloudstack_network_acl": resourceCloudStackNetworkACL(),
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
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+
"strings"
26+
27+
"github.com/apache/cloudstack-go/v2/cloudstack"
28+
"github.com/hashicorp/terraform/helper/schema"
29+
)
30+
31+
func resourceCloudStackKubernetesCluster() *schema.Resource {
32+
return &schema.Resource{
33+
Create: resourceCloudStackKubernetesClusterCreate,
34+
Read: resourceCloudStackKubernetesClusterRead,
35+
Update: resourceCloudStackKubernetesClusterUpdate,
36+
Delete: resourceCloudStackKubernetesClusterDelete,
37+
Importer: &schema.ResourceImporter{
38+
State: importStatePassthrough,
39+
},
40+
41+
Schema: map[string]*schema.Schema{
42+
"name": {
43+
Type: schema.TypeString,
44+
Required: true,
45+
ForceNew: true,
46+
},
47+
48+
"zone": {
49+
Type: schema.TypeString,
50+
Required: true,
51+
ForceNew: true,
52+
},
53+
54+
"kubernetes_version": {
55+
Type: schema.TypeString,
56+
Required: true,
57+
},
58+
59+
"service_offering": {
60+
Type: schema.TypeString,
61+
Required: true,
62+
},
63+
64+
// Begin optional params
65+
"size": {
66+
Type: schema.TypeInt,
67+
Optional: true,
68+
Default: 1,
69+
},
70+
71+
"autoscaling_enabled": {
72+
Type: schema.TypeBool,
73+
Optional: true,
74+
},
75+
76+
"min_size": {
77+
Type: schema.TypeInt,
78+
Optional: true,
79+
},
80+
81+
"max_size": {
82+
Type: schema.TypeInt,
83+
Optional: true,
84+
},
85+
86+
"control_nodes_size": {
87+
Type: schema.TypeInt,
88+
Optional: true,
89+
Computed: true,
90+
ForceNew: true, // For now
91+
},
92+
93+
"description": {
94+
Type: schema.TypeString,
95+
Optional: true,
96+
Computed: true,
97+
},
98+
99+
"keypair": {
100+
Type: schema.TypeString,
101+
Optional: true,
102+
ForceNew: true,
103+
},
104+
105+
"network_id": {
106+
Type: schema.TypeString,
107+
Optional: true,
108+
Computed: true,
109+
ForceNew: true,
110+
},
111+
112+
"ip_address": {
113+
Type: schema.TypeString,
114+
Computed: true,
115+
},
116+
117+
"state": {
118+
Type: schema.TypeString,
119+
Optional: true,
120+
Computed: true,
121+
// Default: "Running",
122+
},
123+
124+
"project": {
125+
Type: schema.TypeString,
126+
Optional: true,
127+
ForceNew: true,
128+
},
129+
},
130+
}
131+
}
132+
133+
func resourceCloudStackKubernetesClusterCreate(d *schema.ResourceData, meta interface{}) error {
134+
cs := meta.(*cloudstack.CloudStackClient)
135+
136+
// State is always Running when created
137+
if state, ok := d.GetOk("state"); ok {
138+
if state.(string) != "Running" {
139+
return fmt.Errorf("State must be 'Running' when first creating a cluster")
140+
}
141+
}
142+
143+
name := d.Get("name").(string)
144+
size := int64(d.Get("size").(int))
145+
serviceOfferingID, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
146+
if e != nil {
147+
return e.Error()
148+
}
149+
zoneID, e := retrieveID(cs, "zone", d.Get("zone").(string))
150+
if e != nil {
151+
return e.Error()
152+
}
153+
kubernetesVersionID, e := retrieveID(cs, "kubernetes_version", d.Get("kubernetes_version").(string))
154+
if e != nil {
155+
return e.Error()
156+
}
157+
158+
// Create a new parameter struct
159+
p := cs.Kubernetes.NewCreateKubernetesClusterParams(name, kubernetesVersionID, name, serviceOfferingID, size, zoneID)
160+
161+
// Set optional params
162+
if description, ok := d.GetOk("description"); ok {
163+
p.SetDescription(description.(string))
164+
}
165+
if keypair, ok := d.GetOk("keypair"); ok {
166+
p.SetKeypair(keypair.(string))
167+
}
168+
if networkID, ok := d.GetOk("network_id"); ok {
169+
p.SetNetworkid(networkID.(string))
170+
}
171+
if controlNodesSize, ok := d.GetOk("control_nodes_size"); ok {
172+
p.SetControlnodes(int64(controlNodesSize.(int)))
173+
}
174+
175+
// If there is a project supplied, we retrieve and set the project id
176+
if err := setProjectid(p, cs, d); err != nil {
177+
return err
178+
}
179+
180+
log.Printf("[DEBUG] Creating Kubernetes Cluster %s", name)
181+
r, err := cs.Kubernetes.CreateKubernetesCluster(p)
182+
if err != nil {
183+
return err
184+
}
185+
186+
log.Printf("[DEBUG] Kubernetes Cluster %s successfully created", name)
187+
d.SetId(r.Id)
188+
189+
if _, ok := d.GetOk("autoscaling_enabled"); ok {
190+
err = autoscaleKubernetesCluster(d, meta)
191+
if err != nil {
192+
return err
193+
}
194+
}
195+
196+
return resourceCloudStackKubernetesClusterRead(d, meta)
197+
}
198+
199+
func resourceCloudStackKubernetesClusterRead(d *schema.ResourceData, meta interface{}) error {
200+
cs := meta.(*cloudstack.CloudStackClient)
201+
202+
log.Printf("[DEBUG] Retrieving Kubernetes Cluster %s", d.Get("name").(string))
203+
204+
// Get the Kubernetes Cluster details
205+
cluster, count, err := cs.Kubernetes.GetKubernetesClusterByID(
206+
d.Id(),
207+
cloudstack.WithProject(d.Get("project").(string)),
208+
)
209+
if err != nil {
210+
if count == 0 {
211+
log.Printf("[DEBUG] Kubernetes Cluster %s does not longer exist", d.Get("name").(string))
212+
d.SetId("")
213+
return nil
214+
}
215+
216+
return err
217+
}
218+
219+
// Update the config
220+
d.SetId(cluster.Id)
221+
d.Set("name", cluster.Name)
222+
d.Set("description", cluster.Description)
223+
d.Set("control_nodes_size", cluster.Controlnodes)
224+
d.Set("size", cluster.Size)
225+
d.Set("autoscaling_enabled", cluster.Autoscalingenabled)
226+
d.Set("min_size", cluster.Minsize)
227+
d.Set("max_size", cluster.Maxsize)
228+
d.Set("keypair", cluster.Keypair)
229+
d.Set("network_id", cluster.Networkid)
230+
d.Set("ip_address", cluster.Ipaddress)
231+
d.Set("state", cluster.State)
232+
233+
setValueOrID(d, "kubernetes_version", cluster.Kubernetesversionname, cluster.Kubernetesversionid)
234+
setValueOrID(d, "service_offering", cluster.Serviceofferingname, cluster.Serviceofferingid)
235+
setValueOrID(d, "project", cluster.Project, cluster.Projectid)
236+
setValueOrID(d, "zone", cluster.Zonename, cluster.Zoneid)
237+
238+
return nil
239+
}
240+
241+
func autoscaleKubernetesCluster(d *schema.ResourceData, meta interface{}) error {
242+
cs := meta.(*cloudstack.CloudStackClient)
243+
p := cs.Kubernetes.NewScaleKubernetesClusterParams(d.Id())
244+
p.SetAutoscalingenabled(d.Get("autoscaling_enabled").(bool))
245+
p.SetMinsize(int64(d.Get("min_size").(int)))
246+
p.SetMaxsize(int64(d.Get("max_size").(int)))
247+
_, err := cs.Kubernetes.ScaleKubernetesCluster(p)
248+
return err
249+
}
250+
251+
func resourceCloudStackKubernetesClusterUpdate(d *schema.ResourceData, meta interface{}) error {
252+
cs := meta.(*cloudstack.CloudStackClient)
253+
d.Partial(true)
254+
255+
if d.HasChange("service_offering") || d.HasChange("size") {
256+
p := cs.Kubernetes.NewScaleKubernetesClusterParams(d.Id())
257+
serviceOfferingID, e := retrieveID(cs, "service_offering", d.Get("service_offering").(string))
258+
if e != nil {
259+
return e.Error()
260+
}
261+
p.SetServiceofferingid(serviceOfferingID)
262+
p.SetSize(int64(d.Get("size").(int)))
263+
_, err := cs.Kubernetes.ScaleKubernetesCluster(p)
264+
if err != nil {
265+
return fmt.Errorf(
266+
"Error Scaling Kubernetes Cluster %s: %s", d.Id(), err)
267+
}
268+
d.SetPartial("service_offering")
269+
d.SetPartial("size")
270+
}
271+
272+
if d.HasChange("autoscaling_enabled") || d.HasChange("min_size") || d.HasChange("max_size") {
273+
err := autoscaleKubernetesCluster(d, meta)
274+
if err != nil {
275+
return err
276+
}
277+
d.SetPartial("autoscaling_enabled")
278+
d.SetPartial("min_size")
279+
d.SetPartial("max_size")
280+
}
281+
282+
if d.HasChange("kubernetes_version") {
283+
kubernetesVersionID, e := retrieveID(cs, "kubernetes_version", d.Get("kubernetes_version").(string))
284+
if e != nil {
285+
return e.Error()
286+
}
287+
p := cs.Kubernetes.NewUpgradeKubernetesClusterParams(d.Id(), kubernetesVersionID)
288+
_, err := cs.Kubernetes.UpgradeKubernetesCluster(p)
289+
if err != nil {
290+
return fmt.Errorf(
291+
"Error Upgrading Kubernetes Cluster %s: %s", d.Id(), err)
292+
}
293+
d.SetPartial("kubernetes_version")
294+
}
295+
296+
if d.HasChange("state") {
297+
state := d.Get("state").(string)
298+
switch state {
299+
case "Running":
300+
p := cs.Kubernetes.NewStartKubernetesClusterParams(d.Id())
301+
_, err := cs.Kubernetes.StartKubernetesCluster(p)
302+
if err != nil {
303+
return fmt.Errorf(
304+
"Error Starting Kubernetes Cluster %s: %s", d.Id(), err)
305+
}
306+
case "Stopped":
307+
p := cs.Kubernetes.NewStopKubernetesClusterParams(d.Id())
308+
_, err := cs.Kubernetes.StopKubernetesCluster(p)
309+
if err != nil {
310+
return fmt.Errorf(
311+
"Error Stopping Kubernetes Cluster %s: %s", d.Id(), err)
312+
}
313+
default:
314+
return fmt.Errorf("State must either be 'Running' or 'Stopped'")
315+
}
316+
d.SetPartial("state")
317+
}
318+
319+
d.Partial(false)
320+
return resourceCloudStackKubernetesClusterRead(d, meta)
321+
}
322+
323+
func resourceCloudStackKubernetesClusterDelete(d *schema.ResourceData, meta interface{}) error {
324+
cs := meta.(*cloudstack.CloudStackClient)
325+
326+
// Create a new parameter struct
327+
p := cs.Kubernetes.NewDeleteKubernetesClusterParams(d.Id())
328+
329+
// Delete the Kubernetes Cluster
330+
_, err := cs.Kubernetes.DeleteKubernetesCluster(p)
331+
if err != nil {
332+
// This is a very poor way to be told the ID does no longer exist :(
333+
if strings.Contains(err.Error(), fmt.Sprintf(
334+
"Invalid parameter id value=%s due to incorrect long value format, "+
335+
"or entity does not exist", d.Id())) {
336+
return nil
337+
}
338+
339+
return fmt.Errorf("Error deleting Kubernetes Cluster: %s", err)
340+
}
341+
342+
return nil
343+
}

cloudstack/resources.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ func retrieveID(cs *cloudstack.CloudStackClient, name string, value string, opts
7070
switch name {
7171
case "disk_offering":
7272
id, _, err = cs.DiskOffering.GetDiskOfferingID(value)
73-
case "service_offering":
74-
id, _, err = cs.ServiceOffering.GetServiceOfferingID(value)
73+
case "kubernetes_version":
74+
id, _, err = cs.Kubernetes.GetKubernetesSupportedVersionID(value)
7575
case "network_offering":
7676
id, _, err = cs.NetworkOffering.GetNetworkOfferingID(value)
7777
case "project":
7878
id, _, err = cs.Project.GetProjectID(value)
79+
case "service_offering":
80+
id, _, err = cs.ServiceOffering.GetServiceOfferingID(value)
7981
case "vpc_offering":
8082
id, _, err = cs.VPC.GetVPCOfferingID(value)
8183
case "zone":

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module github.com/terraform-providers/terraform-provider-cloudstack
22

33
require (
4-
github.com/apache/cloudstack-go/v2 v2.11.0
4+
github.com/apache/cloudstack-go/v2 v2.13.1
55
github.com/go-ini/ini v1.40.0
66
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
77
github.com/hashicorp/go-multierror v1.0.0

0 commit comments

Comments
 (0)