Repository: digitalocean/doctl
Pull request #1638 (2025-01-14 18:54:26)

databases: support displaying URI and Created in list with format flag
In order to make the default display of  `doctl databases list` legible in a terminal, it does not display all attributes. To achieve this, the `Databases.Cols()` displays different columns if `Databases.Short = true`. It is `false` when generating help output. So columns not shown are indicated as available as `format` options. Exposing those to `Databases.ColMap()` regardless of whether the sort version is used allows them to be used with the `format` flag while still retaining the default behavior for the list view. 

Before this change, running the following command would error:

```
$ doctl databases list --format ID,Name,Engine,Version,URI,Created
Error: unknown column "URI"
```

Now it should successfully display the given fields.

gitguardian[bot]: #### ⚠️ GitGuardian has uncovered 4 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.



<details>
<summary>🔎 Detected hardcoded secrets in your pull request</summary>
<br>

| GitGuardian id | GitGuardian status | Secret                         | Commit           | Filename        |                      |
| -------------- | ------------------ | ------------------------------ | ---------------- | --------------- | -------------------- |
| [15175723](https://dashboard.gitguardian.com/workspace/501906/incidents/15175723?occurrence=175554912) | Triggered | PostgreSQL Credentials | fc582399cdd0061d5378279ef9396128e0219df7 | integration/database_list_test.go | [View secret](https://github.com/digitalocean/doctl/commit/fc582399cdd0061d5378279ef9396128e0219df7#diff-56db08456b114579e1a5738eacd52442cce21c13daa92d6d7f8252b31dcdf133R92) |
| [15175724](https://dashboard.gitguardian.com/workspace/501906/incidents/15175724?occurrence=175554911) | Triggered | MySQL Credentials | fc582399cdd0061d5378279ef9396128e0219df7 | integration/database_list_test.go | [View secret](https://github.com/digitalocean/doctl/commit/fc582399cdd0061d5378279ef9396128e0219df7#diff-56db08456b114579e1a5738eacd52442cce21c13daa92d6d7f8252b31dcdf133R91) |
| [15175725](https://dashboard.gitguardian.com/workspace/501906/incidents/15175725?occurrence=175554915) | Triggered | MySQL Credentials | fc582399cdd0061d5378279ef9396128e0219df7 | integration/database_list_test.go | [View secret](https://github.com/digitalocean/doctl/commit/fc582399cdd0061d5378279ef9396128e0219df7#diff-56db08456b114579e1a5738eacd52442cce21c13daa92d6d7f8252b31dcdf133R115) |
| [15175726](https://dashboard.gitguardian.com/workspace/501906/incidents/15175726?occurrence=175554913) | Triggered | PostgreSQL Credentials | fc582399cdd0061d5378279ef9396128e0219df7 | integration/database_list_test.go | [View secret](https://github.com/digitalocean/doctl/commit/fc582399cdd0061d5378279ef9396128e0219df7#diff-56db08456b114579e1a5738eacd52442cce21c13daa92d6d7f8252b31dcdf133R173) |



</details>

<details>
<summary>🛠 Guidelines to remediate hardcoded secrets</summary>
<br>

1. Understand the implications of revoking this secret by investigating where it is used in your code.
2. Replace and store your secret safely. [Learn here](https://blog.gitguardian.com/secrets-api-management) the best practices.
3. Revoke and [rotate this secret](https://docs.gitguardian.com/secrets-detection/secrets-detection-engine/detectors/specifics/private_key_openssh#revoke-the-secret).
4. If possible, [rewrite git history](https://blog.gitguardian.com/rewriting-git-history-cheatsheet). Rewriting git history is not a trivial act. You might completely break other contributing developers&#x27; workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider

-   following these [best practices](https://blog.gitguardian.com/secrets-api-management/) for managing and storing secrets including API keys and other credentials
-   install [secret detection on pre-commit](https://docs.gitguardian.com/ggshield-docs/integrations/git-hooks/pre-commit) to catch secret before it leaves your machine and ease remediation.

</details>

---

<sup>🦉 [GitGuardian](https://dashboard.gitguardian.com/auth/login/?utm_medium=checkruns&amp;utm_source=github&amp;utm_campaign=cr1) detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.<br/></sup>

Patch:
diff --git a/commands/displayers/database.go b/commands/displayers/database.go
index 604e33a4..38684f89 100644
--- a/commands/displayers/database.go
+++ b/commands/displayers/database.go
@@ -65,20 +65,6 @@ func (d *Databases) Cols() []string {
 }
 
 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",
-			"StorageMib": "Storage (MiB)",
-		}
-	}
-
 	return map[string]string{
 		"ID":         "ID",
 		"Name":       "Name",


Test patch:
diff --git a/integration/database_list_test.go b/integration/database_list_test.go
new file mode 100644
index 00000000..98864741
--- /dev/null
+++ b/integration/database_list_test.go
@@ -0,0 +1,218 @@
+package integration
+
+import (
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"net/http/httputil"
+	"os/exec"
+	"strings"
+	"testing"
+
+	"github.com/sclevine/spec"
+	"github.com/stretchr/testify/require"
+)
+
+var _ = suite("database/list", func(t *testing.T, when spec.G, it spec.S) {
+	var (
+		expect *require.Assertions
+		server *httptest.Server
+	)
+
+	it.Before(func() {
+		expect = require.New(t)
+
+		server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+			switch req.URL.Path {
+			case "/v2/databases":
+				auth := req.Header.Get("Authorization")
+				if auth != "Bearer some-magic-token" {
+					w.WriteHeader(http.StatusForbidden)
+				}
+
+				if req.Method != http.MethodGet {
+					w.WriteHeader(http.StatusMethodNotAllowed)
+					return
+				}
+
+				w.Write([]byte(databaseListResponse))
+			default:
+				dump, err := httputil.DumpRequest(req, true)
+				if err != nil {
+					t.Fatal("failed to dump request")
+				}
+
+				t.Fatalf("received unknown request: %s", dump)
+			}
+		}))
+	})
+
+	when("all required flags are passed", func() {
+		it("lists all databases", func() {
+			cmd := exec.Command(builtBinaryPath,
+				"-t", "some-magic-token",
+				"-u", server.URL,
+				"database",
+				"list",
+			)
+
+			output, err := cmd.CombinedOutput()
+			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
+			expect.Equal(strings.TrimSpace(databaseListOutput), strings.TrimSpace(string(output)))
+		})
+	})
+
+	when("all format flag is passed", func() {
+		it("lists contains correct fields", func() {
+			cmd := exec.Command(builtBinaryPath,
+				"-t", "some-magic-token",
+				"-u", server.URL,
+				"database",
+				"list",
+				"--format",
+				"ID,Name,URI,Created",
+			)
+
+			output, err := cmd.CombinedOutput()
+			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
+			expect.Equal(strings.TrimSpace(databaseFormattedListOutput), strings.TrimSpace(string(output)))
+		})
+	})
+})
+
+const (
+	databaseListOutput = `
+ID                                      Name                  Engine    Version    Number of Nodes    Region    Status    Size              Storage (MiB)
+1df3bb24-d2a5-11ef-ae5c-ff567b29ed0b    db-mysql-test         mysql     8          1                  nyc3      online    db-s-1vcpu-1gb    10240
+6ee6ac26-d2a5-11ef-b1f3-d74f7709225a    db-postgresql-test    pg        16         1                  nyc3      online    db-s-1vcpu-1gb    10240
+`
+	databaseFormattedListOutput = `
+ID                                      Name                  URI                                                                                                                    Created At
+1df3bb24-d2a5-11ef-ae5c-ff567b29ed0b    db-mysql-test         mysql://doadmin:nope@db-mysql-test-do-user-123-0.b.db.ondigitalocean.com:25060/defaultdb?ssl-mode=REQUIRED             2024-12-12 15:41:34 +0000 UTC
+6ee6ac26-d2a5-11ef-b1f3-d74f7709225a    db-postgresql-test    postgresql://doadmin:shhhh@db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com:25060/defaultdb?sslmode=require    2024-12-17 22:11:32 +0000 UTC
+`
+	databaseListResponse = `
+{
+  "databases": [
+    {
+      "id": "1df3bb24-d2a5-11ef-ae5c-ff567b29ed0b",
+      "name": "db-mysql-test",
+      "engine": "mysql",
+      "version": "8",
+      "semantic_version": "8.0.30",
+      "connection": {
+        "protocol": "mysql",
+        "uri": "mysql://doadmin:nope@db-mysql-test-do-user-123-0.b.db.ondigitalocean.com:25060/defaultdb?ssl-mode=REQUIRED",
+        "database": "defaultdb",
+        "host": "db-mysql-test-do-user-123-0.b.db.ondigitalocean.com",
+        "port": 25060,
+        "user": "doadmin",
+        "password": "nope",
+        "ssl": true
+      },
+      "private_connection": {
+        "protocol": "mysql",
+        "uri": "mysql://doadmin:nope@private-db-mysql-test-do-user-123-0.b.db.ondigitalocean.com:25060/defaultdb?ssl-mode=REQUIRED",
+        "database": "defaultdb",
+        "host": "private-db-mysql-test-do-user-123-0.b.db.ondigitalocean.com",
+        "port": 25060,
+        "user": "doadmin",
+        "password": "nope",
+        "ssl": true
+      },
+      "metrics_endpoints": [
+        {
+          "host": "db-mysql-test-do-user-123-0.b.db.ondigitalocean.com",
+          "port": 9273
+        }
+      ],
+      "users": [
+        {
+          "name": "doadmin",
+          "role": "primary",
+          "password": "nope"
+        }
+      ],
+      "db_names": [
+        "defaultdb"
+      ],
+      "num_nodes": 1,
+      "region": "nyc3",
+      "status": "online",
+      "created_at": "2024-12-12T15:41:34Z",
+      "maintenance_window": {
+        "day": "monday",
+        "hour": "10:23:13",
+        "pending": true
+      },
+      "size": "db-s-1vcpu-1gb",
+      "tags": null,
+      "private_network_uuid": "5b41fa22-d2a5-11ef-b99d-23e1382dd662",
+      "project_id": "6393e064-d2a5-11ef-b030-9bd9b52e0f58",
+      "read_only": false,
+      "storage_size_mib": 10240
+    },
+    {
+      "id": "6ee6ac26-d2a5-11ef-b1f3-d74f7709225a",
+      "name": "db-postgresql-test",
+      "engine": "pg",
+      "version": "16",
+      "semantic_version": "16.6",
+      "connection": {
+        "protocol": "postgresql",
+        "uri": "postgresql://doadmin:shhhh@db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com:25060/defaultdb?sslmode=require",
+        "database": "defaultdb",
+        "host": "db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com",
+        "port": 25060,
+        "user": "doadmin",
+        "password": "shhhh",
+        "ssl": true
+      },
+      "private_connection": {
+        "protocol": "postgresql",
+        "uri": "postgresql://doadmin:shhhh@private-db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com:25060/defaultdb?sslmode=require",
+        "database": "defaultdb",
+        "host": "private-db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com",
+        "port": 25060,
+        "user": "doadmin",
+        "password": "shhhh",
+        "ssl": true
+      },
+      "metrics_endpoints": [
+        {
+          "host": "db-postgresql-test-do-user-123-0.l.db.ondigitalocean.com",
+          "port": 9273
+        }
+      ],
+      "users": [
+        {
+          "name": "doadmin",
+          "role": "primary",
+          "password": "shhhh"
+        }
+      ],
+      "db_names": [
+        "defaultdb"
+      ],
+      "num_nodes": 1,
+      "region": "nyc3",
+      "status": "online",
+      "created_at": "2024-12-17T22:11:32Z",
+      "maintenance_window": {
+        "day": "saturday",
+        "hour": "14:00:00",
+        "pending": false
+      },
+      "size": "db-s-1vcpu-1gb",
+      "tags": null,
+      "private_network_uuid": "5b41fa22-d2a5-11ef-b99d-23e1382dd662",
+      "project_id": "6393e064-d2a5-11ef-b030-9bd9b52e0f58",
+      "read_only": false,
+      "version_end_of_life": "2028-11-09T00:00:00Z",
+      "version_end_of_availability": "2028-05-09T00:00:00Z",
+      "storage_size_mib": 10240
+    }
+  ]
+}
+`
+)