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

Add ability to customize & overwrite deployer stock commands #3802

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
322 changes: 198 additions & 124 deletions src/Command/InitCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,42 @@ class InitCommand extends Command
{
use CommandCommon;

protected function configure()
/**
* @var string $recipePath
*/
protected $recipePath;

/**
* @var string $language
*/
protected $language;

/**
* @var string $template
*/
protected $template;

/**
* @var string $repository
*/
protected $repository;

/**
* @var string $project
*/
protected $project;

/**
* @var array $hosts
*/
protected $hosts;

/**
* @var string $tempHostFile
*/
protected $tempHostFile;

protected function configure(): void
{
$this
->setName('init')
Expand All @@ -31,144 +66,42 @@ protected function configure()

protected function execute(InputInterface $input, OutputInterface $output): int
{
if (getenv('COLORTERM') === 'truecolor') {
$output->write(<<<EOF
╭───────────────────────────────────────╮
│ │
│ │
│ \e[38;2;94;231;223m_\e[39m\e[38;2;95;231;226m_\e[39m\e[38;2;96;230;228m_\e[39m\e[38;2;96;229;230m_\e[39m \e[38;2;97;226;230m_\e[39m │
│ \e[38;2;98;223;229m|\e[39m \e[38;2;98;220;229m\\\e[39m \e[38;2;99;216;228m_\e[39m\e[38;2;100;213;228m_\e[39m\e[38;2;101;210;228m_\e[39m \e[38;2;101;208;227m_\e[39m\e[38;2;102;205;227m_\e[39m\e[38;2;103;202;227m_\e[39m\e[38;2;104;199;226m|\e[39m \e[38;2;104;196;226m|\e[39m\e[38;2;105;194;225m_\e[39m\e[38;2;106;191;225m_\e[39m\e[38;2;106;188;225m_\e[39m \e[38;2;107;186;224m_\e[39m \e[38;2;108;183;224m_\e[39m \e[38;2;109;181;224m_\e[39m\e[38;2;109;178;223m_\e[39m\e[38;2;110;176;223m_\e[39m \e[38;2;111;174;222m_\e[39m\e[38;2;111;171;222m_\e[39m\e[38;2;112;169;222m_\e[39m │
│ \e[38;2;113;167;221m|\e[39m \e[38;2;113;165;221m|\e[39m \e[38;2;114;163;221m|\e[39m \e[38;2;115;160;220m-\e[39m\e[38;2;115;158;220m_\e[39m\e[38;2;116;156;219m|\e[39m \e[38;2;117;155;219m.\e[39m \e[38;2;117;153;219m|\e[39m \e[38;2;118;151;218m|\e[39m \e[38;2;119;149;218m.\e[39m \e[38;2;119;147;218m|\e[39m \e[38;2;120;145;217m|\e[39m \e[38;2;121;144;217m|\e[39m \e[38;2;121;142;216m-\e[39m\e[38;2;122;140;216m_\e[39m\e[38;2;123;139;216m|\e[39m \e[38;2;123;137;215m_\e[39m\e[38;2;124;136;215m|\e[39m │
│ \e[38;2;124;134;215m|\e[39m\e[38;2;125;133;214m_\e[39m\e[38;2;126;132;214m_\e[39m\e[38;2;126;130;214m_\e[39m\e[38;2;127;129;213m_\e[39m\e[38;2;127;128;213m/\e[39m\e[38;2;130;128;212m|\e[39m\e[38;2;132;129;212m_\e[39m\e[38;2;134;129;212m_\e[39m\e[38;2;137;130;211m_\e[39m\e[38;2;139;131;211m|\e[39m \e[38;2;141;131;211m_\e[39m\e[38;2;143;132;210m|\e[39m\e[38;2;145;132;210m_\e[39m\e[38;2;147;133;209m|\e[39m\e[38;2;149;133;209m_\e[39m\e[38;2;151;134;209m_\e[39m\e[38;2;153;135;208m_\e[39m\e[38;2;155;135;208m|\e[39m\e[38;2;157;136;208m_\e[39m \e[38;2;159;136;207m|\e[39m\e[38;2;161;137;207m_\e[39m\e[38;2;162;137;206m_\e[39m\e[38;2;164;138;206m_\e[39m\e[38;2;166;139;206m|\e[39m\e[38;2;167;139;205m_\e[39m\e[38;2;169;140;205m|\e[39m │
│ \e[38;2;170;140;205m|\e[39m\e[38;2;172;141;204m_\e[39m\e[38;2;173;141;204m|\e[39m \e[38;2;175;142;203m|\e[39m\e[38;2;176;142;203m_\e[39m\e[38;2;177;143;203m_\e[39m\e[38;2;179;143;202m_\e[39m\e[38;2;180;144;202m|\e[39m │
│ │
│ │
╰───────────────────────────────────────╯

EOF
);
} else {
$output->write(<<<EOF
╭───────────────────────────────────────╮
│ │
│ │
│ ____ _ │
│ | \ ___ ___| |___ _ _ ___ ___ │
│ | | | -_| . | | . | | | -_| _| │
│ |____/|___| _|_|___|_ |___|_| │
│ |_| |___| │
│ │
│ │
╰───────────────────────────────────────╯

EOF
);
}

$io = new SymfonyStyle($input, $output);
$recipePath = $input->getOption('path');

$language = $io->choice('Select recipe language', ['php', 'yaml'], 'php');
if (empty($recipePath)) {
$recipePath = "deploy.$language";
}

// Avoid accidentally override of existing file.
if (file_exists($recipePath)) {
$io->warning("$recipePath already exists");
if (!$io->confirm("Do you want to override the existing file?", false)) {
$io->block('👍🏻');
exit(1);
}
}

// Template
$template = $io->choice('Select project template', $this->recipes(), 'common');

// Repo
$default = '';
try {
$process = Process::fromShellCommandline('git remote get-url origin');
$default = $process->mustRun()->getOutput();
$default = trim($default);
} catch (RuntimeException $e) {
}
$repository = $io->ask('Repository', $default);

// Guess host
if (preg_match('/github.com:(?<org>[A-Za-z0-9_.\-]+)\//', $repository, $m)) {
$org = $m['org'];
$tempHostFile = tempnam(sys_get_temp_dir(), 'temp-host-file');
$php = new PhpProcess(<<<EOF
<?php
\$ch = curl_init('https://api.github.com/orgs/$org');
curl_setopt(\$ch, CURLOPT_USERAGENT, 'Deployer');
curl_setopt(\$ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt(\$ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt(\$ch, CURLOPT_MAXREDIRS, 10);
curl_setopt(\$ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt(\$ch, CURLOPT_TIMEOUT, 5);
\$result = curl_exec(\$ch);
curl_close(\$ch);
\$json = json_decode(\$result);
\$host = parse_url(\$json->blog, PHP_URL_HOST);
file_put_contents('$tempHostFile', \$host);
EOF
);
$php->start();
}

// Project
$default = '';
try {
$process = Process::fromShellCommandline('basename "$PWD"');
$default = $process->mustRun()->getOutput();
$default = trim($default);
} catch (RuntimeException $e) {
}
$project = $io->ask('Project name', $default);
$this->printHeader($output);

// Hosts
$host = null;
if (isset($tempHostFile)) {
$host = file_get_contents($tempHostFile);
}
$hostsString = $io->ask('Hosts (comma separated)', $host);
if ($hostsString !== null) {
$hosts = explode(',', $hostsString);
} else {
$hosts = [];
}
$this->setTemplateVars($input, $output);

file_put_contents($recipePath, $this->$language($template, $project, $repository, $hosts));
$language = $this->language;
file_put_contents(
$this->recipePath,
$this->$language()
);

$this->telemetry();
$output->writeln(sprintf(
'<info>Successfully created</info> <comment>%s</comment>',
$recipePath
$this->recipePath
));
return 0;
}

private function php(string $template, string $project, string $repository, array $hosts): string
protected function php(): string
{
$h = "";
foreach ($hosts as $host) {
foreach ($this->hosts as $host) {
$h .= "host('{$host}')\n" .
" ->set('remote_user', 'deployer')\n" .
" ->set('deploy_path', '~/{$project}');\n";
" ->set('deploy_path', '~/{$this->project}');\n";
}

return <<<PHP
<?php
namespace Deployer;

require 'recipe/$template.php';
require 'recipe/$this->template.php';

// Config

set('repository', '{$repository}');
set('repository', '{$this->repository}');

add('shared_files', []);
add('shared_dirs', []);
Expand All @@ -184,23 +117,23 @@ private function php(string $template, string $project, string $repository, arra
PHP;
}

private function yaml(string $template, string $project, string $repository, array $hosts): string
protected function yaml(): string
{
$h = "";
foreach ($hosts as $host) {
foreach ($this->hosts as $host) {
$h .= " $host:\n".
" remote_user: deployer\n" .
" deploy_path: '~/{$project}'\n";
" deploy_path: '~/{$this->project}'\n";
}

$additionalConfigs = $this->getAdditionalConfigs($template);
$additionalConfigs = $this->getAdditionalConfigs($this->template);

return <<<YAML
import:
- recipe/$template.php
- recipe/$this->template.php

config:
repository: '$repository'
repository: '$this->repository'
$additionalConfigs
hosts:
$h
Expand All @@ -214,7 +147,7 @@ private function yaml(string $template, string $project, string $repository, arr
YAML;
}

private function getAdditionalConfigs(string $template): string
protected function getAdditionalConfigs(string $template): string
{
if ($template !== 'common') {
return '';
Expand All @@ -231,7 +164,7 @@ private function getAdditionalConfigs(string $template): string
YAML;
}

private function recipes(): array
protected function recipes(): array
{
$recipes = [];
$dir = new \DirectoryIterator(__DIR__ . '/../../recipe');
Expand All @@ -255,4 +188,145 @@ private function recipes(): array
sort($recipes);
return $recipes;
}

protected function setTemplateVars(InputInterface $input, OutputInterface $output): void
{
$io = new SymfonyStyle($input, $output);

$this->setRecipePath($input, $io);
$this->setTemplate($io);
$this->setRepository($io);
$this->setProject($io);
$this->guessHost();
$this->setHosts($io);
}

protected function printHeader(OutputInterface $output): void
{
if (getenv('COLORTERM') === 'truecolor') {
$output->write(<<<EOF
╭───────────────────────────────────────╮
│ │
│ │
│ \e[38;2;94;231;223m_\e[39m\e[38;2;95;231;226m_\e[39m\e[38;2;96;230;228m_\e[39m\e[38;2;96;229;230m_\e[39m \e[38;2;97;226;230m_\e[39m │
│ \e[38;2;98;223;229m|\e[39m \e[38;2;98;220;229m\\\e[39m \e[38;2;99;216;228m_\e[39m\e[38;2;100;213;228m_\e[39m\e[38;2;101;210;228m_\e[39m \e[38;2;101;208;227m_\e[39m\e[38;2;102;205;227m_\e[39m\e[38;2;103;202;227m_\e[39m\e[38;2;104;199;226m|\e[39m \e[38;2;104;196;226m|\e[39m\e[38;2;105;194;225m_\e[39m\e[38;2;106;191;225m_\e[39m\e[38;2;106;188;225m_\e[39m \e[38;2;107;186;224m_\e[39m \e[38;2;108;183;224m_\e[39m \e[38;2;109;181;224m_\e[39m\e[38;2;109;178;223m_\e[39m\e[38;2;110;176;223m_\e[39m \e[38;2;111;174;222m_\e[39m\e[38;2;111;171;222m_\e[39m\e[38;2;112;169;222m_\e[39m │
│ \e[38;2;113;167;221m|\e[39m \e[38;2;113;165;221m|\e[39m \e[38;2;114;163;221m|\e[39m \e[38;2;115;160;220m-\e[39m\e[38;2;115;158;220m_\e[39m\e[38;2;116;156;219m|\e[39m \e[38;2;117;155;219m.\e[39m \e[38;2;117;153;219m|\e[39m \e[38;2;118;151;218m|\e[39m \e[38;2;119;149;218m.\e[39m \e[38;2;119;147;218m|\e[39m \e[38;2;120;145;217m|\e[39m \e[38;2;121;144;217m|\e[39m \e[38;2;121;142;216m-\e[39m\e[38;2;122;140;216m_\e[39m\e[38;2;123;139;216m|\e[39m \e[38;2;123;137;215m_\e[39m\e[38;2;124;136;215m|\e[39m │
│ \e[38;2;124;134;215m|\e[39m\e[38;2;125;133;214m_\e[39m\e[38;2;126;132;214m_\e[39m\e[38;2;126;130;214m_\e[39m\e[38;2;127;129;213m_\e[39m\e[38;2;127;128;213m/\e[39m\e[38;2;130;128;212m|\e[39m\e[38;2;132;129;212m_\e[39m\e[38;2;134;129;212m_\e[39m\e[38;2;137;130;211m_\e[39m\e[38;2;139;131;211m|\e[39m \e[38;2;141;131;211m_\e[39m\e[38;2;143;132;210m|\e[39m\e[38;2;145;132;210m_\e[39m\e[38;2;147;133;209m|\e[39m\e[38;2;149;133;209m_\e[39m\e[38;2;151;134;209m_\e[39m\e[38;2;153;135;208m_\e[39m\e[38;2;155;135;208m|\e[39m\e[38;2;157;136;208m_\e[39m \e[38;2;159;136;207m|\e[39m\e[38;2;161;137;207m_\e[39m\e[38;2;162;137;206m_\e[39m\e[38;2;164;138;206m_\e[39m\e[38;2;166;139;206m|\e[39m\e[38;2;167;139;205m_\e[39m\e[38;2;169;140;205m|\e[39m │
│ \e[38;2;170;140;205m|\e[39m\e[38;2;172;141;204m_\e[39m\e[38;2;173;141;204m|\e[39m \e[38;2;175;142;203m|\e[39m\e[38;2;176;142;203m_\e[39m\e[38;2;177;143;203m_\e[39m\e[38;2;179;143;202m_\e[39m\e[38;2;180;144;202m|\e[39m │
│ │
│ │
╰───────────────────────────────────────╯

EOF
);
} else {
$output->write(<<<EOF
╭───────────────────────────────────────╮
│ │
│ │
│ ____ _ │
│ | \ ___ ___| |___ _ _ ___ ___ │
│ | | | -_| . | | . | | | -_| _| │
│ |____/|___| _|_|___|_ |___|_| │
│ |_| |___| │
│ │
│ │
╰───────────────────────────────────────╯

EOF
);
}
}

protected function setRecipePath(InputInterface $input, SymfonyStyle $io): void
{
$this->language = $io->choice('Select recipe language', ['php', 'yaml'], 'php');

if (!$this->recipePath) {
$this->recipePath = "deploy.$this->language";
}

// Avoid accidentally override of existing file.
if (file_exists($this->recipePath)) {
$io->warning("$this->recipePath already exists");
if (!$io->confirm("Do you want to override the existing file?", false)) {
$io->block('👍🏻');
exit(1);
}
}
}

protected function setTemplate(SymfonyStyle $io): void
{
$this->template = $io->choice('Select project template', $this->recipes(), 'common');
}

protected function setRepository(SymfonyStyle $io): void
{
$default = '';
try {
$process = Process::fromShellCommandline('git remote get-url origin');
$default = $process->mustRun()->getOutput();
$default = trim($default);
} catch (RuntimeException $e) {
//@TODO Throw error $e->getMessage();
}

$this->repository = $io->ask('Repository', $default);
}

protected function guessHost(): void
{
if (preg_match('/github.com:(?<org>[A-Za-z0-9_.\-]+)\//', $this->repository, $m)) {
$org = $m['org'];
$this->tempHostFile = tempnam(sys_get_temp_dir(), 'temp-host-file');
$php = new PhpProcess(<<<EOF
<?php
\$ch = curl_init('https://api.github.com/orgs/$org');
curl_setopt(\$ch, CURLOPT_USERAGENT, 'Deployer');
curl_setopt(\$ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt(\$ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt(\$ch, CURLOPT_MAXREDIRS, 10);
curl_setopt(\$ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt(\$ch, CURLOPT_TIMEOUT, 5);
\$result = curl_exec(\$ch);
curl_close(\$ch);
\$json = json_decode(\$result);
\$host = parse_url(\$json->blog, PHP_URL_HOST);
file_put_contents('$this->tempHostFile', \$host);
EOF
);
$php->start();
}
}

protected function setProject(SymfonyStyle $io): void
{
$default = '';
try {
$process = Process::fromShellCommandline('basename "$PWD"');
$default = $process->mustRun()->getOutput();
$default = trim($default);
} catch (RuntimeException $e) {
//@TODO Throw error $e->getMessage();
}
$this->project = $io->ask('Project name', $default);
}

protected function setHosts(SymfonyStyle $io): void
{
$host = null;
if (isset($this->tempHostFile)) {
$host = file_get_contents($this->tempHostFile);
}
$hostsString = $io->ask('Hosts (comma separated)', $host);
if ($hostsString !== null) {
$hosts = explode(',', $hostsString);
} else {
$hosts = [];
}

$this->hosts = $hosts;
}
}
Loading