Skip to content
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

Base directory is hardcoded for init script #268

Open
colinphill-mdsol opened this issue Oct 1, 2024 · 13 comments
Open

Base directory is hardcoded for init script #268

colinphill-mdsol opened this issue Oct 1, 2024 · 13 comments

Comments

@colinphill-mdsol
Copy link

I'm writing an adapter around Migratus for an internal task runner. I'd like to write tests for it, and I'd like those tests to use a suite of migrations in my project's test_resources directory. I see there is a :parent-migration-dir option which controls where Migratus looks for the migrations directory, and this project's own tests use that option to run test migrations. The init function, however, does not respect this setting; it always looks in resources.

@yogthos
Copy link
Owner

yogthos commented Oct 1, 2024

Ah yeah, that looks like an omission. Any chance you could do a PR for this?

@colinphill-mdsol
Copy link
Author

Unfortunately my company's OSS policies are...unsettled, and I can't at present make contributions.

@yogthos
Copy link
Owner

yogthos commented Oct 1, 2024

Ah, fair enough. I'll take a look.

@yogthos
Copy link
Owner

yogthos commented Oct 1, 2024

Hmm, it looks like the init-script should already be resolved relative to the migration directory

(defn find-init-script [dir init-script-name]

@colinphill-mdsol
Copy link
Author

When that function is called, the directory provided does not account for this option. It resolves the :migration-dir option against a base path, but the base path is static.

Tracing the data flow:

We dispatch init on store type, which is generally going to be :database.

(defn init
"Initialize the data store"
[config & [name]]
(proto/init (proto/make-store config)))

The database implementation of init passes its entire config to get-migration-dir and get-init-script.

(init [this]
(let [conn (connect* (assoc (:db config) :transaction? (:init-in-transaction? config)))]
(try
(init-db! conn
(utils/get-migration-dir config)
(utils/get-init-script config)
(sql-mig/wrap-modify-sql-fn (:modify-sql-fn config))
(get config :init-in-transaction? true)
(props/load-properties config))

Both of those functions look only at their respective properties, falling back to the defaults of "migrations" and "init.sql".

(defn get-migration-dir
"Gets the :migration-dir from config, or default if missing."
[config]
(get config :migration-dir default-migration-dir))
(defn get-init-script
"Gets the :init-script from config, or default if missing."
[config]
(get config :init-script default-init-script-name))

The init-db! function passes those two values directly to find-init-script and slurps the result.

(defn init-db! [db migration-dir init-script-name modify-sql-fn transaction? properties]
(if-let [init-script (some-> (find-init-script migration-dir init-script-name)
slurp
(inject-properties properties))]

Nothing along the way has accounted for :parent-migration-dir, and at this point in the call stack we no longer have access to that value because we're no longer holding the entire config.

@colinphill-mdsol
Copy link
Author

colinphill-mdsol commented Oct 2, 2024

Here is where the actual hardcoding happens:

(defn find-migration-dir
"Finds the given directory on the classpath. For backward
compatibility, tries the System ClassLoader first, but falls back to
using the Context ClassLoader like Clojure's compiler.
If classloaders return nothing try to find it on a filesystem."
([^String dir]
(or (find-migration-dir (ClassLoader/getSystemClassLoader) default-migration-parent dir)
(-> (Thread/currentThread)
(.getContextClassLoader)
(find-migration-dir default-migration-parent dir))))

And the function we've used to find the script does indeed call that unary overload of find-migration-dir:

(defn find-init-script [dir init-script-name]
(let [dir (utils/ensure-trailing-slash dir)]
(when-let [migration-dir (utils/find-migration-dir dir)]
(if (instance? File migration-dir)
(find-init-script-file migration-dir init-script-name)
(find-init-script-resource dir migration-dir init-script-name)))))

@yogthos
Copy link
Owner

yogthos commented Oct 2, 2024

Ah ok, thanks for digging in. It's been a while since I've looked at it. I'll try get a fix out in the coming days.

@colinphill-mdsol
Copy link
Author

Also worth mentioning: This option isn't actually documented. One alternative is to just say it's an implementation detail and not make this change. I think my use case is a compelling one for promoting it to a full-fledged feature, though.

@yogthos
Copy link
Owner

yogthos commented Oct 2, 2024

Yeah, I think this would be worth doing and doesn't look like it should be too much work.

@colinphill-mdsol
Copy link
Author

Ah, I just discovered that this also happens with the migrate function. Same basic flow – it lands in that unary overload of find-migration-dir. Full support for this option might wind up touching a fair amount of the public API.

@yogthos
Copy link
Owner

yogthos commented Oct 3, 2024

Ah yeah, looks like it might be a bit more fiddly than it initially appeared. Perhaps for your use case it might be easier to just use with-redefs for find-init-script.

@colinphill-mdsol
Copy link
Author

I wound up settling on a workaround of just using a couple .. segments in the configuration. Not ideal, but adequate!

@yogthos
Copy link
Owner

yogthos commented Nov 8, 2024

I'll keep the issue open. I think it'd be good to fix if I get time. Glad the workaround is working in the mean time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants