2016-11-16 13:47:49 +00:00
|
|
|
package mysql
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/WatchBeam/clock"
|
|
|
|
"github.com/go-kit/kit/log"
|
2016-11-18 17:02:51 +00:00
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
"github.com/kolide/kolide-ose/server/config"
|
2016-11-18 17:02:51 +00:00
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
"github.com/kolide/kolide-ose/server/kolide"
|
2016-11-18 17:02:51 +00:00
|
|
|
"github.com/pressly/goose"
|
2016-11-16 13:47:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates an MySQL datastore.
|
|
|
|
func New(dbConnectString string, 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", dbConnectString)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
ds := &Datastore{db, options.logger, c}
|
|
|
|
|
|
|
|
return ds, nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Datastore) Name() string {
|
|
|
|
return "mysql"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Migrate creates database
|
|
|
|
func (d *Datastore) Migrate() error {
|
|
|
|
|
2016-11-18 17:02:51 +00:00
|
|
|
goose.SetDialect("mysql")
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2016-11-18 17:02:51 +00:00
|
|
|
if err := goose.Run("up", d.db.DB, "."); err != nil {
|
2016-11-16 13:47:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-11-25 18:08:22 +00:00
|
|
|
// Initialize preload data needed by the application
|
|
|
|
func (d *Datastore) Initialize() error {
|
|
|
|
if err := d.createBuiltinLabels(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-16 13:47:49 +00:00
|
|
|
// Drop removes database
|
|
|
|
func (d *Datastore) Drop() error {
|
2016-11-18 17:02:51 +00:00
|
|
|
goose.SetDialect("mysql")
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2016-11-18 17:02:51 +00:00
|
|
|
for {
|
|
|
|
version, err := goose.EnsureDBVersion(d.db.DB)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-11-16 13:47:49 +00:00
|
|
|
|
2016-11-18 17:02:51 +00:00
|
|
|
if version == 0 {
|
|
|
|
d.db.Exec("DROP TABLE IF EXISTS `goose_db_version`;")
|
|
|
|
return nil
|
2016-11-16 13:47:49 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 17:02:51 +00:00
|
|
|
if err = goose.Run("down", d.db.DB, "."); err != nil {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetMysqlConnectionString returns a MySQL connection string using the
|
|
|
|
// provided configuration.
|
|
|
|
func GetMysqlConnectionString(conf config.MysqlConfig) string {
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
}
|
2016-11-25 18:08:22 +00:00
|
|
|
|
|
|
|
func (d *Datastore) createBuiltinLabels() error {
|
|
|
|
// Nuke built in labels and recreate them
|
|
|
|
_, err := d.db.Exec("DELETE from labels WHERE label_type = ?", kolide.LabelTypeBuiltIn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
labels := []kolide.Label{
|
2016-12-15 02:27:22 +00:00
|
|
|
{
|
|
|
|
Platform: "all",
|
|
|
|
Name: "All Hosts",
|
|
|
|
Query: "select 1;",
|
|
|
|
LabelType: kolide.LabelTypeBuiltIn,
|
|
|
|
},
|
2016-11-25 18:08:22 +00:00
|
|
|
{
|
|
|
|
Platform: "darwin",
|
|
|
|
Name: "Mac OS X",
|
|
|
|
Query: "select 1 from osquery_info where build_platform = 'darwin';",
|
|
|
|
LabelType: kolide.LabelTypeBuiltIn,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Platform: "ubuntu",
|
|
|
|
Name: "Ubuntu Linux",
|
|
|
|
Query: "select 1 from osquery_info where build_platform = 'ubuntu';",
|
|
|
|
LabelType: kolide.LabelTypeBuiltIn,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Platform: "centos",
|
|
|
|
Name: "CentOS Linux",
|
|
|
|
Query: "select 1 from osquery_info where build_platform = 'centos';",
|
|
|
|
LabelType: kolide.LabelTypeBuiltIn,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Platform: "windows",
|
|
|
|
Name: "MS Windows",
|
|
|
|
Query: "select 1 from osquery_info where build_platform = 'windows';",
|
|
|
|
LabelType: kolide.LabelTypeBuiltIn,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, label := range labels {
|
|
|
|
_, err = d.NewLabel(&label)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|