diff --git a/CHANGELOG.md b/CHANGELOG.md index 57579d21e9..364ed435a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,9 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. * cors: Fix allowing all origins by default [PR](https://github.com/hashicorp/boundary/pull/1134) +* cli: It is now an error to run `boundary database migrate` on an uninitalized db. + Use `boundary database init` instead. + ([PR](https://github.com/hashicorp/boundary/pull/1184)) ## 0.2.0 (2021/04/14) diff --git a/internal/cmd/commands/database/funcs.go b/internal/cmd/commands/database/funcs.go index 37bf801c7b..f1ea8d486b 100644 --- a/internal/cmd/commands/database/funcs.go +++ b/internal/cmd/commands/database/funcs.go @@ -13,9 +13,10 @@ import ( // migrateDatabase updates the schema to the most recent version known by the binary. // It owns the reporting to the UI any errors. +// We expect the database already to be initialized iff initialized is set to true. // Returns a cleanup function which must be called even if an error is returned and // an error code where a non-zero value indicates an error happened. -func migrateDatabase(ctx context.Context, ui cli.Ui, dialect, u string, requireFresh bool) (func(), int) { +func migrateDatabase(ctx context.Context, ui cli.Ui, dialect, u string, initialized bool) (func(), int) { noop := func() {} // This database is used to keep an exclusive lock on the database for the // remainder of the command @@ -52,7 +53,11 @@ func migrateDatabase(ctx context.Context, ui cli.Ui, dialect, u string, requireF ui.Error(fmt.Errorf("Error getting database state: %w", err).Error()) return unlock, 2 } - if requireFresh && st.InitializationStarted { + if initialized && !st.InitializationStarted { + ui.Output(base.WrapAtLength("Database has not been initialized. Please use 'boundary database init' to initialize the boundary database.")) + return unlock, -1 + } + if !initialized && st.InitializationStarted { ui.Output(base.WrapAtLength("Database has already been initialized. Please use 'boundary database migrate' for any upgrade needs.")) return unlock, -1 } diff --git a/internal/cmd/commands/database/funcs_test.go b/internal/cmd/commands/database/funcs_test.go index 5779e86fdd..814c27e4f1 100644 --- a/internal/cmd/commands/database/funcs_test.go +++ b/internal/cmd/commands/database/funcs_test.go @@ -19,14 +19,15 @@ func TestMigrateDatabase(t *testing.T) { cases := []struct { name string - requireFresh bool + initialized bool urlProvider func() string expectedCode int expectedOutput string expectedError string }{ { - name: "basic", + name: "not_initialized_expected_not_intialized", + initialized: false, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) @@ -39,69 +40,80 @@ func TestMigrateDatabase(t *testing.T) { expectedOutput: "Migrations successfully run.\n", }, { - name: "old_version_table_used", + name: "basic_initialized_expects_initialized", + initialized: true, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) }) + dBase, err := sql.Open(dialect, u) require.NoError(t, err) - createStmt := `create table if not exists schema_migrations (version bigint primary key, dirty boolean not null)` - _, err = dBase.Exec(createStmt) + earlyMigrationVersion := 2000 + oState := schema.TestCloneMigrationStates(t) + nState := schema.TestCreatePartialMigrationState(oState["postgres"], earlyMigrationVersion) + oState["postgres"] = nState + + man, err := schema.NewManager(ctx, dialect, dBase, schema.WithMigrationStates(oState)) require.NoError(t, err) + require.NoError(t, man.RollForward(ctx)) return u }, expectedCode: 0, expectedOutput: "Migrations successfully run.\n", }, { - name: "bad_url", - urlProvider: func() string { return "badurl" }, - expectedCode: 2, - expectedError: "Unable to connect to the database at \"badurl\"\n", - }, - { - name: "cant_get_lock", + name: "basic_initialized_expects_not_initialized", + initialized: false, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) }) + dBase, err := sql.Open(dialect, u) require.NoError(t, err) - man, err := schema.NewManager(ctx, dialect, dBase) - require.NoError(t, err) - // This is an advisory lock on the DB which is released when the DB session ends. - err = man.ExclusiveLock(ctx) - require.NoError(t, err) + earlyMigrationVersion := 2000 + oState := schema.TestCloneMigrationStates(t) + nState := schema.TestCreatePartialMigrationState(oState["postgres"], earlyMigrationVersion) + oState["postgres"] = nState + man, err := schema.NewManager(ctx, dialect, dBase, schema.WithMigrationStates(oState)) + require.NoError(t, err) + require.NoError(t, man.RollForward(ctx)) return u }, - expectedCode: 2, - expectedError: "Unable to capture a lock on the database.\n", + expectedCode: -1, + expectedOutput: "Database has already been initialized. Please use 'boundary database migrate'\nfor any upgrade needs.\n", }, { - name: "basic_require_fresh", - requireFresh: true, + name: "old_version_table_used_intialized", + initialized: true, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c()) }) + dBase, err := sql.Open(dialect, u) + require.NoError(t, err) + + createStmt := `create table if not exists schema_migrations (version bigint primary key, dirty boolean not null)` + _, err = dBase.Exec(createStmt) + require.NoError(t, err) return u }, expectedCode: 0, expectedOutput: "Migrations successfully run.\n", }, { - name: "old_version_table_used_require_fresh", - requireFresh: true, + name: "old_version_table_used_not_intialized", + initialized: false, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) @@ -120,15 +132,45 @@ func TestMigrateDatabase(t *testing.T) { expectedOutput: "Database has already been initialized. Please use 'boundary database migrate'\nfor any upgrade needs.\n", }, { - name: "bad_url_require_fresh", - requireFresh: true, + name: "bad_url_initialized", + initialized: true, urlProvider: func() string { return "badurl" }, expectedCode: 2, expectedError: "Unable to connect to the database at \"badurl\"\n", }, { - name: "cant_get_lock_require_fresh", - requireFresh: true, + name: "bad_url_not_intialized", + initialized: false, + urlProvider: func() string { return "badurl" }, + expectedCode: 2, + expectedError: "Unable to connect to the database at \"badurl\"\n", + }, + { + name: "cant_get_lock_initialized", + initialized: true, + urlProvider: func() string { + c, u, _, err := db.StartDbInDocker(dialect) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, c()) + }) + dBase, err := sql.Open(dialect, u) + require.NoError(t, err) + + man, err := schema.NewManager(ctx, dialect, dBase) + require.NoError(t, err) + // This is an advisory lock on the DB which is released when the DB session ends. + err = man.ExclusiveLock(ctx) + require.NoError(t, err) + + return u + }, + expectedCode: 2, + expectedError: "Unable to capture a lock on the database.\n", + }, + { + name: "cant_get_lock_not_initialized", + initialized: false, urlProvider: func() string { c, u, _, err := db.StartDbInDocker(dialect) require.NoError(t, err) @@ -155,7 +197,7 @@ func TestMigrateDatabase(t *testing.T) { t.Run(tc.name, func(t *testing.T) { u := tc.urlProvider() ui := cli.NewMockUi() - clean, errCode := migrateDatabase(ctx, ui, dialect, u, tc.requireFresh) + clean, errCode := migrateDatabase(ctx, ui, dialect, u, tc.initialized) clean() assert.EqualValues(t, tc.expectedCode, errCode) assert.Equal(t, tc.expectedOutput, ui.OutputWriter.String()) diff --git a/internal/cmd/commands/database/init.go b/internal/cmd/commands/database/init.go index 6b049d8b0d..e717bcd823 100644 --- a/internal/cmd/commands/database/init.go +++ b/internal/cmd/commands/database/init.go @@ -239,7 +239,7 @@ func (c *InitCommand) Run(args []string) (retCode int) { return base.CommandUserError } - clean, errCode := migrateDatabase(c.Context, c.UI, dialect, migrationUrl, true) + clean, errCode := migrateDatabase(c.Context, c.UI, dialect, migrationUrl, false) defer clean() switch errCode { case 0: diff --git a/internal/cmd/commands/database/migrate.go b/internal/cmd/commands/database/migrate.go index aae29ae3ee..09aa6a4fa9 100644 --- a/internal/cmd/commands/database/migrate.go +++ b/internal/cmd/commands/database/migrate.go @@ -180,7 +180,7 @@ func (c *MigrateCommand) Run(args []string) (retCode int) { return base.CommandUserError } - clean, errCode := migrateDatabase(c.Context, c.UI, dialect, migrationUrl, false) + clean, errCode := migrateDatabase(c.Context, c.UI, dialect, migrationUrl, true) defer clean() if errCode != 0 { return errCode