Add attachFloatinIps spec on nodepool (#107)
Some checks failed
Build and test / build (1.24.x) (push) Has been cancelled
Build and test / build (1.25.x) (push) Has been cancelled

* feat : add attachFloatinIps
This commit is contained in:
anthony berger 2025-12-15 16:44:24 +01:00 committed by GitHub
parent bef8a80c37
commit f71e5d4e28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 4720 additions and 844 deletions

View file

@ -58,8 +58,9 @@ ovhcloud cloud kube nodepool create <cluster_id> [flags]
```
--anti-affinity Enable anti-affinity for the node pool
--attach-floating-ips Enable FloatingIP creation, if true, a floating IP will be created and attached to each node
--autoscale Enable autoscaling for the node pool
--availability-zones strings Availability zones for the node pool
--availability-zones stringArray Availability zones for the node pool
--desired-nodes int Desired number of nodes
--editor Use a text editor to define parameters
--flavor-name string Flavor name for the nodes (b2-7, b2-15, etc.)

View file

@ -9,6 +9,7 @@ ovhcloud cloud kube nodepool edit <cluster_id> <nodepool_id> [flags]
### Options
```
--attach-floating-ips Enable FloatingIP creation, if true, a floating IP will be created and attached to each node
--autoscale Enable autoscaling for the node pool
--desired-nodes int Desired number of nodes
--editor Use a text editor to define parameters

File diff suppressed because it is too large Load diff

View file

@ -271,6 +271,16 @@
"format": "uuid",
"readOnly": true
},
"state": {
"allOf": [
{
"$ref": "#/components/schemas/iam.ResourceMetadata.StateEnum"
}
],
"description": "Resource state",
"nullable": true,
"readOnly": true
},
"tags": {
"type": "object",
"description": "Resource tags. Tags that were internally computed are prefixed with ovh:",
@ -287,6 +297,16 @@
}
}
},
"iam.ResourceMetadata.StateEnum": {
"type": "string",
"description": "Resource state",
"enum": [
"EXPIRED",
"IN_CREATION",
"OK",
"SUSPENDED"
]
},
"iam.resource.TagFilter": {
"type": "object",
"description": "Resource tag filter",

View file

@ -182,27 +182,7 @@ func initKubeCommand(cloudCmd *cobra.Command) {
Args: cobra.ExactArgs(2),
})
nodepoolEditCmd := &cobra.Command{
Use: "edit <cluster_id> <nodepool_id>",
Short: "Edit the given Kubernetes node pool",
Run: cloud.EditKubeNodepool,
Args: cobra.ExactArgs(2),
}
nodepoolEditCmd.Flags().BoolVar(&cloud.KubeNodepoolSpec.Autoscale, "autoscale", false, "Enable autoscaling for the node pool")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnneededTimeSeconds, "scale-down-unneeded-time-seconds", 0, "How long a node should be unneeded before it is eligible for scale down (seconds)")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnreadyTimeSeconds, "scale-down-unready-time-seconds", 0, "How long an unready node should be unneeded before it is eligible for scale down (seconds)")
nodepoolEditCmd.Flags().Float64Var(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUtilizationThreshold, "scale-down-utilization-threshold", 0, "Sum of CPU or memory of all pods running on the node divided by node's corresponding allocatable resource, below which a node can be considered for scale down")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.DesiredNodes, "desired-nodes", 0, "Desired number of nodes")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MaxNodes, "max-nodes", 0, "Higher limit you accept for the desiredNodes value (100 by default)")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MinNodes, "min-nodes", 0, "Lower limit you accept for the desiredNodes value (0 by default)")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.NodesToRemove, "nodes-to-remove", nil, "List of node IDs to remove from the node pool")
nodepoolEditCmd.Flags().StringToStringVar(&cloud.KubeNodepoolSpec.Template.Metadata.Annotations, "template-annotations", nil, "Annotations to apply to each node")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.Template.Metadata.Finalizers, "template-finalizers", nil, "Finalizers to apply to each node")
nodepoolEditCmd.Flags().StringToStringVar(&cloud.KubeNodepoolSpec.Template.Metadata.Labels, "template-labels", nil, "Labels to apply to each node")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.Template.Spec.CommandLineTaints, "template-taints", nil, "Taints to apply to each node in key=value:effect format")
nodepoolEditCmd.Flags().BoolVar(&cloud.KubeNodepoolSpec.Template.Spec.Unschedulable, "template-unschedulable", false, "Set the nodes as unschedulable")
addInteractiveEditorFlag(nodepoolEditCmd)
nodepoolCmd.AddCommand(nodepoolEditCmd)
nodepoolCmd.AddCommand(getNodepoolEditCmd())
nodepoolCmd.AddCommand(&cobra.Command{
Use: "delete <cluster_id> <nodepool_id>",
@ -477,6 +457,36 @@ There are three ways to define the reset parameters:
return kubeResetCmd
}
func getNodepoolEditCmd() *cobra.Command {
nodepoolEditCmd := &cobra.Command{
Use: "edit <cluster_id> <nodepool_id>",
Short: "Edit the given Kubernetes node pool",
Run: cloud.EditKubeNodepool,
Args: cobra.ExactArgs(2),
}
nodepoolEditCmd.Flags().BoolVar(&cloud.KubeNodepoolSpec.Autoscale, "autoscale", false, "Enable autoscaling for the node pool")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnneededTimeSeconds, "scale-down-unneeded-time-seconds", 0, "How long a node should be unneeded before it is eligible for scale down (seconds)")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnreadyTimeSeconds, "scale-down-unready-time-seconds", 0, "How long an unready node should be unneeded before it is eligible for scale down (seconds)")
nodepoolEditCmd.Flags().Float64Var(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUtilizationThreshold, "scale-down-utilization-threshold", 0, "Sum of CPU or memory of all pods running on the node divided by node's corresponding allocatable resource, below which a node can be considered for scale down")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.DesiredNodes, "desired-nodes", 0, "Desired number of nodes")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MaxNodes, "max-nodes", 0, "Higher limit you accept for the desiredNodes value (100 by default)")
nodepoolEditCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MinNodes, "min-nodes", 0, "Lower limit you accept for the desiredNodes value (0 by default)")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.NodesToRemove, "nodes-to-remove", nil, "List of node IDs to remove from the node pool")
nodepoolEditCmd.Flags().StringToStringVar(&cloud.KubeNodepoolSpec.Template.Metadata.Annotations, "template-annotations", nil, "Annotations to apply to each node")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.Template.Metadata.Finalizers, "template-finalizers", nil, "Finalizers to apply to each node")
nodepoolEditCmd.Flags().StringToStringVar(&cloud.KubeNodepoolSpec.Template.Metadata.Labels, "template-labels", nil, "Labels to apply to each node")
nodepoolEditCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.Template.Spec.CommandLineTaints, "template-taints", nil, "Taints to apply to each node in key=value:effect format")
nodepoolEditCmd.Flags().BoolVar(&cloud.KubeNodepoolSpec.Template.Spec.Unschedulable, "template-unschedulable", false, "Set the nodes as unschedulable")
var attachFloatingIpsEnabled bool
nodepoolEditCmd.Flags().BoolVar(&attachFloatingIpsEnabled, "attach-floating-ips", false, "Enable FloatingIP creation, if true, a floating IP will be created and attached to each node")
cloud.KubeNodepoolSpec.AttachFloatingIps.Enabled = &attachFloatingIpsEnabled
addInteractiveEditorFlag(nodepoolEditCmd)
return nodepoolEditCmd
}
func getKubeNodePoolCreateCmd() *cobra.Command {
nodepoolCreateCmd := &cobra.Command{
Use: "create <cluster_id>",
@ -537,12 +547,13 @@ There are three ways to define the creation parameters:
nodepoolCreateCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnneededTimeSeconds, "scale-down-unneeded-time-seconds", 0, "How long a node should be unneeded before it is eligible for scale down (seconds)")
nodepoolCreateCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUnreadyTimeSeconds, "scale-down-unready-time-seconds", 0, "How long an unready node should be unneeded before it is eligible for scale down (seconds)")
nodepoolCreateCmd.Flags().Float64Var(&cloud.KubeNodepoolSpec.Autoscaling.ScaleDownUtilizationThreshold, "scale-down-utilization-threshold", 0, "Sum of CPU or memory of all pods running on the node divided by node's corresponding allocatable resource, below which a node can be considered for scale down")
nodepoolCreateCmd.Flags().StringSliceVar(&cloud.KubeNodepoolSpec.AvailabilityZones, "availability-zones", nil, "Availability zones for the node pool")
nodepoolCreateCmd.Flags().StringArrayVar(&cloud.KubeNodepoolSpec.AvailabilityZones, "availability-zones", nil, "Availability zones for the node pool")
nodepoolCreateCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.DesiredNodes, "desired-nodes", 0, "Desired number of nodes")
nodepoolCreateCmd.Flags().StringVar(&cloud.KubeNodepoolSpec.FlavorName, "flavor-name", "", "Flavor name for the nodes (b2-7, b2-15, etc.)")
nodepoolCreateCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MaxNodes, "max-nodes", 0, "Higher limit you accept for the desiredNodes value (100 by default)")
nodepoolCreateCmd.Flags().IntVar(&cloud.KubeNodepoolSpec.MinNodes, "min-nodes", 0, "Lower limit you accept for the desiredNodes value (0 by default)")
nodepoolCreateCmd.Flags().BoolVar(&cloud.KubeNodepoolSpec.MonthlyBilled, "monthly-billed", false, "Enable monthly billing for the node pool")
nodepoolCreateCmd.Flags().BoolVar(cloud.KubeNodepoolSpec.AttachFloatingIps.Enabled, "attach-floating-ips", false, "Enable FloatingIP creation, if true, a floating IP will be created and attached to each node")
// Template.Metadata
nodepoolCreateCmd.Flags().StringToStringVar(&cloud.KubeNodepoolSpec.Template.Metadata.Annotations, "template-annotations", nil, "Annotations to apply to each node")

