halfer halfer - 25 days ago 8
PHP Question

How to modify Piwik database connections dynamically?

I run Piwik on a VPS to record usage statistics of a web app I run. I have currently got it deployed as a manual install, and have recently set about the task of Dockerising it. In line with twelve factor app principles, I would like to set it up to read database credentials dynamically, probably from environment variables in the container; this will allow me to use the same image regardless of environment too.

However, I am struggling to work out how to do this. As I see it there are four approaches:


  1. Put
    <?php echo getenv('PIWIK_DATABASE_USER') ?>
    statements into the
    config.ini.php
    config file. However, this results in an error, and I wonder whether this is in fact just an INI file and not a PHP file at all. It seems that other folks have struggled with this too.

  2. Reset the global const
    PIWIK_USER_PATH
    to point to a different configuration/temp base folder using the bootstrap script. I am fairly sure this would work, but is my least favoured solution, since if I want to create a new environment or change an existing one, I have to rebuild the container image.

  3. Use a plugin or the bootstrap system to capture the database credentials event and change them before they are used

  4. Use a plugin or the bootstrap system to overwrite the config using
    Config::getInstance()->__set()
    .



So, it seems that either the bootstrap device or a plugin is a good approach. I can get a
bootstrap.php
to be read, but it looks like it is called quite early on in the bootstrap process, and the
Config
singleton is not populated at that point. For example if I try to use (4) here, I just get an error. I am using this demo code:

<?php

require_once 'vendor/autoload.php';
\Piwik\Config::getInstance()->database['host'] ='localhost';


And here is the error:


PHP Fatal error: Uncaught Piwik\Container\ContainerDoesNotExistException: The root container has not been created yet. in /var/www/html/core/Container/StaticContainer.php:40\nStack trace:\n#0 /var/www/html/core/Container/StaticContainer.php(80): Piwik\Container\StaticContainer::getContainer()\n#1 /var/www/html/core/Config.php(64): Piwik\Container\StaticContainer::get('Piwik\\Config')\n#2 /var/www/html/bootstrap.php(4): Piwik\Config::getInstance()\n#3 /var/www/html/index.php(15): require_once('/var/www/html/b...')\n#4 {main}\n thrown in /var/www/html/core/Container/StaticContainer.php on line 40


I have tried a demo plugin as well, and I have enabled it in the
config.ini.php
, but it does not appear to be included or instantiated, and so its
init()
method is not in a position to change anything. Here is the plugin code:

<?php
// plugins/DatabaseConfiguration/DatabaseConfiguration.php

namespace Piwik\Plugins\DatabaseConfiguration;

echo "Hello";

class DatabaseConfiguration extends \Piwik\Plugin
{
protected function init()
{
}
}


(Note the temporary
echo
to output something random in the web app when the class is included by Piwik).

Here is my turning it on in config:

PluginsInstalled[] = "DatabaseConfiguration"


One logged issue about this includes an offer from a contributor to undertake the necessary work via private consultancy. However, I am hoping to do the necessary hacking myself!

Answer

This question turns out to be a good rubber duck! Here is my solution. I used the plugin approach, it just needed a bit of extra configuration.

In particular I was missing these lines in global.ini.php:

; The below is appended to the global.ini.php config file

[Plugins]
Plugins[] = DatabaseConfiguration

Even though there is another [Plugins] section this seems to work fine, so I have done an append in the Dockerfile thus:

# Inject settings file here
COPY config/config.ini.php /var/www/html/config/config.ini.php
COPY config/global.ini.php.append /tmp/global.ini.php.append

# Append the global config to the existing file (this did not seem to be settable
# in the standard config file)
RUN cat /tmp/global.ini.php.append >> /var/www/html/config/global.ini.php

It would have been nice to add this to the config.inc.php but it did not seem to work for me.

Finally the following code is installed in plugins/DatabaseConfiguration/DatabaseConfiguration.php:

<?php

/**
 * A Piwik plugin to set database credentials based on environment variables
 */

namespace Piwik\Plugins\DatabaseConfiguration;

class DatabaseConfiguration extends \Piwik\Plugin
{
    public function registerEvents()
    {
        return [
            'Db.getDatabaseConfig' => 'getDatabaseConfig'
        ];
    }

    public function getDatabaseConfig(&$dbConfig)
    {
        $dbConfig['host'] = getenv('PIWIK_DATABASE_HOST');
        $dbConfig['dbname'] = getenv('PIWIK_DATABASE_NAME');
        $dbConfig['username'] = getenv('PIWIK_DATABASE_USER');
        $dbConfig['password'] = getenv('PIWIK_DATABASE_PASSWORD');
    }
}

For git users, this plugin is available here.

When I get a moment I will see if the Piwik devs will let me add this feature to their plugins list.