Repository: digitalocean/doctl
Pull request #1586 (2024-09-25 20:55:45)

doks: add custom subnets flags
This allows setting custom subnets on DOKS clusters and create vpc-native clusters.


Patch:
diff --git a/args.go b/args.go
index ab5c34b5..fee16266 100644
--- a/args.go
+++ b/args.go
@@ -66,6 +66,10 @@ const (
 	ArgVPCUUID = "vpc-uuid"
 	// ArgClusterVPCUUID is a cluster vpc-uuid argument.
 	ArgClusterVPCUUID = "vpc-uuid"
+	// ArgClusterSubnet is a cluster pod CIDR argument.
+	ArgClusterSubnet = "cluster-subnet"
+	// ArgServiceSubnet is a cluster service CIDR argument.
+	ArgServiceSubnet = "service-subnet"
 	// ArgClusterNodePool are a cluster's node pools arguments.
 	ArgClusterNodePool = "node-pool"
 	// ArgClusterUpdateKubeconfig updates the local kubeconfig.
diff --git a/commands/kubernetes.go b/commands/kubernetes.go
index 6b067fea..975b59ae 100644
--- a/commands/kubernetes.go
+++ b/commands/kubernetes.go
@@ -277,6 +277,10 @@ After creating a cluster, a configuration context is added to kubectl and made a
 		"A `slug` indicating which Kubernetes version to use when creating the cluster. Use the `doctl kubernetes options versions` command for a list of options")
 	AddStringFlag(cmdKubeClusterCreate, doctl.ArgClusterVPCUUID, "", "",
 		"The UUID of a VPC network to create the cluster in. Must be the UUID of a valid VPC in the same region specified for the cluster. If a VPC is not specified, the cluster is placed in the default VPC network for the region.")
