Skip to content

Commit 18c4a81

Browse files
bhardwajRahulRahul Bhardwaj
and
Rahul Bhardwaj
authored
Databases: Add OpenSearch index CRUD support (#1571)
* Upgrade GODO v1.121.0 * Update DOCTL to include opensearch index CRUD changes * Add more command details. * Add integration tests for opensearch index. --------- Co-authored-by: Rahul Bhardwaj <[email protected]>
1 parent 9282784 commit 18c4a81

20 files changed

+604
-33
lines changed

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Step 1: Build
2-
FROM golang:1.21-alpine AS build
2+
FROM golang:1.22-alpine AS build
33

44
ARG GOARCH=amd64
55
ENV OUT_D /out

commands/databases.go

+62
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ For PostgreSQL and MySQL clusters, you can also provide a disk size in MiB to sc
156156
cmd.AddCommand(databaseConfiguration())
157157
cmd.AddCommand(databaseTopic())
158158
cmd.AddCommand(databaseEvents())
159+
cmd.AddCommand(databaseIndex())
159160

160161
return cmd
161162
}
@@ -2548,3 +2549,64 @@ func RunDatabaseEvents(c *CmdConfig) error {
25482549
item := &displayers.DatabaseEvents{DatabaseEvents: dbEvents}
25492550
return c.Display(item)
25502551
}
2552+
2553+
func databaseIndex() *Command {
2554+
cmd := &Command{
2555+
Command: &cobra.Command{
2556+
Use: "indexes",
2557+
Short: `Display commands to manage indexes for opensearch clusters`,
2558+
Long: `The subcommands under ` + "`" + `doctl databases indexes` + "`" + ` enable the management of indexes for opensearch clusters`,
2559+
},
2560+
}
2561+
2562+
indexListDetails := `
2563+
This command lists the following details for each index in an opensearch cluster:
2564+
2565+
- The Name of the index.
2566+
- The Status of the index.
2567+
- The Health of the index.
2568+
- The Number of Shards in the index.
2569+
- The Number of Replicas in the index.
2570+
- The Number of Documents in the index.
2571+
- The Size of the index.
2572+
`
2573+
2574+
CmdBuilder(cmd, RunDatabaseIndexList, "list <database-uuid>", "Retrieve a list of indexes for a given opensearch cluster", indexListDetails, Writer, displayerType(&displayers.DatabaseOpenSearchIndexes{}), aliasOpt("ls"))
2575+
cmdDatabaseIndexDelete := CmdBuilder(cmd, RunDatabaseIndexDelete, "delete <database-uuid> <index-name>", "Deletes an opensearch index by index name", "", Writer, aliasOpt("rm"))
2576+
AddBoolFlag(cmdDatabaseIndexDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Deletes the opensearch index without a confirmation prompt")
2577+
2578+
return cmd
2579+
}
2580+
2581+
func RunDatabaseIndexList(c *CmdConfig) error {
2582+
if len(c.Args) == 0 {
2583+
return doctl.NewMissingArgsErr(c.NS)
2584+
}
2585+
2586+
databaseID := c.Args[0]
2587+
indexes, err := c.Databases().ListIndexes(databaseID)
2588+
if err != nil {
2589+
return err
2590+
}
2591+
item := &displayers.DatabaseOpenSearchIndexes{DatabaseIndexes: indexes}
2592+
return c.Display(item)
2593+
}
2594+
2595+
func RunDatabaseIndexDelete(c *CmdConfig) error {
2596+
if len(c.Args) < 2 {
2597+
return doctl.NewMissingArgsErr(c.NS)
2598+
}
2599+
2600+
force, err := c.Doit.GetBool(c.NS, doctl.ArgForce)
2601+
if err != nil {
2602+
return err
2603+
}
2604+
2605+
if force || AskForConfirmDelete("opensearch index", 1) == nil {
2606+
databaseID := c.Args[0]
2607+
indexName := c.Args[1]
2608+
return c.Databases().DeleteIndex(databaseID, indexName)
2609+
}
2610+
2611+
return errOperationAborted
2612+
}

commands/databases_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ func TestDatabasesCommand(t *testing.T) {
262262
"sql-mode",
263263
"configuration",
264264
"topics",
265+
"indexes",
265266
)
266267
}
267268

commands/displayers/database.go

