Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/resources/k8s_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ In addition to all arguments above, the following attributes are exported:
- `status` - The status of the pool.
- `nodes` - (List of) The nodes in the default pool.
- `name` - The name of the node.
- `private_ips` - The list of private IPv4 and IPv6 addresses associated with the node.
- `id` - The ID of the IP address resource.
- `address` - The private IP address.
- `public_ip` - The public IPv4. (Deprecated, Please use the official Kubernetes provider and the kubernetes_nodes data source)
- `public_ip_v6` - The public IPv6. (Deprecated, Please use the official Kubernetes provider and the kubernetes_nodes data source)
- `status` - The status of the node.
Expand Down
6 changes: 6 additions & 0 deletions internal/services/ipam/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type GetResourcePrivateIPsOptions struct {
ResourceID *string
ResourceName *string
PrivateNetworkID *string
ProjectID *string
}

// GetResourcePrivateIPs fetches the private IP addresses of a resource in a private network.
Expand All @@ -100,6 +101,11 @@ func GetResourcePrivateIPs(ctx context.Context, m interface{}, region scw.Region
if opts.ResourceType != nil {
req.ResourceType = *opts.ResourceType
}

// Project ID needs to be specified in order to force the IPAM API to check IAM permissions and send a 403 response code if not authorized
if opts.ProjectID != nil {
req.ProjectID = opts.ProjectID
}
}

resp, err := ipamAPI.ListIPs(req, scw.WithContext(ctx))
Expand Down
17 changes: 17 additions & 0 deletions internal/services/k8s/helpers_k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func convertNodes(res *k8s.ListNodesResponse) []map[string]interface{} {

for _, node := range res.Nodes {
n := make(map[string]interface{})
n["id"] = node.ID
n["name"] = node.Name
n["status"] = node.Status.String()

Expand Down Expand Up @@ -117,3 +118,19 @@ func getNodes(ctx context.Context, k8sAPI *k8s.API, pool *k8s.Pool) ([]map[strin

return convertNodes(nodes), nil
}

func getClusterProjectID(ctx context.Context, k8sAPI *k8s.API, pool *k8s.Pool) (string, error) {
cluster, err := k8sAPI.GetCluster(&k8s.GetClusterRequest{
Region: pool.Region,
ClusterID: pool.ClusterID,
}, scw.WithContext(ctx))
if err != nil {
return "", fmt.Errorf("get pool project ID: error getting cluster %s", pool.ClusterID)
}

if cluster.ProjectID == "" {
return "", fmt.Errorf("no project ID found for cluster %s", pool.ClusterID)
}

return cluster.ProjectID, nil
}
80 changes: 78 additions & 2 deletions internal/services/k8s/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/regional"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/locality/zonal"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/services/ipam"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/types"
"github.com/scaleway/terraform-provider-scaleway/v2/internal/verify"
)
Expand Down Expand Up @@ -192,6 +193,11 @@ func ResourcePool() *schema.Resource {
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID of the node",
},
"name": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -214,6 +220,25 @@ func ResourcePool() *schema.Resource {
Description: "The public IPv6 address of the node",
Deprecated: "Please use the official Kubernetes provider and the kubernetes_nodes data source",
},
"private_ips": {
Type: schema.TypeList,
Computed: true,
Description: "List of private IPv4 and IPv6 addresses associated with the node",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID of the IP address resource",
},
"address": {
Type: schema.TypeString,
Computed: true,
Description: "The private IP address",
},
},
},
},
},
},
},
Expand Down Expand Up @@ -389,7 +414,6 @@ func ResourceK8SPoolRead(ctx context.Context, d *schema.ResourceData, m interfac
_ = d.Set("container_runtime", pool.ContainerRuntime)
_ = d.Set("created_at", pool.CreatedAt.Format(time.RFC3339))
_ = d.Set("updated_at", pool.UpdatedAt.Format(time.RFC3339))
_ = d.Set("nodes", nodes)
_ = d.Set("status", pool.Status)
_ = d.Set("kubelet_args", flattenKubeletArgs(pool.KubeletArgs))
_ = d.Set("region", region)
Expand All @@ -401,7 +425,59 @@ func ResourceK8SPoolRead(ctx context.Context, d *schema.ResourceData, m interfac
_ = d.Set("placement_group_id", zonal.NewID(pool.Zone, *pool.PlacementGroupID).String())
}

return nil
// Get nodes' private IPs
diags := diag.Diagnostics{}

projectID, err := getClusterProjectID(ctx, k8sAPI, pool)
if err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Unable to get nodes private IPs",
Detail: err.Error(),
})
} else {
for i, nodeMap := range nodes {
nodeNameInterface, ok := nodeMap["name"]
if !ok {
continue
}

nodeName, ok := nodeNameInterface.(string)
if !ok {
continue
}

opts := &ipam.GetResourcePrivateIPsOptions{
ResourceName: &nodeName,
ProjectID: &projectID,
}

privateIPs, err := ipam.GetResourcePrivateIPs(ctx, m, region, opts)
if err != nil {
if httperrors.Is403(err) {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Unauthorized to read nodes' private IPs, please check your IAM permissions",
Detail: err.Error(),
})

break
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: "Unable to get nodes private IPs from IPAM API",
Detail: err.Error(),
})
}
}

nodes[i]["private_ips"] = privateIPs
}
}

_ = d.Set("nodes", nodes)

return diags
}

func ResourceK8SPoolUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
Expand Down
6 changes: 6 additions & 0 deletions internal/services/k8s/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func TestAccPool_Basic(t *testing.T) {
resource.TestCheckResourceAttr("scaleway_k8s_pool.default", "tags.1", "scaleway_k8s_cluster"),
resource.TestCheckResourceAttr("scaleway_k8s_pool.default", "tags.2", "default"),
testAccCheckK8SPoolServersAreInPrivateNetwork(tt, "scaleway_k8s_cluster.minimal", "scaleway_k8s_pool.default", "scaleway_vpc_private_network.minimal"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.0.id"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.0.address"),
),
},
{
Expand All @@ -69,6 +71,10 @@ func TestAccPool_Basic(t *testing.T) {
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.minimal", "nodes.0.public_ip"), // Deprecated attributes
testAccCheckK8SPoolServersAreInPrivateNetwork(tt, "scaleway_k8s_cluster.minimal", "scaleway_k8s_pool.default", "scaleway_vpc_private_network.minimal"),
testAccCheckK8SPoolServersAreInPrivateNetwork(tt, "scaleway_k8s_cluster.minimal", "scaleway_k8s_pool.minimal", "scaleway_vpc_private_network.minimal"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.0.id"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.0.address"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.1.id"),
resource.TestCheckResourceAttrSet("scaleway_k8s_pool.default", "nodes.0.private_ips.1.address"),
),
},
{
Expand Down
Loading
Loading