Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for sql databases #4

Merged
merged 9 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add support for sql databases
  • Loading branch information
anbraten committed Apr 22, 2022
commit 9174ec4e7fab8acdc5409aff75aa617ec712c9ac
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ spec:

- mongo :white_check_mark:
- couchdb :white_check_mark:
- mysql :hammer:
- postgres :clock1:
- mysql :white_check_mark:
- postgres :white_check_mark:
- mssql :white_check_mark:

## Deployment

Expand Down
2 changes: 1 addition & 1 deletion adapters/adapter_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type DatabaseAdapter interface {
CreateDatabase(ctx context.Context, database string) error
DeleteDatabase(ctx context.Context, database string) error
HasDatabaseUserWithAccess(ctx context.Context, database string, username string) (bool, error)
CreateDatabaseUser(ctx context.Context, database string, username string, password string) error
CreateDatabaseUser(ctx context.Context, database string, username string, quotedPassword string) error
DeleteDatabaseUser(ctx context.Context, database string, username string) error
Close(ctx context.Context) error
}
1 change: 0 additions & 1 deletion adapters/couchdb_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func (adapter couchdbAdapter) HasDatabaseUserWithAccess(ctx context.Context, dat
}

func (adapter couchdbAdapter) CreateDatabaseUser(ctx context.Context, database string, username string, password string) error {

exists, err := adapter.db.DBExists(ctx, "_users")
if err != nil {
return err
Expand Down
9 changes: 5 additions & 4 deletions adapters/couchdb_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ func TestCouchDB(t *testing.T) {
databasePort := "5984"

ctx := context.Background()
couchdbUrl := fmt.Sprintf("http:https://admin:1234@%s:%s", databaseHost, databasePort)
adapter, err := adapters.GetCouchdbConnection(ctx, couchdbUrl)
url := fmt.Sprintf("http:https://%s:%s@%s:%s", "admin", "pA%sw0rd", databaseHost, databasePort)
adapter, err := adapters.GetCouchdbConnection(ctx, url)
if err != nil {
t.Fatalf("Error opening database connection: %s", err)
}

clientConnectTest := func(databaseName string, databaseUsername string, databasePassword string) error {
client, err := kivik.New("couch", fmt.Sprintf("http:https://%s:%s@%s:%s", databaseUsername, databasePassword, databaseHost, databasePort))
clientConnectTest := func(ctx context.Context, databaseName string, databaseUsername string, databasePassword string) error {
url := fmt.Sprintf("http:https://%s:%s@%s:%s", databaseUsername, databasePassword, databaseHost, databasePort)
client, err := kivik.New("couch", url)
if err != nil {
return err
}
Expand Down
9 changes: 5 additions & 4 deletions adapters/mongo_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ func TestMongoDB(t *testing.T) {
databasePort := "27017"

ctx := context.Background()
mongodbUrl := fmt.Sprintf("mongodb:https://admin:1234@%s:%s/?authSource=admin", databaseHost, databasePort)
adapter, err := adapters.GetMongoConnection(ctx, mongodbUrl)
url := fmt.Sprintf("mongodb:https://%s:%s@%s:%s/?authSource=admin", "admin", "pA%sw0rd", databaseHost, databasePort)
adapter, err := adapters.GetMongoConnection(ctx, url)
if err != nil {
t.Fatalf("Error opening database connection: %s", err)
}

clientConnectTest := func(databaseName string, databaseUsername string, databasePassword string) error {
clientOpts := options.Client().ApplyURI(fmt.Sprintf("mongodb:https://%s:%s@%s:%s/%s", databaseUsername, databasePassword, databaseHost, databasePort, databaseName))
clientConnectTest := func(ctx context.Context, databaseName string, databaseUsername string, databasePassword string) error {
url := fmt.Sprintf("mongodb:https://%s:%s@%s:%s/%s", databaseUsername, databasePassword, databaseHost, databasePort, databaseName)
clientOpts := options.Client().ApplyURI(url)
client, err := mongo.Connect(ctx, clientOpts)
if err != nil {
return err
Expand Down
75 changes: 75 additions & 0 deletions adapters/mssql_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package adapters

import (
"context"
"database/sql"
"fmt"

_ "github.com/denisenkom/go-mssqldb"
)

type mssqlAdapter struct {
db *sql.DB
}

func (adapter mssqlAdapter) HasDatabase(ctx context.Context, database string) (bool, error) {
var count int
query := fmt.Sprintf("SELECT COUNT(*) FROM master.sys.databases WHERE name=@p1")
err := adapter.db.QueryRowContext(ctx, query, database).Scan(&count)
if err != nil {
return false, err
}
return count == 1, nil
}

func (adapter mssqlAdapter) CreateDatabase(ctx context.Context, database string) error {
_, err := adapter.db.ExecContext(ctx, "CREATE DATABASE @p1;", database)
return err
}

func (adapter mssqlAdapter) DeleteDatabase(ctx context.Context, database string) error {
_, err := adapter.db.ExecContext(ctx, "DROP DATABASE @p1;", database)
return err
}

func (adapter mssqlAdapter) HasDatabaseUserWithAccess(ctx context.Context, database string, username string) (bool, error) {
// TODO implement
return false, nil
}

func (adapter mssqlAdapter) CreateDatabaseUser(ctx context.Context, database string, username string, password string) error {
// make password sql safe
quotedPassword := QuoteLiteral(password)
query := fmt.Sprintf("CREATE USER %s WITH PASSWORD = %s", username, quotedPassword)
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", database, username)
_, err = adapter.db.ExecContext(ctx, query)

return err
}

func (adapter mssqlAdapter) DeleteDatabaseUser(ctx context.Context, database string, username string) error {
// TODO implement
return nil
}

func (adapter mssqlAdapter) Close(ctx context.Context) error {
return adapter.db.Close()
}

func GetMssqlConnection(ctx context.Context, url string) (*mssqlAdapter, error) {
db, err := sql.Open("mssql", url)
if err != nil {
return nil, err
}

adapter := mssqlAdapter{
db: db,
}

return &adapter, nil
}
35 changes: 35 additions & 0 deletions adapters/mssql_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package adapters_test

import (
"context"
"database/sql"
"fmt"
"testing"

"github.com/anbraten/k8s-external-database-operator/adapters"
)

func TestMsSqlDB(t *testing.T) {
databaseHost := "localhost"
databasePort := "1433"

ctx := context.Background()
url := fmt.Sprintf("mssql:https://%s:%s@%s:%s", "sa", "pA%sw0rd", databaseHost, databasePort)
adapter, err := adapters.GetCouchdbConnection(ctx, url)
if err != nil {
t.Fatalf("Error opening database connection: %s", err)
}

clientConnectTest := func(ctx context.Context, databaseName string, databaseUsername string, databasePassword string) error {
url := fmt.Sprintf("sqlserver:https://%s:%s@%s:%s", databaseUsername, databasePassword, databaseHost, databasePort)
client, err := sql.Open("sqlserver", url)
if err != nil {
return err
}

_, err = client.ExecContext(ctx, "CREATE TABLE test (id int);")
return err
}

testHelper(t, ctx, adapter, clientConnectTest)
}
55 changes: 39 additions & 16 deletions adapters/mysql_adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package adapters
import (
"context"
"database/sql"
"fmt"

// SQL driver for mysql
// load SQL driver for mysql
_ "github.com/go-sql-driver/mysql"
)

Expand All @@ -13,40 +14,62 @@ type mysqlAdapter struct {
}

func (adapter mysqlAdapter) HasDatabase(ctx context.Context, database string) (bool, error) {
return false, nil
var count int
query := "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=?"
err := adapter.db.QueryRowContext(ctx, query, database).Scan(&count)
return count == 1, err
}

func (adapter mysqlAdapter) CreateDatabase(ctx context.Context, name string) error {
_, err := adapter.db.Exec("CREATE DATABASE IF NOT EXISTS $1;", name)
func (adapter mysqlAdapter) CreateDatabase(ctx context.Context, database string) error {
query := fmt.Sprintf("CREATE DATABASE %s;", database)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

func (adapter mysqlAdapter) DeleteDatabase(ctx context.Context, name string) error {
_, err := adapter.db.Exec("DROP DATABASE IF EXISTS $1;", name)
func (adapter mysqlAdapter) DeleteDatabase(ctx context.Context, database string) error {
query := fmt.Sprintf("DROP DATABASE %s;", database)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

func (adapter mysqlAdapter) HasDatabaseUserWithAccess(ctx context.Context, username string, database string) (bool, error) {
// TODO implement
return false, nil
func (adapter mysqlAdapter) HasDatabaseUserWithAccess(ctx context.Context, database string, username string) (bool, error) {
var count int
query := "SELECT COUNT(*) FROM mysql.db WHERE Db=? AND USER=?"
err := adapter.db.QueryRowContext(ctx, query, database, username).Scan(&count)
return count == 1, err
}

func (adapter mysqlAdapter) CreateDatabaseUser(ctx context.Context, username string, password string, database string) error {
// TODO implement
return nil
func (adapter mysqlAdapter) CreateDatabaseUser(ctx context.Context, database string, username string, password string) error {
// make password sql safe
quotedPassword := QuoteLiteral(password)
query := fmt.Sprintf("CREATE USER '%s'@'%%' IDENTIFIED BY %s;", username, quotedPassword)
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("GRANT ALL PRIVILEGES ON %s.* TO '%s'@'%%';", database, username)
_, err = adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

_, err = adapter.db.ExecContext(ctx, "FLUSH PRIVILEGES;")
return err
}

func (adapter mysqlAdapter) DeleteDatabaseUser(ctx context.Context, database string, username string) error {
// TODO implement
return nil
query := fmt.Sprintf("DROP USER %s;", username)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

func (adapter mysqlAdapter) Close(ctx context.Context) error {
return adapter.db.Close()
}

func GetMysqlConnection(ctx context.Context, host string, adminUsername string, adminPassword string) (*mysqlAdapter, error) {
db, err := sql.Open("mysql", adminUsername+":"+adminPassword+"@"+host)
func GetMysqlConnection(ctx context.Context, dsn string) (*mysqlAdapter, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
Expand Down
35 changes: 35 additions & 0 deletions adapters/mysql_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package adapters_test

import (
"context"
"database/sql"
"fmt"
"testing"

"github.com/anbraten/k8s-external-database-operator/adapters"
)

func TestMySqlDB(t *testing.T) {
databaseHost := "localhost"
databasePort := "3306"

ctx := context.Background()
url := fmt.Sprintf("%s:%s@tcp(%s:%s)/", "root", "pA%sw0rd", databaseHost, databasePort)
adapter, err := adapters.GetMysqlConnection(ctx, url)
if err != nil {
t.Fatalf("Error opening database connection: %s", err)
}

clientConnectTest := func(ctx context.Context, databaseName string, databaseUsername string, databasePassword string) error {
url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", databaseUsername, databasePassword, databaseHost, databasePort, databaseName)
client, err := sql.Open("mysql", url)
if err != nil {
return err
}

_, err = client.ExecContext(ctx, "CREATE TABLE test (id int);")
return err
}

testHelper(t, ctx, adapter, clientConnectTest)
}
90 changes: 90 additions & 0 deletions adapters/postgres_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package adapters

import (
"context"
"database/sql"
"fmt"

// load SQL driver for postgres
_ "github.com/lib/pq"
)

type postgresAdapter struct {
db *sql.DB
}

func (adapter postgresAdapter) HasDatabase(ctx context.Context, database string) (bool, error) {
var count int
query := fmt.Sprintf("SELECT COUNT(*) FROM pg_database WHERE datname=%s", database)
err := adapter.db.QueryRowContext(ctx, query).Scan(&count)
if err != nil {
return false, err
}
return count == 1, nil
}

func (adapter postgresAdapter) CreateDatabase(ctx context.Context, database string) error {
query := fmt.Sprintf("CREATE DATABASE %s", database)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

func (adapter postgresAdapter) DeleteDatabase(ctx context.Context, database string) error {
query := fmt.Sprintf("DROP DATABASE %s", database)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

func (adapter postgresAdapter) HasDatabaseUserWithAccess(ctx context.Context, database string, username string) (bool, error) {
var hasPrivilege bool
query := fmt.Sprintf("SELECT has_database_privilege(%s, %s, 'CONNECT');", username, database)
err := adapter.db.QueryRowContext(ctx, query).Scan(&hasPrivilege)
if err != nil {
return false, err
}
return hasPrivilege, nil
}

func (adapter postgresAdapter) CreateDatabaseUser(ctx context.Context, database string, username string, password string) error {
// make password sql safe
quotedPassword := QuoteLiteral(password)
query := fmt.Sprintf("CREATE USER %s WITH PASSWORD %s", username, quotedPassword)
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", database, username)
_, err = adapter.db.ExecContext(ctx, query)

return err
}

func (adapter postgresAdapter) DeleteDatabaseUser(ctx context.Context, database string, username string) error {
query := fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s; REVOKE ALL ON SCHEMA public FROM %s;", database, username, username)
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("DROP OWNED BY %s; DROP USER %s;", username, username)
_, err = adapter.db.ExecContext(ctx, query)
return err
}

func (adapter postgresAdapter) Close(ctx context.Context) error {
return adapter.db.Close()
}

func GetPostgresConnection(ctx context.Context, url string) (*postgresAdapter, error) {
db, err := sql.Open("postgres", url)
if err != nil {
return nil, err
}

adapter := postgresAdapter{
db: db,
}

return &adapter, nil
}
Loading