Skip to content

Commit

Permalink
Databases: Add OpenSearch index CRUD support (digitalocean#1571)
Browse files Browse the repository at this point in the history
* 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 <rahulbhardwaj@digitalocean.com>
  • Loading branch information
bhardwajRahul and Rahul Bhardwaj authored Aug 30, 2024
1 parent 9282784 commit 18c4a81
Show file tree
Hide file tree
Showing 20 changed files with 604 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Step 1: Build
FROM golang:1.21-alpine AS build
FROM golang:1.22-alpine AS build

ARG GOARCH=amd64
ENV OUT_D /out
Expand Down
62 changes: 62 additions & 0 deletions commands/databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ For PostgreSQL and MySQL clusters, you can also provide a disk size in MiB to sc
cmd.AddCommand(databaseConfiguration())
cmd.AddCommand(databaseTopic())
cmd.AddCommand(databaseEvents())
cmd.AddCommand(databaseIndex())

return cmd
}
Expand Down Expand Up @@ -2548,3 +2549,64 @@ func RunDatabaseEvents(c *CmdConfig) error {
item := &displayers.DatabaseEvents{DatabaseEvents: dbEvents}
return c.Display(item)
}

func databaseIndex() *Command {
cmd := &Command{
Command: &cobra.Command{
Use: "indexes",
Short: `Display commands to manage indexes for opensearch clusters`,
Long: `The subcommands under ` + "`" + `doctl databases indexes` + "`" + ` enable the management of indexes for opensearch clusters`,
},
}

indexListDetails := `
This command lists the following details for each index in an opensearch cluster:
- The Name of the index.
- The Status of the index.
- The Health of the index.
- The Number of Shards in the index.
- The Number of Replicas in the index.
- The Number of Documents in the index.
- The Size of the index.
`

CmdBuilder(cmd, RunDatabaseIndexList, "list <database-uuid>", "Retrieve a list of indexes for a given opensearch cluster", indexListDetails, Writer, displayerType(&displayers.DatabaseOpenSearchIndexes{}), aliasOpt("ls"))
cmdDatabaseIndexDelete := CmdBuilder(cmd, RunDatabaseIndexDelete, "delete <database-uuid> <index-name>", "Deletes an opensearch index by index name", "", Writer, aliasOpt("rm"))
AddBoolFlag(cmdDatabaseIndexDelete, doctl.ArgForce, doctl.ArgShortForce, false, "Deletes the opensearch index without a confirmation prompt")

return cmd
}

func RunDatabaseIndexList(c *CmdConfig) error {
if len(c.Args) == 0 {
return doctl.NewMissingArgsErr(c.NS)
}

databaseID := c.Args[0]
indexes, err := c.Databases().ListIndexes(databaseID)
if err != nil {
return err
}
item := &displayers.DatabaseOpenSearchIndexes{DatabaseIndexes: indexes}
return c.Display(item)
}

func RunDatabaseIndexDelete(c *CmdConfig) error {
if len(c.Args) < 2 {
return doctl.NewMissingArgsErr(c.NS)
}

force, err := c.Doit.GetBool(c.NS, doctl.ArgForce)
if err != nil {
return err
}

if force || AskForConfirmDelete("opensearch index", 1) == nil {
databaseID := c.Args[0]
indexName := c.Args[1]
return c.Databases().DeleteIndex(databaseID, indexName)
}

return errOperationAborted
}
1 change: 1 addition & 0 deletions commands/databases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ func TestDatabasesCommand(t *testing.T) {
"sql-mode",
"configuration",
"topics",
"indexes",
)
}

Expand Down
57 changes: 57 additions & 0 deletions commands/displayers/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -1691,3 +1691,60 @@ func (dr *DatabaseEvents) KV() []map[string]any {
}
return out
}

type DatabaseOpenSearchIndexes struct {
DatabaseIndexes do.DatabaseIndexes
}

var _ Displayable = &DatabaseOpenSearchIndexes{}

func (dt *DatabaseOpenSearchIndexes) JSON(out io.Writer) error {
return writeJSON(dt.DatabaseIndexes, out)
}

func (dt *DatabaseOpenSearchIndexes) Cols() []string {
return []string{
"Index Name",
"Status",
"Health",
"Size",
"Docs",
"Create At",
"Number of Shards",
"Number of Replica",
}
}

func (dt *DatabaseOpenSearchIndexes) ColMap() map[string]string {

return map[string]string{
"Index Name": "Index Name",
"Status": "Status",
"Health": "Health",
"Size": "Size",
"Docs": "Docs",
"Create At": "Create At",
"Number of Shards": "Number of Shards",
"Number of Replica": "Number of Replica",
}
}

