Skip to content

Commit 72a7252

Browse files
feat(ArangoDB): add additional ArangoDB connection setup
1 parent 2e1d598 commit 72a7252

5 files changed

Lines changed: 157 additions & 43 deletions

File tree

dbee/adapters/arango.go

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/gob"
55
"fmt"
66
"net/url"
7+
"strings"
78

89
"github.com/arangodb/go-driver/v2/arangodb"
910
"github.com/arangodb/go-driver/v2/connection"
@@ -22,37 +23,66 @@ var _ core.Adapter = (*Arango)(nil)
2223

2324
type Arango struct{}
2425

26+
// ArangoDB connection string format
27+
// http://<username>:<password>@<hostname>:<port>/_db/<database>?
2528
func (p *Arango) Connect(rawUrl string) (core.Driver, error) {
2629
u, err := url.Parse(rawUrl)
2730
if err != nil {
28-
return nil, fmt.Errorf("arango: invalid url: %w", err)
31+
return nil, fmt.Errorf("failed to parse connection string")
2932
}
30-
// Set authentication
33+
if u.Scheme != "http" && u.Scheme != "https" {
34+
return nil, fmt.Errorf("unexpected scheme: %q", u.Scheme)
35+
}
36+
insecureSkipVerify := u.Query().Get("insecure_skip_verify") == "true"
3137
dbUrl := fmt.Sprintf("%s://%s", u.Scheme, u.Host)
3238
endpoint := connection.NewRoundRobinEndpoints([]string{dbUrl})
33-
conn := connection.NewHttpConnection(connection.DefaultHTTPConfigurationWrapper(endpoint, true))
34-
if u.Scheme == "https" {
35-
conn = connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, false))
39+
conn := connection.NewHttpConnection(connection.DefaultHTTPConfigurationWrapper(endpoint, insecureSkipVerify))
40+
if u.Scheme == "https" && u.Query().Get("use_http2") == "true" {
41+
conn = connection.NewHttp2Connection(connection.DefaultHTTP2ConfigurationWrapper(endpoint, insecureSkipVerify))
3642
}
3743

3844
if u.User != nil {
3945
// Basic Authentication
4046
username := u.User.Username()
41-
password, _ := u.User.Password()
47+
isRootUser := username == "root"
48+
allowEmptyRootPassword := u.Query().Get("allow_empty_root_password") == "true"
49+
password, ok := u.User.Password()
50+
if !ok && !(isRootUser && allowEmptyRootPassword) {
51+
return nil, fmt.Errorf("arango: missing password")
52+
}
4253
auth := connection.NewJWTAuthWrapper(username, password)
4354
conn = auth(conn)
4455
}
4556

4657
conn = connection.NewConnectionAsyncWrapper(conn)
58+
dbName := parseDatabaseNameFromPath(u.Path)
4759

4860
// Create a client
4961
client := arangodb.NewClient(conn)
5062
return &arangoDriver{
5163
c: client,
52-
dbName: "_system",
64+
dbName: dbName,
5365
}, nil
5466
}
5567