+57
Original file line numberDiff line numberDiff line change
@@ -1691,3 +1691,60 @@ func (dr *DatabaseEvents) KV() []map[string]any {
16911691
}
16921692
return out
16931693
}
1694+
1695+
type DatabaseOpenSearchIndexes struct {
1696+
DatabaseIndexes do.DatabaseIndexes
1697+
}
1698+
1699+
var _ Displayable = &DatabaseOpenSearchIndexes{}
1700+
1701+
func (dt *DatabaseOpenSearchIndexes) JSON(out io.Writer) error {
1702+
return writeJSON(dt.DatabaseIndexes, out)
1703+
}
1704+
1705+
func (dt *DatabaseOpenSearchIndexes) Cols() []string {
1706+
return []string{
1707+
"Index Name",
1708+
"Status",
1709+
"Health",
1710+
"Size",
1711+
"Docs",
1712+
"Create At",
1713+
"Number of Shards",
1714+
"Number of Replica",
1715+
}
1716+
}
1717+
1718+
func (dt *DatabaseOpenSearchIndexes) ColMap() map[string]string {
1719+
1720+
return map[string]string{
1721+
"Index Name": "Index Name",
1722+
"Status": "Status",
1723+
"Health": "Health",
1724+
"Size": "Size",
1725+
"Docs": "Docs",
1726+
"Create At": "Create At",
1727+
"Number of Shards": "Number of Shards",
1728+
"Number of Replica": "Number of Replica",
1729+
}
1730+
}
1731+
1732+
func (dt *DatabaseOpenSearchIndexes) KV() []map[string]any {
1733+
out := make([]map[string]any, 0, len(dt.DatabaseIndexes))
1734+
1735+
for _, t := range dt.DatabaseIndexes {
1736+
o := map[string]any{
1737+
"Index Name": t.IndexName,
1738+
"Number of Shards": t.NumberofShards,
1739+
"Number of Replica": t.NumberofReplicas,
1740+
"Status": t.Status,
1741+
"Health": t.Health,
1742+
"Size": t.Size,
1743+
"Docs": t.Docs,
1744+
"Create At": t.CreateTime,
1745+
}
1746+
out = append(out, o)
1747+
}
1748+
1749+
return out
1750+
}

do/databases.go

