Repository: digitalocean/doctl
Pull request #1454 (2023-10-26 23:00:00)

Support scalable storage for PostgreSQL/MySQL databases
**What**

- Adds the ability to provide `--storage-size-mib` when doing CreateCluster or ResizeCluster operations on a database
- Includes the StorageSizeMib as a column in the databases display output


Patch:
diff --git a/args.go b/args.go
index 6e2499ea..d2ee8242 100644
--- a/args.go
+++ b/args.go
@@ -368,6 +368,8 @@ const (
 	ArgDatabaseEngine = "engine"
 	// ArgDatabaseNumNodes is the number of nodes in the database cluster
 	ArgDatabaseNumNodes = "num-nodes"
+	// ArgDatabaseStorageSizeMib is the amount of disk space, in MiB, that should be allocated to the database cluster
+	ArgDatabaseStorageSizeMib = "storage-size-mib"
 	// ArgDatabaseMaintenanceDay is the new day for the maintenance window
 	ArgDatabaseMaintenanceDay = "day"
 	// ArgDatabaseMaintenanceHour is the new hour for the maintenance window
diff --git a/commands/databases.go b/commands/databases.go
index 13c58290..8f0a7656 100644
--- a/commands/databases.go
+++ b/commands/databases.go
@@ -69,6 +69,7 @@ func Databases() *Command {
 
 	nodeSizeDetails := "The size of the nodes in the database cluster, e.g. `db-s-1vcpu-1gb`` for a 1 CPU, 1GB node. For a list of available size slugs, visit: https://docs.digitalocean.com/reference/api/api-reference/#tag/Databases"
 	nodeNumberDetails := "The number of nodes in the database cluster. Valid values are 1-3. In addition to the primary node, up to two standby nodes may be added for high availability."
+	storageSizeMiBDetails := "The amount of disk space allocated to the cluster. Applicable for PostgreSQL and MySQL clusters. This will be set to a default level based on the plan size selected, but can be increased in increments up to a maximum amount. For ranges, visit: https://www.digitalocean.com/pricing/managed-databases"
 	cmdDatabaseCreate := CmdBuilder(cmd, RunDatabaseCreate, "create <name>", "Create a database cluster", `This command creates a database cluster with the specified name.
 
 There are a number of flags that customize the configuration, all of which are optional. Without any flags set, a single-node, single-CPU PostgreSQL database cluster will be created.`, Writer,
@@ -76,6 +77,7 @@ There are a number of flags that customize the configuration, all of which are o
 	AddIntFlag(cmdDatabaseCreate, doctl.ArgDatabaseNumNodes, "", defaultDatabaseNodeCount, nodeNumberDetails)
 	AddStringFlag(cmdDatabaseCreate, doctl.ArgRegionSlug, "", defaultDatabaseRegion, "The region where the database cluster will be created, e.g. `nyc1` or `sfo2`")
 	AddStringFlag(cmdDatabaseCreate, doctl.ArgSizeSlug, "", defaultDatabaseNodeSize, nodeSizeDetails)
+	AddIntFlag(cmdDatabaseCreate, doctl.ArgDatabaseStorageSizeMib, "", 0, storageSizeMiBDetails)
 	AddStringFlag(cmdDatabaseCreate, doctl.ArgDatabaseEngine, "", defaultDatabaseEngine, "The database engine to be used for the cluster. Possible values are: `pg` for PostgreSQL, `mysql`, `redis`, `mongodb`, and `kafka`.")
 	AddStringFlag(cmdDatabaseCreate, doctl.ArgVersion, "", "", "The database engine version, e.g. 14 for PostgreSQL version 14")
 	AddStringFlag(cmdDatabaseCreate, doctl.ArgPrivateNetworkUUID, "", "", "The UUID of a VPC to create the database cluster in; the default VPC for the region will be used if excluded")
@@ -115,10 +117,15 @@ You must specify the desired number of nodes and size of the nodes. For example:
 
 	doctl databases resize ca9f591d-9999-5555-a0ef-1c02d1d1e352 --num-nodes 2 --size db-s-16vcpu-64gb
 
-Database nodes cannot be resized to smaller sizes due to the risk of data loss.`, Writer,
+Database nodes cannot be resized to smaller sizes due to the risk of data loss.
+
+In addition, for PostgreSQL and MySQL clusters, you can provide a disk size in MiB, which will set the total storage (up to a certain range) to the cluster independently. Storage cannot be reduced from its current levels. For example:
+
+	doctl databases resize ca9f591d-9999-5555-a0ef-1c02d1d1e352 --num-nodes 2 --size db-s-16vcpu-64gb --storage-size-mib 2048000`, Writer,
 		aliasOpt("rs"))
 	AddIntFlag(cmdDatabaseResize, doctl.ArgDatabaseNumNodes, "", 0, nodeNumberDetails, requiredOpt())
 	AddStringFlag(cmdDatabaseResize, doctl.ArgSizeSlug, "", "", nodeSizeDetails, requiredOpt())
+	AddIntFlag(cmdDatabaseResize, doctl.ArgDatabaseStorageSizeMib, "", 0, storageSizeMiBDetails)
 
 	cmdDatabaseMigrate := CmdBuilder(cmd, RunDatabaseMigrate, "migrate <database-id>", "Migrate a database cluster to a new region", `This command migrates the specified database cluster to a new region`, Writer,
 		aliasOpt("m"))
@@ -292,6 +299,12 @@ func buildDatabaseCreateRequestFromArgs(c *CmdConfig) (*godo.DatabaseCreateReque
 
 	r.PrivateNetworkUUID = privateNetworkUUID
 
+	storageSizeMibInt, err := c.Doit.GetInt(c.NS, doctl.ArgDatabaseStorageSizeMib)
+	if err != nil {
+		return nil, err
+	}
+	r.StorageSizeMib = uint64(storageSizeMibInt)
+
 	return r, nil
 }
 
@@ -494,6 +507,12 @@ func buildDatabaseResizeRequestFromArgs(c *CmdConfig) (*godo.DatabaseResizeReque
 	}
 	r.SizeSlug = size
 
+	storageSizeMibInt, err := c.Doit.GetInt(c.NS, doctl.ArgDatabaseStorageSizeMib)
+	if err != nil {
+		return nil, err
+	}
+	r.StorageSizeMib = uint64(storageSizeMibInt)
+
 	return r, nil
 }
 
diff --git a/commands/displayers/database.go b/commands/displayers/database.go
index 157c8747..0653d2c4 100644
--- a/commands/displayers/database.go
+++ b/commands/displayers/database.go
@@ -45,6 +45,7 @@ func (d *Databases) Cols() []string {
 			"Region",
 			"Status",
 			"Size",
+			"StorageMib",
 		}
 	}
 
@@ -59,34 +60,37 @@ func (d *Databases) Cols() []string {
 		"Size",
 		"URI",
 		"Created",
+		"StorageMib",
 	}
 }
 
 func (d *Databases) ColMap() map[string]string {
 	if d.Short {
 		return map[string]string{
-			"ID":       "ID",
-			"Name":     "Name",
-			"Engine":   "Engine",
-			"Version":  "Version",
-			"NumNodes": "Number of Nodes",
-			"Region":   "Region",
-			"Status":   "Status",
-			"Size":     "Size",
+			"ID":         "ID",
+			"Name":       "Name",
+			"Engine":     "Engine",
+			"Version":    "Version",
+			"NumNodes":   "Number of Nodes",
+			"Region":     "Region",
+			"Status":     "Status",
+			"Size":       "Size",
+			"StorageMib": "Storage (MiB)",
 		}
 	}
 
 	return map[string]string{
-		"ID":       "ID",
-		"Name":     "Name",
-		"Engine":   "Engine",
-		"Version":  "Version",
-		"NumNodes": "Number of Nodes",
-		"Region":   "Region",
-		"Status":   "Status",
-		"Size":     "Size",
-		"URI":      "URI",
-		"Created":  "Created At",
+		"ID":         "ID",
+		"Name":       "Name",
+		"Engine":     "Engine",
+		"Version":    "Version",
+		"NumNodes":   "Number of Nodes",
+		"Region":     "Region",
+		"Status":     "Status",
+		"Size":       "Size",
+		"StorageMib": "Storage (MiB)",
+		"URI":        "URI",
+		"Created":    "Created At",
 	}
 }
 
