Commit a100ff15 authored by Hendrik Heneke's avatar Hendrik Heneke
Browse files

Initial version.

parents
Pipeline #360 passed with stage
in 26 seconds
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Definition;
use HHIT\ConfigGeneratorBundle\Generator\IO\File;
use HHIT\ConfigGeneratorBundle\Generator\IO\JsonReader;
use HHIT\ConfigGeneratorBundle\Generator\IO\Path;
use HHIT\ConfigGeneratorBundle\Generator\IO\ReaderFactory;
use HHIT\ConfigGeneratorBundle\Generator\StringUtils;
class DefinitionReader
{
private string $projectDir;
public function __construct(string $projectDir)
{
$this->projectDir = $projectDir;
}
/**
* @param string|null $definitionFileName
* @return array<Definition>
*/
public function read(?string $definitionFileName = null): array
{
$definitionFile = $definitionFileName ? new File($definitionFileName) : new File($this->projectDir . '/cfgen.json');
$reader = new JsonReader($definitionFile);
$definitions = [];
foreach ($reader->readAsJson() as $id => $definition) {
$templateFile = StringUtils::startsWith($definition['template'], './') ?
new File($definition['template'], $definitionFile) : new File($definition['template']);
$valuesFile = StringUtils::startsWith($definition['values'], './') ?
new File($definition['values'], $definitionFile) : new File($definition['values']);
$destinationFile = StringUtils::startsWith($definition['destination'], './') ?
new File($definition['destination'], $definitionFile) : new File($definition['destination']);
$definitions[] = new Definition($id, $templateFile, $definition['type'], $valuesFile, $destinationFile);
}
return $definitions;
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator;
use HHIT\ConfigGeneratorBundle\Generator\Compiler\Compiler;
use HHIT\ConfigGeneratorBundle\Generator\Definition\DefinitionReader;
use HHIT\ConfigGeneratorBundle\Generator\IO\FileWriter;
use HHIT\ConfigGeneratorBundle\Generator\Definition\Definition;
use HHIT\ConfigGeneratorBundle\Generator\Validator\ValidationException;
use HHIT\ConfigGeneratorBundle\Generator\Validator\ValidatorFactory;
use HHIT\ConfigGeneratorBundle\Generator\Values\ValuesLoaderFactory;
use Symfony\Component\Console\Output\OutputInterface;
class Generator
{
private DefinitionReader $definitionReader;
private ValuesLoaderFactory $valuesLoaderFactory;
private ValidatorFactory $validatorFactory;
public function __construct(
DefinitionReader $definitionReader,
ValuesLoaderFactory $valuesLoaderFactory,
ValidatorFactory $validatorFactory
) {
$this->definitionReader = $definitionReader;
$this->valuesLoaderFactory = $valuesLoaderFactory;
$this->validatorFactory = $validatorFactory;
}
public function processConfigurations(
string $vaultType,
bool $overwrite,
?string $configurationFile = null,
?OutputInterface $output = null
): bool {
$success = [];
foreach ($this->definitionReader->read($configurationFile) as $definition) {
/**
* @var $definition Definition
*/
$this->writeln($output, "<info>Processing configuration {$definition->getId()}</info>");
try {
$valuesLoader = $this->valuesLoaderFactory->create($definition->getValuesFile(), $vaultType);
$compiler = new Compiler($definition->getTemplateFile(), $valuesLoader);
$content = $compiler->compile();
try {
$this->validatorFactory->createValidator($definition->getType())->validate($content);
$definition->getDestinationFile()->parent(false);
$writer = new FileWriter($definition->getDestinationFile());
$writer->write($compiler->compile(), $overwrite);
$success[] = true;
} catch (ValidationException $ve) {
$this->writeln($output, "<error>generated content for {$definition->getId()} is invalid: {$ve->getMessage()}</error>");
throw $ve;
}
} catch (\Throwable $t) {
if (!$t instanceof ValidationException) {
$this->writeln($output, "<error>generating configuration file {$definition->getId()} failed: {$t->getMessage()}</error>");
}
$success[] = false;
}
}
return empty($success) ? true : !in_array(false, $success);
}
private function writeln(?OutputInterface $output, $message)
{
if ($output) {
$output->writeln($message);
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
class Directory extends Path
{
public function __construct(string $path, ?Path $relativeTo = null)
{
parent::__construct($path, $relativeTo);
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
class File extends Path
{
public function __construct(string $path, ?Path $relativeTo = null)
{
parent::__construct($path, $relativeTo);
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
class FileReader
{
private File $file;
public function __construct(File $file)
{
$this->file = $file;
}
public function read()
{
if (!$this->file->exists()) {
throw new \RuntimeException("File {$this->file} does not exist!");
}
return file_get_contents($this->file->asString());
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
class FileWriter
{
private File $file;
public function __construct(File $file)
{
$this->file = $file;
}
public function write($data, $overwrite = false)
{
if (!$overwrite) {
if ($this->file->exists()) {
throw new \RuntimeException("File '{$this->file->asString()}' exists but was not configured to be overwritten!");
}
}
if (!$this->file->parent()->exists()) {
$this->file->mkdir();
}
if (file_put_contents($this->file->asString(), $data) === false) {
throw new \RuntimeException("Could not write to file '{$this->file->asString()}'!");
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
class JsonReader extends FileReader
{
public function __construct(File $file)
{
parent::__construct($file);
}
public function readAsJson(bool $assoc = true, int $depth = 512)
{
$str = $this->read();
if ($str) {
try {
return json_decode($str, $assoc, $depth, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new \RuntimeException('Reading JSON failed', 0, $e);
}
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\IO;
use HHIT\ConfigGeneratorBundle\Generator\StringUtils;
abstract class Path
{
private string $path;
protected function __construct(string $path, ?File $relativeTo = null)
{
$this->path = $relativeTo ? $relativeTo->parent()->asString() . '/' . StringUtils::removeFromStart(trim($path), './') : trim($path);
}
public function asString(): string
{
return $this->path;
}
public function exists(): bool
{
clearstatcache();
return file_exists($this->path);
}
public function parent(): Directory
{
return new Directory(dirname($this->path));
}
public function mkdir($mode = 0777)
{
$parent = $this->parent();
if (mkdir($parent->asString(), $mode, true) === false) {
throw new \RuntimeException("Could not create directory '{$parent->asString()}'!");
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Secrets;
class SecretProviderFactory
{
private SymfonyVaultSecretProvider $symfonyProvider;
public function __construct(SymfonyVaultSecretProvider $symfonyProvider)
{
$this->symfonyProvider = $symfonyProvider;
}
public function create(string $type): SecretProviderInterface
{
switch ($type) {
case 'symfony':
return $this->symfonyProvider;
default:
throw new \RuntimeException("Secret provider with type '{$type}' is not supported!");
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Secrets;
interface SecretProviderInterface
{
public function getSecret(string $key): string;
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Secrets;
use Symfony\Bundle\FrameworkBundle\Secrets\DotenvVault;
use Symfony\Bundle\FrameworkBundle\Secrets\SodiumVault;
class SymfonyVaultSecretProvider implements SecretProviderInterface
{
private SodiumVault $sodiumVault;
private DotenvVault $dotenvVault;
public function __construct(SodiumVault $sodiumVault, DotenvVault $dotenvVault)
{
$this->sodiumVault = $sodiumVault;
$this->dotenvVault = $dotenvVault;
}
public function getSecret(string $key): string
{
$secret = null;
if (array_key_exists($key, $this->dotenvVault->list(false))) {
$secret = $this->dotenvVault->reveal($key);
}
if ($secret) {
return $secret;
}
$secret = $this->sodiumVault->reveal($key);
if ($secret) {
return $secret;
} else {
throw new \RuntimeException("Secret '{$key}' does not exist!");
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator;
class StringUtils
{
public static function startsWith(string $string, string $search): bool
{
return mb_strpos($string, $search) === 0;
}
public static function removeFromStart(string $string, string $remove): string
{
return ltrim($string, $remove);
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Validator;
class JSONValidator implements ValidatorInterface
{
public function validate(string $content)
{
try {
json_decode($content, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
throw new ValidationException("JSON is invalid: {$e->getMessage()}", $e->getCode(), $e);
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Validator;
use Throwable;
class ValidationException extends \RuntimeException
{
public function __construct(string $message, $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Validator;
class ValidatorFactory
{
public function createValidator(string $type): ValidatorInterface
{
switch ($type) {
case 'json':
return new JSONValidator();
case 'yaml':
return new YamlValidator();
default:
throw new \RuntimeException("No validator exists for type '{$type}'!");
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Validator;
interface ValidatorInterface
{
public function validate(string $content);
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Validator;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;
class YamlValidator implements ValidatorInterface
{
public function validate(string $content)
{
try {
Yaml::parse($content);
} catch (ParseException $e) {
throw new ValidationException("YAML is invalid: {$e->getMessage()}", $e->getCode(), $e);
}
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Values;
use HHIT\ConfigGeneratorBundle\Generator\IO\File;
use HHIT\ConfigGeneratorBundle\Generator\IO\JsonReader;
use HHIT\ConfigGeneratorBundle\Generator\Secrets\SecretProviderInterface;
class ValuesLoader extends JsonReader
{
private array $context = [];
private SecretProviderInterface $provider;
public function __construct(File $file, SecretProviderInterface $provider)
{
parent::__construct($file);
$this->provider = $provider;
}
public function getValues()
{
if (!$this->context) {
$this->context = $this->postProcess($this->readAsJson());
}
return $this->context;
}
private function postProcess(array $array)
{
foreach ($array as $key => $value) {
if (is_array($value)) {
if (array_key_exists('$type', $value) && array_key_exists('arg', $value)) {
$externalValueType = $value['$type'];
$externalValueArg = $value['arg'];
switch ($externalValueType) {
case 'secret':
$array[$key] = $this->provider->getSecret($externalValueArg);
break;
default:
throw new \RuntimeException("External value with type '{$externalValueType}' is not supported!");
}
} else {
$array[$key] = $this->postProcess($value);
}
}
}
return $array;
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Generator\Values;
use HHIT\ConfigGeneratorBundle\Generator\IO\File;
use HHIT\ConfigGeneratorBundle\Generator\Secrets\SecretProviderFactory;
class ValuesLoaderFactory
{
private SecretProviderFactory $secretProviderFactory;
public function __construct(SecretProviderFactory $secretProviderFactory)
{
$this->secretProviderFactory = $secretProviderFactory;
}
public function create(File $file, string $vaultType): ValuesLoader
{
return new ValuesLoader($file, $this->secretProviderFactory->create($vaultType));
}
}
<?php
declare(strict_types=1);
namespace HHIT\ConfigGeneratorBundle\Kernel;
use HHIT\ConfigGeneratorBundle\ConfigGeneratorBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class StandaloneKernel extends BaseKernel
{
public function registerBundles()
{
return [
new FrameworkBundle(),
new ConfigGeneratorBundle()
];
}
public function registerContainerConfiguration(LoaderInterface $loader)
{
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment