playground: Database schema migration over TLS.
Added pgadmin tool wrapping the rubenv/sql-migrate library and using
v.io/x/lib/dbutil for TLS connections.
Fixed wspr install in Dockerfile.
MultiPart: 1/2
Change-Id: Iac8de313c2131e36e5144494afdbfb26f3b60a1f
diff --git a/client/stylesheets/components/results.css b/client/stylesheets/components/results.css
index c8312d0..737ab10 100644
--- a/client/stylesheets/components/results.css
+++ b/client/stylesheets/components/results.css
@@ -1,3 +1,7 @@
+/* Copyright 2015 The Vanadium Authors. All rights reserved. */
+/* Use of this source code is governed by a BSD-style */
+/* license that can be found in the LICENSE file. */
+
.results {
display: flex;
flex-direction: column;
diff --git a/client/test/test-component-results.js b/client/test/test-component-results.js
index 815cc6f..374ad3c 100644
--- a/client/test/test-component-results.js
+++ b/client/test/test-component-results.js
@@ -1,3 +1,7 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
var test = require('tape');
var results = require('../browser/components/results');
var raf = require('raf');
diff --git a/go/src/v.io/x/playground/.gitignore b/go/src/v.io/x/playground/.gitignore
index 17618d0..a5f062c 100644
--- a/go/src/v.io/x/playground/.gitignore
+++ b/go/src/v.io/x/playground/.gitignore
@@ -1,3 +1,4 @@
netrc
config/*.json
!config/db-*-example.json
+!config/db-*-default.json
diff --git a/go/src/v.io/x/playground/Dockerfile b/go/src/v.io/x/playground/Dockerfile
index c70158d..cb701f6 100644
--- a/go/src/v.io/x/playground/Dockerfile
+++ b/go/src/v.io/x/playground/Dockerfile
@@ -35,6 +35,7 @@
RUN npm install --production $V23_ROOT/release/javascript/core
# Install Vanadium libraries and playground binaries.
+RUN v23 go install -a -tags wspr -v v.io/x/ref/services/wspr/...
RUN v23 go install -v v.io/...
# Uncomment the following lines to install a version of the builder tool using
diff --git a/go/src/v.io/x/playground/Makefile b/go/src/v.io/x/playground/Makefile
index e41b9b4..55a266a 100644
--- a/go/src/v.io/x/playground/Makefile
+++ b/go/src/v.io/x/playground/Makefile
@@ -37,6 +37,16 @@
--origin='*' \
--use-docker=false
+.PHONY: pgadmin
+pgadmin:
+ v23 go install v.io/x/playground/pgadmin
+
+.PHONY: updatedb
+updatedb: config/db.json pgadmin
+ pgadmin \
+ --sqlconf=$< \
+ migrate up
+
config/db.json:
@echo "You are missing config/db.json, create this file based"
@echo "on the appropriate examples in the config directory."
diff --git a/go/src/v.io/x/playground/README.md b/go/src/v.io/x/playground/README.md
index 9392bfa..88f6550 100644
--- a/go/src/v.io/x/playground/README.md
+++ b/go/src/v.io/x/playground/README.md
@@ -82,19 +82,17 @@
Create playground databases:
- MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS playground;
+ MariaDB [(none)]> CREATE DATABASE IF NOT EXISTS pg_moria;
Create a playground user who has access to the playground database:
- MariaDB [(none)]> GRANT ALL PRIVILEGES ON playground.* TO 'playground'@'localhost';
+ MariaDB [(none)]> GRANT ALL PRIVILEGES ON pg_moria.* TO 'pg_gandalf'@'localhost' IDENTIFIED BY 'mellon';
-Create config/db.json from example:
+Create `config/db.json` from default:
- $ cp config/db-local-example.json config/db.json
+ $ cp config/db-local-default.json config/db.json
-Edit config/db.json and set username, password, and database.
-
-TODO(ivanpi): Describe cloud storage.
+Alternatively, make your own from example.
# Running tests
@@ -119,30 +117,28 @@
## Running migrations
-Build the `sql-migrate` tool:
+Migrations use the `github.com/rubenv/sql-migrate` library, wrapped in a tool
+`pgadmin` to allow TLS connections.
- $ v23 go install github.com/rubenv/sql-migrate/sql-migrate
+Create the database and `config/db.json` file following instructions above.
-Edit config/migrate.yml. Find or define whatever environment you plan to
-migrate, and make sure the datasource is correct.
+To migrate up, first run with -n (dry run):
-To see the current migration status, run:
+ $ $V23_ROOT/release/projects/playground/go/bin/pgadmin -sqlconf=./config/db.json migrate up -n
- $ $V23_ROOT/third_party/go/bin/sql-migrate status -config=./config/migrate.yml -env=<environment>
+If everything looks good, run the same command without -n; alternatively, run:
-To migrate up, first run with -dryrun:
-
- $ $V23_ROOT/third_party/go/bin/sql-migrate up -config=./config/migrate.yml -env=<environment> -dryrun
-
-If everything looks good, run the tool without -dryrun:
-
- $ $V23_ROOT/third_party/go/bin/sql-migrate up -config=./config/migrate.yml -env=<environment>
+ $ make updatedb
You can undo the last migration with:
- $ $V23_ROOT/third_party/go/bin/sql-migrate down -limit=1 -config=./config/migrate.yml -env=<environment>
+ $ $V23_ROOT/release/projects/playground/go/bin/pgadmin -sqlconf=./config/db.json migrate down -limit=1
-For more options and infomation, see https://github.com/rubenv/sql-migrate#usage
+For more options and infomation, run:
+
+ $ $V23_ROOT/release/projects/playground/go/bin/pgadmin help
+
+and see https://github.com/rubenv/sql-migrate
## Writing migrations
diff --git a/go/src/v.io/x/playground/config/db-local-default.json b/go/src/v.io/x/playground/config/db-local-default.json
new file mode 100644
index 0000000..3fdbcd9
--- /dev/null
+++ b/go/src/v.io/x/playground/config/db-local-default.json
@@ -0,0 +1,4 @@
+{
+ "dataSourceName": "pg_gandalf:mellon@tcp(localhost:3306)/pg_moria",
+ "tlsDisable": true
+}
diff --git a/go/src/v.io/x/playground/config/migrate.yml b/go/src/v.io/x/playground/config/migrate.yml
deleted file mode 100644
index 84dc6e8..0000000
--- a/go/src/v.io/x/playground/config/migrate.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-development:
- dialect: mysql
- datasource: playground:@tcp(localhost:3306)/playground?parseTime=true
- dir: ./migrations
- table: migrations
-
-production:
- dialect: mysql
- datasource: <username>:<password>@tcp(<ip_address>:3306)/<database>
- dir: ./migrations
- table: migrations
-
diff --git a/go/src/v.io/x/playground/pgadmin/main.go b/go/src/v.io/x/playground/pgadmin/main.go
new file mode 100644
index 0000000..cbbdaf2
--- /dev/null
+++ b/go/src/v.io/x/playground/pgadmin/main.go
@@ -0,0 +1,37 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Admin tool for managing playground database and default bundles.
+
+package main
+
+import (
+ "flag"
+ "os"
+
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/dbutil"
+)
+
+func main() {
+ os.Exit(cmdPGAdmin.Main())
+}
+
+var cmdPGAdmin = &cmdline.Command{
+ Name: "pgadmin",
+ Short: "Playground database management tool",
+ Long: `
+Tool for managing the playground database and default bundles.
+Supports database schema migration.
+TODO(ivanpi): bundle bootstrap
+`,
+ Children: []*cmdline.Command{cmdMigrate},
+}
+
+var (
+ flagDryRun = flag.Bool("n", false, "Show what commands will run, but do not execute them.")
+
+ // Path to SQL configuration file, as described in v.io/x/lib/dbutil/mysql.go. Required parameter for most commands.
+ flagSQLConf = flag.String("sqlconf", "", "Path to SQL configuration file. "+dbutil.SqlConfigFileDescription)
+)
diff --git a/go/src/v.io/x/playground/pgadmin/migrate.go b/go/src/v.io/x/playground/pgadmin/migrate.go
new file mode 100644
index 0000000..fcb2b25
--- /dev/null
+++ b/go/src/v.io/x/playground/pgadmin/migrate.go
@@ -0,0 +1,147 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Wrapper around rubenv/sql-migrate to allow MySQL SSL connections using
+// dbutil (uses dbutil sqlconf files and flags with playground-specific
+// defaults instead of rubenv/sql-migrate YAML config).
+//
+// WARNING: MySQL doesn't support rolling back DDL transactions, so any failure
+// after migrations have started requires restoring from backup or manually
+// repairing database state!
+
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "os"
+
+ "github.com/rubenv/sql-migrate"
+
+ "v.io/x/lib/cmdline"
+ "v.io/x/lib/dbutil"
+)
+
+const mysqlWarning = `
+WARNING: MySQL doesn't support rolling back DDL transactions, so any failure
+after migrations have started requires restoring from backup or manually
+repairing database state!
+`
+
+// TODO(ivanpi): Add status command and sanity checks (e.g. "skipped" migrations are incorrectly applied by rubenv/sql-migrate).
+// TODO(ivanpi): Guard against version skew corrupting data (e.g. add version check to client).
+
+var cmdMigrate = &cmdline.Command{
+ Name: "migrate",
+ Short: "Database schema migrations",
+ Long: `
+See github.com/rubenv/sql-migrate
+` + mysqlWarning,
+ Children: []*cmdline.Command{cmdMigrateUp, cmdMigrateDown},
+}
+
+var cmdMigrateUp = &cmdline.Command{
+ Run: runWithDBConn(runMigrate(migrate.Up)),
+ Name: "up",
+ Short: "Apply new database schema migrations",
+ Long: `
+See github.com/rubenv/sql-migrate
+` + mysqlWarning,
+}
+
+var cmdMigrateDown = &cmdline.Command{
+ Run: runWithDBConn(runMigrate(migrate.Down)),
+ Name: "down",
+ Short: "Roll back database schema migrations",
+ Long: `
+See github.com/rubenv/sql-migrate
+` + mysqlWarning,
+}
+
+const (
+ migrationsTable = "migrations"
+ sqlDialect = "mysql"
+ pgMigrationsDir = "${V23_ROOT}/release/projects/playground/go/src/v.io/x/playground/migrations"
+)
+
+var (
+ flagMigrationsDir string
+ flagMigrationsLimit int
+)
+
+func init() {
+ cmdMigrate.Flags.StringVar(&flagMigrationsDir, "dir", pgMigrationsDir, "Path to directory containing migrations.")
+ cmdMigrateUp.Flags.IntVar(&flagMigrationsLimit, "limit", 0, "Maximum number of up migrations to apply. 0 for unlimited.")
+ cmdMigrateDown.Flags.IntVar(&flagMigrationsLimit, "limit", 1, "Maximum number of down migrations to apply. 0 for unlimited.")
+}
+
+// Returns a DBCommand for applying migrations in the provided direction.
+func runMigrate(direction migrate.MigrationDirection) DBCommand {
+ return func(db *sql.DB, cmd *cmdline.Command, args []string) error {
+ migrate.SetTable(migrationsTable)
+
+ source := migrate.FileMigrationSource{
+ Dir: os.ExpandEnv(flagMigrationsDir),
+ }
+
+ if *flagDryRun {
+ planned, _, err := migrate.PlanMigration(db, sqlDialect, source, direction, flagMigrationsLimit)
+ if err != nil {
+ return fmt.Errorf("Failed getting migrations to apply: %v", err)
+ }
+ for i, m := range planned {
+ fmt.Fprintf(cmd.Stdout(), "#%d: %q\n", i, m.Migration.Id)
+ for _, q := range m.Queries {
+ fmt.Fprint(cmd.Stdout(), q)
+ }
+ }
+ return nil
+ } else {
+ amount, err := migrate.ExecMax(db, sqlDialect, source, direction, flagMigrationsLimit)
+ if err != nil {
+ return fmt.Errorf("Migration FAILED (applied %d migrations): %v", amount, err)
+ }
+ fmt.Fprintf(cmd.Stdout(), "Successfully applied %d migrations\n", amount)
+ return nil
+ }
+ }
+}
+
+// Command to be wrapped with runWithDBConn().
+type DBCommand func(db *sql.DB, cmd *cmdline.Command, args []string) error
+
+// runWithDBConn is a wrapper method that handles opening and closing the
+// database connection.
+func runWithDBConn(fx DBCommand) cmdline.Runner {
+ return func(cmd *cmdline.Command, args []string) (rerr error) {
+ if *flagSQLConf == "" {
+ return cmd.UsageErrorf("SQL configuration file (-sqlconf) must be provided")
+ }
+
+ // Open database connection from config,
+ db, err := dbutil.NewSqlDBConnFromFile(*flagSQLConf, "SERIALIZABLE")
+ if err != nil {
+ return fmt.Errorf("Error opening database connection: %v", err)
+ }
+ // Best effort close.
+ defer func() {
+ if cerr := db.Close(); cerr != nil {
+ cerr = fmt.Errorf("Failed closing database connection: %v", cerr)
+ // Merge errors.
+ if rerr == nil {
+ rerr = cerr
+ } else {
+ rerr = fmt.Errorf("%v\n%v", rerr, cerr)
+ }
+ }
+ }()
+ // Ping database to check connection.
+ if err := db.Ping(); err != nil {
+ return fmt.Errorf("Error connecting to database: %v", err)
+ }
+
+ // Run wrapped function.
+ return fx(db, cmd, args)
+ }
+}