func (dt *DatabaseOpenSearchIndexes) KV() []map[string]any {
out := make([]map[string]any, 0, len(dt.DatabaseIndexes))

for _, t := range dt.DatabaseIndexes {
o := map[string]any{
"Index Name": t.IndexName,
"Number of Shards": t.NumberofShards,
"Number of Replica": t.NumberofReplicas,
"Status": t.Status,
"Health": t.Health,
"Size": t.Size,
"Docs": t.Docs,
"Create At": t.CreateTime,
}
out = append(out, o)
}

return out
}
45 changes: 45 additions & 0 deletions do/databases.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ type DatabaseEvent struct {
// DatabaseEvents is a slice of DatabaseEvent
type DatabaseEvents []DatabaseEvent

// DatabaseIndexes is a slice of DatabaseIndex
type DatabaseIndexes []DatabaseIndex

// DatabaseIndex is a wrapper for godo.DatabaseIndex
type DatabaseIndex struct {
*godo.DatabaseIndex
}

// DatabasesService is an interface for interacting with DigitalOcean's Database API
type DatabasesService interface {
List() (Databases, error)
Expand Down Expand Up @@ -194,6 +202,9 @@ type DatabasesService interface {
DeleteTopic(string, string) error

ListDatabaseEvents(string) (DatabaseEvents, error)

ListIndexes(string) (DatabaseIndexes, error)
DeleteIndex(string, string) error
}

type databasesService struct {
Expand Down Expand Up @@ -789,3 +800,37 @@ func (ds *databasesService) ListDatabaseEvents(databaseID string) (DatabaseEvent
}
return list, nil
}

func (ds *databasesService) ListIndexes(databaseID string) (DatabaseIndexes, error) {
f := func(opt *godo.ListOptions) ([]any, *godo.Response, error) {
list, resp, err := ds.client.Databases.ListIndexes(context.TODO(), databaseID, opt)
if err != nil {
return nil, nil, err
}

si := make([]any, len(list))
for i := range list {
si[i] = list[i]
}

return si, resp, err
}

si, err := PaginateResp(f)
if err != nil {
return nil, err
}

list := make(DatabaseIndexes, len(si))
for i := range si {
t := si[i].(godo.DatabaseIndex)
list[i] = DatabaseIndex{DatabaseIndex: &t}
}
return list, nil
}

func (ds *databasesService) DeleteIndex(databaseID, indexName string) error {
_, err := ds.client.Databases.DeleteIndex(context.TODO(), databaseID, indexName)

return err
}
29 changes: 29 additions & 0 deletions do/mocks/DatabasesService.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.118.0
github.com/digitalocean/godo v1.121.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
Expand All @@ -30,7 +30,7 @@ require (
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.22.0
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.19.0
golang.org/x/oauth2 v0.22.0
golang.org/x/sys v0.20.0 // indirect
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.26.2
Expand Down Expand Up @@ -121,7 +121,7 @@ require (
go.opentelemetry.io/otel/trace v1.19.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.10.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
Expand Down
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/godo v1.118.0 h1:lkzGFQmACrVCp7UqH1sAi4JK/PWwlc5aaxubgorKmC4=
github.com/digitalocean/godo v1.118.0/go.mod h1:Vk0vpCot2HOAJwc5WE8wljZGtJ3ZtWIc8MQ8rF38sdo=
github.com/digitalocean/godo v1.121.0 h1:ilXiHuEnhbJs2fmFEPX0r/QQ6KfiOIMAhJN3f8NiCfI=
github.com/digitalocean/godo v1.121.0/go.mod h1:WQVH83OHUy6gC4gXpEVQKtxTd4L5oCp+5OialidkPLY=
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=
Expand Down Expand Up @@ -517,8 +517,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -599,8 +599,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Expand Down
69 changes: 69 additions & 0 deletions integration/database_opensearch_index_delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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/index/delete", 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/some-database-id/indexes/some-index-id":
auth := req.Header.Get("Authorization")
if auth != "Bearer some-magic-token" {
w.WriteHeader(http.StatusTeapot)
}

if req.Method != http.MethodDelete {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

w.WriteHeader(http.StatusNoContent)
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("deletes the database", func() {
cmd := exec.Command(builtBinaryPath,
"-t", "some-magic-token",
"-u", server.URL,
"database",
"indexes",
"delete",
"some-database-id",
"some-index-id",
"-f",
)

output, err := cmd.CombinedOutput()
expect.NoError(err, fmt.Sprintf("received error output: %s", output))
expect.Equal(strings.TrimSpace(""), strings.TrimSpace(string(output)))
expect.Empty(output)
})
})
})
Loading

0 comments on commit 18c4a81

Please sign in to comment.