Skip to content

Commit 1295c06

Browse files
authored
feat(k8s): wait for pool to be ready and add 'nodes' in its output (#393)
1 parent f2564c9 commit 1295c06

7 files changed

+395
-33
lines changed

scaleway/helpers_k8s.go

+54-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type KubeconfigStruct struct {
3737
const (
3838
K8SClusterWaitForReadyTimeout = 10 * time.Minute
3939
K8SClusterWaitForDeletedTimeout = 10 * time.Minute
40+
K8SPoolWaitForReadyTimeout = 10 * time.Minute
4041
)
4142

4243
func k8sAPIWithRegion(d *schema.ResourceData, m interface{}) (*k8s.API, scw.Region, error) {
@@ -69,7 +70,7 @@ func waitK8SClusterReady(k8sAPI *k8s.API, region scw.Region, clusterID string) e
6970
if cluster.Status == k8s.ClusterStatusReady {
7071
return nil
7172
}
72-
return fmt.Errorf("Cluster %s has state %s, wants %s", clusterID, cluster.Status.String(), k8s.ClusterStatusReady.String())
73+
return fmt.Errorf("cluster %s has state %s, wants %s", clusterID, cluster.Status, k8s.ClusterStatusReady)
7374
}
7475

7576
func waitK8SClusterDeleted(k8sAPI *k8s.API, region scw.Region, clusterID string) error {
@@ -85,7 +86,58 @@ func waitK8SClusterDeleted(k8sAPI *k8s.API, region scw.Region, clusterID string)
8586
return err
8687
}
8788

88-
return fmt.Errorf("Cluster %s has state %s, wants %s", clusterID, cluster.Status.String(), k8s.ClusterStatusDeleted.String())
89+
return fmt.Errorf("cluster %s has state %s, wants %s", clusterID, cluster.Status, k8s.ClusterStatusDeleted)
90+
}
91+
92+
func waitK8SPoolReady(k8sAPI *k8s.API, region scw.Region, poolID string) error {
93+
pool, err := k8sAPI.WaitForPool(&k8s.WaitForPoolRequest{
94+
PoolID: poolID,
95+
Region: region,
96+
Timeout: scw.DurationPtr(K8SPoolWaitForReadyTimeout),
97+
})
98+
99+
if err != nil {
100+
return err
101+
}
102+
103+
if pool.Status == k8s.PoolStatusReady {
104+
return nil
105+
}
106+
return fmt.Errorf("pool %s has state %s, wants %s", poolID, pool.Status, k8s.PoolStatusReady)
107+
}
108+
109+
// convert a list of nodes to a list of map
110+
func convertNodes(res *k8s.ListNodesResponse) []map[string]interface{} {
111+
var result []map[string]interface{}
112+
for _, node := range res.Nodes {
113+
n := make(map[string]interface{})
114+
n["name"] = node.Name
115+
n["status"] = node.Status.String()
116+
if node.PublicIPV4 != nil && node.PublicIPV4.String() != "<nil>" {
117+
n["public_ip"] = node.PublicIPV4.String()
118+
}
119+
if node.PublicIPV6 != nil && node.PublicIPV6.String() != "<nil>" {
120+
n["public_ip_v6"] = node.PublicIPV6.String()
121+
}
122+
result = append(result, n)
123+
}
124+
return result
125+
}
126+
127+
func getNodes(k8sAPI *k8s.API, pool *k8s.Pool) ([]map[string]interface{}, error) {
128+
req := &k8s.ListNodesRequest{
129+
Region: pool.Region,
130+
ClusterID: pool.ClusterID,
131+
PoolID: &pool.ID,
132+
}
133+
134+
nodes, err := k8sAPI.ListNodes(req, scw.WithAllPages())
135+
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
return convertNodes(nodes), nil
89141
}
90142

91143
func clusterAutoscalerConfigFlatten(cluster *k8s.Cluster) []map[string]interface{} {

scaleway/resource_k8s_cluster_beta.go

+99-30
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ func resourceScalewayK8SClusterBeta() *schema.Resource {
194194
k8s.RuntimeCrio.String(),
195195
}, false),
196196
},
197+
"wait_for_pool_ready": {
198+
Type: schema.TypeBool,
199+
Optional: true,
200+
Default: false,
201+
Description: "Whether to wait for the pool to be ready",
202+
},
197203
// Computed elements
198204
"pool_id": {
199205
Type: schema.TypeString,
@@ -210,6 +216,34 @@ func resourceScalewayK8SClusterBeta() *schema.Resource {
210216
Computed: true,
211217
Description: "The date and time of the last update of the default pool",
212218
},
219+
"nodes": {
220+
Type: schema.TypeList,
221+
Computed: true,
222+
Elem: &schema.Resource{
223+
Schema: map[string]*schema.Schema{
224+
"name": {
225+
Type: schema.TypeString,
226+
Computed: true,
227+
Description: "The name of the node",
228+
},
229+
"status": {
230+
Type: schema.TypeString,
231+
Computed: true,
232+
Description: "The status of the node",
233+
},
234+
"public_ip": {
235+
Type: schema.TypeString,
236+
Computed: true,
237+
Description: "The public IPv4 address of the node",
238+
},
239+
"public_ip_v6": {
240+
Type: schema.TypeString,
241+
Computed: true,
242+
Description: "The public IPv6 address of the node",
243+
},
244+
},
245+
},
246+
},
213247
"status": {
214248
Type: schema.TypeString,
215249
Computed: true,
@@ -408,51 +442,42 @@ func resourceScalewayK8SClusterBetaCreate(d *schema.ResourceData, m interface{})
408442

409443
d.SetId(newRegionalId(region, res.ID))
410444

411-
err = waitK8SClusterReady(k8sAPI, region, res.ID)
445+
err = waitK8SClusterReady(k8sAPI, region, res.ID) // wait for the cluster status to be ready
412446
if err != nil {
413447
return err
414448
}
415449

450+
if d.Get("default_pool.0.wait_for_pool_ready").(bool) { // wait for the pool status to be ready (if specified)
451+
pool, err := readDefaultPool(d, m) // ensure that 'default_pool.0.pool_id' is set
452+
if err != nil {
453+
return err
454+
}
455+
456+
err = waitK8SPoolReady(k8sAPI, region, expandID(pool.ID))
457+
if err != nil {
458+
return err
459+
}
460+
}
461+
416462
return resourceScalewayK8SClusterBetaRead(d, m)
417463
}
418464

419465
// resourceScalewayK8SClusterBetaDefaultPoolRead is only called after a resourceScalewayK8SClusterBetaCreate
420466
// thus ensuring the uniqueness of the only pool listed
421467
func resourceScalewayK8SClusterBetaDefaultPoolRead(d *schema.ResourceData, m interface{}) error {
422-
k8sAPI, region, clusterID, err := k8sAPIWithRegionAndID(m, d.Id())
468+
k8sAPI, region, _, err := k8sAPIWithRegionAndID(m, d.Id())
423469
if err != nil {
424470
return err
425471
}
426472

427-
////
428-
// Read default Pool
429-
////
430-
431-
var pool *k8s.Pool
432-
433-
if defaultPoolID, ok := d.GetOk("default_pool.0.pool_id"); ok {
434-
poolResp, err := k8sAPI.GetPool(&k8s.GetPoolRequest{
435-
Region: region,
436-
PoolID: expandID(defaultPoolID.(string)),
437-
})
438-
if err != nil {
439-
return err
440-
}
441-
pool = poolResp
442-
} else {
443-
response, err := k8sAPI.ListPools(&k8s.ListPoolsRequest{
444-
Region: region,
445-
ClusterID: clusterID,
446-
})
447-
if err != nil {
448-
return err
449-
}
450-
451-
if len(response.Pools) != 1 {
452-
return fmt.Errorf("Newly created pool on cluster %s has %d pools instead of 1", clusterID, len(response.Pools))
453-
}
473+
pool, err := readDefaultPool(d, m)
474+
if err != nil {
475+
return err
476+
}
454477

455-
pool = response.Pools[0]
478+
nodes, err := getNodes(k8sAPI, pool)
479+
if err != nil {
480+
return err
456481
}
457482

458483
defaultPool := map[string]interface{}{}
@@ -466,6 +491,8 @@ func resourceScalewayK8SClusterBetaDefaultPoolRead(d *schema.ResourceData, m int
466491
defaultPool["container_runtime"] = pool.ContainerRuntime
467492
defaultPool["created_at"] = pool.CreatedAt.String()
468493
defaultPool["updated_at"] = pool.UpdatedAt.String()
494+
defaultPool["nodes"] = nodes
495+
defaultPool["wait_for_pool_ready"] = d.Get("default_pool.0.wait_for_pool_ready")
469496
defaultPool["status"] = pool.Status.String()
470497

471498
if pool.PlacementGroupID != nil {
@@ -479,6 +506,41 @@ func resourceScalewayK8SClusterBetaDefaultPoolRead(d *schema.ResourceData, m int
479506
return nil
480507
}
481508

509+
func readDefaultPool(d *schema.ResourceData, m interface{}) (*k8s.Pool, error) {
510+
k8sAPI, region, clusterID, err := k8sAPIWithRegionAndID(m, d.Id())
511+
if err != nil {
512+
return nil, err
513+
}
514+
515+
var pool *k8s.Pool
516+
517+
if defaultPoolID, ok := d.GetOk("default_pool.0.pool_id"); ok {
518+
poolResp, err := k8sAPI.GetPool(&k8s.GetPoolRequest{
519+
Region: region,
520+
PoolID: expandID(defaultPoolID.(string)),
521+
})
522+
if err != nil {
523+
return nil, err
524+
}
525+
pool = poolResp
526+
} else {
527+
response, err := k8sAPI.ListPools(&k8s.ListPoolsRequest{
528+
Region: region,
529+
ClusterID: clusterID,
530+
})
531+
if err != nil {
532+
return nil, err
533+
}
534+
535+
if len(response.Pools) != 1 {
536+
return nil, fmt.Errorf("Newly created pool on cluster %s has %d pools instead of 1", clusterID, len(response.Pools))
537+
}
538+
539+
pool = response.Pools[0]
540+
}
541+
return pool, nil
542+
}
543+
482544
func resourceScalewayK8SClusterBetaRead(d *schema.ResourceData, m interface{}) error {
483545
k8sAPI, region, clusterID, err := k8sAPIWithRegionAndID(m, d.Id())
484546
if err != nil {
@@ -662,6 +724,13 @@ func resourceScalewayK8SClusterBetaDefaultPoolUpdate(d *schema.ResourceData, m i
662724
}
663725
}
664726
}
727+
728+
if d.Get("default_pool.0.wait_for_pool_ready").(bool) { // wait for the pool to be ready if specified
729+
err = waitK8SPoolReady(k8sAPI, region, expandID(defaultPoolID))
730+
if err != nil {
731+
return err
732+
}
733+
}
665734
}
666735

667736
return resourceScalewayK8SClusterBetaDefaultPoolRead(d, m)

scaleway/resource_k8s_cluster_beta_test.go

+78
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,66 @@ func TestAccScalewayK8SClusterBetaDefaultPoolRecreate(t *testing.T) {
249249
})
250250
}
251251

252+
func TestAccScalewayK8SClusterBetaDefaultPoolWait(t *testing.T) {
253+
resource.ParallelTest(t, resource.TestCase{
254+
PreCheck: func() { testAccPreCheck(t) },
255+
Providers: testAccProviders,
256+
CheckDestroy: testAccCheckScalewayK8SClusterBetaDestroy,
257+
Steps: []resource.TestStep{
258+
{
259+
Config: testAccCheckScalewayK8SClusterBetaConfigPoolWait("1.17.3", 1),
260+
Check: resource.ComposeTestCheckFunc(
261+
testAccCheckScalewayK8SClusterBetaExists("scaleway_k8s_cluster_beta.pool"),
262+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "version", "1.17.3"),
263+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "cni", "cilium"),
264+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "status", k8s.ClusterStatusReady.String()),
265+
resource.TestCheckResourceAttrSet("scaleway_k8s_cluster_beta.pool", "default_pool.0.pool_id"),
266+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.size", "1"),
267+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.node_type", "gp1_xs"),
268+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.min_size", "1"),
269+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.max_size", "1"),
270+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.status", k8s.PoolStatusReady.String()),
271+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.nodes.0.status", k8s.NodeStatusReady.String()),
272+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.wait_for_pool_ready", "true"),
273+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "tags.0", "terraform-test"),
274+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "tags.1", "scaleway_k8s_cluster_beta"),
275+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "tags.2", "default-pool"),
276+
),
277+
},
278+
{
279+
Config: testAccCheckScalewayK8SClusterBetaConfigPoolWait("1.17.3", 2), // add a node and wait for the pool to be ready
280+
Check: resource.ComposeTestCheckFunc(
281+
testAccCheckScalewayK8SClusterBetaExists("scaleway_k8s_cluster_beta.pool"),
282+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "status", k8s.ClusterStatusReady.String()),
283+
resource.TestCheckResourceAttrSet("scaleway_k8s_cluster_beta.pool", "default_pool.0.pool_id"),
284+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.size", "2"),
285+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.min_size", "1"),
286+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.max_size", "2"),
287+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.status", k8s.PoolStatusReady.String()),
288+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.nodes.0.status", k8s.NodeStatusReady.String()),
289+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.nodes.1.status", k8s.NodeStatusReady.String()), // check that the new node has the "ready" status
290+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.wait_for_pool_ready", "true"),
291+
),
292+
},
293+
{
294+
Config: testAccCheckScalewayK8SClusterBetaConfigPoolWait("1.17.3", 1), // remove a node and wait for the pool to be ready
295+
Check: resource.ComposeTestCheckFunc(
296+
testAccCheckScalewayK8SClusterBetaExists("scaleway_k8s_cluster_beta.pool"),
297+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "status", k8s.ClusterStatusReady.String()),
298+
resource.TestCheckResourceAttrSet("scaleway_k8s_cluster_beta.pool", "default_pool.0.pool_id"),
299+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.size", "1"),
300+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.min_size", "1"),
301+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.max_size", "1"),
302+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.status", k8s.PoolStatusReady.String()),
303+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.nodes.0.status", k8s.NodeStatusReady.String()),
304+
resource.TestCheckNoResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.nodes.1"), // check that the second node does not exist anymore
305+
resource.TestCheckResourceAttr("scaleway_k8s_cluster_beta.pool", "default_pool.0.wait_for_pool_ready", "true"),
306+
),
307+
},
308+
},
309+
})
310+
}
311+
252312
func TestAccScalewayK8SClusterBetaAutoUpgrade(t *testing.T) {
253313
resource.ParallelTest(t, resource.TestCase{
254314
PreCheck: func() { testAccPreCheck(t) },
@@ -411,6 +471,24 @@ resource "scaleway_k8s_cluster_beta" "pool" {
411471
}`, version)
412472
}
413473

474+
func testAccCheckScalewayK8SClusterBetaConfigPoolWait(version string, size int) string {
475+
return fmt.Sprintf(`
476+
resource "scaleway_k8s_cluster_beta" "pool" {
477+
cni = "cilium"
478+
version = "%s"
479+
name = "default-pool"
480+
default_pool {
481+
node_type = "gp1_xs"
482+
size = %d
483+
min_size = 1
484+
max_size = %d
485+
container_runtime = "docker"
486+
wait_for_pool_ready = true
487+
}
488+
tags = [ "terraform-test", "scaleway_k8s_cluster_beta", "default-pool" ]
489+
}`, version, size, size)
490+
}
491+
414492
func testAccCheckScalewayK8SClusterBetaConfigPoolWithPlacementGroup(version string) string {
415493
return fmt.Sprintf(`
416494
resource "scaleway_instance_placement_group" "pool_placement_group" {

0 commit comments

Comments
 (0)