-
Notifications
You must be signed in to change notification settings - Fork 249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Database migration initialization #90
Database migration initialization #90
Conversation
pkg/sqlite/migrator.go
Outdated
|
||
const ( | ||
// Hardcoded path where the db_migrations live | ||
defaultMigrationsPath = "./pkg/sqlite/db_migrations" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doesn't this mean that migrations only work if you have the operator-registry
repo check out locally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that is true. Regardless you at the very least need the migrations folder. For the initializer, this is currently only run today from inside a container image that has the relevant files copied into it. We could also add a flag to the initializer that takes the migration folder as input, but in either case those files will need to be pulled down.
We could also, in theory, save the files themselves as source code so they could be compiled as part of the binary and then written to a directory before being read. That would make this more portable if we found that significantly valuable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A quick google led me to this: https://github.com/gobuffalo/packr - if this looks reasonable to add to this PR quickly (say <1hr?) can you go ahead and do that? I think the UX is much better if we don't have to ship migration files around.
If it looks like it would take longer than that, we don't have to bother with it now, and we can revisit the idea in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ecordell I don't really like this tool because it makes a bunch of assumptions about the underlying filesystem of your files and also requires that you wrap go build
inside their packr
command. I just messed around with https://github.com/go-bindata/go-bindata and it seems like it's a little bit better for our specific use case -- it just generates a .go
file with compressed binary versions of the files. We still need to use a binary tool to generate them, but we should be able to just add something to the makefile to download and run it when we need to generate the files.
I'm going to update this PR in a little bit with a second commit with an implementation that includes that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for finding that!
3545430
to
bc4ce2a
Compare
cmd/configmap-server/main.go
Outdated
@@ -98,7 +98,7 @@ func runCmdFunc(cmd *cobra.Command, args []string) error { | |||
logger.Fatalf("error getting configmap: %s", err) | |||
} | |||
|
|||
sqlLoader, err := sqlite.NewSQLLiteLoader(dbName) | |||
sqlLoader, err := sqlite.NewSQLLiteLoader(dbName, "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use some different patterns in other places that I think could clean this up.
Either a builder with good defaults
// uses the compiled migrations
sqlite.NewSQLLiteLoader(dbName)
// for tests
sqlite.NewSQLLiteLoader(dbName).WithMigrator(*testMigrator)
or the variadic option pattern
sqlite.NewSQLLiteLoader(options.WithDBName(dbName), options.WithMigrator(default))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this is a good point. I went with the trivial implementation instead of using a builder because the constructor currently has actual parsing logic that requires the migrator to exist since there is currently no distinction between calling NewSQLLiteLoader
and actually initializing the database -- I was attempting to retain that step as an atomic function call instead of splitting it into several steps.
But using the option pattern is a really good idea, I'll try to make this more digestible for the default case that way.
pkg/sqlite/load_test.go
Outdated
@@ -131,7 +131,7 @@ func TestAddPackageChannels(t *testing.T) { | |||
for _, tt := range tests { | |||
t.Run(tt.description, func(t *testing.T) { | |||
db := fmt.Sprintf("%d.db", rand.Int()) | |||
store, err := NewSQLLiteLoader(db) | |||
store, err := NewSQLLiteLoader(db, "./db_migrations") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably be testing the compiled migrations, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do further down in the file:
migrator, err := NewSQLLiteMigrator(store.db, "")
But actually testing the values of the default migrations would involve either keeping the latest version in a configuration file or actually parsing the version. At that point, we would be maintaining test code that provides no functionality except to execute tests: IMO that is unnecessary overhead and technical debt.
That's why I implemented all the value checking using a set of test migrations that can be statically defined and their values referenced directly in the tests, and the actual check of the real data is just "does this throw an error when parsed".
Do you feel like there is significant value to be gained from ensuring something I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, just realized this is for load_test.go
, not the new test file I added. Yeah in this case you are right this should be updated to just use the correct default initialization.
bc4ce2a
to
3b57071
Compare
@ecordell Updated. ptal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a couple of bookkeeping comments but this looks good!
If you're tired of looking at migrations, I think we can go ahead and merge this once the remaining comments are addressed.
...but, there's still something that's slightly bugging me. The current approach uses a framework that assumes the inputs are files, so we use go-bindata to get around that.
But mostly, migrations are just a bit of sql that could very easily fit in a constant in our binary. Additionally, some migrations may require non-sql code to run as well (doing backfills, etc) and I don't see a great way to do that with this.
For the most part, all we need is:
- A table that tracks the current version
- Some abstraction (in go) around a migration that allows you to go
up
ordown
(and most of those are a singlesql.Execute
) - An ordered list of those abstractions to walk through
I went back to the migrate lib you pulled in and found this as well: golang-migrate/migrate#15
Which led me to this package:
https://github.com/fnproject/fn/tree/master/api/datastore/sql/migratex
It seems simple enough to pull in or to write something similar ourselves.
What do you think about this?
Makefile
Outdated
|
||
$(CMDS): | ||
go build $(MOD_FLAGS) $(extra_flags) -o $@ ./cmd/$(shell basename $@) | ||
|
||
build: clean $(CMDS) | ||
build: clean generate-migration-bundle $(CMDS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we want this as part of the build
command so that CI will catch when we don't correctly commit the generated migration bundle to git.
Makefile
Outdated
@@ -37,4 +43,4 @@ container-codegen: | |||
|
|||
clean: | |||
@rm -rf ./bin | |||
|
|||
@rm -f ./pkg/sqlite/migrations.go |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like the rest of our codegen, we probably want to go ahead and commit these artifacts.
go.mod
Outdated
github.com/golang/mock v1.2.0 | ||
github.com/golang/protobuf v1.3.1 | ||
github.com/googleapis/gnostic v0.2.0 // indirect | ||
github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db | ||
github.com/imdario/mergo v0.3.7 // indirect | ||
github.com/mattn/go-sqlite3 v1.10.0 | ||
github.com/operator-framework/operator-lifecycle-manager v3.11.0+incompatible // indirect |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did this come from? We recently did some work to remove the dependency on OLM.
(also this is picking up the 3.11 version which is like 0.7.0 I think)
@@ -0,0 +1,23 @@ | |||
package sqlite | |||
|
|||
type DbOptions struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Discussed offline: We'll stick with the current implementation and revisit in the future if/when we need migrations that require more than SQL. Just need those dependencies fixed up and we can merge @kevinrizza ! |
3b57071
to
4621b4f
Compare
pkg/sqlite/load.go
Outdated
@@ -20,7 +20,7 @@ type SQLLoader struct { | |||
|
|||
var _ registry.Load = &SQLLoader{} | |||
|
|||
func NewSQLLiteLoader(outFilename string) (*SQLLoader, error) { | |||
func NewSQLLiteLoader(outFilename, migrationsPath string) (*SQLLoader, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did I hallucinate a change to this API? I thought this became variadic with config options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you did not, i rebased poorly :) latest should contain the right bits.
This commit introduces a day 0 set of building blocks to implement a database migration framework into the sqlite database. Adding the following changes: - Introduce the `golang-migrate` project as a dependency and vendor it - Introduce a `db_migrations` folder with an initial no-op migration to bootstrap the initial database version for newly created databases - Update the initialization of the database when creating the initial schema to hard set the database migration version on db creation before loading any data This commit does not introduce a method of actually running future migrations against existing databases -- that is left for future implementation based on this groundwork when a set of commands that updates the database in place exists and requires the migration. The intention of this commit is also to ensure that future initialized registry databases have an initial version that can be used for migrations in future releases.
Use the go-bindata tool to generate a golang migrations file to compile the content of the db_migrations folder into the bundle. This ensures that the compiled binaries are portable and do not require around the migrations themselves along with the binary. Also updated the makefile to pull the go-bindata file and generate the `migration.go` file as part of `make build`
4621b4f
to
fcf1ff1
Compare
/lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ecordell, kevinrizza The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
This commit introduces a day 0 set of building blocks to implement a
database migration framework into the sqlite database. Adding the
following changes:
golang-migrate
project as a dependency and vendor itdb_migrations
folder with an initial no-op migration tobootstrap the initial database version for newly created databases
schema to hard set the database migration version on db creation
before loading any data
This commit does not introduce a method of actually running future
migrations against existing databases -- that is left for future
implementation based on this groundwork when a set of commands that
updates the database in place exists and requires the migration.
The intention of this commit is also to ensure that future initialized
registry databases have an initial version that can be used for
migrations in future releases.