feat: ✨ 📝 - Add command for Showroom BO Instanciation

This commit is contained in:
Vincent BRIDIER 2024-09-05 13:51:44 +02:00
parent f68303105a
commit 6e3b19a5aa
No known key found for this signature in database
GPG key ID: 8571BFA20420D512
11 changed files with 683 additions and 64 deletions

View file

@ -14,3 +14,15 @@
or
`artisan uwil:gitlab:set_variables -P project_id`
### Showroom BO Instances
```bash
artisan uwil:gitlab:showroom-bo-setup "BRAND NAME"
ex:
artisan uwil:gitlab:showroom-bo-setup "Nature and Respect"
```
## Available cert issuers
[Documentation here](https://lab.s-web.fr/ldc-web/general/documentation/-/wikis/Deployment#available-cert-issuers)

View file

@ -4,13 +4,14 @@ 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\Encryption\Encrypter;
use Illuminate\Validation\ValidationException;
use Psy\Readline\Hoa\ConsoleException;
use SebastianBergmann\Diff\Differ;
@ -94,7 +95,7 @@ class SetGitlabCdVariables extends Command
'APP_KEY' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'APP_KEY',
value: $this->generateRandomKey(),
value: Common::generateRandomKey(),
protected: true,
masked: true,
))->isProjectDefault()->wasPrefixed(),
@ -296,11 +297,11 @@ class SetGitlabCdVariables extends Command
*
* ! This command may throw exceptions if there is a unhandled difference between actual vars and template.
*
* @return void
* @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(): void
public function handle(): int
{
$this->warn(
'! This command may throw exceptions if there is a unhandled difference between actual vars and template.'
@ -395,55 +396,20 @@ class SetGitlabCdVariables extends Command
// * Remove existing vars before inserting all
$this->removeExistingProjectCdVars($projectId);
$this->saveProjectCdVarsToGitlab($projectId, $newVars);
$this->info('Done !!!');
$this->info('Please check on ' . $this->getProjetCdVarsUrl($projectId));
}
// * PRIVATE GITLAB
/**
* Save compiled vars on Gitlab project
*
* @param integer $projectId
* @param array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>> $newVars
* @return void
*/
private function saveProjectCdVarsToGitlab(int $projectId, array $newVars): void
{
try {
$updateProjectVar = function (int $projectId, CdEnvVariable $var) {
$this->client->projects()->addVariable(
$projectId,
$var->getFormatedGitlabKey(),
$var->value,
$var->protected,
$var->environment_scope->value,
[
'masked' => $var->masked,
'raw' => $var->raw
]
);
};
foreach ($newVars as $varOrVarList) {
if ($varOrVarList instanceof CdEnvVariable) {
$updateProjectVar($projectId, $varOrVarList);
continue;
}
foreach ($varOrVarList as $var) {
$updateProjectVar($projectId, $var);
}
}
} catch (\Exception $e) {
if (!Gitlab::saveProjectCdVarsToGitlab($projectId, $newVars)) {
$this->warn(
'Sorry an error just happened, please check actual env vars to prevent project crash ok k8s'
);
dump($e);
exit;
} //end try
return 1;
}
$this->info('Done !!!');
$this->info('Please check on ' . $this->getProjetCdVarsUrl($projectId));
return 0;
}
// * PRIVATE GITLAB
/**
* Remove all existing project CD vars
*
@ -848,18 +814,4 @@ class SetGitlabCdVariables extends Command
}; //end while
return $value;
}
/**
* Generate a random key for the application.
*
* ! Copy from \Illuminate\Foundation\Console\KeyGenerateCommand
*
* @return string
*/
protected function generateRandomKey()
{
return 'base64:' . base64_encode(
Encrypter::generateKey(config('app.cipher'))
);
}
}

View file

