817 lines
32 KiB
PHP
817 lines
32 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Enums\EnvironmentScope;
|
|
use App\Lib\CdEnvVariable;
|
|
use App\Lib\Common;
|
|
use App\Lib\Gitlab;
|
|
use GrahamCampbell\GitLab\GitLabManager;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Validator;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Psy\Readline\Hoa\ConsoleException;
|
|
use SebastianBergmann\Diff\Differ;
|
|
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
|
|
|
|
class SetGitlabCdVariables extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'uwil:gitlab:set_variables {--P|projectId=}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = "Set default variables for an Uwil Project";
|
|
|
|
/**
|
|
* The gitlab client
|
|
*
|
|
* @var \Gitlab\Client
|
|
*/
|
|
private \Gitlab\Client $client;
|
|
|
|
/**
|
|
* The vars that should be present by default
|
|
*
|
|
* * These are type env_var.
|
|
*
|
|
* @var array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>>
|
|
* Keyed with `\App\Enums\EnvironmentScope`.
|
|
*/
|
|
private static array $defaultEnvVars = [];
|
|
|
|
/**
|
|
* Create a new console command instance.
|
|
*
|
|
* @return void
|
|
*/
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
|
|
// * Init default vars reference
|
|
if (empty(self::$defaultEnvVars)) {
|
|
self::$defaultEnvVars = [
|
|
'APP_DEBUG' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'APP_DEBUG',
|
|
value: 'false',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'APP_DEBUG',
|
|
value: 'false',
|
|
protected: true,
|
|
masked: false,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'APP_ENV' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'APP_ENV',
|
|
value: 'staging',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'APP_ENV',
|
|
value: 'production',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'APP_KEY' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'APP_KEY',
|
|
value: Common::generateRandomKey(),
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
'APP_NAME' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'APP_NAME',
|
|
value: 'ChangeMe',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
'DOMAIN' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'DOMAIN',
|
|
value: '$CI_PROJECT_NAME.re7-s-web.fr',
|
|
protected: true,
|
|
raw: false,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'DOMAIN',
|
|
value: 'changeme.changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'CERT_ISSUER' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'CERT_ISSUER',
|
|
value: 're7-letsencrypt-production-dns01',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'CERT_ISSUER',
|
|
value: 'letsencrypt-production-http01',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'DB_DATABASE' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'DB_DATABASE',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'DB_DATABASE',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'DB_USERNAME' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'DB_USERNAME',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'DB_USERNAME',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'DB_PASSWORD' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'DB_PASSWORD',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'DB_PASSWORD',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_ENCRYPTION' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'MAIL_ENCRYPTION',
|
|
value: 'null',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'MAIL_ENCRYPTION',
|
|
value: 'tls',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_HOST' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'MAIL_HOST',
|
|
value: 'null',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'MAIL_HOST',
|
|
value: 'smtp-relay.brevo.com',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_PORT' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'MAIL_PORT',
|
|
value: 'null',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'MAIL_PORT',
|
|
value: '587',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_USERNAME' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'MAIL_USERNAME',
|
|
value: 'null',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'MAIL_USERNAME',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_PASSWORD' => [
|
|
EnvironmentScope::staging->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::staging,
|
|
key: 'MAIL_PASSWORD',
|
|
value: 'null',
|
|
protected: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
EnvironmentScope::production->value => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::production,
|
|
key: 'MAIL_PASSWORD',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
],
|
|
'MAIL_FROM_ADDRESS' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'MAIL_FROM_ADDRESS',
|
|
value: 'no-reply@changeme.changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->isProjectDefault()->wasPrefixed(),
|
|
'PVC_SIZE' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'PVC_SIZE',
|
|
value: '1Gi',
|
|
protected: true,
|
|
))->wasPrefixed(),
|
|
'LDC_WEB_API_KEY' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'LDC_WEB_API_KEY',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->wasPrefixed(),
|
|
'SENTRY_LARAVEL_DSN' => (new CdEnvVariable(
|
|
environment_scope: EnvironmentScope::all,
|
|
key: 'SENTRY_LARAVEL_DSN',
|
|
value: 'changeme',
|
|
protected: true,
|
|
masked: true,
|
|
))->wasPrefixed(),
|
|
];
|
|
self::$defaultEnvVars = Arr::sortRecursive(self::$defaultEnvVars);
|
|
} //end if
|
|
|
|
$this->client = app(GitLabManager::class);
|
|
}
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*
|
|
* ! This command may throw exceptions if there is a unhandled difference between actual vars and template.
|
|
*
|
|
* @return integer
|
|
* @throws \RuntimeException If an error occurs while comunicating with gitlab.
|
|
* @throws \Psy\Readline\Hoa\ConsoleException If the user cancels this command.
|
|
*/
|
|
public function handle(): int
|
|
{
|
|
$this->warn(
|
|
'! This command may throw exceptions if there is a unhandled difference between actual vars and template.'
|
|
);
|
|
$this->warn('!! Sensitive data is masked !!');
|
|
|
|
$projects = $this->client->projects();
|
|
|
|
$projectId = $this->option('projectId');
|
|
/** @var int|null $projectId */
|
|
$projectId = \is_numeric($projectId) ? intval($projectId) : null;
|
|
|
|
/** @var string|null */
|
|
$projectNameWithNamespace = null;
|
|
|
|
if (is_null($projectId)) {
|
|
$this->getInput(
|
|
'Quel projet veut-on paramètrer ?',
|
|
[
|
|
'required',
|
|
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
|
function (
|
|
string $parameter,
|
|
string|null $value,
|
|
callable $fail
|
|
) use (
|
|
$projects,
|
|
&$projectId,
|
|
&$projectNameWithNamespace
|
|
) {
|
|
$results = collect($projects->all(['search' => $value]));
|
|
if (!$results->count()) {
|
|
$fail('Project not found');
|
|
return;
|
|
}
|
|
$projectNameWithNamespace = $results->count() > 1 ? $this->getChoice(
|
|
'Veuillez choisir parmis les résultats',
|
|
$results->pluck('name_with_namespace')->all()
|
|
) : strval(Arr::get(Arr::get($results, 0), 'name_with_namespace'));
|
|
|
|
if (empty($projectNameWithNamespace)) {
|
|
$fail('Project not found');
|
|
return;
|
|
}
|
|
|
|
$this->info("Selected project {$projectNameWithNamespace}");
|
|
|
|
$projectId = Arr::get($results->where(
|
|
'name_with_namespace',
|
|
$projectNameWithNamespace
|
|
)->firstOrFail(), 'id');
|
|
}
|
|
]
|
|
);
|
|
} else {
|
|
$projectNameWithNamespace = Arr::get($this->client->projects()->show($projectId), 'name_with_namespace');
|
|
|
|
if (empty($projectNameWithNamespace)) {
|
|
throw new \RuntimeException(
|
|
"Could not get 'name_with_namespace' for project project id `{$projectId}`"
|
|
);
|
|
}
|
|
} //end if
|
|
|
|
/** @var int $projectId */
|
|
|
|
$this->info("Using project id `{$projectId}` name `{$projectNameWithNamespace}`");
|
|
|
|
\sleep(6);
|
|
|
|
$actualProjectVars = Arr::sort($this->getProjectCdVars($projectId));
|
|
|
|
$newVars = Arr::sort($this->mergeVars(self::$defaultEnvVars, $actualProjectVars));
|
|
|
|
$this->showAndConfirmDiff($actualProjectVars, $newVars);
|
|
|
|
$this->warn('The following vars will be injected and replace previous CD vars');
|
|
$this->line($this->stringifyVars($newVars));
|
|
|
|
if (!$this->confirm('Save these to git lab ?')) {
|
|
throw new ConsoleException('Operation aborted');
|
|
}
|
|
|
|
if (
|
|
$this->getChoice(
|
|
'Again: Previous vars will be lost and these will be saved, are you sure ?',
|
|
['yes I am sure', 'no cancel please']
|
|
) === 'no cancel please'
|
|
) {
|
|
throw new ConsoleException('Operation aborted');
|
|
}
|
|
|
|
// * Remove existing vars before inserting all
|
|
$this->removeExistingProjectCdVars($projectId);
|
|
if (!Gitlab::saveProjectCdVarsToGitlab($projectId, $newVars)) {
|
|
$this->warn(
|
|
'Sorry an error just happened, please check actual env vars to prevent project crash ok k8s'
|
|
);
|
|
return 1;
|
|
}
|
|
|
|
$this->info('Done !!!');
|
|
$this->info('Please check on ' . $this->getProjetCdVarsUrl($projectId));
|
|
return 0;
|
|
}
|
|
|
|
// * PRIVATE GITLAB
|
|
|
|
/**
|
|
* Remove all existing project CD vars
|
|
*
|
|
* @param integer $projectId
|
|
* @return void
|
|
*/
|
|
private function removeExistingProjectCdVars(int $projectId): void
|
|
{
|
|
collect($this->queryProjectCdVars($projectId))->each(function (array $gitlabVar) use ($projectId) {
|
|
try {
|
|
$this->client->projects()->removeVariable(
|
|
$projectId,
|
|
Arr::get($gitlabVar, 'key'),
|
|
[
|
|
'filter' => [
|
|
'environment_scope' => Arr::get($gitlabVar, 'environment_scope')
|
|
]
|
|
]
|
|
);
|
|
} catch (\Exception $e) {
|
|
$this->warn(
|
|
'Sorry an error just happened, please check actual env vars to prevent project crash ok k8s'
|
|
);
|
|
dump($e);
|
|
exit;
|
|
} //end try
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get Project vars
|
|
*
|
|
* @param integer $projectId The project id for which we want cd variables.
|
|
* @return array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>>
|
|
*/
|
|
private function getProjectCdVars(int $projectId): array
|
|
{
|
|
return Arr::sortRecursive(collect($this->queryProjectCdVars($projectId))
|
|
->map(function (array $envVar) {
|
|
$envVar = collect($envVar);
|
|
$env_scope = EnvironmentScope::make($envVar->get('environment_scope', ''));
|
|
if (!$env_scope) {
|
|
throw new \Error('Unhandled scope \'' . $envVar->get('environment_scope', '') . '\'');
|
|
}
|
|
$envVar->put('environment_scope', $env_scope);
|
|
return $envVar->all();
|
|
})->filter(fn (array $envVar) => Arr::get($envVar, 'variable_type') === 'env_var')
|
|
->map(fn (array $envVar) => CdEnvVariable::make(
|
|
$envVar,
|
|
$this->wasGlCdVarPrefixed($envVar),
|
|
$this->wasGlCdVarDefault($envVar)
|
|
))
|
|
->groupBy(fn (CdEnvVariable $envVar) => $envVar->key, true)
|
|
->map(function (Collection $envVars) {
|
|
if (
|
|
$envVars->count() === 1 and
|
|
$envVars->first()->environment_scope->equals(EnvironmentScope::all)
|
|
) {
|
|
return $envVars->first();
|
|
}
|
|
return $envVars->values()
|
|
->keyBy(fn (CdEnvVariable $envVar) => $envVar->environment_scope->value)
|
|
->all();
|
|
})->all());
|
|
}
|
|
|
|
/**
|
|
* Is the GL var default ?
|
|
*
|
|
* @param array $envVar
|
|
* @return boolean
|
|
*/
|
|
private function wasGlCdVarDefault(array $envVar): bool
|
|
{
|
|
$virtual_var = CdEnvVariable::make($envVar);
|
|
/** @var \App\Lib\CdEnvVariable|array<\App\Lib\CdEnvVariable>|null $var */
|
|
$var = Arr::get(self::$defaultEnvVars, $virtual_var->key);
|
|
if (!$var) {
|
|
return false;
|
|
}
|
|
if (is_array($var)) {
|
|
/** @var \App\Lib\CdEnvVariable|null $var */
|
|
$var = Arr::get($var, $virtual_var->environment_scope->value);
|
|
if (!$var) {
|
|
return false;
|
|
}
|
|
}
|
|
return $var->isDefault;
|
|
}
|
|
|
|
/**
|
|
* Is the GL var prefixed ?
|
|
*
|
|
* @param array $envVar
|
|
* @return boolean
|
|
*/
|
|
private function wasGlCdVarPrefixed(array $envVar): bool
|
|
{
|
|
$virtual_var = CdEnvVariable::make($envVar);
|
|
/** @var \App\Lib\CdEnvVariable|array<\App\Lib\CdEnvVariable>|null $var */
|
|
$var = Arr::get(self::$defaultEnvVars, $virtual_var->key);
|
|
if (!$var) {
|
|
return false;
|
|
}
|
|
if (is_array($var)) {
|
|
/** @var \App\Lib\CdEnvVariable|null $var */
|
|
$var = Arr::get($var, $virtual_var->environment_scope->value);
|
|
if (!$var) {
|
|
return false;
|
|
}
|
|
}
|
|
return $var->wasPrefixed;
|
|
}
|
|
|
|
/**
|
|
* Query Gitlab project vars
|
|
*
|
|
* @param integer $projectId
|
|
* @return array
|
|
*/
|
|
private function queryProjectCdVars(int $projectId): array
|
|
{
|
|
// ! per_page max is 100, if this limit is reached need to implement pagination scrapping.
|
|
return $this->client->projects()->variables($projectId, ['per_page' => 100]);
|
|
}
|
|
|
|
/**
|
|
* Get project CD vars url
|
|
*
|
|
* @param integer $projectId
|
|
* @return string
|
|
*/
|
|
private function getProjetCdVarsUrl(int $projectId): string
|
|
{
|
|
return Str::of(Arr::get($this->client->projects()->show($projectId), 'web_url'))
|
|
->finish('/')->append('-/settings/ci_cd');
|
|
}
|
|
|
|
// * PRIVATE MISC
|
|
|
|
/**
|
|
* Show diff and request user consent
|
|
*
|
|
* @param array $actualProjectVars
|
|
* @param array $newVars
|
|
* @return void
|
|
* @throws \Psy\Readline\Hoa\ConsoleException If user wants to abort.
|
|
* @throws \RuntimeException If diff is empty.
|
|
*/
|
|
private function showAndConfirmDiff(array $actualProjectVars, array $newVars): void
|
|
{
|
|
// * Calc diff
|
|
$builder = new UnifiedDiffOutputBuilder(
|
|
// * custom header
|
|
"--- Original\n+++ New\n",
|
|
// * do not add line numbers to the diff
|
|
false
|
|
);
|
|
$differ = new Differ($builder);
|
|
$diff = Str::of($differ->diff(
|
|
$this->stringifyVars(...$actualProjectVars),
|
|
$this->stringifyVars(...$newVars),
|
|
))->explode("\n");
|
|
|
|
$diff->each(function (string $line) {
|
|
if (Str::of($line)->startsWith('-')) {
|
|
$this->error($line);
|
|
return;
|
|
}
|
|
if (Str::of($line)->startsWith('+')) {
|
|
$this->info($line);
|
|
return;
|
|
}
|
|
$this->line($line);
|
|
});
|
|
|
|
if ($diff->count() <= 3) {
|
|
throw new \RuntimeException(
|
|
'There is no diff between skeletton Cd vars and actual vars, no need to update, Operation aborted'
|
|
);
|
|
}
|
|
|
|
if (!$this->confirm('The following diff will be set, continue ?')) {
|
|
throw new ConsoleException('Operation aborted');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stringify a var list
|
|
*
|
|
* @param \App\Lib\CdEnvVariable|array ...$vars
|
|
* @return string
|
|
*/
|
|
private function stringifyVars(CdEnvVariable|array ...$vars): string
|
|
{
|
|
$out = Str::of('');
|
|
foreach ($vars as $var) {
|
|
if (is_array($var)) {
|
|
$out = $out->append($this->stringifyVars(...$var));
|
|
continue;
|
|
}
|
|
$out = $out->append($var)->newLine();
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Join variables
|
|
*
|
|
* @param array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>> ...$cdVars
|
|
* @return array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>>
|
|
*/
|
|
private function mergeVars(array ...$cdVars): array
|
|
{
|
|
/** @var array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>>|null */
|
|
$firstArr = Arr::pull($cdVars, 0);
|
|
if (!$firstArr) {
|
|
return [];
|
|
}
|
|
collect($cdVars)->each(function (array $cdVarArray) use (&$firstArr) {
|
|
/** @var array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>>|null $cdVarArray */
|
|
foreach ($cdVarArray as $key => $cdVarList) {
|
|
/** @var \App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>|null The default config for actual key */
|
|
$firstArrCdVarList = Arr::get($firstArr, $key);
|
|
|
|
// ? A default config exists for this key ?
|
|
if (!$firstArrCdVarList) {
|
|
// * Assign and continue
|
|
Arr::set($firstArr, $key, $cdVarList);
|
|
continue;
|
|
}
|
|
// * Need to merge props form here.
|
|
// * This should indicate that environment_scope is all
|
|
if ($firstArrCdVarList instanceof \App\Lib\CdEnvVariable) {
|
|
$this->assertEnvironmentScopeIs(EnvironmentScope::all, ...Arr::wrap($cdVarList));
|
|
|
|
// ? Are we going to set all scopes var to an existing array of var with different scopes ?
|
|
if (is_array($cdVarList)) {
|
|
if ($firstArrCdVarList->environment_scope->equals(EnvironmentScope::all)) {
|
|
Arr::set($firstArr, $key, $this->askWhichShouldBeKeptOnMergeScopes(
|
|
$cdVarList,
|
|
$firstArrCdVarList
|
|
));
|
|
continue;
|
|
}
|
|
Arr::set($firstArr, $key, $cdVarList);
|
|
continue;
|
|
}
|
|
// * Replace firArrVar.
|
|
Arr::set($firstArr, $key, $cdVarList);
|
|
continue;
|
|
} //end if
|
|
|
|
if ($cdVarList instanceof \App\Lib\CdEnvVariable) {
|
|
if ($cdVarList->environment_scope->equals(EnvironmentScope::all)) {
|
|
Arr::set($firstArr, $key, $this->askWhichShouldBeKeptOnMergeScopes(
|
|
$firstArrCdVarList,
|
|
$cdVarList
|
|
));
|
|
continue;
|
|
}
|
|
// * Single var with env other than all
|
|
$cdVarList = Arr::wrap($cdVarList);
|
|
}
|
|
|
|
// * This should indicate that there is no environment_scope in array.
|
|
$newCdVarList = $firstArrCdVarList;
|
|
$this->assertEnvironmentScopeIsNot(EnvironmentScope::all, ...$cdVarList);
|
|
foreach ($cdVarList as $environment_scope_value => $cdVar) {
|
|
// ! The can merge all scope with other scopes
|
|
$newCdVarList[$environment_scope_value] = $cdVar;
|
|
}
|
|
Arr::set($firstArr, $key, $newCdVarList);
|
|
} //end foreach
|
|
});
|
|
return $firstArr;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param array<string,\App\Lib\CdEnvVariable> $firstArrCdVarList
|
|
* @param \App\Lib\CdEnvVariable $varWithAllScope
|
|
* @return array<string,\App\Lib\CdEnvVariable>|CdEnvVariable
|
|
*/
|
|
private function askWhichShouldBeKeptOnMergeScopes(
|
|
array $firstArrCdVarList,
|
|
CdEnvVariable $varWithAllScope
|
|
): array|CdEnvVariable {
|
|
$this->info(' --- VAR ARRAY --->');
|
|
collect($firstArrCdVarList)->each(fn (CdEnvVariable $var) => $this->info($var));
|
|
$this->info(' --- VAR `All` scope --->');
|
|
$this->info($varWithAllScope);
|
|
|
|
$message =
|
|
'How do you want to proceed in order to merge `All` scope var into a multiple scopes var array ?';
|
|
$replaceMessage = 'Replace the var array with the `All` scope var';
|
|
$keepMessage = 'Replace the `All` scope var with var array';
|
|
switch ($this->getChoice($message, [$replaceMessage, $keepMessage], $replaceMessage)) {
|
|
case $replaceMessage:
|
|
return $varWithAllScope;
|
|
case $keepMessage:
|
|
default:
|
|
return $firstArrCdVarList;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asset that a variable `environment_scope` has a certain value.
|
|
*
|
|
* @param \App\Enums\EnvironmentScope $environment_scope
|
|
* @param \App\Lib\CdEnvVariable ...$var
|
|
* @return void
|
|
* @throws \RuntimeException If the variable `environment_scope` is different that expected.
|
|
*/
|
|
private function assertEnvironmentScopeIs(
|
|
EnvironmentScope $environment_scope,
|
|
CdEnvVariable ...$var
|
|
): void {
|
|
foreach ($var as $cdVar) {
|
|
if (!$cdVar->environment_scope->equals($environment_scope)) {
|
|
$scopeLabel = $environment_scope->label();
|
|
$this->error(' --- CONTEXT --->');
|
|
collect($var)->each(fn (CdEnvVariable $var) => $this->info($var));
|
|
$scopeLabel = $environment_scope->label();
|
|
throw new \RuntimeException(
|
|
"environment_scope should have been `{$scopeLabel}` into var {$cdVar}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asset that a variable `environment_scope` dow not have a certain value.
|
|
*
|
|
* @param \App\Enums\EnvironmentScope $environment_scope
|
|
* @param \App\Lib\CdEnvVariable ...$var
|
|
* @return void
|
|
* @throws \RuntimeException If the variable `environment_scope` is different that expected.
|
|
*/
|
|
private function assertEnvironmentScopeIsNot(
|
|
EnvironmentScope $environment_scope,
|
|
CdEnvVariable ...$var
|
|
): void {
|
|
foreach ($var as $cdVar) {
|
|
if ($cdVar->environment_scope->equals($environment_scope)) {
|
|
$scopeLabel = $environment_scope->label();
|
|
throw new \RuntimeException(
|
|
"environment_scope shouldn't be `{$scopeLabel}` on var {$cdVar}"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Request user to make a choice
|
|
*
|
|
* @param string $askMessage
|
|
* @param array<string> $choices
|
|
* @param string|null $default
|
|
* @return string
|
|
*/
|
|
private function getChoice(
|
|
string $askMessage,
|
|
array $choices,
|
|
string $default = null
|
|
): string {
|
|
return $this->choice($askMessage, $choices, $default);
|
|
}
|
|
|
|
/**
|
|
* Get user input
|
|
*
|
|
* @param string $askMessage Ask something to the user.
|
|
* @param string|array $validate As you would pass to laravel validator.
|
|
* @param string $errorMessage Message to show in case of wrong input.
|
|
* @return string
|
|
*/
|
|
private function getInput(
|
|
string $askMessage,
|
|
string|array $validate = 'min:3|max:255',
|
|
string $errorMessage = 'Invalid value try again'
|
|
): string {
|
|
$value = null;
|
|
while (is_null($value)) {
|
|
try {
|
|
/** @var string|null $tmp */
|
|
$tmp = $this->ask($askMessage);
|
|
$validator = Validator::make(['input' => $tmp], [
|
|
'input' => \is_string($validate) ? "required|{$validate}" :
|
|
\array_merge(['required'], $validate)
|
|
]);
|
|
$validator->validate();
|
|
/** @var string $value */
|
|
$value = $tmp;
|
|
} catch (ValidationException $e) {
|
|
$this->error(sprintf(
|
|
"{$errorMessage} %s",
|
|
\implode(',', collect($e->errors())->flatten()->all())
|
|
));
|
|
continue;
|
|
}
|
|
}; //end while
|
|
return $value;
|
|
}
|
|
}
|