diff --git a/README.md b/README.md index 04aece6..09322b3 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/app/Console/Commands/SetGitlabCdVariables.php b/app/Console/Commands/SetGitlabCdVariables.php index 25b2b26..0012b12 100644 --- a/app/Console/Commands/SetGitlabCdVariables.php +++ b/app/Console/Commands/SetGitlabCdVariables.php @@ -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> $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')) - ); - } } diff --git a/app/Console/Commands/ShowroomBoSetup.php b/app/Console/Commands/ShowroomBoSetup.php new file mode 100644 index 0000000..a68b20b --- /dev/null +++ b/app/Console/Commands/ShowroomBoSetup.php @@ -0,0 +1,466 @@ +> + * 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 + } +} diff --git a/app/Lib/CdEnvVariable.php b/app/Lib/CdEnvVariable.php index 44d4426..5393fc2 100644 --- a/app/Lib/CdEnvVariable.php +++ b/app/Lib/CdEnvVariable.php @@ -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 diff --git a/app/Lib/Common.php b/app/Lib/Common.php new file mode 100644 index 0000000..56012da --- /dev/null +++ b/app/Lib/Common.php @@ -0,0 +1,22 @@ +> $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; + } +} diff --git a/app/Objects/Gitlab/Schedule.php b/app/Objects/Gitlab/Schedule.php new file mode 100644 index 0000000..2237644 --- /dev/null +++ b/app/Objects/Gitlab/Schedule.php @@ -0,0 +1,45 @@ + $this->cron, + 'description' => $this->description, + 'ref' => $this->ref, + 'active' => $this->active, + 'cron_timezone' => $this->cron_timezone + ]; + } +} diff --git a/config/filesystems.php b/config/filesystems.php index e99cd57..b0e64dd 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -56,6 +56,12 @@ return [ 'throw' => false, ], + 'gitlabExports' => [ + 'driver' => 'local', + 'root' => resource_path('gitlab_exports'), + 'throw' => false, + ] + ], /* diff --git a/config/gitlab.php b/config/gitlab.php index 59b00a6..410a9a3 100644 --- a/config/gitlab.php +++ b/config/gitlab.php @@ -67,4 +67,8 @@ return [ ], + 'showroom-bo' => [ + 'instances_group_id' => env('GITLAB_SHOWROOM_INSTANCE_GROUP_ID', 286) + ] + ]; diff --git a/phpcs.xml b/phpcs.xml index 3e28d83..c00d4c3 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -6,7 +6,6 @@ app/ config/ - database/factories routes/ diff --git a/resources/gitlab_exports/showroom-bo.tar.gz b/resources/gitlab_exports/showroom-bo.tar.gz new file mode 100644 index 0000000..c424c1b Binary files /dev/null and b/resources/gitlab_exports/showroom-bo.tar.gz differ