@@ -95,16 +99,17 @@ func (d *Databases) KV() []map[string]interface{} {
 
 	for _, db := range d.Databases {
 		o := map[string]interface{}{
-			"ID":       db.ID,
-			"Name":     db.Name,
-			"Engine":   db.EngineSlug,
-			"Version":  db.VersionSlug,
-			"NumNodes": db.NumNodes,
-			"Region":   db.RegionSlug,
-			"Status":   db.Status,
-			"Size":     db.SizeSlug,
-			"URI":      db.Connection.URI,
-			"Created":  db.CreatedAt,
+			"ID":         db.ID,
+			"Name":       db.Name,
+			"Engine":     db.EngineSlug,
+			"Version":    db.VersionSlug,
+			"NumNodes":   db.NumNodes,
+			"Region":     db.RegionSlug,
+			"Status":     db.Status,
+			"Size":       db.SizeSlug,
+			"StorageMib": db.StorageSizeMib,
+			"URI":        db.Connection.URI,
+			"Created":    db.CreatedAt,
 		}
 		out = append(out, o)
 	}


Test patch:
diff --git a/commands/databases_test.go b/commands/databases_test.go
index 4b9e52ba..2d9de874 100644
--- a/commands/databases_test.go
+++ b/commands/databases_test.go
@@ -62,6 +62,7 @@ var (
 			},
 			PrivateNetworkUUID: "1fe49b6c-ac8e-11e9-98cb-3bec94f411bc",
 			Tags:               []string{"testing"},
+			StorageSizeMib:     20480,
 		},
 	}
 
@@ -341,6 +342,7 @@ func TestDatabasesCreate(t *testing.T) {
 		SizeSlug:           testDBCluster.SizeSlug,
 		PrivateNetworkUUID: testDBCluster.PrivateNetworkUUID,
 		Tags:               testDBCluster.Tags,
+		StorageSizeMib:     testDBCluster.StorageSizeMib,
 	}
 
 	// Successful call
@@ -355,6 +357,7 @@ func TestDatabasesCreate(t *testing.T) {
 		config.Doit.Set(config.NS, doctl.ArgDatabaseNumNodes, testDBCluster.NumNodes)
 		config.Doit.Set(config.NS, doctl.ArgPrivateNetworkUUID, testDBCluster.PrivateNetworkUUID)
 		config.Doit.Set(config.NS, doctl.ArgTag, testDBCluster.Tags)
+		config.Doit.Set(config.NS, doctl.ArgDatabaseStorageSizeMib, testDBCluster.StorageSizeMib)
 
 		err := RunDatabaseCreate(config)
 		assert.NoError(t, err)
@@ -507,8 +510,9 @@ func TestDatabaseMigrate(t *testing.T) {
 
 func TestDatabaseResize(t *testing.T) {
 	r := &godo.DatabaseResizeRequest{
-		SizeSlug: testDBCluster.SizeSlug,
-		NumNodes: testDBCluster.NumNodes,
+		SizeSlug:       testDBCluster.SizeSlug,
+		NumNodes:       testDBCluster.NumNodes,
+		StorageSizeMib: testDBCluster.StorageSizeMib,
 	}
 
 	// Success
@@ -517,6 +521,7 @@ func TestDatabaseResize(t *testing.T) {
 		config.Args = append(config.Args, testDBCluster.ID)
 		config.Doit.Set(config.NS, doctl.ArgSizeSlug, testDBCluster.SizeSlug)
 		config.Doit.Set(config.NS, doctl.ArgDatabaseNumNodes, testDBCluster.NumNodes)
+		config.Doit.Set(config.NS, doctl.ArgDatabaseStorageSizeMib, testDBCluster.StorageSizeMib)
 
 		err := RunDatabaseResize(config)
 		assert.NoError(t, err)
@@ -528,6 +533,7 @@ func TestDatabaseResize(t *testing.T) {
 		config.Args = append(config.Args, testDBCluster.ID)
 		config.Doit.Set(config.NS, doctl.ArgSizeSlug, testDBCluster.SizeSlug)
 		config.Doit.Set(config.NS, doctl.ArgDatabaseNumNodes, testDBCluster.NumNodes)
+		config.Doit.Set(config.NS, doctl.ArgDatabaseStorageSizeMib, testDBCluster.StorageSizeMib)
 
 		err := RunDatabaseResize(config)
 		assert.EqualError(t, err, errTest.Error())