2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/WatchBeam/clock"
|
|
|
|
|
"github.com/go-kit/kit/log"
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
|
"github.com/kolide/kolide-ose/server/config"
|
2017-01-05 17:27:56 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/datastore/mysql/migrations/data"
|
|
|
|
|
"github.com/kolide/kolide-ose/server/datastore/mysql/migrations/tables"
|
2016-11-16 13:47:49 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/kolide"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
defaultSelectLimit = 1000
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Datastore is an implementation of kolide.Datastore interface backed by
|
|
|
|
|
// MySQL
|
|
|
|
|
type Datastore struct {
|
|
|
|
|
db *sqlx.DB
|
|
|
|
|
logger log.Logger
|
|
|
|
|
clock clock.Clock
|
2017-01-03 16:54:24 +00:00
|
|
|
config config.MysqlConfig
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New creates an MySQL datastore.
|
2017-01-03 16:54:24 +00:00
|
|
|
func New(config config.MysqlConfig, c clock.Clock, opts ...DBOption) (*Datastore, error) {
|
2016-11-16 13:47:49 +00:00
|
|
|
options := &dbOptions{
|
|
|
|
|
maxAttempts: defaultMaxAttempts,
|
|
|
|
|
logger: log.NewNopLogger(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, setOpt := range opts {
|
|
|
|
|
setOpt(options)
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
db, err := sqlx.Open("mysql", generateMysqlConnectionString(config))
|
2016-11-16 13:47:49 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var dbError error
|
|
|
|
|
for attempt := 0; attempt < options.maxAttempts; attempt++ {
|
|
|
|
|
dbError = db.Ping()
|
|
|
|
|
if dbError == nil {
|
|
|
|
|
// we're connected!
|
|
|
|
|
break
|
|
|
|
|
}
|
2016-11-28 15:35:05 +00:00
|
|
|
interval := time.Duration(attempt) * time.Second
|
2016-11-16 13:47:49 +00:00
|
|
|
options.logger.Log("mysql", fmt.Sprintf(
|
2016-11-28 15:35:05 +00:00
|
|
|
"could not connect to db: %v, sleeping %v", dbError, interval))
|
|
|
|
|
time.Sleep(interval)
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dbError != nil {
|
|
|
|
|
return nil, dbError
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
ds := &Datastore{
|
|
|
|
|
db: db,
|
|
|
|
|
logger: options.logger,
|
|
|
|
|
clock: c,
|
|
|
|
|
config: config,
|
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
|
|
|
|
return ds, nil
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Datastore) Name() string {
|
|
|
|
|
return "mysql"
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-05 17:27:56 +00:00
|
|
|
func (d *Datastore) MigrateTables() error {
|
|
|
|
|
if err := tables.MigrationClient.Up(d.db.DB, ""); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2017-01-05 17:27:56 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2017-01-05 17:27:56 +00:00
|
|
|
func (d *Datastore) MigrateData() error {
|
|
|
|
|
if err := data.MigrationClient.Up(d.db.DB, ""); err != nil {
|
2016-11-16 13:47:49 +00:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Drop removes database
|
|
|
|
|
func (d *Datastore) Drop() error {
|
2017-01-03 16:54:24 +00:00
|
|
|
tables := []struct {
|
|
|
|
|
Name string `db:"TABLE_NAME"`
|
|
|
|
|
}{}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
sql := `
|
|
|
|
|
SELECT TABLE_NAME
|
|
|
|
|
FROM INFORMATION_SCHEMA.TABLES
|
|
|
|
|
WHERE TABLE_SCHEMA = ?;
|
|
|
|
|
`
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
if err := d.db.Select(&tables, sql, d.config.Database); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
tx, err := d.db.Begin()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
_, err = tx.Exec("SET FOREIGN_KEY_CHECKS = 0")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return tx.Rollback()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, table := range tables {
|
|
|
|
|
_, err = tx.Exec(fmt.Sprintf("DROP TABLE %s;", table.Name))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return tx.Rollback()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_, err = tx.Exec("SET FOREIGN_KEY_CHECKS = 1")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return tx.Rollback()
|
|
|
|
|
}
|
|
|
|
|
return tx.Commit()
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
|
2016-12-22 17:07:47 +00:00
|
|
|
// HealthCheck returns an error if the MySQL backend is not healthy.
|
|
|
|
|
func (d *Datastore) HealthCheck() error {
|
|
|
|
|
_, err := d.db.Exec("select 1")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
// Close frees resources associated with underlying mysql connection
|
|
|
|
|
func (d *Datastore) Close() error {
|
|
|
|
|
return d.db.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Datastore) log(msg string) {
|
|
|
|
|
d.logger.Log("comp", d.Name(), "msg", msg)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func appendListOptionsToSQL(sql string, opts kolide.ListOptions) string {
|
|
|
|
|
if opts.OrderKey != "" {
|
|
|
|
|
direction := "ASC"
|
|
|
|
|
if opts.OrderDirection == kolide.OrderDescending {
|
|
|
|
|
direction = "DESC"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql = fmt.Sprintf("%s ORDER BY %s %s", sql, opts.OrderKey, direction)
|
|
|
|
|
}
|
|
|
|
|
// REVIEW: If caller doesn't supply a limit apply a default limit of 1000
|
|
|
|
|
// to insure that an unbounded query with many results doesn't consume too
|
|
|
|
|
// much memory or hang
|
|
|
|
|
if opts.PerPage == 0 {
|
|
|
|
|
opts.PerPage = defaultSelectLimit
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql = fmt.Sprintf("%s LIMIT %d", sql, opts.PerPage)
|
|
|
|
|
|
|
|
|
|
offset := opts.PerPage * opts.Page
|
|
|
|
|
|
|
|
|
|
if offset > 0 {
|
|
|
|
|
sql = fmt.Sprintf("%s OFFSET %d", sql, offset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return sql
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-03 16:54:24 +00:00
|
|
|
// generateMysqlConnectionString returns a MySQL connection string using the
|
2016-11-16 13:47:49 +00:00
|
|
|
// provided configuration.
|
2017-01-03 16:54:24 +00:00
|
|
|
func generateMysqlConnectionString(conf config.MysqlConfig) string {
|
2016-11-16 13:47:49 +00:00
|
|
|
return fmt.Sprintf(
|
2016-12-01 18:31:16 +00:00
|
|
|
"%s:%s@(%s)/%s?charset=utf8&parseTime=true&loc=UTC",
|
2016-11-16 13:47:49 +00:00
|
|
|
conf.Username,
|
|
|
|
|
conf.Password,
|
|
|
|
|
conf.Address,
|
|
|
|
|
conf.Database,
|
|
|
|
|
)
|
|
|
|
|
}
|