Skip to content

Commit

Permalink
Merge pull request #30 from neo4j/table-output
Browse files Browse the repository at this point in the history
Table output
  • Loading branch information
angrykoala authored Sep 6, 2024
2 parents 542e654 + 9126ff3 commit 2d32a4a
Show file tree
Hide file tree
Showing 26 changed files with 153 additions and 25 deletions.
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20240904-114713.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Support for table output on aura cli
time: 2024-09-04T11:47:13.635555255+01:00
6 changes: 2 additions & 4 deletions common/clicfg/clicfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,12 @@ func (config *Config) Write() error {
return err
}

n, err := f.Write(content)
_, err = f.Write(content)

if err != nil {
return err
}

fmt.Printf("wrote %d bytes to config file\n", n)

return nil
}

Expand Down Expand Up @@ -292,6 +290,6 @@ func bindEnvironmentVariables(Viper *viper.Viper) {
func setDefaultValues(Viper *viper.Viper) {
Viper.SetDefault("aura.base-url", DefaultAuraBaseUrl)
Viper.SetDefault("aura.auth-url", DefaultAuraAuthUrl)
Viper.SetDefault("aura.output", "json")
Viper.SetDefault("aura.output", "default")
Viper.SetDefault("aura.credentials", []AuraCredential{})
}
6 changes: 6 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ require (
golang.org/x/sys v0.22.0
)

require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
)

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jedib0t/go-pretty/v6 v6.5.9
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,25 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU=
github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
64 changes: 63 additions & 1 deletion neo4j/aura/internal/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"encoding/json"
"errors"

"github.com/jedib0t/go-pretty/v6/table"
"github.com/neo4j/cli/common/clictx"
"github.com/spf13/cobra"
)

// Prints a response body
func PrintBody(cmd *cobra.Command, body []byte) error {
func PrintBody(cmd *cobra.Command, body []byte, fields []string) error {
config, ok := clictx.Config(cmd.Context())
if !ok {
return errors.New("error fetching cli configuration values")
Expand All @@ -30,10 +31,71 @@ func PrintBody(cmd *cobra.Command, body []byte) error {
return err
}
cmd.Println(pretty.String())
case "table", "default":
err := printTable(cmd, body, fields)
if err != nil {
return err
}

default:
// This is in case the value is unknown
cmd.Println(string(body))
}
}

return nil
}

func printTable(cmd *cobra.Command, body []byte, fields []string) error {
values, err := parseBody(body)
if err != nil {
return err
}

t := table.NewWriter()

header := table.Row{}
for _, f := range fields {
header = append(header, f)
}

t.AppendHeader(header)
for _, v := range values {
row := table.Row{}
for _, f := range fields {
formattedValue := v[f]

if v[f] == nil {
formattedValue = ""
}

row = append(row, formattedValue)
}
t.AppendRow(row)
}

t.SetStyle(table.StyleLight)
cmd.Println(t.Render())
return nil
}

func parseBody(body []byte) ([]map[string]any, error) {
var values []map[string]any
var jsonWithArray struct{ Data []map[string]any }

err := json.Unmarshal(body, &jsonWithArray)

// Try unmarshalling array first, if not it creates an array from the single item
if err == nil {
values = jsonWithArray.Data
} else {
var jsonWithSingleItem struct{ Data map[string]any }
err := json.Unmarshal(body, &jsonWithSingleItem)
if err != nil {
return nil, err
}
values = []map[string]any{jsonWithSingleItem.Data}
}

return values, nil
}
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/config/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ func TestListConfigDefault(t *testing.T) {
out, err := io.ReadAll(b)
assert.Nil(err)

assert.Equal(fmt.Sprintf("{\n\t\"base-url\": \"%s\",\n\t\"auth-url\": \"%s\",\n\t\"output\": \"json\",\n\t\"credentials\": []\n}\n", clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), string(out))
assert.Equal(fmt.Sprintf("{\n\t\"base-url\": \"%s\",\n\t\"auth-url\": \"%s\",\n\t\"output\": \"default\",\n\t\"credentials\": []\n}\n", clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), string(out))
}
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/config/set_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ func TestSetConfig(t *testing.T) {
out, err := testfs.GetTestConfig(fs)
assert.Nil(err)

assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"test","output":"json","credentials":[]}}`, clicfg.DefaultAuraBaseUrl), out)
assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"test","output":"default","credentials":[]}}`, clicfg.DefaultAuraBaseUrl), out)
}
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/credential/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestAddCredential(t *testing.T) {
out, err := testfs.GetTestConfig(fs)
assert.Nil(err)

assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"json","credentials":[{"name":"test","client-id":"testclientid","client-secret":"testclientsecret","access-token":"","token-expiry":0}]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), out)
assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"default","credentials":[{"name":"test","client-id":"testclientid","client-secret":"testclientsecret","access-token":"","token-expiry":0}]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), out)
}

