fleet/server/datastore/mysql/mysql.go
Zachary Wasserman 5371647d2c Add builtin labels via migration (#728)
Changing from the existing method of adding built in labels at server startup.
This new method should be friendlier to long term changes, and falls in line
with the new pattern established for osquery options.

Fixes #702
2017-01-06 12:30:43 -08:00

185 lines
3.8 KiB
Go

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"
"github.com/kolide/kolide-ose/server/datastore/mysql/migrations/data"
"github.com/kolide/kolide-ose/server/datastore/mysql/migrations/tables"
"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
config config.MysqlConfig
}
// New creates an MySQL datastore.
func New(config config.MysqlConfig, c clock.Clock, opts ...DBOption) (*Datastore, error) {
options := &dbOptions{
maxAttempts: defaultMaxAttempts,
logger: log.NewNopLogger(),
}
for _, setOpt := range opts {
setOpt(options)
}
db, err := sqlx.Open("mysql", generateMysqlConnectionString(config))
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
}
interval := time.Duration(attempt) * time.Second
options.logger.Log("mysql", fmt.Sprintf(
"could not connect to db: %v, sleeping %v", dbError, interval))
time.Sleep(interval)
}
if dbError != nil {
return nil, dbError
}
ds := &Datastore{
db: db,
logger: options.logger,
clock: c,
config: config,
}
return ds, nil
}
func (d *Datastore) Name() string {
return "mysql"
}
func (d *Datastore) MigrateTables() error {
if err := tables.MigrationClient.Up(d.db.DB, ""); err != nil {
return err
}
return nil
}
func (d *Datastore) MigrateData() error {
if err := data.MigrationClient.Up(d.db.DB, ""); err != nil {
return err
}
return nil
}
// Drop removes database
func (d *Datastore) Drop() error {
tables := []struct {
Name string `db:"TABLE_NAME"`
}{}
sql := `
SELECT TABLE_NAME
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = ?;
`
if err := d.db.Select(&tables, sql, d.config.Database); err != nil {
return err
}
tx, err := d.db.Begin()
if err != nil {
return err
}
_, 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()
}
// HealthCheck returns an error if the MySQL backend is not healthy.
func (d *Datastore) HealthCheck() error {
_, err := d.db.Exec("select 1")
return err
}
// 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
}
// generateMysqlConnectionString returns a MySQL connection string using the
// provided configuration.
func generateMysqlConnectionString(conf config.MysqlConfig) string {
return fmt.Sprintf(
"%s:%s@(%s)/%s?charset=utf8&parseTime=true&loc=UTC",
conf.Username,
conf.Password,
conf.Address,
conf.Database,
)
}