View file

@ -0,0 +1,579 @@
// SPDX-FileCopyrightText: 2025 OVH SAS <opensource@ovh.net>
//
// SPDX-License-Identifier: Apache-2.0
package cmd_test
import (
"net/http"
"github.com/jarcoal/httpmock"
"github.com/maxatome/go-testdeep/td"
"github.com/maxatome/tdhttpmock"
"github.com/ovh/ovhcloud-cli/internal/cmd"
)
// List Nodepool.
func (ms *MockSuite) TestCloudKubeNodepoolListCmd(assert, require *td.T) {
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12345/nodepool",
httpmock.NewStringResponder(200, `[
{
"id": "rototo",
"name": "nodepool-2025-12-04",
"flavor": "b3-8",
"currentNodes": 2,
"status": "READY"
},
{
"id": "rototo2",
"name": "nodepool-2025-12-05",
"flavor": "b3-8",
"currentNodes": 3,
"status": "UPSCALING"
}
]`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "list", "MyMksID-12345", "--cloud-project", "fakeProjectID")
require.CmpNoError(err)
assert.String(out, `
id name flavor currentNodes status
rototo nodepool-2025-12-04 b3-8 2 READY
rototo2 nodepool-2025-12-05 b3-8 3 UPSCALING
💡 Use option --json or --yaml to get the raw output with all information`[1:])
}
// Get a Nodepool.
func (ms *MockSuite) TestCloudKubeNodepoolGetCmd(assert, require *td.T) {
httpmock.RegisterResponder("GET", "https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12345/nodepool/MyNodePool",
httpmock.NewStringResponder(200, `
{
"id": "MyNodePoolId",
"projectId": "fakeProjectID",
"name": "nodepool1",
"flavor": "b3-32",
"status": "READY",
"sizeStatus": "CAPACITY_OK",
"autoscale": false,
"monthlyBilled": false,
"antiAffinity": false,
"desiredNodes": 0,
"minNodes": 0,
"maxNodes": 100,
"currentNodes": 0,
"availableNodes": 0,
"upToDateNodes": 0,
"createdAt": "2025-12-04T15:29:32.487775Z",
"updatedAt": "2025-12-05T15:51:08Z",
"autoscaling": {
"scaleDownUtilizationThreshold": 0.5,
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200
},
"template": {
"metadata": {
"labels": {},
"annotations": {},
"finalizers": []
},
"spec": {
"unschedulable": false,
"taints": []
}
},
"attachFloatingIps": {
"enabled": false
},
"availabilityZones": [
"myzone"
]
}`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "get", "MyMksID-12345", "MyNodePool", "--cloud-project", "fakeProjectID")
require.CmpNoError(err)
assert.Cmp(cleanWhitespacesHelper(out), `
# 🚀 Managed Kubernetes Node Pool MyNodePool
*nodepool1*
## General information
**Status**: READY
**Project ID**: fakeProjectID
**Availability Zones**: myzone
**Monthly Billed**: false
**Flavor**: b3-32
**Creation date**: 2025-12-04T15:29:32.487775Z
**Update date**: 2025-12-05T15:51:08Z
**Anti Affinity**: false
**Autoscale**: false
**AttachFloatingIP**: false
**Autoscaling**:
- Scale Down Unneeded Time (s): 600
- Scale Down Unready Time (s): 1200
- Scale Down Utilization Threshold*: 0.5
* Sum of CPU or memory of all pods running on the node divided by node's
corresponding allocatable resource.
## Node pool state
**Ready nodes**: 0
**Current Nodes**: 0
**Desired Nodes**: 0
**Max Nodes**: 100
**Min Nodes**: 0
**Size Status**: CAPACITY_OK
**Up To Date Nodes**: 0
## Template
### Metadata
**Annotations**:
**Finalizers**:
**Labels**:
### Spec
**Taints**:
**Unschedulable**: false
💡 Use option --json or --yaml to get the raw output with all information
`)
}
// Create a Nodepool with the attachFloatingIps flag.
// The nodepool spec must be set to true.
func (ms *MockSuite) TestCloudKubeNodepoolCreateCmdWithAttachFloatingIps(assert, require *td.T) {
httpmock.RegisterMatcherResponder(
http.MethodPost,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12345/nodepool",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": true
},
"availabilityZones": [
"myzone"
],
"desiredNodes": 11,
"flavorName": "b3-8",
"name": "mynodepoolname"
}`)),
httpmock.NewStringResponder(200, `{
"id": "mynodepoolid",
"name": "mynodepoolname"
}`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "create", "MyMksID-12345", "--flavor-name", "b3-8", "--name", "mynodepoolname", "--availability-zones", "myzone", "--desired-nodes", "11", "--cloud-project", "fakeProjectID", "--attach-floating-ips")
require.CmpNoError(err)
assert.String(out, `✅ Node pool mynodepoolid created successfully`)
}
// Create a Nodepool without the attachFloatingIps flag.
// The nodepool spec must be set to false
func (ms *MockSuite) TestCloudKubeNodepoolCreateCmdWithoutAttachFloatingIps(assert, require *td.T) {
httpmock.RegisterMatcherResponder(
http.MethodPost,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": false
},
"availabilityZones": [
"myzone2"
],
"desiredNodes": 12,
"flavorName": "b3-16",
"name": "mynodepoolname2"
}`)),
httpmock.NewStringResponder(200, `{
"id": "mynodepool2id",
"name": "mynodepoolname2"
}`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "create", "MyMksID-12346", "--flavor-name", "b3-16", "--name", "mynodepoolname2", "--availability-zones", "myzone2", "--desired-nodes", "12", "--cloud-project", "fakeProjectID")
require.CmpNoError(err)
assert.String(out, `✅ Node pool mynodepool2id created successfully`)
}
// Create a Nodepool with the attachFloatingIps flag set to false.
// The nodepool spec must be set to false.
func (ms *MockSuite) TestCloudKubeNodepoolCreateCmdWithAttachFloatingIpsSetFalse(assert, require *td.T) {
httpmock.RegisterMatcherResponder(
http.MethodPost,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": false
},
"availabilityZones": [
"myzone3"
],
"desiredNodes": 12,
"flavorName": "b3-16",
"name": "mynodepoolname3"
}`)),
httpmock.NewStringResponder(200, `{
"id": "mynodepool3id",
"name": "mynodepoolname3"
}`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "create", "MyMksID-12346", "--flavor-name", "b3-16", "--name", "mynodepoolname3", "--availability-zones", "myzone3", "--desired-nodes", "12", "--cloud-project", "fakeProjectID", "--attach-floating-ips=false")
require.CmpNoError(err)
assert.String(out, `✅ Node pool mynodepool3id created successfully`)
}
// Update a Nodepool with attachFloatingIps disabled with the flag attachFloatingIps
// The spec must be updated from false to true.
func (ms *MockSuite) TestCloudKubeNodepoolEditCmdWithAttachFloatingIpsTrue(assert, require *td.T) {
httpmock.RegisterResponder("GET",
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
httpmock.NewStringResponder(200, `
{
"id": "MyNodePoolId",
"projectId": "fakeProjectID",
"name": "nodepool1",
"flavor": "b3-32",
"status": "READY",
"sizeStatus": "CAPACITY_OK",
"autoscale": false,
"monthlyBilled": false,
"antiAffinity": false,
"desiredNodes": 0,
"minNodes": 0,
"maxNodes": 100,
"currentNodes": 0,
"availableNodes": 0,
"upToDateNodes": 0,
"createdAt": "2025-12-04T15:29:32.487775Z",
"updatedAt": "2025-12-05T15:51:08Z",
"autoscaling": {
"scaleDownUtilizationThreshold": 0.5,
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200
},
"template": {
"metadata": {
"labels": {},
"annotations": {},
"finalizers": []
},
"spec": {
"unschedulable": false,
"taints": []
}
},
"attachFloatingIps": {
"enabled": false
},
"availabilityZones": [
"myzone"
]
}`).Once())
httpmock.RegisterMatcherResponder(
http.MethodPut,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": true
},
"autoscale": false,
"autoscaling": {
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200,
"scaleDownUtilizationThreshold": 0.5
},
"desiredNodes": 0,
"maxNodes": 100,
"minNodes": 0,
"template": {
"metadata": {
"annotations": {},
"finalizers": [],
"labels": {}
},
"spec": {
"taints": [],
"unschedulable": false
}
}
}`)),
httpmock.NewStringResponder(200, `✅ Resource updated successfully`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "edit", "MyMksID-12346", "MyNodePoolId", "--cloud-project", "fakeProjectID", "--attach-floating-ips")
require.CmpNoError(err)
assert.Cmp(cleanWhitespacesHelper(out), `✅ Resource updated successfully`)
}
// Update a Nodepool with attachFloatingIps enabled specifying the flag attachFloatingIps=false
// The spec must be updated from true to false.
func (ms *MockSuite) TestCloudKubeNodepoolEditCmdWithAttachFloatingIpsFalse(assert, require *td.T) {
httpmock.RegisterResponder("GET",
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
httpmock.NewStringResponder(200, `
{
"id": "MyNodePoolId",
"projectId": "fakeProjectID",
"name": "nodepool1",
"flavor": "b3-32",
"status": "READY",
"sizeStatus": "CAPACITY_OK",
"autoscale": false,
"monthlyBilled": false,
"antiAffinity": false,
"desiredNodes": 0,
"minNodes": 0,
"maxNodes": 100,
"currentNodes": 0,
"availableNodes": 0,
"upToDateNodes": 0,
"createdAt": "2025-12-04T15:29:32.487775Z",
"updatedAt": "2025-12-05T15:51:08Z",
"autoscaling": {
"scaleDownUtilizationThreshold": 0.5,
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200
},
"template": {
"metadata": {
"labels": {},
"annotations": {},
"finalizers": []
},
"spec": {
"unschedulable": false,
"taints": []
}
},
"attachFloatingIps": {
"enabled": true
},
"availabilityZones": [
"myzone"
]
}`).Once())
httpmock.RegisterMatcherResponder(
http.MethodPut,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": false
},
"autoscale": false,
"autoscaling": {
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200,
"scaleDownUtilizationThreshold": 0.5
},
"desiredNodes": 0,
"maxNodes": 100,
"minNodes": 0,
"template": {
"metadata": {
"annotations": {},
"finalizers": [],
"labels": {}
},
"spec": {
"taints": [],
"unschedulable": false
}
}
}`)),
httpmock.NewStringResponder(200, `✅ Resource updated successfully`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "edit", "MyMksID-12346", "MyNodePoolId", "--cloud-project", "fakeProjectID", "--attach-floating-ips=false")
require.CmpNoError(err)
assert.Cmp(cleanWhitespacesHelper(out), `✅ Resource updated successfully`)
}
// Update a Nodepool with attachFloatingIps enabled without specify the flag
// The spec must not be updated and must be true.
func (ms *MockSuite) TestCloudKubeNodepoolEditCmdWithoutAttachFloatingIpsTrue(assert, require *td.T) {
httpmock.RegisterResponder("GET",
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
httpmock.NewStringResponder(200, `
{
"id": "MyNodePoolId",
"projectId": "fakeProjectID",
"name": "nodepool1",
"flavor": "b3-32",
"status": "READY",
"sizeStatus": "CAPACITY_OK",
"autoscale": false,
"monthlyBilled": false,
"antiAffinity": false,
"desiredNodes": 0,
"minNodes": 0,
"maxNodes": 100,
"currentNodes": 0,
"availableNodes": 0,
"upToDateNodes": 0,
"createdAt": "2025-12-04T15:29:32.487775Z",
"updatedAt": "2025-12-05T15:51:08Z",
"autoscaling": {
"scaleDownUtilizationThreshold": 0.5,
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200
},
"template": {
"metadata": {
"labels": {},
"annotations": {},
"finalizers": []
},
"spec": {
"unschedulable": false,
"taints": []
}
},
"attachFloatingIps": {
"enabled": true
},
"availabilityZones": [
"myzone"
]
}`).Once())
httpmock.RegisterMatcherResponder(
http.MethodPut,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": true
},
"autoscale": false,
"autoscaling": {
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200,
"scaleDownUtilizationThreshold": 0.5
},
"desiredNodes": 0,
"maxNodes": 100,
"minNodes": 0,
"template": {
"metadata": {
"annotations": {},
"finalizers": [],
"labels": {}
},
"spec": {
"taints": [],
"unschedulable": false
}
}
}`)),
httpmock.NewStringResponder(200, `✅ Resource updated successfully`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "edit", "MyMksID-12346", "MyNodePoolId", "--cloud-project", "fakeProjectID")
require.CmpNoError(err)
assert.Cmp(cleanWhitespacesHelper(out), `✅ Resource updated successfully`)
}
// Update Nodepool with attachFloatingIps disabled without specify the flag
// The spec must not be updated and must be false.
func (ms *MockSuite) TestCloudKubeNodepoolEditCmdWithoutAttachFloatingIpsFalse(assert, require *td.T) {
httpmock.RegisterResponder("GET",
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
httpmock.NewStringResponder(200, `
{
"id": "MyNodePoolId",
"projectId": "fakeProjectID",
"name": "nodepool1",
"flavor": "b3-32",
"status": "READY",
"sizeStatus": "CAPACITY_OK",
"autoscale": false,
"monthlyBilled": false,
"antiAffinity": false,
"desiredNodes": 0,
"minNodes": 0,
"maxNodes": 100,
"currentNodes": 0,
"availableNodes": 0,
"upToDateNodes": 0,
"createdAt": "2025-12-04T15:29:32.487775Z",
"updatedAt": "2025-12-05T15:51:08Z",
"autoscaling": {
"scaleDownUtilizationThreshold": 0.5,
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200
},
"template": {
"metadata": {
"labels": {},
"annotations": {},
"finalizers": []
},
"spec": {
"unschedulable": false,
"taints": []
}
},
"attachFloatingIps": {
"enabled": false
},
"availabilityZones": [
"myzone"
]
}`).Once())
httpmock.RegisterMatcherResponder(
http.MethodPut,
"https://eu.api.ovh.com/v1/cloud/project/fakeProjectID/kube/MyMksID-12346/nodepool/MyNodePoolId",
tdhttpmock.JSONBody(td.JSON(`
{
"attachFloatingIps": {
"enabled": false
},
"autoscale": false,
"autoscaling": {
"scaleDownUnneededTimeSeconds": 600,
"scaleDownUnreadyTimeSeconds": 1200,
"scaleDownUtilizationThreshold": 0.5
},
"desiredNodes": 0,
"maxNodes": 100,
"minNodes": 0,
"template": {
"metadata": {
"annotations": {},
"finalizers": [],
"labels": {}
},
"spec": {
"taints": [],
"unschedulable": false
}
}
}`)),
httpmock.NewStringResponder(200, `✅ Resource updated successfully`).Once())
out, err := cmd.Execute("cloud", "kube", "nodepool", "edit", "MyMksID-12346", "MyNodePoolId", "--cloud-project", "fakeProjectID")
require.CmpNoError(err)
assert.Cmp(cleanWhitespacesHelper(out), `✅ Resource updated successfully`)
}

