open-vault/physical/mysql.go

205 lines
5.1 KiB
Go
Raw Normal View History

package physical
import (
"database/sql"
"errors"
2015-06-12 05:56:25 +00:00
"fmt"
"sort"
2015-06-12 17:31:46 +00:00
"strings"
"time"
"github.com/armon/go-metrics"
_ "github.com/go-sql-driver/mysql"
)
var (
2015-06-12 05:56:25 +00:00
MySQLPrepareStmtFailure = errors.New("failed to prepare statement")
MySQLExecuteStmtFailure = errors.New("failed to execute statement")
)
// MySQLBackend is a physical backend that stores data
// within MySQL database.
type MySQLBackend struct {
2015-06-12 05:56:25 +00:00
table string
database string
client *sql.DB
statements map[string]*sql.Stmt
}
// newMySQLBackend constructs a MySQL backend using the given API client and
// server address and credential for accessing mysql database.
func newMySQLBackend(conf map[string]string) (Backend, error) {
// Get or set MySQL server address. Defaults to localhost and default port(3306)
address, ok := conf["address"]
if !ok {
address = "127.0.0.1:3306"
}
// Get the MySQL credentials to perform read/write operations.
username, ok := conf["username"]
password, ok := conf["password"]
// Get the MySQL database and table details.
database, ok := conf["database"]
if !ok {
2015-06-12 05:56:25 +00:00
return nil, fmt.Errorf("database name is missing in the configuration")
}
table, ok := conf["table"]
if !ok {
2015-06-12 05:56:25 +00:00
return nil, fmt.Errorf("table name is missing in the configuration")
}
// Create MySQL handle for the database.
dsn := username + ":" + password + "@tcp(" + address + ")/" + database
db, err := sql.Open("mysql", dsn)
if err != nil {
2015-06-12 05:56:25 +00:00
return nil, fmt.Errorf("failed to open handler with database")
}
2015-06-12 05:56:25 +00:00
// Create the required table if it doesn't exists.
create_query := "CREATE TABLE IF NOT EXISTS " + database + "." + table + " (vault_key varchar(512), vault_value mediumblob, PRIMARY KEY (vault_key))"
create_stmt, err := db.Prepare(create_query)
if err != nil {
return nil, MySQLPrepareStmtFailure
}
2015-06-12 05:56:25 +00:00
defer create_stmt.Close()
2015-06-12 05:56:25 +00:00
_, err = create_stmt.Exec()
if err != nil {
return nil, MySQLExecuteStmtFailure
}
2015-06-12 05:56:25 +00:00
// Map of query type as key to prepared statement.
2015-06-12 09:47:45 +00:00
statements := make(map[string]*sql.Stmt)
2015-06-12 05:56:25 +00:00
// Prepare statement for put query.
insert_query := "INSERT INTO " + database + "." + table + " VALUES( ?, ? ) ON DUPLICATE KEY UPDATE vault_value=VALUES(vault_value)"
insert_stmt, err := db.Prepare(insert_query)
if err != nil {
return nil, MySQLPrepareStmtFailure
}
statements["put"] = insert_stmt
// Prepare statement for select query.
select_query := "SELECT vault_value FROM " + database + "." + table + " WHERE vault_key = ?"
select_stmt, err := db.Prepare(select_query)
if err != nil {
return nil, MySQLPrepareStmtFailure
}
statements["get"] = select_stmt
// Prepare statement for delete query.
delete_query := "DELETE FROM " + database + "." + table + " WHERE vault_key = ?"
delete_stmt, err := db.Prepare(delete_query)
if err != nil {
return nil, MySQLPrepareStmtFailure
}
statements["delete"] = delete_stmt
// Setup the backend.
m := &MySQLBackend{
2015-06-12 05:56:25 +00:00
client: db,
table: table,
database: database,
statements: statements,
}
return m, nil
}
// Put is used to insert or update an entry.
func (m *MySQLBackend) Put(entry *Entry) error {
defer metrics.MeasureSince([]string{"mysql", "put"}, time.Now())
2015-06-12 05:56:25 +00:00
_, err := m.statements["put"].Exec(entry.Key, entry.Value)
if err != nil {
return err
}
return nil
}
// Get is used to fetch and entry.
func (m *MySQLBackend) Get(key string) (*Entry, error) {
defer metrics.MeasureSince([]string{"mysql", "get"}, time.Now())
var result []byte
2015-06-12 05:56:25 +00:00
err := m.statements["get"].QueryRow(key).Scan(&result)
if err != nil {
return nil, nil
}
2015-06-12 17:31:46 +00:00
// Handle a non-existing value
if result == nil {
return nil, nil
}
ent := &Entry{
Key: key,
Value: result,
}
return ent, nil
}
// Delete is used to permanently delete an entry
func (m *MySQLBackend) Delete(key string) error {
defer metrics.MeasureSince([]string{"mysql", "delete"}, time.Now())
2015-06-12 05:56:25 +00:00
_, err := m.statements["delete"].Exec(key)
if err != nil {
return err
}
return nil
}
// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (m *MySQLBackend) List(prefix string) ([]string, error) {
defer metrics.MeasureSince([]string{"mysql", "list"}, time.Now())
2015-06-12 09:47:45 +00:00
// Query to get all keys matching a prefix.
2015-06-12 05:56:25 +00:00
list_query := "SELECT vault_key FROM " + m.database + "." + m.table + " WHERE vault_key LIKE '" + prefix + "%'"
rows, err := m.client.Query(list_query)
if err != nil {
return nil, MySQLExecuteStmtFailure
}
columns, err := rows.Columns()
if err != nil {
2015-06-12 05:56:25 +00:00
return nil, fmt.Errorf("failed to get columns")
}
values := make([]sql.RawBytes, len(columns))
scanArgs := make([]interface{}, len(values))
for i := range values {
scanArgs[i] = &values[i]
}
keys := []string{}
for rows.Next() {
err = rows.Scan(scanArgs...)
if err != nil {
2015-06-12 05:56:25 +00:00
return nil, fmt.Errorf("failed to scan rows")
}
2015-06-12 17:31:46 +00:00
for _, key := range values {
key := strings.TrimPrefix(string(key), prefix)
if i := strings.Index(string(key), "/"); i == -1 {
// Add objects only from the current 'folder'
keys = append(keys, string(key))
} else if i != -1 {
// Add truncated 'folder' paths
keys = appendIfMissing(keys, string(key[:i+1]))
}
}
}
sort.Strings(keys)
return keys, nil
}