Warn before running migrations (#1385)

- Refactor MigrationStatus() to return relevant info
- Warn before running migrations

Closes #1368
This commit is contained in:
Zachary Wasserman
2017-03-09 10:40:52 -08:00
committed by GitHub
parent f510691ad0
commit b4e40cf466
10 changed files with 101 additions and 30 deletions
+4
View File
@@ -4,6 +4,10 @@
* Kolide will now warn on startup if there are database migrations not yet completed.
* Kolide will prompt for confirmation before running database migrations.
To disable this, use `kolide prepare db --no-prompt`.
* Kolide now supports emoji, so you can 🔥 to your heart's content.
* When setting the platform for a scheduled query, selecting "All" now clears individually selected platforms.
+33
View File
@@ -1,6 +1,10 @@
package cli
import (
"bufio"
"fmt"
"os"
"github.com/WatchBeam/clock"
kitlog "github.com/go-kit/kit/log"
"github.com/kolide/kolide/server/config"
@@ -27,6 +31,8 @@ To setup kolide infrastructure, use one of the available commands.
},
}
noPrompt := false
var dbCmd = &cobra.Command{
Use: "db",
Short: "Given correct database configurations, prepare the databases for use",
@@ -38,6 +44,29 @@ To setup kolide infrastructure, use one of the available commands.
initFatal(err, "creating db connection")
}
status, err := ds.MigrationStatus()
if err != nil {
initFatal(err, "retrieving migration status")
}
switch status {
case kolide.AllMigrationsCompleted:
fmt.Println("Migrations already completed. Nothing to do.")
return
case kolide.SomeMigrationsCompleted:
if !noPrompt {
fmt.Printf("################################################################################\n" +
"# WARNING:\n" +
"# This will perform Kolide database migrations. Please back up your data before\n" +
"# continuing.\n" +
"#\n" +
"# Press Enter to continue, or Control-c to exit.\n" +
"################################################################################\n")
bufio.NewScanner(os.Stdin).Scan()
}
}
if err := ds.MigrateTables(); err != nil {
initFatal(err, "migrating db schema")
}
@@ -45,9 +74,13 @@ To setup kolide infrastructure, use one of the available commands.
if err := ds.MigrateData(); err != nil {
initFatal(err, "migrating builtin data")
}
fmt.Println("Migrations completed.")
},
}
dbCmd.PersistentFlags().BoolVar(&noPrompt, "no-prompt", false, "disable prompting before migrations (for use in scripts)")
prepareCmd.AddCommand(dbCmd)
var testDataCmd = &cobra.Command{
+17 -4
View File
@@ -68,7 +68,13 @@ the way that the kolide server works.
initFatal(err, "initializing datastore")
}
if ds.MigrationStatus() != nil {
migrationStatus, err := ds.MigrationStatus()
if err != nil {
initFatal(err, "retrieving migration status")
}
switch migrationStatus {
case kolide.SomeMigrationsCompleted:
fmt.Printf("################################################################################\n"+
"# WARNING:\n"+
"# Your Kolide database is missing required migrations. This is likely to cause\n"+
@@ -77,9 +83,16 @@ the way that the kolide server works.
"# Run `%s prepare db` to perform migrations.\n"+
"################################################################################\n",
os.Args[0])
if config.Logging.Debug {
fmt.Println("error: ", err.Error())
}
case kolide.NoMigrationsCompleted:
fmt.Printf("################################################################################\n"+
"# ERROR:\n"+
"# Your Kolide database is not initialized. Kolide cannot start up.\n"+
"#\n"+
"# Run `%s prepare db` to initialize the database.\n"+
"################################################################################\n",
os.Args[0])
os.Exit(1)
}
if initializingDS, ok := ds.(initializer); ok {
+2
View File
@@ -78,6 +78,8 @@ Before running the updated server, perform necessary database migrations:
kolide prepare db
```
Note, if you would like to run this in a script, you can use the `--no-prompt` option to disable prompting before the migrations.
The updated Kolide server should now be ready to run:
```
+13 -4
View File
@@ -4,6 +4,7 @@ import (
"testing"
"github.com/kolide/kolide/server/kolide"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -13,12 +14,20 @@ func testMigrationStatus(t *testing.T, ds kolide.Datastore) {
}
require.Nil(t, ds.Drop())
require.NotNil(t, ds.MigrationStatus())
status, err := ds.MigrationStatus()
require.Nil(t, err)
assert.EqualValues(t, kolide.NoMigrationsCompleted, status)
require.Nil(t, ds.MigrateTables())
require.NotNil(t, ds.MigrationStatus())
// Should return nil with all migrations completed
status, err = ds.MigrationStatus()
require.Nil(t, err)
assert.EqualValues(t, kolide.SomeMigrationsCompleted, status)
require.Nil(t, ds.MigrateData())
require.Nil(t, ds.MigrationStatus())
status, err = ds.MigrationStatus()
require.Nil(t, err)
assert.EqualValues(t, kolide.AllMigrationsCompleted, status)
}
+2 -2
View File
@@ -128,8 +128,8 @@ func (d *Datastore) MigrateData() error {
return nil
}
func (m *Datastore) MigrationStatus() error {
return nil
func (m *Datastore) MigrationStatus() (kolide.MigrationStatus, error) {
return 0, nil
}
func (d *Datastore) Drop() error {
+16 -14
View File
@@ -103,40 +103,42 @@ func (d *Datastore) MigrateData() error {
return nil
}
func (d *Datastore) MigrationStatus() error {
func (d *Datastore) MigrationStatus() (kolide.MigrationStatus, error) {
if tables.MigrationClient.Migrations == nil || data.MigrationClient.Migrations == nil {
return errors.New("unexpected nil migrations list")
return 0, errors.New("unexpected nil migrations list")
}
lastTablesMigration, err := tables.MigrationClient.Migrations.Last()
if err != nil {
return errors.New("missing tables migrations")
return 0, errors.New("missing tables migrations")
}
currentTablesVersion, err := tables.MigrationClient.GetDBVersion(d.db.DB)
if err != nil {
return errors.New("cannot get table migration status")
}
if currentTablesVersion != lastTablesMigration.Version {
return errors.New("table migrations must be run")
return 0, errors.New("cannot get table migration status")
}
lastDataMigration, err := data.MigrationClient.Migrations.Last()
if err != nil {
return errors.New("missing data migrations")
return 0, errors.New("missing data migrations")
}
currentDataVersion, err := data.MigrationClient.GetDBVersion(d.db.DB)
if err != nil {
return errors.New("cannot get table migration status")
return 0, errors.New("cannot get table migration status")
}
if currentDataVersion != lastDataMigration.Version {
return errors.New("data migrations must be run")
}
switch {
case currentDataVersion == 0 && currentTablesVersion == 0:
return kolide.NoMigrationsCompleted, nil
return nil
case currentTablesVersion != lastTablesMigration.Version ||
currentDataVersion != lastDataMigration.Version:
return kolide.SomeMigrationsCompleted, nil
default:
return kolide.AllMigrationsCompleted, nil
}
}
// Drop removes database
+9 -1
View File
@@ -26,9 +26,17 @@ type Datastore interface {
MigrateData() error
// MigrationStatus returns nil if migrations are complete, and an error
// if migrations need to be run.
MigrationStatus() error
MigrationStatus() (MigrationStatus, error)
}
type MigrationStatus int
const (
NoMigrationsCompleted = iota
SomeMigrationsCompleted
AllMigrationsCompleted
)
// NotFoundError is returned when the datastore resource cannot be found.
type NotFoundError interface {
error
+2 -2
View File
@@ -38,8 +38,8 @@ func (m *Store) MigrateTables() error {
func (m *Store) MigrateData() error {
return nil
}
func (m *Store) MigrationStatus() error {
return nil
func (m *Store) MigrationStatus() (kolide.MigrationStatus, error) {
return 0, nil
}
func (m *Store) Name() string {
return "mock"
+3 -3
View File
@@ -49,7 +49,7 @@ copy_db() {
migrate_kolide_db() {
dbname=$1
./build/kolide prepare db \
./build/kolide prepare db --no-prompt \
--mysql_address=127.0.0.1:3310 \
--mysql_database=${dbname} \
--mysql_username=${CLOUDSQL_USER} \
@@ -63,7 +63,7 @@ deploy_pr() {
$exec_template -json="$jsn" -template=./tools/ci/k8s-templates/pr-deployment.template > /tmp/deployment.yml
# TODO(@groob):
# we have to deploy a new copy of redis for each PR. In the future,
# we have to deploy a new copy of redis for each PR. In the future,
# it would be nice to deploy a single redis instance and allow multiple DBs to connect.
$exec_template -json="$jsn" -template=./tools/ci/k8s-templates/redis-pr-service.template > /tmp/redis-service.yml
$exec_template -json="$jsn" -template=./tools/ci/k8s-templates/redis-pr-deployment.template > /tmp/redis-deployment.yml
@@ -117,7 +117,7 @@ main() {
migrate_kolide_db "${dbname}"
deploy_pr
fi
docker stop $(docker ps -a -q)
}