{"id":13562496,"url":"https://github.com/gordalina/hush","last_synced_at":"2025-05-16T03:02:03.005Z","repository":{"id":45571445,"uuid":"279943485","full_name":"gordalina/hush","owner":"gordalina","description":"Hush is a runtime configuration loader for Elixir applications","archived":false,"fork":false,"pushed_at":"2024-12-26T20:37:35.000Z","size":187,"stargazers_count":134,"open_issues_count":1,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-10T23:46:15.037Z","etag":null,"topics":["aws","elixir","google-cloud","hush","secret-management"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gordalina.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"gordalina"}},"created_at":"2020-07-15T18:14:03.000Z","updated_at":"2025-02-15T20:27:38.000Z","dependencies_parsed_at":"2024-01-05T21:54:19.353Z","dependency_job_id":"2a171126-814f-4622-8f9d-4774c0f86bc8","html_url":"https://github.com/gordalina/hush","commit_stats":null,"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordalina%2Fhush","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordalina%2Fhush/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordalina%2Fhush/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gordalina%2Fhush/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gordalina","download_url":"https://codeload.github.com/gordalina/hush/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254459077,"owners_count":22074604,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","elixir","google-cloud","hush","secret-management"],"created_at":"2024-08-01T13:01:09.238Z","updated_at":"2025-05-16T03:02:02.937Z","avatar_url":"https://github.com/gordalina.png","language":"Elixir","funding_links":["https://github.com/sponsors/gordalina"],"categories":["Configuration"],"sub_categories":[],"readme":"# Hush\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/gordalina/hush/ci.yml?branch=main\u0026style=flat-square)](https://github.com/gordalina/hush/actions/workflows/ci.yml)\n[![Coverage Status](https://img.shields.io/codecov/c/github/gordalina/hush?style=flat-square)](https://app.codecov.io/gh/gordalina/hush)\n[![hex.pm version](https://img.shields.io/hexpm/v/hush?style=flat-square)](https://hex.pm/packages/hush)\n[![hex.pm downloads](https://img.shields.io/hexpm/dt/hush?style=flat-square)]([LICENSE](https://hex.pm/packages/hush))\n\nHush is designed to help developers configure their applications at runtime and in release mode, retrieving configuration from multiple providers, without having to depend on secret files or hardcoded configuration.\n\nDocumentation can be found at [https://hexdocs.pm/hush](https://hexdocs.pm/hush).\n\n## Overview\n\nHush can be used to inject configuration that is not known at compile time, such as environmental variables (e.g.: Heroku's PORT env var), sensitive credentials such as your database password, or any other information you need.\n\n```elixir\n# config/prod.exs\nalias Hush.Provider.{AwsSecretsManager, FileSystem, GcpSecretManager, SystemEnvironment}\n\nconfig :hush, FileSystem, search_paths: [\"/secrets\"]\n\nconfig :app, App,\n  pool_size: {:hush, SystemEnvironment, \"POOL_SIZE\", cast: :integer},\n  ssl_certificate: {:hush, FileSystem, \"cert.pem\"},\n  url: {:hush, GcpSecretManager, \"APP_URL\", [apply: \u0026{:ok, \"https://\" \u003c\u003e \u00261}]},\n  password: {:hush, AwsSecretsManager, \"PASSWORD\"}\n```\n\nHush resolves configuration from using providers. It ships with `SystemEnvironment` and `FileSystem` providers, but multiple providers exist. You can also [write your own easily](#writing-your-own-provider).\n\n| Provider            | Description                                                                               | Link                                                            |\n| ------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------- |\n| `SystemEnvironment` | Reads environmental variables.                                                            |                                                                 |\n| `FileSystem`        | Reads contents of file.                                                                   |                                                                 |\n| `AwsSecretsManager` | Load secrets from [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/).         | [GitHub](https://github.com/gordalina/hush_aws_secrets_manager) |\n| `GcpSecretManager`  | Load secrets from [Google Cloud Secret Manager](https://cloud.google.com/secret-manager). | [GitHub](https://github.com/gordalina/hush_gcp_secret_manager)  |\n\n## Installation\n\nAdd `hush` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:hush, \"~\u003e 1.2\"}\n  ]\nend\n```\n\nRun `mix deps.get` to install it.\n\nSome providers may need to initialize applications or even start processes to function correctly. The providers will be explicit about whether they need to be loaded at startup or not. `GcpSecretsManager` unlike `SystemEnvironment` is one such example. To load the provider you need to configure it like so. **Note:** `SystemEnvironment` and `FileSystem` do not need to be loaded at startup.\n\n```elixir\n# config/config.exs\n\nalias Hush.Providers.GcpSecretManager\n\nconfig :hush,\n  providers: [\n    GcpSecretManager\n  ]\n```\n\n## Usage\n\nHush can be loaded in two ways, at runtime in your application, or as a [Config.Provider](https://hexdocs.pm/elixir/Config.Provider.html) in release mode. A [sample app](https://github.com/gordalina/hush_sample_app) has been written so you can see how it's configured.\n\n### Loading at runtime\n\n```elixir\n# application.ex\n\ndef start(_type, _args) do\n  Hush.resolve!()\nend\n```\n\n### Loading via in release mode\n\nTo load hush as a config provider, you need to define in your `releases` in `mix.exs`.\n\n```elixir\ndef project do\n  [\n    # ...\n    releases: [\n      app: [\n        config_providers: [{Hush.ConfigProvider, nil}]\n      ]\n    ]\n  ]\n  end\n```\n\nIf you are using Hush in runtime and release mode, make sure to only resolve configuration in non release mode:\n\n```elixir\n# application.ex\n\ndef start(_, _) do\n  unless Hush.release_mode?(), do: Hush.resolve!()\nend\n```\n\n## Configuration format\n\nHush will resolve any tuple in the following format into a value.\n\n```elixir\n{:hush, Hush.Provider, \"key\", options \\\\ []}\n```\n\n- `Hush.Provider` can be any module that implements its behaviour.\n- `\"key\"` is passed to the provider to retrieve the data.\n- `options` is a a Keyword list with the following properties:\n  - `default: any()` - If the provider can't find the value, hush will return this value\n  - `optional: boolean()` - By default, Hush will raise an error if it cannot find a value and there's no default, unless you mark it as `optional`.\n  - `apply: fun(any()) :: {:ok, any()} | {:error, String.t()}` - Apply a function to the value resolved by Hush.\n  - `cast: :string | :atom | :charlist | :float | :integer | :boolean | :module` - You can ask Hush to cast the value to a Elixir native type.\n  - `to_file: string()` - Write the data to the path give in `to_file()` and return the path.\n\nAfter Hush resolves a value it runs them through Transfomers.\n\n### Examples\n\nBy default if a given `key` is not found by the provider, Hush will raise an error. To prevent this, provide a `default` or `optional: true` in the `options` component of the tuple.\n\n#### Default\n\n```elixir\n# config/config.exs\nalias Hush.Provider.SystemEnvironment\n\nconfig :app,\n  url: {:hush, SystemEnvironment, \"HOST\", default: \"example.domain\"}\n\n# result without environmental variable\nassert \"example.domain\" == Application.get_env(:app, :url)\n\n# result with env HOST=production.domain\nassert \"production.domain\" == Application.get_env(:app, :url)\n```\n\n#### Casting\n\nHere we are reading the `PORT` environmental variable, casting it to an integer and returning it\n\n```elixir\n# config/config.exs\nalias Hush.Provider.SystemEnvironment\n\nconfig :app,\n  port: {:hush, SystemEnvironment, \"PORT\", cast: :integer, default: 4000}\n\n# result without environmental variable\nassert 4000 == Application.get_env(:app, :url)\n\n# result with env PORT=443\nassert 443 == Application.get_env(:app, :url)\n```\n\n#### Optional\n\n```elixir\n# config/dev.exs\nalias Hush.Provider.SystemEnvironment\n\nconfig :app,\n  can_be_nil: {:hush, SystemEnvironment, \"KEY\", optional: true}\n\n# result without environmental variable\nassert nil == Application.get_env(:app, :can_be_nil)\n\n# result with env KEY=\"is not nil\"\nassert \"is not nil\" == Application.get_env(:app, :can_be_nil)\n```\n\n## Concurrency \u0026 Cache\n\nBy default Hush will fetch secrets from providers concurrently and will save them to a short-lived cache to prevent fetching the same secret multiple times.\n\nThe defaults for these values are `System.schedulers_online/0` for concurrency and `5000ms` for concurrency timeouts.\n\nThese values can be modified with the following configuration:\n\n```elixir\n# config/config.exs\nconfig :hush,\n  max_concurrency: 10,\n  timeout: 5000, # milliseconds\n```\n\n## Transfomers\n\nBy default Hush ships with the following transformers:\n\n- **Hush.Transfomer.Cast**: Takes an argument `cast` and converts a value into a specific type.\n- **Hush.Transfomer.ToFile**: Takes an arugment `to_file` and outputs the value into the path provided.\n\nIt is possible to add more transformers by the following configuration:\n\n```elixir\n# config/prod.exs\n\nalias Hush.Provider.SystemEnvironment\n\nconfig :hush,\n  transfomers: [\n    App.Hush.JsonToMapTransfomer\n  ]\n\nconfig :app,\n  allowed_urls: {:hush, SystemEnvironment, \"alloweds_urls\", [json: true]}\n```\n\nIt is also possible to override the transforms Hush will process, and the order they will execute in. See [below](#overriding-transformers) for more information.\n\n### Writing your own transfomer\n\nThe currently [shipped](https://github.com/gordalina/hush/blob/master/lib/transformer/cast.ex) [transfomers](https://github.com/gordalina/hush/blob/master/lib/transformer/to_file.ex) are good examples on how to implement transformers.\n\nTransformers are executed in order they are defined, first is `Cast`, next is `ToFile` and then the ones configured by you, e.g.:\n\n```elixir\n# config/prod.exs\n\nconfig :hush,\n  transformers: [\n    App.Hush.JsonTransformer\n  ]\n```\n\nLets dissect a transformer as an example. A transformer has to implement the `Hush.Transformer` behaviour, and as such it has to implement the `key/0` and `transform/2` functions.\n\nA transformer is going to be executed if a configuration tuple requests it by passing the value of `key/0` into its options. An example is seeing the `json` parameter being passed into the `value` configuration. Hush will process any transformers in which their `key/0` function returns `:json`.\n\nOnce a configuration tuple requests a transfomer, a function `transform/2` is called, where the first argument is what is passed as a value of the `key/0` (in the example below it would be `:abort_on_failure`), and the second argument would be the current value returned by the provider transformed by any previous transformers.\n\n```elixir\n# config/prod.exs\n\nconfig :app,\n  value: {:hush, SystemEnvironment, \"key\", [json: :abort_on_failure]}\n```\n\n```elixir\n# lib/app/hush/JsonTransformer.ex\n\ndefmodule App.Hush.JsonTransformer do\n  @behaviour Hush.Transformer\n\n  @impl true\n  @spec key() :: :json\n  def key(), do :json\n\n  @impl true\n  @spec transform(config :: any(), value :: any()) :: {:ok, any()} | {:error, String.t()}\n  def transform(config, value) do\n    try do\n      Jason.decode!(value)\n    rescue\n      error -\u003e\n        case config do\n          :abort_on_failure -\u003e\n            {:error, \"Couldn't convert #{value} to json: #{error.message}\"}\n          _ -\u003e\n            {:ok, nil}\n        end\n    end\n  end\nend\n```\n\n### Overriding Transformers\n\nThe following example woud take a value passed as an environment variable `ALLOWED_URLS='[\"http://example.com\"]'` into a file named `/tmp/urls.json` with the contents `[\"https://example.com\"]`, all due to the order in which the transformers are executed and the fact that `override_transformers` is `true`.\n\n```elixir\n# config/prod.exs\n\nconfig :hush,\n  override_transformers: true,\n  transformers: [\n    Hush.Transformer.Cast,\n    App.Hush.HttpToHttpsTransformer,\n    App.Hush.JsonTransformer,\n    Hush.Transformer.ToFile,\n  ]\n\nconfig :app,\n  value: {:hush, SystemEnvironment, \"ALLOWED_URLS\", [http_to_https: true, json: true, to_file: \"/tmp/urls.json\" ]}\n```\n\n```elixir\n# lib/app/hush/HttpToHttpsTransfomer.ex\n\ndefmodule App.HttpToHttpsTransfomer do\n  @behaviour Hush.Transformer\n\n  @impl true\n  @spec key() :: :http_to_https\n  def key(), do :http_to_https\n\n  @impl true\n  @spec transform(config :: any(), value :: any()) :: {:ok, any()} | {:error, String.t()}\n  def transfomer(_config, value) do\n    {:ok, Enum.map(value, \u0026http_to_https(\u00262))}\n  end\n\n  def http_to_https(value) do\n    Regex.replace(~r/^http:/, value, \"https:\")\n  end\nend\n```\n\n## Providers\n\n### Writing your own provider\n\nAn example provider is `Hush.Provider.SystemEnvironment`, which reads\nenvironmental variables at runtime. Here's an example of how that provider\nwould look in a app configuration.\n\n```elixir\n  alias Hush.Provider.SystemEnvironment\n\n  config :app, Web.Endpoint,\n    http: [port: {:hush, SystemEnvironment, \"PORT\", [cast: :integer, default: 4000]}]\n```\n\nThis behaviour expects two functions:\n\n- ```elixir\n  load(config :: Keyword.t()) :: :ok | {:ok, [child_spec()]} | {:error, any()}\n  ```\n\n  This function is called at startup time, here you can perform any initialization you need, such as loading applications that you depend on. If you need to startup any processes, you can return a list of `child_spec()` which will be brought up by Hush's supervisor and brought down after hush runs.\n\n- ```elixir\n  fetch(key :: String.t()) :: {:ok, String.t()} | {:error, :not_found} | {:error, any()}\n  ```\n\n  This function is called when hush is resolving a key with you provider.\n  Ensure that you implement a `{:error, :not_found}` if the value can't be found as hush will replace with it a default one if the user providede one.\n\n  Note: All values are required by default, so if the user did not supply a default or made it optional, hush will trigger the error, you don't need to handle that use-case.\n\nTo implement that provider we can use the following code.\n\n```elixir\n  defmodule Hush.Provider.SystemEnvironment do\n  @moduledoc \"\"\"\n  Provider to resolve runtime environmental variables\n  \"\"\"\n\n  @behaviour Hush.Provider\n\n  @impl Hush.Provider\n  @spec load(config :: Keyword.t()) :: :ok | {:ok, [child_spec()]} | {:error, any()}\n  def load(_config), do: :ok\n\n  @impl Hush.Provider\n  @spec fetch(key :: String.t()) :: {:ok, String.t()} | {:error, :not_found}\n  def fetch(key) do\n    case System.get_env(key) do\n      nil -\u003e {:error, :not_found}\n      value -\u003e {:ok, value}\n    end\n  end\nend\n```\n\n## Compatibility\n\n| Hush | Erlang/OTP | Elixir |\n| - | - | - |\n| `\u003e= 1.2.0` | `\u003e= 23.0.0` | `\u003e= 1.12.0` |\n| `\u003e= 1.0.0` | `\u003e= 21.0.0` | `\u003e= 1.10.0` |\n| `\u003c= 0.5.0` | `\u003e= 20.0.0` | `\u003e= 1.9.0` |\n\n## License\n\nHush is released under the Apache License 2.0 - see the [LICENSE](LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgordalina%2Fhush","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgordalina%2Fhush","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgordalina%2Fhush/lists"}