+45
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ type DatabaseEvent struct {
133133
// DatabaseEvents is a slice of DatabaseEvent
134134
type DatabaseEvents []DatabaseEvent
135135

136+
// DatabaseIndexes is a slice of DatabaseIndex
137+
type DatabaseIndexes []DatabaseIndex
138+
139+
// DatabaseIndex is a wrapper for godo.DatabaseIndex
140+
type DatabaseIndex struct {
141+
*godo.DatabaseIndex
142+
}
143+
136144
// DatabasesService is an interface for interacting with DigitalOcean's Database API
137145
type DatabasesService interface {
138146
List() (Databases, error)
@@ -194,6 +202,9 @@ type DatabasesService interface {
194202
DeleteTopic(string, string) error
195203

196204
ListDatabaseEvents(string) (DatabaseEvents, error)
205+
206+
ListIndexes(string) (DatabaseIndexes, error)
207+
DeleteIndex(string, string) error
197208
}
198209

199210
type databasesService struct {
@@ -789,3 +800,37 @@ func (ds *databasesService) ListDatabaseEvents(databaseID string) (DatabaseEvent
789800
}
790801
return list, nil
791802
}
803+
804+
func (ds *databasesService) ListIndexes(databaseID string) (DatabaseIndexes, error) {
805+
f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) {
806+
list, resp, err := ds.client.Databases.ListIndexes(context.TODO(), databaseID, opt)
807+
if err != nil {
808+
return nil, nil, err
809+
}
810+
811+
si := make([]any, len(list))
812+
for i := range list {
813+
si[i] = list[i]
814+
}
815+
816+
return si, resp, err
817+
}
818+
819+
si, err := PaginateResp(f)
820+
if err != nil {
821+
return nil, err
822+
}
823+
824+
list := make(DatabaseIndexes, len(si))
825+
for i := range si {
826+
t := si[i].(godo.DatabaseIndex)
827+
list[i] = DatabaseIndex{DatabaseIndex: &t}
828+
}
829+
return list, nil
830+
}
831+
832+
func (ds *databasesService) DeleteIndex(databaseID, indexName string) error {
833+
_, err := ds.client.Databases.DeleteIndex(context.TODO(), databaseID, indexName)
834+
835+
return err
836+
}

do/mocks/DatabasesService.go

+29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.22
55
require (
66
github.com/blang/semver v3.5.1+incompatible
77
github.com/creack/pty v1.1.21
8-
github.com/digitalocean/godo v1.118.0
8+
github.com/digitalocean/godo v1.121.0
99
github.com/docker/cli v24.0.5+incompatible
1010
github.com/docker/docker v25.0.6+incompatible
1111
github.com/docker/docker-credential-helpers v0.7.0 // indirect
@@ -30,7 +30,7 @@ require (
3030
github.com/stretchr/testify v1.8.4
3131
golang.org/x/crypto v0.22.0
3232
golang.org/x/net v0.24.0 // indirect
33-
golang.org/x/oauth2 v0.19.0
33+
golang.org/x/oauth2 v0.22.0
3434
golang.org/x/sys v0.20.0 // indirect
3535
gopkg.in/yaml.v2 v2.4.0
3636
k8s.io/api v0.26.2
@@ -121,7 +121,7 @@ require (
121121
go.opentelemetry.io/otel/trace v1.19.0 // indirect
122122
golang.org/x/mod v0.11.0 // indirect
123123
golang.org/x/text v0.14.0 // indirect
124-
golang.org/x/time v0.5.0 // indirect
124+
golang.org/x/time v0.6.0 // indirect
125125
golang.org/x/tools v0.10.0 // indirect
126126
gopkg.in/inf.v0 v0.9.1 // indirect
127127
gopkg.in/ini.v1 v1.66.4 // indirect

go.sum

+6-6
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
9191
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9292
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
9393
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
94-
github.com/digitalocean/godo v1.118.0 h1:lkzGFQmACrVCp7UqH1sAi4JK/PWwlc5aaxubgorKmC4=
95-
github.com/digitalocean/godo v1.118.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo=
94+
github.com/digitalocean/godo v1.121.0 h1:ilXiHuEnhbJs2fmFEPX0r/QQ6KfiOIMAhJN3f8NiCfI=
95+
github.com/digitalocean/godo v1.121.0/go.mod h1:WQVH83OHUy6gC4gXpEVQKtxTd4L5oCp+5OialidkPLY=
9696
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
9797
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
9898
github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
@@ -517,8 +517,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
517517
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
518518
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
519519
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
520-
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
521-
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
520+
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
521+
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
522522
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
523523
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
524524
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -599,8 +599,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
599599
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
600600
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
601601
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
602-
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
603-
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
602+
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
603+
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
604604
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
605605
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
606606
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package integration
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"net/http/httputil"
8+
"os/exec"
9+
"strings"
10+
"testing"
11+
12+
"github.com/sclevine/spec"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
var _ = suite("database/index/delete", func(t *testing.T, when spec.G, it spec.S) {
17+
var (
18+
expect *require.Assertions
19+
server *httptest.Server
20+
)
21+
22+
it.Before(func() {
23+
expect = require.New(t)
24+
25+
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
26+
switch req.URL.Path {
27+
case "/v2/databases/some-database-id/indexes/some-index-id":
28+
auth := req.Header.Get("Authorization")
29+
if auth != "Bearer some-magic-token" {
30+
w.WriteHeader(http.StatusTeapot)
31+
}
32+
33+
if req.Method != http.MethodDelete {
34+
w.WriteHeader(http.StatusMethodNotAllowed)
35+
return
36+
}
37+
38+
w.WriteHeader(http.StatusNoContent)
39+
default:
40+
dump, err := httputil.DumpRequest(req, true)
41+
if err != nil {
42+
t.Fatal("failed to dump request")
43+
}
44+
45+
t.Fatalf("received unknown request: %s", dump)
46+
}
47+
}))
48+
})
49+
50+
when("all required flags are passed", func() {
51+
it("deletes the database", func() {
52+
cmd := exec.Command(builtBinaryPath,
53+
"-t", "some-magic-token",
54+
"-u", server.URL,
55+
"database",
56+
"indexes",
57+
"delete",
58+
"some-database-id",
59+
"some-index-id",
60+
"-f",
61+
)
62+
63+
output, err := cmd.CombinedOutput()
64+
expect.NoError(err, fmt.Sprintf("received error output: %s", output))
65+
expect.Equal(strings.TrimSpace(""), strings.TrimSpace(string(output)))
66+
expect.Empty(output)
67+
})
68+
})
69+
})

0 commit comments

Comments
 (0)