Not planned Use Environment Variables For Configuration Options

ibnesayeed

Well-known member
In a setup where the application is deployed using a private repository revision control system, it is not a good idea to keep the credentials and other config options hard coded in the config.php file and commit it to the repository. One common practice in this scenario is to optionally allow system environment variables for configuration. This will also make XenForo more friendly to containerize (such as using Docker), where a single docker image can be used in production and test instances just by passing different environment variable values at run time (to connect to a different database for instance). Currently we can do this by mounting an external config file in the container, but it is a hassle.

One option to support environment variable base configuration is to do something like this in the config.php:
PHP:
$config['db']['username'] = '' ?: $_ENV['XF_db_username'];

However, this approach will only allow overwriting configurations that are already listed in the config.php file with additional code to fallback to the ENV var. To make it generic and allow arbitrary config variables to be added, something needs to be done where the defaults are set. The order of precedence should be like this:
Code:
from_config_file ?: from_env_var ?: default

So, I looked into the code where config variables are being set to see how it's working. Based on my understanding, I wrote a function to filter relevant ENV vars and parse them to load the config options. I assumed that any ENV var that starts with "XF_" is a relevant one and config nesting is flattened using "_" as the delimiter. Here is my code:
PHP:
<?php

// Filter and parse ENV Vars to update default config
function getEnvConfig($prefix = 'XF')
{
    $envConfig = array();
    foreach ($_ENV AS $key => $value)
    {
        $parts = explode('_', $key);
        $first = array_shift($parts);
        if ($first == $prefix && count($parts))
        {
            $target =& $envConfig;
            foreach ($parts AS $part)
            {
                $target =& $target[$part];
            }
            $target = $value;
        }
    }
    return $envConfig;
}

// Test the function
$_ENV = array(
    'XF_externalDataPath'  => '/tmp/data',
    'XF_db_username'       => 'test',
    'XF_db_password'       => 'secret',
    'XF_db_dbname'         => 'testdb',
    'XF_addOn_custom_conf' => 'testing',
    'XF'                   => 'edge_condition',
    'OTHER_ENV'            => 'ignored'
);
print("\n# Environment variables\n");
print_r($_ENV);
$envConfig = getEnvConfig();
print("\n# Filtered and parsed config variables\n");
print_r($envConfig);

The output looks like this:
PHP:
# Environment variables
Array
(
    [XF_externalDataPath] => /tmp/data
    [XF_db_username] => test
    [XF_db_password] => secret
    [XF_db_dbname] => testdb
    [XF_addOn_custom_conf] => testing
    [XF] => edge_condition
    [OTHER_ENV] => ignored
)

# Filtered and parsed config variables
Array
(
    [externalDataPath] => /tmp/data
    [db] => Array
        (
            [username] => test
            [password] => secret
            [dbname] => testdb
        )

    [addOn] => Array
        (
            [custom] => Array
                (
                    [conf] => testing
                )

        )

)

I believe, if the return value of the above method "getEnvConfig()" is used in the "loadConfig()" method of the Application.php file as following, it will give the desired functionality.
PHP:
$envConfig = getEnvConfig();
$outputConfig->merge($defaultConfig)
    ->merge($envConfig) // Merge after the default config
    ->merge(new Zend_Config($config))
    ->setReadOnly();

I don't write PHP very often and this code is for the proof of concept purpose only. As such it can only handle string values, but it should be possible to allow passing numbers, boolean, or comma separated lists (to explode into an array) as values.
 
Upvote 1
This suggestion has been closed. Votes are no longer accepted.
I believe that if you need to read configuration from the environment, it would be best to make it explicit by doing it in your config.php file directly. You would know your setup and would know what you're explicitly expecting to be in the environment. If you make something magic, it adds an additional layer of confusion when trying to debug. Essentially, I'm recommending just pulling from $_ENV directly in config.php in the locations where you want to use it (perhaps throwing an error if expected elements aren't there). You could even use your demo code in config.php if you do want to keep the magic.

The vast majority of our customers are likely in a setup where they don't actually even control the environment variables. If you are and prefer to pull stuff like this from the environment, I think just setting it up for your needs directly (and IMO, explicitly) is the best approach.
 
Back
Top Bottom