Skip to content

Commit

Permalink
feat: add support for sql databases (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
anbraten committed May 2, 2022
1 parent ab55025 commit 2f0e74b
Show file tree
Hide file tree
Showing 20 changed files with 713 additions and 79 deletions.
42 changes: 42 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Go Test

on:
push:
branches:
- main
tags:
- '*'
pull_request:
branches:
- main

jobs:
build:
name: Test on Go ${{ matrix.go-version }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.18.x]
os: [ubuntu-latest]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v1
with:
go-version: ${{ matrix.go-version }}
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Start containers
run: docker-compose up -d

- name: Test adapters on ${{ matrix.os }}
env:
GO111MODULE: on
run: |
go test -v -race ./adapters/...
- name: Stop containers
if: always()
run: docker-compose down
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
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
11 changes: 7 additions & 4 deletions adapters/couchdb_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ 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
}
defer client.Close(ctx)

_, _, err = client.DB(databaseName).CreateDoc(ctx, map[string]interface{}{"test": "test"})
return err
}
Expand Down
10 changes: 6 additions & 4 deletions adapters/mongo_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@ 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
}
defer client.Disconnect(ctx)

_, err = client.Database(databaseName).Collection("test").InsertOne(ctx, map[string]interface{}{"test": "test"})
return err
}
Expand Down
92 changes: 92 additions & 0 deletions adapters/mssql_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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='%s';", database)
err := adapter.db.QueryRowContext(ctx, query).Scan(&count)
if err != nil {
return false, err
}
return count == 1, nil
}

func (adapter mssqlAdapter) CreateDatabase(ctx context.Context, database string) error {
query := fmt.Sprintf("EXEC ('sp_configure ''contained database authentication'', 1; reconfigure;');")
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("CREATE DATABASE [%s] CONTAINMENT=PARTIAL;", database)
_, err = adapter.db.ExecContext(ctx, query)
return err
}

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

func (adapter mssqlAdapter) HasDatabaseUserWithAccess(ctx context.Context, database string, username string) (bool, error) {
var count int
query := fmt.Sprintf("USE [%s]; SELECT COUNT(*) FROM sys.database_principals WHERE authentication_type=2 AND name='%s';", database, username)
err := adapter.db.QueryRowContext(ctx, query).Scan(&count)
if err != nil {
return false, err
}
return count == 1, 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("USE [%s]; CREATE USER [%s] WITH PASSWORD=%s", database, username, quotedPassword)
_, err := adapter.db.ExecContext(ctx, query)
if err != nil {
return err
}

query = fmt.Sprintf("USE [%s]; ALTER ROLE db_owner ADD MEMBER [%s];", database, username)
_, err = adapter.db.ExecContext(ctx, query)
return err
}

func (adapter mssqlAdapter) DeleteDatabaseUser(ctx context.Context, database string, username string) error {
query := fmt.Sprintf("USE [%s]; DROP USER %s;", database, username)
_, err := adapter.db.ExecContext(ctx, query)
return err
}

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("sqlserver", url)
if err != nil {
return nil, err
}

adapter := mssqlAdapter{
db: db,
}

if err := adapter.db.PingContext(ctx); err != nil {
return nil, err
}

return &adapter, nil
}
36 changes: 36 additions & 0 deletions adapters/mssql_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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("sqlserver:https://%s:%s@%s:%s", "sa", "pA_sw0rd", databaseHost, databasePort)
adapter, err := adapters.GetMssqlConnection(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?database=%s", databaseUsername, databasePassword, databaseHost, databasePort, databaseName)
client, err := sql.Open("sqlserver", url)
if err != nil {
return err
}
defer client.Close()

_, 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
36 changes: 36 additions & 0 deletions adapters/mysql_adapter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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
}
defer client.Close()

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

testHelper(t, ctx, adapter, clientConnectTest)
}

0 comments on commit 2f0e74b

Please sign in to comment.