mirror of
https://github.com/ovh/ovhcloud-cli.git
synced 2026-01-11 19:46:33 +00:00
Add attachFloatinIps spec on nodepool (#107)
* feat : add attachFloatinIps
This commit is contained in:
parent
bef8a80c37
commit
f71e5d4e28
8 changed files with 4720 additions and 844 deletions
|
|
@ -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.)
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
579
internal/cmd/cloud_kube_nodepool_test.go
Normal file
579
internal/cmd/cloud_kube_nodepool_test.go
Normal 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`)
|
||||
}
|
||||
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -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"}}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue