Repository: digitalocean/doctl
Pull request #1648 (2025-01-27 20:16:57)

LBAAS-3620: support load balancers network_stack
support load balancers network_stack


Patch:
diff --git a/args.go b/args.go
index 3eb6a684..00113ddb 100644
--- a/args.go
+++ b/args.go
@@ -385,6 +385,8 @@ const (
 	ArgTargetLoadBalancerIDs = "target-lb-ids"
 	// ArgLoadBalancerNetwork is the type of network the load balancer is accessible from.
 	ArgLoadBalancerNetwork = "network"
+	// ArgLoadBalancerNetworkStack is the network stack type the load balancer will be configured with (e.g IPv4, Dual Stack: IPv4 and IPv6).
+	ArgLoadBalancerNetworkStack = "network_stack"
 
 	// ArgFirewallName is a name of the firewall.
 	ArgFirewallName = "name"
diff --git a/commands/load_balancers.go b/commands/load_balancers.go
index ce562fbf..0bc13376 100644
--- a/commands/load_balancers.go
+++ b/commands/load_balancers.go
@@ -101,6 +101,8 @@ With the load-balancer command, you can list, create, or delete load balancers,
 	AddStringSliceFlag(cmdLoadBalancerCreate, doctl.ArgTargetLoadBalancerIDs, "", []string{},
 		"A comma-separated list of Load Balancer IDs to add as target to the global load balancer ")
 	AddStringFlag(cmdLoadBalancerCreate, doctl.ArgLoadBalancerNetwork, "", "", "The type of network the load balancer is accessible from, e.g.: `EXTERNAL` or `INTERNAL`")
+	AddStringFlag(cmdLoadBalancerCreate, doctl.ArgLoadBalancerNetworkStack, "", "", "The network stack type determines the allocation of ipv4/ipv6 addresses to the load balancer, e.g.: `IPV4` or `DUALSTACK`"+
+		" (NOTE: this feature is in private preview, contact DigitalOcean support to review its public availability.)")
 
 	cmdRecordUpdate := CmdBuilder(cmd, RunLoadBalancerUpdate, "update <load-balancer-id>",
 		"Update a load balancer's configuration", `Use this command to update the configuration of a specified load balancer. Using all applicable flags, the command should contain a full representation of the load balancer including existing attributes, such as the load balancer's name, region, forwarding rules, and Droplet IDs. Any attribute that is not provided is reset to its default value.`, Writer, aliasOpt("u"))
@@ -702,6 +704,12 @@ func buildRequestFromArgs(c *CmdConfig, r *godo.LoadBalancerRequest) error {
 	}
 	r.Network = strings.ToUpper(network)
 
+	networkStack, err := c.Doit.GetString(c.NS, doctl.ArgLoadBalancerNetworkStack)
+	if err != nil {
+		return err
+	}
+	r.NetworkStack = strings.ToUpper(networkStack)
+
 	return nil
 }
 
diff --git a/go.mod b/go.mod
index fef8d32f..bd597bb0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.22
 require (
 	github.com/blang/semver v3.5.1+incompatible
 	github.com/creack/pty v1.1.21
-	github.com/digitalocean/godo v1.134.0
+	github.com/digitalocean/godo v1.135.0
 	github.com/docker/cli v24.0.5+incompatible
 	github.com/docker/docker v25.0.6+incompatible
 	github.com/docker/docker-credential-helpers v0.7.0 // indirect
diff --git a/go.sum b/go.sum
index 5bde7cf4..98415bb4 100644
--- a/go.sum
+++ b/go.sum
@@ -93,6 +93,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/digitalocean/godo v1.134.0 h1:dT7aQR9jxNOQEZwzP+tAYcxlj5szFZScC33+PAYGQVM=
 github.com/digitalocean/godo v1.134.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
+github.com/digitalocean/godo v1.135.0 h1:H/A2wFBLo3hUDAw0xjHayHaNxY2hRvM3xxa+lTpriT8=
+github.com/digitalocean/godo v1.135.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
 github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
 github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
 github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
diff --git a/vendor/github.com/digitalocean/godo/CHANGELOG.md b/vendor/github.com/digitalocean/godo/CHANGELOG.md
index 091f2ec8..ce1110c8 100644
--- a/vendor/github.com/digitalocean/godo/CHANGELOG.md
+++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Change Log
 
+##  [v1.135.0] - 2025-01-27
+- #766 - @dhij - kubernetes: add cluster autoscaler config
+- #775 - @jvasilevsky - LBASA-3620: add network_stack field to load balancers model
+- #773 - @blesswinsamuel - Add field to customize the offline page during app maintenance
+
 ##  [v1.134.0] - 2025-01-15
 - #771 - @d-honeybadger - add ID field to KubernetesClusterUser response
 - #768 - @lee-aaron - support Spaces Keys API
diff --git a/vendor/github.com/digitalocean/godo/apps.gen.go b/vendor/github.com/digitalocean/godo/apps.gen.go
index 63457cda..7b9f057d 100644
--- a/vendor/github.com/digitalocean/godo/apps.gen.go
+++ b/vendor/github.com/digitalocean/godo/apps.gen.go
@@ -470,6 +470,8 @@ type AppMaintenanceSpec struct {
 	Enabled bool `json:"enabled,omitempty"`
 	// Indicates whether the app should be archived. Setting this to true implies that enabled is set to true. Note that this feature is currently in closed beta.
 	Archive bool `json:"archive,omitempty"`
+	// A custom offline page to display when maintenance mode is enabled or the app is archived.
+	OfflinePageURL string `json:"offline_page_url,omitempty"`
 }
 
 // AppRouteSpec struct for AppRouteSpec
diff --git a/vendor/github.com/digitalocean/godo/apps_accessors.go b/vendor/github.com/digitalocean/godo/apps_accessors.go
index 4d9a214a..9a5bf601 100644
--- a/vendor/github.com/digitalocean/godo/apps_accessors.go
+++ b/vendor/github.com/digitalocean/godo/apps_accessors.go
@@ -1453,6 +1453,14 @@ func (a *AppMaintenanceSpec) GetEnabled() bool {
 	return a.Enabled
 }
 
+// GetOfflinePageURL returns the OfflinePageURL field.
+func (a *AppMaintenanceSpec) GetOfflinePageURL() string {
+	if a == nil {
+		return ""
+	}
+	return a.OfflinePageURL
+}
+
 // GetAppID returns the AppID field.
 func (a *AppProposeRequest) GetAppID() string {
 	if a == nil {
diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go
index f3bdca9b..de918ba2 100644
--- a/vendor/github.com/digitalocean/godo/godo.go
+++ b/vendor/github.com/digitalocean/godo/godo.go
@@ -21,7 +21,7 @@ import (
 )
 
 const (
-	libraryVersion = "1.134.0"
+	libraryVersion = "1.135.0"
 	defaultBaseURL = "https://api.digitalocean.com/"
 	userAgent      = "godo/" + libraryVersion
 	mediaType      = "application/json"
diff --git a/vendor/github.com/digitalocean/godo/kubernetes.go b/vendor/github.com/digitalocean/godo/kubernetes.go
index 8a4085a0..8353e78c 100644
--- a/vendor/github.com/digitalocean/godo/kubernetes.go
+++ b/vendor/github.com/digitalocean/godo/kubernetes.go
@@ -78,20 +78,22 @@ type KubernetesClusterCreateRequest struct {
 
 	NodePools []*KubernetesNodePoolCreateRequest `json:"node_pools,omitempty"`
 
-	MaintenancePolicy    *KubernetesMaintenancePolicy    `json:"maintenance_policy"`
-	AutoUpgrade          bool                            `json:"auto_upgrade"`
-	SurgeUpgrade         bool                            `json:"surge_upgrade"`
-	ControlPlaneFirewall *KubernetesControlPlaneFirewall `json:"control_plane_firewall,omitempty"`
+	MaintenancePolicy              *KubernetesMaintenancePolicy              `json:"maintenance_policy"`
+	AutoUpgrade                    bool                                      `json:"auto_upgrade"`
+	SurgeUpgrade                   bool                                      `json:"surge_upgrade"`
+	ControlPlaneFirewall           *KubernetesControlPlaneFirewall           `json:"control_plane_firewall,omitempty"`
+	ClusterAutoscalerConfiguration *KubernetesClusterAutoscalerConfiguration `json:"cluster_autoscaler_configuration,omitempty"`
 }
 
 // KubernetesClusterUpdateRequest represents a request to update a Kubernetes cluster.
 type KubernetesClusterUpdateRequest struct {
-	Name                 string                          `json:"name,omitempty"`
-	Tags                 []string                        `json:"tags,omitempty"`
-	MaintenancePolicy    *KubernetesMaintenancePolicy    `json:"maintenance_policy,omitempty"`
-	AutoUpgrade          *bool                           `json:"auto_upgrade,omitempty"`
-	SurgeUpgrade         bool                            `json:"surge_upgrade,omitempty"`
-	ControlPlaneFirewall *KubernetesControlPlaneFirewall `json:"control_plane_firewall,omitempty"`
+	Name                           string                                    `json:"name,omitempty"`
+	Tags                           []string                                  `json:"tags,omitempty"`
+	MaintenancePolicy              *KubernetesMaintenancePolicy              `json:"maintenance_policy,omitempty"`
+	AutoUpgrade                    *bool                                     `json:"auto_upgrade,omitempty"`
+	SurgeUpgrade                   bool                                      `json:"surge_upgrade,omitempty"`
+	ControlPlaneFirewall           *KubernetesControlPlaneFirewall           `json:"control_plane_firewall,omitempty"`
+	ClusterAutoscalerConfiguration *KubernetesClusterAutoscalerConfiguration `json:"cluster_autoscaler_configuration,omitempty"`
 
 	// Convert cluster to run highly available control plane
 	HA *bool `json:"ha,omitempty"`
@@ -205,11 +207,12 @@ type KubernetesCluster struct {
 
 	NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
 
-	MaintenancePolicy    *KubernetesMaintenancePolicy    `json:"maintenance_policy,omitempty"`
-	AutoUpgrade          bool                            `json:"auto_upgrade,omitempty"`
-	SurgeUpgrade         bool                            `json:"surge_upgrade,omitempty"`
-	RegistryEnabled      bool                            `json:"registry_enabled,omitempty"`
-	ControlPlaneFirewall *KubernetesControlPlaneFirewall `json:"control_plane_firewall,omitempty"`
+	MaintenancePolicy              *KubernetesMaintenancePolicy              `json:"maintenance_policy,omitempty"`
+	AutoUpgrade                    bool                                      `json:"auto_upgrade,omitempty"`
+	SurgeUpgrade                   bool                                      `json:"surge_upgrade,omitempty"`
+	RegistryEnabled                bool                                      `json:"registry_enabled,omitempty"`
+	ControlPlaneFirewall           *KubernetesControlPlaneFirewall           `json:"control_plane_firewall,omitempty"`
+	ClusterAutoscalerConfiguration *KubernetesClusterAutoscalerConfiguration `json:"cluster_autoscaler_configuration,omitempty"`
 
 	Status    *KubernetesClusterStatus `json:"status,omitempty"`
 	CreatedAt time.Time                `json:"created_at,omitempty"`
@@ -252,6 +255,12 @@ type KubernetesControlPlaneFirewall struct {
 	AllowedAddresses []string `json:"allowed_addresses"`
 }
 
+// KubernetesClusterAutoscalerConfiguration represents Kubernetes cluster autoscaler configuration.
+type KubernetesClusterAutoscalerConfiguration struct {
+	ScaleDownUtilizationThreshold *float64 `json:"scale_down_utilization_threshold"`
+	ScaleDownUnneededTime         *string  `json:"scale_down_unneeded_time"`
+}
+
 // KubernetesMaintenancePolicyDay represents the possible days of a maintenance
 // window
 type KubernetesMaintenancePolicyDay int
diff --git a/vendor/github.com/digitalocean/godo/load_balancers.go b/vendor/github.com/digitalocean/godo/load_balancers.go
index a12729dd..54bec0c4 100644
--- a/vendor/github.com/digitalocean/godo/load_balancers.go
+++ b/vendor/github.com/digitalocean/godo/load_balancers.go
@@ -22,6 +22,10 @@ const (
 	// Load Balancer network types
 	LoadBalancerNetworkTypeExternal = "EXTERNAL"
 	LoadBalancerNetworkTypeInternal = "INTERNAL"
+
+	// Load Balancer network_stack types
+	LoadBalancerNetworkStackIPv4      = "IPV4"
+	LoadBalancerNetworkStackDualstack = "DUALSTACK"
 )
 
 // LoadBalancersService is an interface for managing load balancers with the DigitalOcean API.
@@ -74,6 +78,7 @@ type LoadBalancer struct {
 	GLBSettings                  *GLBSettings     `json:"glb_settings,omitempty"`
 	TargetLoadBalancerIDs        []string         `json:"target_load_balancer_ids,omitempty"`
 	Network                      string           `json:"network,omitempty"`
+	NetworkStack                 string           `json:"network_stack,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancer.
@@ -108,6 +113,7 @@ func (l LoadBalancer) AsRequest() *LoadBalancerRequest {
 		HTTPIdleTimeoutSeconds:       l.HTTPIdleTimeoutSeconds,
 		TargetLoadBalancerIDs:        append([]string(nil), l.TargetLoadBalancerIDs...),
 		Network:                      l.Network,
+		NetworkStack:                 l.NetworkStack,
 	}
 
 	if l.DisableLetsEncryptDNSRecords != nil {
@@ -247,6 +253,7 @@ type LoadBalancerRequest struct {
 	GLBSettings                  *GLBSettings     `json:"glb_settings,omitempty"`
 	TargetLoadBalancerIDs        []string         `json:"target_load_balancer_ids,omitempty"`
 	Network                      string           `json:"network,omitempty"`
+	NetworkStack                 string           `json:"network_stack,omitempty"`
 }
 
 // String creates a human-readable description of a LoadBalancerRequest.
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 244cae52..447dcc02 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -61,7 +61,7 @@ github.com/creack/pty
 # github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
 ## explicit
 github.com/davecgh/go-spew/spew
-# github.com/digitalocean/godo v1.134.0
+# github.com/digitalocean/godo v1.135.0
 ## explicit; go 1.22
 github.com/digitalocean/godo
 github.com/digitalocean/godo/metrics


Test patch:
diff --git a/commands/load_balancers_test.go b/commands/load_balancers_test.go
index dd74405a..e55a3d39 100644
--- a/commands/load_balancers_test.go
+++ b/commands/load_balancers_test.go
@@ -116,7 +116,8 @@ func TestLoadBalancerCreate(t *testing.T) {
 				Deny:  []string{"cidr:1.2.0.0/16"},
 				Allow: []string{"ip:1.2.3.4", "ip:1.2.3.5"},
 			},
-			Network: "EXTERNAL",
+			Network:      "EXTERNAL",
+			NetworkStack: "IPV4",
 		}
 		disableLetsEncryptDNSRecords := true
 		r.DisableLetsEncryptDNSRecords = &disableLetsEncryptDNSRecords
@@ -137,6 +138,7 @@ func TestLoadBalancerCreate(t *testing.T) {
 		config.Doit.Set(config.NS, doctl.ArgDenyList, []string{"cidr:1.2.0.0/16"})
 		config.Doit.Set(config.NS, doctl.ArgAllowList, []string{"ip:1.2.3.4", "ip:1.2.3.5"})
 		config.Doit.Set(config.NS, doctl.ArgLoadBalancerNetwork, "EXTERNAL")
+		config.Doit.Set(config.NS, doctl.ArgLoadBalancerNetworkStack, "IPV4")
 
 		err := RunLoadBalancerCreate(config)
 		assert.NoError(t, err)
@@ -184,7 +186,8 @@ func TestLoadBalancerCreateGLB(t *testing.T) {
 				"019cb059-603f-4828-8be4-641a20f25006",
 				"023da268-bc81-468f-aa4d-9abdc4f69935",
 			},
-			Network: "EXTERNAL",
+			Network:      "EXTERNAL",
+			NetworkStack: "IPV4",
 		}
 		disableLetsEncryptDNSRecords := true
 		r.DisableLetsEncryptDNSRecords = &disableLetsEncryptDNSRecords
@@ -207,6 +210,7 @@ func TestLoadBalancerCreateGLB(t *testing.T) {
 			"023da268-bc81-468f-aa4d-9abdc4f69935",
 		})
 		config.Doit.Set(config.NS, doctl.ArgLoadBalancerNetwork, "EXTERNAL")
+		config.Doit.Set(config.NS, doctl.ArgLoadBalancerNetworkStack, "IPV4")
 
 		err := RunLoadBalancerCreate(config)
 		assert.NoError(t, err)