Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: override configurations with env variables #3863

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
618d0b3
wip: override config from env vars
Feb 4, 2024
1824a0d
docs and phpcs changes
Feb 4, 2024
1e1d30f
feat: refactor config environment loader
Feb 11, 2024
b6c75ed
add unittest and some changes for mocking and making the logic work
Flyingmana Feb 18, 2024
c6eabc3
tests: finish some more tests w/ website and store data overrides
Feb 18, 2024
63a9c95
Merge branch 'main' into feature/ENV-variables-override-config
fballiano Feb 19, 2024
f6743b3
Merge branch 'main' into feature/ENV-variables-override-config
fballiano Feb 25, 2024
50bdaf8
wip: override config from env vars
Feb 4, 2024
67b82e7
docs and phpcs changes
Feb 4, 2024
fe15216
feat: refactor config environment loader
Feb 11, 2024
2da4951
add unittest and some changes for mocking and making the logic work
Flyingmana Feb 18, 2024
3eda641
tests: finish some more tests w/ website and store data overrides
Feb 18, 2024
b2abcf0
Merge branch 'feature/ENV-variables-override-config' of https://githu…
Mar 2, 2024
b3da428
fix: PHP8 test error when constructing Mage_Core_Model_Config
Mar 2, 2024
e3d9424
Updated copyright
fballiano Mar 2, 2024
36e55c7
fix: config overrides should not work with invalid config key
Mar 3, 2024
7b5da9b
feat: add allow list for regexp; alter tests
Mar 17, 2024
b2db566
Merge branch 'main' into feature/ENV-variables-override-config
Mar 17, 2024
fd28543
fix: add tests for dash and underscore for group/section or field
Mar 17, 2024
5f39a4a
Merge branch 'main' into feature/ENV-variables-override-config
fballiano Apr 8, 2024
86e85e7
Merge branch 'main' into feature/ENV-variables-override-config
Apr 26, 2024
0fa2a84
chore: move property below constants
Apr 26, 2024
3a28479
chore: change method visibility from public to protected; move accord…
Apr 26, 2024
b487236
feat: make tests compatible to visibility changes on helper class
Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions app/code/core/Mage/Core/Helper/EnvironmentConfigLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* OpenMage
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available at https://opensource.org/license/osl-3-0-php
*
* @category Mage
* @package Mage_Core
* @copyright Copyright (c) 2006-2020 Magento, Inc. (https://www.magento.com)
* @copyright Copyright (c) 2016-present The OpenMage Contributors (https://www.openmage.org)
fballiano marked this conversation as resolved.
Show resolved Hide resolved
* @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/

/**
* Core Environment helper
*
* @category Mage
* @package Mage_Core
*/
class Mage_Core_Helper_EnvironmentConfigLoader extends Mage_Core_Helper_Abstract
{
protected $_moduleName = 'Mage_Core';
protected const ENV_STARTS_WITH = 'OPENMAGE_CONFIG';
protected const ENV_KEY_SEPARATOR = '__';
protected const CONFIG_KEY_DEFAULT = 'DEFAULT';
protected const CONFIG_KEY_WEBSITES = 'WEBSITES';
protected const CONFIG_KEY_STORES = 'STORES';

protected array $envStore = [];

/**
* Load configuration values from ENV variables into xml config object
*
* Environment variables work on this schema:
*
* self::ENV_STARTS_WITH . self::ENV_KEY_SEPARATOR (OPENMAGE_CONFIG__)
* ^ Prefix (required)
* <SCOPE>__
* ^ Where scope is DEFAULT, WEBSITES__<WEBSITE_CODE> or STORES__<STORE_CODE>
* <SYSTEM_VARIABLE_NAME>
* ^ Where GROUP, SECTION and FIELD are separated by self::ENV_KEY_SEPARATOR
*
* Each example will override the 'general/store_information/name' value.
* Override from the default configuration:
* @example OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME=default
* Override the website 'base' configuration:
* @example OPENMAGE_CONFIG__WEBSITES__BASE__GENERAL__STORE_INFORMATION__NAME=website
* Override the store 'german' configuration:
* @example OPENMAGE_CONFIG__STORES__GERMAN__GENERAL__STORE_INFORMATION__NAME=store_german
*
* @param Varien_Simplexml_Config $xmlConfig
* @return void
*/
public function overrideEnvironment(Varien_Simplexml_Config $xmlConfig)
{
$env = $this->getEnv();

foreach ($env as $configKey => $value) {
if (!str_starts_with($configKey, static::ENV_STARTS_WITH)) {
continue;
}

$configKeyParts = array_filter(
explode(
static::ENV_KEY_SEPARATOR,
$configKey
),
'trim'
);
list($_, $scope) = $configKeyParts;

switch ($scope) {
case static::CONFIG_KEY_DEFAULT:
list($_, $_, $section, $group, $field) = $configKeyParts;
$path = $this->buildPath($section, $group, $field);
$xmlConfig->setNode($this->buildNodePath($scope, $path), $value);
break;

case static::CONFIG_KEY_WEBSITES:
case static::CONFIG_KEY_STORES:
list($_, $_, $code, $section, $group, $field) = $configKeyParts;
$path = $this->buildPath($section, $group, $field);
$nodePath = sprintf('%s/%s/%s', strtolower($scope), strtolower($code), $path);
$xmlConfig->setNode($nodePath, $value);
break;
}
}
}

/**
* @internal method mostly for mocking
*/
public function setEnvStore(array $envStorage): void
{
$this->envStore = $envStorage;
}

public function getEnv(): array
{
if (empty($this->envStore)) {
$this->envStore = getenv();
}
return $this->envStore;
}

/**
* Build configuration path.
*
* @param string $section
* @param string $group
* @param string $field
* @return string
*/
public function buildPath($section, $group, $field): string
{
return strtolower(implode('/', [$section, $group, $field]));
}

/**
* Build configuration node path.
*
* @param string $scope
* @param string $path
* @return string
*/
public function buildNodePath($scope, $path): string
{
return strtolower($scope) . '/' . $path;
}
}
1 change: 1 addition & 0 deletions app/code/core/Mage/Core/Model/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ protected function _initModules()
Varien_Profiler::stop('mage::app::init::apply_db_schema_updates');
}
$this->_config->loadDb();
$this->_config->loadEnv();
$this->_config->saveCache();
}
} finally {
Expand Down
18 changes: 18 additions & 0 deletions app/code/core/Mage/Core/Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ public function init($options = [])
$this->_useCache = false;
$this->loadModules();
$this->loadDb();
$this->loadEnv();
$this->saveCache();
}
} finally {
Expand Down Expand Up @@ -422,6 +423,23 @@ public function loadDb()
return $this;
}