func TestAddCredentialIfAlreadyExists(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/credential/remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ func TestRemoveCredential(t *testing.T) {
out, err := testfs.GetTestConfig(fs)
assert.Nil(err)

assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"json","credentials":[]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), string(out))
assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"default","credentials":[]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), string(out))
}
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/credential/use_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestUseCredential(t *testing.T) {
out, err := testfs.GetTestConfig(fs)
assert.Nil(err)

assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"json","default-credential":"test","credentials":[{"name":"test","client-id":"testclientid","client-secret":"testclientsecret","access-token":"","token-expiry":0}]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), out)
assert.Equal(fmt.Sprintf(`{"aura":{"base-url":"%s","auth-url":"%s","output":"default","default-credential":"test","credentials":[{"name":"test","client-id":"testclientid","client-secret":"testclientsecret","access-token":"","token-expiry":0}]}}`, clicfg.DefaultAuraBaseUrl, clicfg.DefaultAuraAuthUrl), out)
}

func TestUseCredentialIfDoesNotExist(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Once the key has a status of ready you can use it for creating new instances by
}
// NOTE: Instance delete should not return OK (200), it always returns 202
if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "created", "cloud_provider", "key_id", "region", "type"})
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ func TestCreateCustomerManagedKeysWithTenantIDInConfig(t *testing.T) {
"name": "test-cred",
"access-token": "dsa",
"token-expiry": 123
}],
}],
"output": "json",
"default-credential": "test-cred"
}
}`)
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/customermanagedkey/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func NewGetCmd() *cobra.Command {
}

if statusCode == http.StatusOK {
output.PrintBody(cmd, resBody)
output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "created", "cloud_provider", "key_id", "region", "type"})

}

Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/customermanagedkey/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You can filter keys in a particular tenant using --tenant-id. If the tenant flag
}

if statusCode == http.StatusOK {
output.PrintBody(cmd, resBody)
output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id"})

}

Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ For Enterprise instances you can specify a --customer-managed-key-id flag to use

// NOTE: Instance create should not return OK (200), it always returns 202, checking both just in case
if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
if err := output.PrintBody(cmd, resBody); err != nil {
if err := output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "connection_url", "username", "password", "cloud_provider", "region", "type"}); err != nil {
return err
}

Expand Down
1 change: 1 addition & 0 deletions neo4j/aura/internal/subcommands/instance/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ func TestCreateFreeInstanceWithConfigTenantId(t *testing.T) {
"access-token": "dsa",
"token-expiry": 123
}],
"output": "json",
"default-credential": "test-cred"
}
}`)
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ If another operation is being performed on the instance you are trying to delete
}
// NOTE: Instance delete should not return OK (200), it always returns 202
if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "connection_url", "cloud_provider", "region", "type", "memory"})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewGetCmd() *cobra.Command {
}

if statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "connection_url", "cloud_provider", "region", "type", "memory", "storage", "customer_managed_key_id"})
if err != nil {
return err
}
Expand Down
51 changes: 51 additions & 0 deletions neo4j/aura/internal/subcommands/instance/get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,57 @@ func TestGetInstance(t *testing.T) {
`)
}

func TestGetInstanceWithTableOutput(t *testing.T) {
helper := testutils.NewAuraTestHelper(t)
defer helper.Close()

instanceId := "2f49c2b3"

mockHandler := helper.NewRequestHandlerMock(fmt.Sprintf("/v1/instances/%s", instanceId), http.StatusOK, `{
"data": {
"id": "2f49c2b3",
"name": "Production",
"status": "running",
"tenant_id": "YOUR_TENANT_ID",
"cloud_provider": "gcp",
"connection_url": "YOUR_CONNECTION_URL",
"metrics_integration_url": "YOUR_METRICS_INTEGRATION_ENDPOINT",
"region": "europe-west1",
"type": "enterprise-db",
"memory": "8GB",
"storage": "16GB"
}
}`)

// TODO: Make a better way to override config
helper.SetConfig(`{
"aura": {
"credentials": [{
"name": "test-cred",
"access-token": "dsa",
"token-expiry": 123
}],
"default-credential": "test-cred",
"output": "default"
}
}`)

helper.ExecuteCommand(fmt.Sprintf("instance get %s", instanceId))

mockHandler.AssertCalledTimes(1)
mockHandler.AssertCalledWithMethod(http.MethodGet)

helper.AssertOut(`
┌──────────┬────────────┬────────────────┬─────────┬─────────────────────┬────────────────┬──────────────┬───────────────┬────────┬─────────┬─────────────────────────┐
│ ID │ NAME │ TENANT_ID │ STATUS │ CONNECTION_URL │ CLOUD_PROVIDER │ REGION │ TYPE │ MEMORY │ STORAGE │ CUSTOMER_MANAGED_KEY_ID │
├──────────┼────────────┼────────────────┼─────────┼─────────────────────┼────────────────┼──────────────┼───────────────┼────────┼─────────┼─────────────────────────┤
│ 2f49c2b3 │ Production │ YOUR_TENANT_ID │ running │ YOUR_CONNECTION_URL │ gcp │ europe-west1 │ enterprise-db │ 8GB │ 16GB │ │
└──────────┴────────────┴────────────────┴─────────┴─────────────────────┴────────────────┴──────────────┴───────────────┴────────┴─────────┴─────────────────────────┘
`)

}

func TestGetInstanceNotFoundError(t *testing.T) {
helper := testutils.NewAuraTestHelper(t)
defer helper.Close()
Expand Down
3 changes: 1 addition & 2 deletions neo4j/aura/internal/subcommands/instance/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ You can filter instances in a particular tenant using --tenant-id. If the tenant
}

if statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "cloud_provider"})
if err != nil {
return err
}

}
return nil
},
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/pause.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ If another operation is being performed on the instance you are trying to pause,

// NOTE: Instance pause should not return OK (200), it always returns 202
if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "status", "tenant_id", "connection_url", "cloud_provider", "region", "type", "memory"})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/resume.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ If another operation is being performed on the instance you are trying to resume

// NOTE: Instance resume should not return OK (200), it always returns 202
if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "connection_url", "cloud_provider", "region", "type", "memory"})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/instance/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Resizing an instance is an asynchronous operation. The instance remains availabl
}

if statusCode == http.StatusAccepted || statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name", "tenant_id", "status", "connection_url", "cloud_provider", "region", "type", "memory"})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/tenant/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewGetCmd() *cobra.Command {
}

if statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name"})
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion neo4j/aura/internal/subcommands/tenant/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewListCmd() *cobra.Command {
}

if statusCode == http.StatusOK {
err = output.PrintBody(cmd, resBody)
err = output.PrintBody(cmd, resBody, []string{"id", "name"})
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion neo4j/aura/internal/test/testutils/auratesthelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,8 @@ func NewAuraTestHelper(t *testing.T) AuraTestHelper {
"access-token": "dsa",
"token-expiry": 123
}],
"default-credential": "test-cred"
"default-credential": "test-cred",
"output": "json"
}
}`

Expand Down

0 comments on commit 2d32a4a

Please sign in to comment.