+	AddStringFlag(cmdKubeClusterCreate, doctl.ArgClusterSubnet, "", "",
+		"The CIDR block to use for the pod network. Must be a valid CIDR block. Defaults to `10.244.0.0/16`. If left empty/default the cluster will be created with a virtual network. If a custom one is provided, the cluster will be created as vpc-native cluster. VPC-native CIDR blocks cannot overlap within an account.")
+	AddStringFlag(cmdKubeClusterCreate, doctl.ArgServiceSubnet, "", "",
+		"The CIDR block to use for the service network. Must be a valid CIDR block. Defaults to `10.245.0.0/16`. If left empty/default the cluster will be created with a virtual network. If a custom one is provided, the cluster will be created as vpc-native cluster. VPC-native CIDR blocks cannot overlap within an account.")
 	AddBoolFlag(cmdKubeClusterCreate, doctl.ArgAutoUpgrade, "", false,
 		"Enables automatic upgrades to new patch releases during the cluster's maintenance window. Defaults to `false`. To enable automatic upgrade, supply `--auto-upgrade=true`.")
 	AddBoolFlag(cmdKubeClusterCreate, doctl.ArgSurgeUpgrade, "", true,
@@ -1633,6 +1637,22 @@ func buildClusterCreateRequestFromArgs(c *CmdConfig, r *godo.KubernetesClusterCr
 	// empty "" is fine, the default region VPC will be resolved
 	r.VPCUUID = vpcUUID
 
+	podCIDR, err := c.Doit.GetString(c.NS, doctl.ArgClusterSubnet)
+	if err != nil {
+		return err
+	}
+	r.ClusterSubnet = podCIDR
+	svcCIDR, err := c.Doit.GetString(c.NS, doctl.ArgServiceSubnet)
+	if err != nil {
+		return err
+	}
+	r.ServiceSubnet = svcCIDR
+	// empty "" is fine, the default is still to use a virtual network and not be vpc-native.
+	// either both have to be set (vpc-native) or none (virtual network)
+	if c.Doit.IsSet(doctl.ArgClusterSubnet) != c.Doit.IsSet(doctl.ArgServiceSubnet) {
+		return fmt.Errorf("flags %q and %q both have to be set for vpc-native clusters or both unset for virtual network clusters", doctl.ArgClusterSubnet, doctl.ArgServiceSubnet)
+	}
+
 	version, err := getVersionOrLatest(c)
 	if err != nil {
 		return err
diff --git a/go.mod b/go.mod
index caae3c46..59323d6a 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.125.0
+	github.com/digitalocean/godo v1.126.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 1d888d85..1b4b4872 100644
--- a/go.sum
+++ b/go.sum
@@ -93,6 +93,10 @@ 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.125.0 h1:wGPBQRX9Wjo0qCF0o8d25mT3A84Iw8rfHnZOPyvHcMQ=
 github.com/digitalocean/godo v1.125.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
+github.com/digitalocean/godo v1.125.1-0.20240925184037-40ea734536f0 h1:hEi5W+TPrYUjq1PLt1lJmhrt+ezpzUrAvwYr9f1Xo4U=
+github.com/digitalocean/godo v1.125.1-0.20240925184037-40ea734536f0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
+github.com/digitalocean/godo v1.126.0 h1:+Znh7VMQj/E8ArbjWnc7OKGjWfzC+I8OCSRp7r1MdD8=
+github.com/digitalocean/godo v1.126.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 b045687a..37127216 100644
--- a/vendor/github.com/digitalocean/godo/CHANGELOG.md
+++ b/vendor/github.com/digitalocean/godo/CHANGELOG.md
@@ -1,5 +1,10 @@
 # Change Log
 
+## [v1.126.0] - 2024-09-25
+
+- #732 - @gottwald - DOKS: add custom CIDR fields
+- #727 - @loosla - [databases]: add support for Kafka advanced configuration
+
 ## [v1.125.0] - 2024-09-17
 
 - #726 - @loosla - [databases]: add support for MongoDB advanced configuration
diff --git a/vendor/github.com/digitalocean/godo/databases.go b/vendor/github.com/digitalocean/godo/databases.go
index c0c91e2b..e168186f 100644
--- a/vendor/github.com/digitalocean/godo/databases.go
+++ b/vendor/github.com/digitalocean/godo/databases.go
@@ -3,6 +3,7 @@ package godo
 import (
 	"context"
 	"fmt"
+	"math/big"
 	"net/http"
 	"strings"
 	"time"
@@ -153,10 +154,12 @@ type DatabasesService interface {
 	GetRedisConfig(context.Context, string) (*RedisConfig, *Response, error)
 	GetMySQLConfig(context.Context, string) (*MySQLConfig, *Response, error)
 	GetMongoDBConfig(context.Context, string) (*MongoDBConfig, *Response, error)
+	GetKafkaConfig(context.Context, string) (*KafkaConfig, *Response, error)
 	UpdatePostgreSQLConfig(context.Context, string, *PostgreSQLConfig) (*Response, error)
 	UpdateRedisConfig(context.Context, string, *RedisConfig) (*Response, error)
 	UpdateMySQLConfig(context.Context, string, *MySQLConfig) (*Response, error)
 	UpdateMongoDBConfig(context.Context, string, *MongoDBConfig) (*Response, error)
+	UpdateKafkaConfig(context.Context, string, *KafkaConfig) (*Response, error)
 	ListOptions(todo context.Context) (*DatabaseOptions, *Response, error)
 	UpgradeMajorVersion(context.Context, string, *UpgradeVersionRequest) (*Response, error)
 	ListTopics(context.Context, string, *ListOptions) ([]DatabaseTopic, *Response, error)
@@ -659,6 +662,27 @@ type MongoDBConfig struct {
 	Verbosity                       *int    `json:"verbosity,omitempty"`
 }
 
+// KafkaConfig holds advanced configurations for Kafka database clusters.
+type KafkaConfig struct {
+	GroupInitialRebalanceDelayMs       *int     `json:"group_initial_rebalance_delay_ms,omitempty"`
+	GroupMinSessionTimeoutMs           *int     `json:"group_min_session_timeout_ms,omitempty"`
+	GroupMaxSessionTimeoutMs           *int     `json:"group_max_session_timeout_ms,omitempty"`
+	MessageMaxBytes                    *int     `json:"message_max_bytes,omitempty"`
+	LogCleanerDeleteRetentionMs        *int64   `json:"log_cleaner_delete_retention_ms,omitempty"`
+	LogCleanerMinCompactionLagMs       *uint64  `json:"log_cleaner_min_compaction_lag_ms,omitempty"`
+	LogFlushIntervalMs                 *uint64  `json:"log_flush_interval_ms,omitempty"`
+	LogIndexIntervalBytes              *int     `json:"log_index_interval_bytes,omitempty"`
+	LogMessageDownconversionEnable     *bool    `json:"log_message_downconversion_enable,omitempty"`
+	LogMessageTimestampDifferenceMaxMs *uint64  `json:"log_message_timestamp_difference_max_ms,omitempty"`
+	LogPreallocate                     *bool    `json:"log_preallocate,omitempty"`
+	LogRetentionBytes                  *big.Int `json:"log_retention_bytes,omitempty"`
+	LogRetentionHours                  *int     `json:"log_retention_hours,omitempty"`
+	LogRetentionMs                     *big.Int `json:"log_retention_ms,omitempty"`
+	LogRollJitterMs                    *uint64  `json:"log_roll_jitter_ms,omitempty"`
+	LogSegmentDeleteDelayMs            *int     `json:"log_segment_delete_delay_ms,omitempty"`
+	AutoCreateTopicsEnable             *bool    `json:"auto_create_topics_enable,omitempty"`
+}
+
 type databaseUserRoot struct {
 	User *DatabaseUser `json:"user"`
 }
@@ -703,6 +727,10 @@ type databaseMongoDBConfigRoot struct {
 	Config *MongoDBConfig `json:"config"`
 }
 
+type databaseKafkaConfigRoot struct {
+	Config *KafkaConfig `json:"config"`
+}
+
 type databaseBackupsRoot struct {
 	Backups []DatabaseBackup `json:"backups"`
 }
@@ -1546,6 +1574,38 @@ func (svc *DatabasesServiceOp) UpdateMongoDBConfig(ctx context.Context, database
 	return resp, nil
 }
 
+// GetKafkaConfig retrieves the config for a Kafka database cluster.
+func (svc *DatabasesServiceOp) GetKafkaConfig(ctx context.Context, databaseID string) (*KafkaConfig, *Response, error) {
+	path := fmt.Sprintf(databaseConfigPath, databaseID)
+	req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+	root := new(databaseKafkaConfigRoot)
+	resp, err := svc.client.Do(ctx, req, root)
+	if err != nil {
+		return nil, resp, err
+	}
+	return root.Config, resp, nil
+}
+
+// UpdateKafkaConfig updates the config for a Kafka database cluster.
+func (svc *DatabasesServiceOp) UpdateKafkaConfig(ctx context.Context, databaseID string, config *KafkaConfig) (*Response, error) {
+	path := fmt.Sprintf(databaseConfigPath, databaseID)
+	root := &databaseKafkaConfigRoot{
+		Config: config,
+	}
+	req, err := svc.client.NewRequest(ctx, http.MethodPatch, path, root)
+	if err != nil {
+		return nil, err
+	}
+	resp, err := svc.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+	return resp, nil
+}
+
 // ListOptions gets the database options available.
 func (svc *DatabasesServiceOp) ListOptions(ctx context.Context) (*DatabaseOptions, *Response, error) {
 	root := new(databaseOptionsRoot)
diff --git a/vendor/github.com/digitalocean/godo/godo.go b/vendor/github.com/digitalocean/godo/godo.go
index 45c0f115..3702ac1f 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.125.0"
+	libraryVersion = "1.126.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 8ef9d241..9b3bcfa1 100644
--- a/vendor/github.com/digitalocean/godo/kubernetes.go
+++ b/vendor/github.com/digitalocean/godo/kubernetes.go
@@ -65,11 +65,13 @@ type KubernetesServiceOp struct {
 
 // KubernetesClusterCreateRequest represents a request to create a Kubernetes cluster.
 type KubernetesClusterCreateRequest struct {
-	Name        string   `json:"name,omitempty"`
-	RegionSlug  string   `json:"region,omitempty"`
-	VersionSlug string   `json:"version,omitempty"`
-	Tags        []string `json:"tags,omitempty"`
-	VPCUUID     string   `json:"vpc_uuid,omitempty"`
+	Name          string   `json:"name,omitempty"`
+	RegionSlug    string   `json:"region,omitempty"`
+	VersionSlug   string   `json:"version,omitempty"`
+	Tags          []string `json:"tags,omitempty"`
+	VPCUUID       string   `json:"vpc_uuid,omitempty"`
+	ClusterSubnet string   `json:"cluster_subnet,omitempty"`
+	ServiceSubnet string   `json:"service_subnet,omitempty"`
 
 	// Create cluster with highly available control plane
 	HA bool `json:"ha"`
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 09e25066..05131883 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.125.0
+# github.com/digitalocean/godo v1.126.0
 ## explicit; go 1.22
 github.com/digitalocean/godo
 github.com/digitalocean/godo/metrics


Test patch:
diff --git a/commands/kubernetes_test.go b/commands/kubernetes_test.go
index d20b4cf0..4c3d3528 100644
--- a/commands/kubernetes_test.go
+++ b/commands/kubernetes_test.go
@@ -547,6 +547,19 @@ func TestKubernetesCreate(t *testing.T) {
 		err = testK8sCmdService().RunKubernetesClusterCreate("c-8", 3)(config)
 		assert.NoError(t, err)
 
+		// Test vpc-native
+		const podNetwork = "10.100.0.0/16"
+		const serviceNetwork = "10.101.0.0/16"
+		config.Doit.Set(config.NS, doctl.ArgClusterSubnet, podNetwork)
+		config.Doit.Set(config.NS, doctl.ArgServiceSubnet, serviceNetwork)
+		r.ClusterSubnet = podNetwork
+		r.ServiceSubnet = serviceNetwork
+		testCluster.ClusterSubnet = podNetwork
+		testCluster.ServiceSubnet = serviceNetwork
+		tm.kubernetes.EXPECT().Create(&r).Return(&testCluster, nil)
+		err = testK8sCmdService().RunKubernetesClusterCreate("c-8", 3)(config)
+		assert.NoError(t, err)
+
 		// Test with 1-clicks specified
 		config.Doit.Set(config.NS, doctl.ArgOneClicks, []string{"slug1", "slug2"})
 		tm.kubernetes.EXPECT().Create(&r).Return(&testCluster, nil)