/**
* Load environment variables and override config
*
* @return self
*/
public function loadEnv(): Mage_Core_Model_Config
{
if ($this->_isLocalConfigLoaded && Mage::isInstalled()) {
Varien_Profiler::start('config/load-env');
/** @var Mage_Core_Helper_EnvironmentConfigLoader $environmentConfigLoaderHelper */
$environmentConfigLoaderHelper = Mage::helper('core/environmentConfigLoader');
$environmentConfigLoaderHelper->overrideEnvironment($this);
Varien_Profiler::stop('config/load-env');
}
return $this;
}

/**
* Reinitialize configuration
*
Expand Down
152 changes: 152 additions & 0 deletions dev/tests/unit/Mage/Core/Helper/EnvironmentConfigLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);

namespace OpenMage\Tests\Unit\Core\Helper;

use PHPUnit\Framework\TestCase;
use Mage_Core_Helper_EnvironmentConfigLoader;
use Mage_Core_Model_Config;

class EnvironmentConfigLoaderTest extends TestCase
{
protected const ENV_CONFIG_DEFAULT_PATH = 'OPENMAGE_CONFIG__DEFAULT__GENERAL__STORE_INFORMATION__NAME';
protected const ENV_CONFIG_DEFAULT_VALUE = 'default_new_value';
protected const ENV_CONFIG_WEBSITE_PATH = 'OPENMAGE_CONFIG__WEBSITES__BASE__GENERAL__STORE_INFORMATION__NAME';
protected const ENV_CONFIG_WEBSITE_VALUE = 'website_new_value';
protected const ENV_CONFIG_STORE_PATH = 'OPENMAGE_CONFIG__STORES__GERMAN__GENERAL__STORE_INFORMATION__NAME';
protected const ENV_CONFIG_STORE_VALUE = 'store_german_new_value';

public function setup(): void
{
\Mage::setRoot('');
}

public function testBuildPath()
{
$environmentConfigLoaderHelper = new Mage_Core_Helper_EnvironmentConfigLoader();
$path = $environmentConfigLoaderHelper->buildPath('GENERAL', 'STORE_INFORMATION', 'NAME');
$this->assertEquals('general/store_information/name', $path);
}

public function testBuildNodePath()
{
$loader = new Mage_Core_Helper_EnvironmentConfigLoader();
$nodePath = $loader->buildNodePath('DEFAULT', 'general/store_information/name');
$this->assertEquals('default/general/store_information/name', $nodePath);
}

/**
* @dataProvider envOverridesDataProvider
*
*/
public function testEnvOverrides(array $config)
{
$xmlStruct = $this->getTestXml();

$xmlDefault = new \Varien_Simplexml_Config();
$xmlDefault->loadString($xmlStruct);
$xml = new \Varien_Simplexml_Config();
$xml->loadString($xmlStruct);

$this->assertEquals('test_default', (string)$xml->getNode('default/general/store_information/name'));
$this->assertEquals('test_website', (string)$xml->getNode('websites/base/general/store_information/name'));
$this->assertEquals('test_store', (string)$xml->getNode('stores/german/general/store_information/name'));

// act
$loader = new Mage_Core_Helper_EnvironmentConfigLoader();
$loader->setEnvStore([
$config['path'] => $config['value']
]);
$loader->overrideEnvironment($xml);

switch ($config['case']) {
case 'DEFAULT':
$defaultValue = $xmlDefault->getNode('default/general/store_information/name');
$valueAfterOverride = $xml->getNode('default/general/store_information/name');
break;
case 'STORE':
$defaultValue = $xmlDefault->getNode('stores/german/general/store_information/name');
$valueAfterOverride = $xml->getNode('general/store_information/name');
break;
case 'WEBSITE':
$defaultValue = $xmlDefault->getNode('default/general/store_information/name');
$valueAfterOverride = $xml->getNode('website/base/store_information/name');
break;
}

// assert
$this->assertNotEquals((string)$defaultValue, (string)$valueAfterOverride, 'Default value was not overridden.');
}