@ -0,0 +1,466 @@
<?php
namespace App\Console\Commands;
use App\Enums\EnvironmentScope;
use App\Lib\CdEnvVariable;
use App\Lib\Common;
use App\Lib\Gitlab;
use App\Objects\Gitlab\Schedule;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class ShowroomBoSetup extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'uwil:gitlab:showroom-bo-setup {name}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Setup showroom BO instance from template';
/**
* 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 = [];
/**
* Execute the console command.
*
* @return integer
*/
public function handle(): int
{
$name = $this->argument('name');
$this->info("Template upload...");
$templateUpload = $this->uploadTemplate($name);
if (!$templateUpload->created()) {
$this->error("Error occured on template upload. See logs.");
return 1;
}
$projectId = $templateUpload->collect()->get('id');
$this->info("Template uploaded.");
$this->info("Setting vars...");
$this->setDefaultVars($name);
$this->info("Vars are set.");
$this->info("Vars upload...");
if (!Gitlab::saveProjectCdVarsToGitlab($projectId, self::$defaultEnvVars)) {
$this->warn(
'Sorry an error just happened, please check actual env vars to prevent project crash ok k8s'
);
return 1;
}
$this->info("Vars uploaded.");
$this->info("Schedules upload...");
if (
!Gitlab::setPipelineSchedules($projectId, new Schedule(
'0 14 * * *',
"Deploy STAGING",
"trunk",
false,
'Europe/Paris'
))
) {
$this->warn(
'Sorry an error just happened on STAGING Schedule upload'
);
return 1;
}
if (
!Gitlab::setPipelineSchedules($projectId, new Schedule(
'0 14 * * *',
"Deploy PRODUCTION",
"master",
false,
'Europe/Paris'
))
) {
$this->warn(
'Sorry an error just happened on PRODUCTION Schedule upload'
);
return 1;
}
$this->info("Schedules uploaded.");
$this->newLine();
$this->info("Instance created successfully! Think to set the secret variables before running the pipelines");
return 0;
}
/**
* uploadTemplate method
*
* @param string $name
* @return \Illuminate\Http\Client\Response
*/
private function uploadTemplate(string $name): \Illuminate\Http\Client\Response
{
/**
* https://docs.gitlab.com/ee/api/project_import_export.html#import-a-file
*
* file string The file to be uploaded. REQUIRED
* path string Name and path for new project. REQUIRED
* name string The name of the project to be imported. Defaults to the path of the project if not provided.
* namespace integer or string The ID or path of the namespace to import the project to.
* Defaults to the current user's namespace.
* Requires at least the Maintainer role on the destination group to import to.
*/
$file = fopen(Storage::disk('gitlabExports')->path('showroom-bo.tar.gz'), 'r');
$response = Http::withHeaders([
'Private-Token' => config('gitlab.connections.main.token')
])->attach(
'file',
$file,
'showroom-bo.tar.gz'
)->post(
sprintf(
"%s/api/v4/projects/import",
config('gitlab.connections.main.url')
),
[
'path' => Str::slug($name),
'name' => $name,
'namespace' => config('gitlab.showroom-bo.instances_group_id')
]
);
return $response;
}
/**
* setDefaultVars method
*
* @param string $name
* @return void
*/
private function setDefaultVars(string $name): void
{
// * Init default vars reference
if (empty(self::$defaultEnvVars)) {
self::$defaultEnvVars = [
'APP_NAME' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'APP_NAME',
value: $name,
protected: true,
))->isProjectDefault(),
'APP_ENV' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'APP_ENV',
value: 'staging',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'APP_ENV',
value: 'production',
protected: true,
masked: true,
))->isProjectDefault(),
],
'APP_KEY' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'APP_KEY',
value: Common::generateRandomKey(),
protected: true,
masked: true,
))->isProjectDefault(),
'APP_DEBUG' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'APP_DEBUG',
value: 'false',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'APP_DEBUG',
value: 'false',
protected: true,
))->isProjectDefault(),
],
'DOMAIN' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'DOMAIN',
value: '$CI_PROJECT_NAME-expo.re7-s-web.fr',
protected: true,
masked: true,
raw: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'DOMAIN',
value: sprintf(
"%s-expo.uwil.fr",
Str::slug($name)
),
protected: true,
masked: true,
))->isProjectDefault(),
],
'DB_DATABASE' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'DB_DATABASE',
value: sprintf(
"staging-expo-%s",
Str::slug($name)
),
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'DB_DATABASE',
value: sprintf(
"prod-expo-%s",
Str::slug($name)
),
protected: true,
masked: true,
))->isProjectDefault(),
],
'DB_USERNAME' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'DB_USERNAME',
value: sprintf(
"staging-expo-%s",
Str::slug($name)
),
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'DB_USERNAME',
value: sprintf(
"prod-expo-%s",
Str::slug($name)
),
protected: true,
masked: true,
))->isProjectDefault(),
],
'DB_PASSWORD' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'DB_PASSWORD',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'DB_PASSWORD',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
],
'MAIL_ENCRYPTION' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'MAIL_ENCRYPTION',
value: 'null',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'MAIL_ENCRYPTION',
value: 'tls',
protected: true,
))->isProjectDefault(),
],
'MAIL_HOST' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'MAIL_HOST',
value: 'null',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'MAIL_HOST',
value: 'smtp-relay.brevo.com',
protected: true,
masked: true,
))->isProjectDefault(),
],
'MAIL_PORT' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'MAIL_PORT',
value: 'null',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'MAIL_PORT',
value: '587',
protected: true,
))->isProjectDefault(),
],
'MAIL_USERNAME' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'MAIL_USERNAME',
value: 'null',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'MAIL_USERNAME',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
],
'MAIL_PASSWORD' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'MAIL_PASSWORD',
value: 'null',
protected: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'MAIL_PASSWORD',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
],
'MAIL_FROM_ADDRESS' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'MAIL_FROM_ADDRESS',
value: 'no-reply@uwil.fr',
protected: true,
masked: true,
))->isProjectDefault(),
'LDC_WEB_API_KEY' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'LDC_WEB_API_KEY',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
'SENTRY_LARAVEL_DSN' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'SENTRY_LARAVEL_DSN',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
'SAML2_SP_CERT_x509' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'SAML2_SP_CERT_x509',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'SAML2_SP_CERT_x509',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
],
'SAML2_SP_CERT_PRIVATEKEY' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'SAML2_SP_CERT_PRIVATEKEY',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'SAML2_SP_CERT_PRIVATEKEY',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
],
'SAML2_CONTACT_TECHNICAL_EMAIL' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'SAML2_CONTACT_TECHNICAL_EMAIL',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
'SAML2_CONTACT_SUPPORT_EMAIL' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'SAML2_CONTACT_SUPPORT_EMAIL',
value: 'changeme',
protected: true,
masked: true,
))->isProjectDefault(),
'SAML2_DEFAULT_TENANT_UUID' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'SAML2_DEFAULT_TENANT_UUID',
value: 'support',
protected: true,
))->isProjectDefault(),
'PVC_SIZE' => (new CdEnvVariable(
environment_scope: EnvironmentScope::all,
key: 'PVC_SIZE',
value: '3Gi',
protected: true,
))->isProjectDefault(),
'IMAGE_TAG' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'IMAGE_TAG',
value: 'changeme',
protected: true,
masked: true,
)),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'IMAGE_TAG',
value: 'changeme',
protected: true,
masked: true,
)),
],
'CERT_ISSUER' => [
EnvironmentScope::staging->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::staging,
key: 'CERT_ISSUER',
value: 're7-letsencrypt-production-dns01',
protected: true,
masked: true,
))->isProjectDefault(),
EnvironmentScope::production->value => (new CdEnvVariable(
environment_scope: EnvironmentScope::production,
key: 'CERT_ISSUER',
value: 'letsencrypt-production-dns01',
protected: true,
masked: true,
))->isProjectDefault(),
],
];
self::$defaultEnvVars = Arr::sortRecursive(self::$defaultEnvVars);
} //end if
}
}