68+
func parseDatabaseNameFromPath(s string) string {
69+
if s == "" {
70+
return "_system"
71+
}
72+
pathParts := strings.Split(s, "/")
73+
lastPartWasDb := false
74+
for _, part := range pathParts {
75+
if lastPartWasDb {
76+
return part
77+
}
78+
if part == "_db" {
79+
lastPartWasDb = true
80+
}
81+
}
82+
83+
return "_system"
84+
}
85+
5686
// Arango helpers will be different as they require http to get collections
5787
func (*Arango) GetHelpers(opts *core.TableOptions) map[string]string {
5888
return map[string]string{

dbee/adapters/arango_driver.go

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10-
"log"
1110

1211
"github.com/arangodb/go-driver/v2/arangodb"
1312

@@ -26,11 +25,6 @@ type arangoDriver struct {
2625
}
2726

2827
func (a *arangoDriver) ListDatabases() (current string, available []string, err error) {
29-
if a == nil || a.c == nil {
30-
return "", nil, errors.New("arangoDriver is not initialized")
31-
}
32-
33-
log.Print("Fetching list of databases")
3428
databases, err := a.c.AccessibleDatabases(context.Background())
3529
if err != nil {
3630
return "", nil, fmt.Errorf("failed to list databases: %w", err)
@@ -45,17 +39,11 @@ func (a *arangoDriver) ListDatabases() (current string, available []string, err
4539
}
4640

4741
func (a *arangoDriver) SelectDatabase(name string) error {
48-
if a == nil {
49-
return errors.New("arangoDriver is not initialized")
50-
}
51-
log.Printf("Selecting database: %s", name)
5242
a.dbName = name
5343
return nil
5444
}
5545

56-
func (a *arangoDriver) Close() {
57-
log.Print("Closing connection (not yet implemented)")
58-
}
46+
func (a *arangoDriver) Close() {}
5947

6048
func (a *arangoDriver) Columns(opts *core.TableOptions) ([]*core.Column, error) {
6149
db, err := a.c.GetDatabase(context.Background(), a.dbName, nil)
@@ -70,7 +58,7 @@ func (a *arangoDriver) Columns(opts *core.TableOptions) ([]*core.Column, error)
7058
COLLECT attribute = a WITH COUNT INTO len
7159
SORT len DESC
7260
LIMIT 10
73-
sort attribute
61+
SORT attribute
7462
RETURN {attribute}`
7563

7664
bindVars := map[string]any{"@col": opts.Table}
@@ -89,7 +77,7 @@ func (a *arangoDriver) Columns(opts *core.TableOptions) ([]*core.Column, error)
8977
}
9078
column, ok := doc["attribute"].(string)
9179
if !ok {
92-
column = ""
80+
return nil, fmt.Errorf("failed to read document: %w", err)
9381
}
9482
columns = append(columns, &core.Column{Type: "collection", Name: column})
9583
}
@@ -131,14 +119,20 @@ func (a *arangoDriver) Query(ctx context.Context, query string) (core.ResultStre
131119
}
132120

133121
func (a *arangoDriver) Structure() ([]*core.Structure, error) {
134-
if a == nil || a.c == nil {
135-
return nil, errors.New("arangoDriver is not initialized")
136-
}
137-
138122
ctx := context.Background()
139-
databases, err := a.c.Databases(ctx)
140-
if err != nil {
141-
return nil, fmt.Errorf("failed to list databases: %w", err)
123+
databases := []arangodb.Database{}
124+
err := errors.New("database not selected")
125+
if a.dbName == "_system" { // if the database is _system, we'll walk all databases
126+
databases, err = a.c.Databases(ctx)
127+
if err != nil {
128+
return nil, fmt.Errorf("failed to list databases: %w", err)
129+
}
130+
} else {
131+
database, err := a.c.GetDatabase(ctx, a.dbName, nil)
132+
if err != nil {
133+
return nil, fmt.Errorf("failed to get database: %w", err)
134+
}
135+
databases = []arangodb.Database{database}
142136
}
143137

144138
structures := make([]*core.Structure, len(databases))

dbee/adapters/arango_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ func TestArango_Connect(t *testing.T) {
2929
{
3030
name: "should fail with missing password with root user",
3131
connectionURL: "http://root@hostname.com:8529",
32-
database: "_system",
32+
wantErr: true,
33+
messageErr: "arango: missing password",
3334
},
34-
3535
{
3636
name: "should succeed with valid connection with root user",
3737
connectionURL: "http://root@hostname.com:8529?allow_empty_root_password=true",
@@ -47,6 +47,11 @@ func TestArango_Connect(t *testing.T) {
4747
connectionURL: "http://token:dummytoken@hostname.com:8529/_db/testdb",
4848
database: "testdb",
4949
},
50+
{
51+
name: "should succeed with valid connection with options",
52+
connectionURL: "http://token:dummytoken@hostname.com:8529/_db/testdb?insecure_skip_verify",
53+
database: "testdb",
54+
},
5055
}
5156
for _, tt := range tests {
5257
t.Run(tt.name, func(t *testing.T) {

dbee/tests/integration/arangodb_integration_test.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,80 @@ import (
1515
)
1616

1717
// ArangoDBTestSuite is the test suite for the ArangoDB adapter.
18-
type ArangoDBTestSuite struct {
18+
type BaseArangoDBTestSuite struct {
1919
tsuite.Suite
2020
ctr *th.ArangoDBContainer
2121
ctx context.Context
2222
d *core.Connection
2323
}
2424

25+
type ArangoDBTestSuite struct {
26+
BaseArangoDBTestSuite
27+
}
28+
29+
type NonSystemArangoDBTestSuite struct {
30+
BaseArangoDBTestSuite
31+
}
32+
33+
type PasswordlessArangoDBTestSuite struct {
34+
BaseArangoDBTestSuite
35+
}
36+
2537
// TestArangoDBTestSuite is the entrypoint for go test.
2638
func TestArangoDBTestSuite(t *testing.T) {
2739
tsuite.Run(t, new(ArangoDBTestSuite))
2840
}
2941

30-
func (suite *ArangoDBTestSuite) SetupSuite() {
42+
func TestNonSystemArangoDBTestSuite(t *testing.T) {
43+
tsuite.Run(t, new(NonSystemArangoDBTestSuite))
44+
}
45+
46+
func TestPasswordlessArangoDBTestSuite(t *testing.T) {
47+
tsuite.Run(t, new(PasswordlessArangoDBTestSuite))
48+
}
49+
50+
func (suite *BaseArangoDBTestSuite) SetupSuite() {
3151
suite.ctx = context.Background()
3252
ctr, err := th.NewArangoDBContainer(suite.ctx, &core.ConnectionParams{
3353
ID: "test-arangodb",
3454
Name: "test-arangodb",
55+
}, &th.ArangoDBContainerParams{
56+
Passwordless: false,
57+
DatabaseName: "_system",
58+
})
59+
if err != nil {
60+
log.Fatal(err)
61+
}
62+
63+
suite.ctr = ctr
64+
suite.d = ctr.Driver
65+
}
66+
67+
func (suite *NonSystemArangoDBTestSuite) SetupSuite() {
68+
suite.ctx = context.Background()
69+
ctr, err := th.NewArangoDBContainer(suite.ctx, &core.ConnectionParams{
70+
ID: "test-arangodb",
71+
Name: "test-arangodb",
72+
}, &th.ArangoDBContainerParams{
73+
Passwordless: false,
74+
DatabaseName: "non-system",
75+
})
76+
if err != nil {
77+
log.Fatal(err)
78+
}
79+
80+
suite.ctr = ctr
81+
suite.d = ctr.Driver
82+
}
83+
84+
func (suite *PasswordlessArangoDBTestSuite) SetupSuite() {
85+
suite.ctx = context.Background()
86+
ctr, err := th.NewArangoDBContainer(suite.ctx, &core.ConnectionParams{
87+
ID: "test-arangodb",
88+
Name: "test-arangodb",
89+
}, &th.ArangoDBContainerParams{
90+
Passwordless: true,
91+
DatabaseName: "_system",
3592
})
3693
if err != nil {
3794
log.Fatal(err)
@@ -166,4 +223,3 @@ func (suite *ArangoDBTestSuite) TestShouldReturnColumns() {
166223
assert.NoError(t, err)
167224
assert.Equal(t, want, got)
168225
}
169-

dbee/tests/testhelpers/arangodb.go

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,40 @@ type ArangoDBContainer struct {
2121
Driver *core.Connection
2222
}
2323

24+
type ArangoDBContainerParams struct {
25+
Passwordless bool
26+
DatabaseName string
27+
}
28+
2429
// NewArangoDBContainer creates a new ArangoDB container with
2530
// default adapter and connection. The params.URL is overwritten.
26-
func NewArangoDBContainer(ctx context.Context, params *core.ConnectionParams) (*ArangoDBContainer, error) {
31+
func NewArangoDBContainer(ctx context.Context, params *core.ConnectionParams, containerParams *ArangoDBContainerParams) (*ArangoDBContainer, error) {
2732
seedFile, err := GetTestDataFile("arangodb_seed.json")
2833
if err != nil {
2934
return nil, err
3035
}
36+
37+
passwordless := false
38+
if containerParams != nil {
39+
passwordless = containerParams.Passwordless
40+
}
41+
42+
env := make(map[string]string, 0)
43+
if passwordless {
44+
env["ARANGO_ROOT_PASSWORD"] = "rootpassword"
45+
} else {
46+
env["ARANGO_NO_AUTH"] = "1"
47+
}
48+
3149
log.Printf("%s", seedFile.Name())
3250
req := tc.ContainerRequest{
3351
Image: "arangodb:3.12",
3452
ExposedPorts: []string{"8529:8529/tcp"},
3553
WaitingFor: wait.ForLog("ArangoDB (version 3.12.4 [linux]) is ready for business. Have fun!").WithStartupTimeout(1 * time.Minute),
36-
Env: map[string]string{
37-
"ARANGO_ROOT_PASSWORD": "rootpassword",
38-
},
54+
Env: env,
3955
Files: []tc.ContainerFile{
4056
{
41-
HostFilePath: "../testdata/arangodb_seed.json",
42-
// Reader: seedFile,
57+
HostFilePath: "../testdata/arangodb_seed.json",
4358
ContainerFilePath: "/docker-entrypoint-initdb.d/arangodb_seed.json",
4459
FileMode: 0o755,
4560
},
@@ -54,14 +69,25 @@ func NewArangoDBContainer(ctx context.Context, params *core.ConnectionParams) (*
5469
return nil, err
5570
}
5671

57-
exitCode, output, err := ctr.Exec(ctx, []string{
72+
args := []string{
5873
"arangoimport",
59-
"--server.password", "rootpassword",
6074
"--file", "/docker-entrypoint-initdb.d/arangodb_seed.json",
6175
"--type", "json",
6276
"--collection", "testcollection",
6377
"--create-collection",
64-
})
78+
}
79+
if !passwordless {
80+
args = append(args, "--server.password", "rootpassword")
81+
}
82+
83+
if containerParams != nil {
84+
if containerParams.DatabaseName != "" && containerParams.DatabaseName != "_system" {
85+
args = append(args, "--server.database", containerParams.DatabaseName)
86+
args = append(args, "--create-database")
87+
}
88+
}
89+
90+
exitCode, output, err := ctr.Exec(ctx, args)
6591
if err != nil {
6692
return nil, err
6793
}
@@ -78,6 +104,9 @@ func NewArangoDBContainer(ctx context.Context, params *core.ConnectionParams) (*
78104
}
79105

80106
connURL := "http://root:rootpassword@localhost:8529"
107+
if passwordless {
108+
connURL = "http://root@localhost:8529"
109+
}
81110
if params.Type == "" {
82111
params.Type = "arangodb"
83112
}

0 commit comments

Comments
 (0)