View file

@ -130,13 +130,16 @@ type kubeNodepoolSpec struct {
ScaleDownUtilizationThreshold float64 `json:"scaleDownUtilizationThreshold,omitempty"`
} `json:"autoscaling,omitzero"`
AvailabilityZones []string `json:"availabilityZones,omitempty"`
DesiredNodes int `json:"desiredNodes,omitempty"`
FlavorName string `json:"flavorName,omitempty"`
MaxNodes int `json:"maxNodes,omitempty"`
MinNodes int `json:"minNodes,omitempty"`
MonthlyBilled bool `json:"monthlyBilled,omitempty"`
Name string `json:"name,omitempty"`
Template struct {
AttachFloatingIps struct {
Enabled *bool `json:"enabled,omitempty"`
} `json:"attachFloatingIps,omitzero"`
DesiredNodes int `json:"desiredNodes,omitempty"`
FlavorName string `json:"flavorName,omitempty"`
MaxNodes int `json:"maxNodes,omitempty"`
MinNodes int `json:"minNodes,omitempty"`
MonthlyBilled bool `json:"monthlyBilled,omitempty"`
Name string `json:"name,omitempty"`
Template struct {
Metadata struct {
Annotations map[string]string `json:"annotations,omitempty"`
Finalizers []string `json:"finalizers,omitempty"`
@ -490,6 +493,11 @@ func EditKubeNodepool(cmd *cobra.Command, args []string) {
return
}
// The --attach-floating-ips flag hasn't been set by the user.
if !cmd.Flags().Changed("attach-floating-ips") {
KubeNodepoolSpec.AttachFloatingIps.Enabled = nil
}
if err := common.EditResource(
cmd,
"/cloud/project/{serviceName}/kube/{kubeId}/nodepool/{nodepoolId}",

View file

@ -15,6 +15,13 @@ _{{index .Result "name"}}_
**Anti Affinity**: {{index .Result "antiAffinity"}}
**Autoscale**: {{index .Result "autoscale"}}
**AttachFloatingIP**: {{ range $k, $v := index .Result "attachFloatingIps" }}
{{- if $k = "enabled" -}}
{{$v}}
{{end}}
{{- end}}
**Autoscaling**:
- Scale Down Unneeded Time (s): {{index .Result "autoscaling" "scaleDownUnneededTimeSeconds"}}
- Scale Down Unready Time (s): {{index .Result "autoscaling" "scaleDownUnreadyTimeSeconds"}}