public function envOverridesDataProvider(): array
{
return [
[
'Case DEFAULT with ' . static::ENV_CONFIG_DEFAULT_PATH . ' overrides.' => [
'case' => 'DEFAULT',
'path' => static::ENV_CONFIG_DEFAULT_PATH,
'value' => static::ENV_CONFIG_DEFAULT_VALUE
]
],
[
'Case STORE with ' . static::ENV_CONFIG_STORE_PATH . ' overrides.' => [
'case' => 'STORE',
'path' => static::ENV_CONFIG_STORE_PATH,
'value' => static::ENV_CONFIG_STORE_VALUE
]
],
[
'Case WEBSITE with ' . static::ENV_CONFIG_WEBSITE_PATH . ' overrides.' => [
'case' => 'WEBSITE',
'path' => static::ENV_CONFIG_WEBSITE_PATH,
'value' => static::ENV_CONFIG_WEBSITE_VALUE
]
]
];
}

/**
* @return string
*/
public function getTestXml(): string
{
return <<<XML
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<modules>
<Mage_Core>
<active>true</active>
<codePool>core</codePool>
</Mage_Core>
</modules>

<default>
<general>
<store_information>
<name>test_default</name>
</store_information>
</general>
</default>

<websites>
<base>
<general>
<store_information>
<name>test_website</name>
</store_information>
</general>
</base>
</websites>
<stores>
<german>
<general>
<store_information>
<name>test_store</name>
</store_information>
</general>
</german>
</stores>
</config>
XML;
}
}
Loading