View file

@ -35,7 +35,7 @@ class CdEnvVariable implements \Stringable
*
* @var string
*/
private static string $prefix = '1_LRVL';
private static string $prefix = 'APP';
/**
* The variable is default

22
app/Lib/Common.php Normal file
View file

@ -0,0 +1,22 @@
<?php
namespace App\Lib;
use Illuminate\Encryption\Encrypter;
class Common
{
/**
* Generate a random key for the application.
*
* ! Copy from \Illuminate\Foundation\Console\KeyGenerateCommand
*
* @return string
*/
public static function generateRandomKey()
{
return 'base64:' . base64_encode(
Encrypter::generateKey(config('app.cipher'))
);
}
}

113
app/Lib/Gitlab.php Normal file
View file

@ -0,0 +1,113 @@
<?php
namespace App\Lib;
use App\Objects\Gitlab\Schedule;
use GrahamCampbell\GitLab\GitLabManager;
use Illuminate\Support\Facades\Log;
class Gitlab
{
/**
* Class instance property
*
* @var mixed
*/
private static $instance;
/**
* The gitlab client
*
* @var \Gitlab\Client
*/
private static \Gitlab\Client $client;
/**
* __construct method
*
* @return void
*/
public function __construct()
{
self::$client = app(GitLabManager::class);
}
/**
* getInstance method
*
* @return \App\Lib\Gitlab
*/
public static function getInstance(): \App\Lib\Gitlab
{
if (is_null(self::$instance)) {
self::$instance = new Gitlab();
}
return self::$instance;
}
/**
* Save compiled vars on Gitlab project
*
* @param integer $projectId
* @param array<string,\App\Lib\CdEnvVariable|array<string,\App\Lib\CdEnvVariable>> $newVars
* @return boolean
*/
public static function saveProjectCdVarsToGitlab(int $projectId, array $newVars): bool
{
self::getInstance();
try {
$updateProjectVar = function (int $projectId, CdEnvVariable $var) {
Log::debug(
sprintf(
"Add variable %s with value %s",
$var->key,
$var->value
)
);
self::$client->projects()->addVariable(
$projectId,
$var->getFormatedGitlabKey(),
$var->value,
$var->protected,
$var->environment_scope->value,
[
'masked' => $var->masked,
'raw' => $var->raw
]
);
};
foreach ($newVars as $varOrVarList) {
if ($varOrVarList instanceof CdEnvVariable) {
$updateProjectVar($projectId, $varOrVarList);
continue;
}
foreach ($varOrVarList as $var) {
$updateProjectVar($projectId, $var);
}
}
} catch (\Exception $e) {
Log::critical($e->getMessage());
return false;
} //end try
return true;
}
/**
* setPipelineSchedules method
*
* @param integer $projectId
* @param \App\Objects\Gitlab\Schedule $schedule
* @return boolean
*/
public static function setPipelineSchedules(int $projectId, Schedule $schedule): bool
{
self::getInstance();
try {
self::$client->schedules()->create($projectId, $schedule->toArray());
} catch (\Exception $e) {
Log::critical($e->getMessage());
return false;
} //end try
return true;
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace App\Objects\Gitlab;
class Schedule
{
/**
* https://docs.gitlab.com/ee/api/pipeline_schedules.html#create-a-new-pipeline-schedule
*
* @param string $cron The cron schedule, for example: 0 1 * * *. required.
* @param string $description The description of the pipeline schedule. required.
* @param string $ref The branch or tag name that is triggered. Both the short (e.g. main) and full
* (e.g. refs/heads/main or refs/tags/main) ref versions are accepted. If a short version is provided, it is
* automatically expanded to the full ref version but, if the ref is ambiguous, it will be rejected required.
* @param boolean $active The activation of pipeline schedule. If false is set, the pipeline schedule
* is initially deactivated (default: true).
* @param string $cron_timezone The time zone supported by ActiveSupport::TimeZone, for example:
* Pacific Time (US & Canada) (default: UTC).
* @return void
*/
public function __construct(
public string $cron,
public string $description,
public string $ref,
public bool $active,
public string $cron_timezone
) {
}
/**
* toArray method
*
* @return array{cron: string, description: string, ref: string, active: bool, cron_timezone: string}
*/
public function toArray()
{
return [
'cron' => $this->cron,
'description' => $this->description,
'ref' => $this->ref,
'active' => $this->active,
'cron_timezone' => $this->cron_timezone
];
}
}

View file

@ -56,6 +56,12 @@ return [
'throw' => false,
],
'gitlabExports' => [
'driver' => 'local',
'root' => resource_path('gitlab_exports'),
'throw' => false,
]
],
/*

View file

@ -67,4 +67,8 @@ return [
],
'showroom-bo' => [
'instances_group_id' => env('GITLAB_SHOWROOM_INSTANCE_GROUP_ID', 286)
]
];

View file

@ -6,7 +6,6 @@
<arg name="extensions" value="php" />
<file>app/</file>
<file>config/</file>
<file>database/factories</file>
<file>routes/</file>
<rule ref="PSR1" />
<rule ref="PSR12" />

Binary file not shown.