Proposal: Envfile

In the process of moving the configuration of the GitHub.com application to be backed by ENV vars, I find that I'm writing a lot of boilerplate code for setting defaults and casting values. While the result is an application that is easier to configure and more portable, I'm not sure that the configuration is easier to understand.

This got me thinking about a way to make application configuration more explicit, consistent, and self-documenting. I'd like to propose adding support for an `Envfile`.

`Envfile` is a manifest or schema inspired by `Gemfile` and `Rakefile` which:

1. explicitly defines the required configuration for an application
2. sets defaults
3. validates required variables
4. creates accessors for reading the values from `ENV`

## string

A `string` is the simplest variable that can be defined.

```ruby
desc "The main external hostname."
string :host_name, :default => "example.com"
```

This defines a reader on `Dotenv.env.host_name` (or `Rails.application.config.host_name` for a Rails application), returning the default value if `ENV["HOST_NAME"]` is not set.

## boolean

```ruby
desc "Boolean attribute specifying whether the site is in SSL mode."
boolean :ssl, :default => true
```

This defines a reader `Dotenv.env.ssl?` and cast the values "0", "1", "true", or "false" into boolean values in Ruby.

## integer

`integer` will validate and cast environment variables to integers.

```ruby
desc "The max duration (in seconds) a request can take before it times out."
integer :request_timeout, :default => 10
```

## `:required`

The `:required` option can be used to enforce that a variable be defined by the environment.

```ruby
desc "A PEM of the encryption key used to encrypt sensitive data."
string :encryption_key, :required => true
```

When the `Envfile` is loaded, it will raise an exception if the required keys are not set.

```
ArgumentError: ENCRYPTION_KEY must be set in `ENV`
```

## `:default`

The `:default` option can make use of variable substitution to use existing environment variables.

```ruby
desc "The host name where the API is served from."
string :api_host_name, :default => "api.$HOST_NAME"
```

A block passed to a declaration will be called if a variable is not set and can be used to execute logic for setting the default.

```ruby
desc "Is the current environment connected to the public internet?"
boolean :online do
  check_for_internet_connection? ? google_reachable? : true
end

desc "Should a check be performed to detect if there is an internet connection?"
boolean :check_for_internet_connection?, :default => false
```

## namespace

`namespace` will put a set of variables under a common namespace.

```ruby
namespace :elasticsearch do
  string :port, :default => 9200
  string :host, :default => "localhost:$ELASTICSEARCH_PORT"
end
```

This will prefix all the variable names (for example, `ELASTICSEARCH_HOST`) and define the accessors as `Dotenv.env.elasticsearch.host`.

## rake env

All of this then makes it easy to inspect the environment variables available to the application.

```
$ rake env

# The main external hostname.
HOST_NAME=example.com

# Boolean attribute specifying whether the site is in SSL mode.
SSL=true

# A PEM of the encryption key used to encrypt sensitive data. (Required)
# ENCRYPTION_KEY=

# Is the current environment connected to the public internet?
# ONLINE=

# The max duration (in seconds) a request can take before it times out.
REQUEST_TIMEOUT=10

The host name where the API is served from.
API_HOST_NAME=api.$HOST_NAME

ELASTICSEARCH_PORT=9200

ELASTICSEARCH_HOST=localhost:$ELASTICSEARCH_PORT
```

## Why?

So, what's the point of all this? Couldn't the same thing be accomplished with a well documented `.env` file, or a `Config` module in my app with real methods? Those would work, but an `Envfile` has the advantage that:

0. All available configuration is explicitly defined, documented and easily inspectable.
0. All configuration can be overridden by environment variables, conforming to the [Twelve-Factor App](http://12factor.net/config) methodology.
0. The manifest serves as documentation, and since it's executable and used by the application, will always be up to date.

## Status

This is still a work in progress. I haven't tried it in a real application yet, so it might turn out to be a terrible idea. Given my history of insisting that dotenv be used only in development, I'm not convinced that this should be part of dotenv.

I would love feedback in any form.

## Questions

- [ ] Is there any prior art for this concept?
- [ ] Does this belong in dotenv, or should it be a stand-alone project?
- [ ] Is there a better name than `Envfile`? `Configfile`?
- [ ] Where should the accessors be defined? I don't love `Dotenv.env`. `Dotenv.config`? `$E`?
- [ ] Is there a better way to describe the variables than the rake-inspired `desc`?
- [ ] Would it be worth creating a non-ruby syntax to make this concept portable to other languages?