Merge "playground: add light solarized theme based on Vanadium style"
diff --git a/client/browser/api/index.js b/client/browser/api/index.js
index 09daa8a..c13a196 100644
--- a/client/browser/api/index.js
+++ b/client/browser/api/index.js
@@ -19,7 +19,7 @@
   timeout: 5 * 60 * 1000,
   // Temporarily default to the staging load balancer until bundle lists are
   // available from the API.
-  url: 'https://playground-api.v.io',
+  url: 'https://playground-api.staging.v.io',
   debug: false
 };
 
@@ -106,10 +106,10 @@
   // TODO(jasoncampbell): remove this list once a list API endpoint is
   // available.
   var ids = [
-    '_051039d58946cfbe3a603fd7ed3149b0501b8f1e13ee0778e9c30e08d8a94d3',
-    '_9999dc7e35426090058fb0240a8a2c1b128ea958e092b0d53ac5f2a5ca4c54a',
-    '_eee960846ba210e4d9b6b01463625a2e5bf15b9dab61d76b4fc38ae04317bbd',
-    '_5f199e7f67cc6b60efddf2d37cdf2c0d1aeec488007981e972c1e0a43f91c3e'
+    '_cadcfa075a6ac6d1939d12a64ac6e57bc7256c0422fb5d0690b3d8618779565',
+    '_be43fb9b2d03087dfd7c84437fd37dac7f6977d8cac330b9fce6aad94414558',
+    '_5385edd72b550c57bee83b100731338c70349ac7354dc4353665a1998fa7c8c',
+    '_46f8b66f0e80be00adc6222ac0235b1f8e70183daa64ec5924b14267dc6f0fd'
   ];
 
   var workers = ids.map(createWorker);
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 77c430a..55a266a 100644
--- a/go/src/v.io/x/playground/Makefile
+++ b/go/src/v.io/x/playground/Makefile
@@ -34,8 +34,19 @@
 		--sqlconf=$< \
 		--listen-timeout=0 \
 		--address=$(host):$(port) \
+		--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 19cf776..88f6550 100644
--- a/go/src/v.io/x/playground/README.md
+++ b/go/src/v.io/x/playground/README.md
@@ -54,11 +54,11 @@
 
 Run the compiler binary:
 
-    $ $V23_ROOT/release/projects/playground/go/bin/compilerd --listen-timeout=0 --address=localhost:8181
+    $ $V23_ROOT/release/projects/playground/go/bin/compilerd --listen-timeout=0 --address=localhost:8181 --origin='*'
 
 Or, run it without Docker (for faster iterations during development):
 
-    $ PATH=$V23_ROOT/release/go/bin:$V23_ROOT/release/projects/playground/go/bin:$PATH compilerd --listen-timeout=0 --address=localhost:8181 --use-docker=false
+    $ PATH=$V23_ROOT/release/go/bin:$V23_ROOT/release/projects/playground/go/bin:$PATH compilerd --listen-timeout=0 --address=localhost:8181 --origin='*' --use-docker=false
 
 The server should now be running at http://localhost:8181 and responding to
 compile requests at http://localhost:8181/compile.
@@ -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/compilerd/main.go b/go/src/v.io/x/playground/compilerd/main.go
index fc9ce91..88c01e9 100644
--- a/go/src/v.io/x/playground/compilerd/main.go
+++ b/go/src/v.io/x/playground/compilerd/main.go
@@ -42,6 +42,8 @@
 
 	address = flag.String("address", ":8181", "Address to listen on.")
 
+	origin = flag.String("origin", "https://playground.v.io", "The origin where the playground client is hosted. This will be used in CORS headers to allow XHRs to the playground API. Use '*' to allow all origins.")
+
 	// compilerd exits cleanly on SIGTERM or after a random amount of time,
 	// between listenTimeout/2 and listenTimeout.
 	listenTimeout = flag.Duration("listen-timeout", 60*time.Minute, "Maximum amount of time to listen for before exiting. A value of 0 disables the timeout.")
@@ -194,9 +196,7 @@
 // Returns false iff response processing should not continue.
 func handleCORS(w http.ResponseWriter, r *http.Request) bool {
 	// CORS headers.
-	// TODO(nlacasse): Fill the origin header in with actual playground origin
-	// before going to production.
-	w.Header().Set("Access-Control-Allow-Origin", "*")
+	w.Header().Set("Access-Control-Allow-Origin", *origin)
 	w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
 	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding")
 
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)
+	}
+}