Elixir/Phoenix: How to use third-party modules in

2019-06-15 02:24发布

问题:

It seems that the way configuration files in phoenix are loaded and compiled pose a problem when using third-party modules in config.exs or dev.exs/prod.exs/test.exs.

Example: To set up Guardian for JWT authentication I am trying to use the JOSE.JWK module for JWK creation / loading in my config.exs. I can use the module alright in the console with iex -S mix phoenix.server. It is of course installed as a dependency. The error I'm getting is

** (Mix.Config.LoadError) could not load config config/config.exs
    ** (UndefinedFunctionError) undefined function JOSE.JWK.from_file/2 (module JOSE.JWK is not available)

This is the code in my config.exs

# Configure Guardian for JWT Authentication
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "MyApp",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key: System.get_env("GUARDIAN_KEY_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("GUARDIAN_KEY_FILE")),
  serializer: MyApp.GuardianSerializer

It works when I wrap the call to JOSE.JWK.from_file/2 in an anonymous function. But of course the value of Guardian.config(:secret_key) is then the anonymous function itself and not its return value:

# Configure Guardian for JWT Authentication
config :guardian, Guardian,
  allowed_algos: ["HS512"], # optional
  verify_module: Guardian.JWT,  # optional
  issuer: "MyApp",
  ttl: { 30, :days },
  verify_issuer: true, # optional
  secret_key: fn -> System.get_env("GUARDIAN_KEY_PASSPHRASE") |> JOSE.JWK.from_file(System.get_env("GUARDIAN_KEY_FILE")) end,
  serializer: MyApp.GuardianSerializer

This is ok in this example since Guardian accepts a function for this config value. But I can imagine other situations where this could be a problem.

Is this limitation on purpose? Am I missing something? Is there a way around this?

回答1:

Since configuration is evaluated before the dependencies are compiled you can't use code from dependencies in the configuration.

The reason is simple: configuration could change how a dependency is compiled. You need to decide what to do first - compile to evaluate configurations. Decision has been taken to evaluate configuration first since it's far more useful (and frequent) to manipulate compilation through configs than to use dependencies to configure other applications - most frequently configuration is just raw data.



回答2:

If you first compile a module that lives outside of elixirc_paths before running mix and it's in the elixir search path, it will find it. Just do Whatever.foo(:bar)

It would make more sense to wrap up the pre-compilation as a mix task, and call mix to make sure config deps are up-to-date before calling a 3rd-party dep.

It would be helpful if there were a pre-config mix hook and/or a config/lib which is precompiled before evaluating config. Otherwise, configs will grow and become disgusting piles of code, even when using import_config which doesn't let you modularize and DRY out code.

Another hackaround is to have a minimal mix task which loads just enough of your app to get values and write them to stdout, and then use a Port to get them elsewhere where needed (Rails does this in places).



回答3:

Turns out it is possible to do this with Mix, using archives.

They need to be installed manually, but can be built quite easily using a separate Mix project and mix archive.build.

They are limited in that archive projects can't have dependencies of their own.

See the docs for details. https://hexdocs.pm/mix/Mix.Tasks.Archive.Build.html#content