CooperConfig.Provider (CooperConfig v0.1.0)

Copy Markdown View Source

A Config.Provider that loads a CASC file via Cooper.load_file/2 and merges it into the release's app config at boot -- the same slot Config.Reader (config/runtime.exs) normally fills.

Usage

Point a release at it in mix.exs:

def project do
  [
    ...,
    releases: releases()
  ]
end

defp releases do
  [
    my_app: [
      config_providers: [
        {CooperConfig.Provider, path: "/etc/my_app/config.casc"}
      ]
    ]
  ]
end

:path accepts anything Config.Provider.resolve_config_path!/1 does -- a literal string, {:system, "ENV_VAR"}, or {:system, "ENV_VAR", "/default/path"} -- so the path itself can come from the environment.

Options

Besides :path (required), every other option is passed straight through to Cooper.load_file/2 -- see its moduledoc for what each one does:

  • :env -- an override layer, not a replacement: System.get_env/0 (and any .env file, see :dotenv below) is always consulted too, for any name not given an explicit entry here. Defaults to Cooper.load_file/2's own default (nothing extra layered on top of System.get_env/0/.env) if omitted.
  • :resolvers, :tags, :import_schemes -- default to %{}.
  • :dotenv, :dotenv_env, :dotenv_files -- .env file layering, on by default; see Cooper.Dotenv for the full rules. Omitted here means Cooper's own default applies.

:cache/:watch_env are deliberately not exposed -- load/2 always calls Cooper.load_file/2 with cache: false. Config.Provider callbacks run before the release's own supervision tree starts, which is also what starts :cooper's OTP application and, with it, the Cooper.Cache process the cache relies on -- caching here wouldn't just be pointless for a file read exactly once per boot, it would crash trying to reach a cache process that isn't running yet.

Plus one option of its own:

Errors

A load failure (unreadable file, parse/resolve error, ...) raises -- Config.Provider callbacks run before the release's supervision tree starts, so there's no supervisor to catch and restart from, and booting further with incomplete config is worse than not booting.

Summary

Functions

Validates opts and builds the state load/2 receives.

Loads the CASC file described by state (as built by init/1) and merges it into config via Config.Reader.merge/2.

Types

opts()

@type opts() :: [
  path: Config.Provider.config_path(),
  env: %{optional(String.t()) => String.t()},
  resolvers: %{
    optional(String.t()) => (String.t() -> {:ok, term()} | {:error, term()})
  },
  tags: %{optional(String.t()) => (term() -> {:ok, term()} | {:error, term()})},
  import_schemes: %{
    optional(String.t()) => (String.t() -> {:ok, String.t()} | {:error, term()})
  },
  dotenv: boolean(),
  dotenv_env: atom() | nil,
  dotenv_files: [String.t()],
  reveal_secrets: boolean()
]

Functions

init(opts)

Validates opts and builds the state load/2 receives.

Raises if :path is missing or fails Config.Provider.validate_config_path!/1.

load(config, state)

Loads the CASC file described by state (as built by init/1) and merges it into config via Config.Reader.merge/2.

Raises if the file can't be read, parsed, or resolved -- see the moduledoc's "Errors" section.