Skip to content
Snippets Groups Projects
Commit ee39f4c7 authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms
Browse files

fixes #6, fixes #8

parent ac6a2170
No related branches found
No related tags found
1 merge request!2fixes #6, fixes #8
location ~ \.php(?:$|/) {
include fastcgi_params;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_intercept_errors on;
fastcgi_pass $fastcgi_backend;
fastcgi_index index.php;
fastcgi_param SERVER_NAME $http_host;
fastcgi_param DOCUMENT_ROOT $site_document_root;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_FILENAME $request_filename;
}
...@@ -4,14 +4,18 @@ namespace Studip\Dockerized\Commands; ...@@ -4,14 +4,18 @@ namespace Studip\Dockerized\Commands;
use Studip\Dockerized\Config; use Studip\Dockerized\Config;
use Studip\Dockerized\Creators\DockerComposeConfiguration; use Studip\Dockerized\Creators\DockerComposeConfiguration;
use Studip\Dockerized\Creators\NginxConfiguration; use Studip\Dockerized\Creators\NginxConfiguration;
use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\Console\Style\SymfonyStyle;
final class Compile extends \Symfony\Component\Console\Command\Command final class Compile extends Command
{ {
use GetCompiledPath;
protected function configure() protected function configure()
{ {
$this->setName('compile'); $this->setName('compile');
...@@ -21,10 +25,14 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -21,10 +25,14 @@ final class Compile extends \Symfony\Component\Console\Command\Command
p', p',
InputOption::VALUE_OPTIONAL, InputOption::VALUE_OPTIONAL,
'Path to store the compiled files', 'Path to store the compiled files',
realpath(__DIR__ . '/../../compiled') $this->getCompiledPath()
); );
$this->addOption('nginx', null, InputOption::VALUE_NEGATABLE, 'Compile nginx configuration', true); $this->addOption('nginx', null, InputOption::VALUE_NEGATABLE, 'Compile nginx configuration', true);
$this->addOption('docker', null, InputOption::VALUE_NEGATABLE, 'Compile docker configuration', true); $this->addOption('docker', null, InputOption::VALUE_NEGATABLE, 'Compile docker configuration', true);
$this->addOption('build', 'b', InputOption::VALUE_NEGATABLE, 'Whether to build the containers');
$this->addOption('force', 'F', InputOption::VALUE_NONE, 'Force compilation');
$this->addOption('start', 's', InputOption::VALUE_NONE, 'Start container after compilation (requires "build")');
} }
protected function initialize(InputInterface $input, OutputInterface $output) protected function initialize(InputInterface $input, OutputInterface $output)
...@@ -48,25 +56,41 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -48,25 +56,41 @@ final class Compile extends \Symfony\Component\Console\Command\Command
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$compiledPath = realpath($input->getOption('path')); $compiledPath = realpath($input->getOption('path'));
$force = $input->getOption('force');
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$writeConfigFile = function (string $filename, string $content) use ($compiledPath, $io): void { $writeConfigFile = function (string $filename, string $content) use ($compiledPath, $force, $io): bool {
file_put_contents( $filepath = "{$compiledPath}/{$filename}";
"{$compiledPath}/{$filename}",
$content if (
); !file_exists($filepath)
|| $content !== file_get_contents($filepath)
|| $force
) {
file_put_contents($filepath, $content);
$io->success('Config file ' . $filename . ' written'); $io->success('Config file ' . $filename . ' written');
return true;
}
return false;
}; };
$changed = 0;
if ($input->getOption('nginx')) { if ($input->getOption('nginx')) {
$writeConfigFile( $changed += (int) $writeConfigFile(
'nginx.conf', 'nginx.conf',
$this->createNginxConfiguration() $this->createNginxConfiguration()
); );
$changed += (int) $writeConfigFile(
'nginx-sites.conf',
$this->createNginxSites()
);
} }
if ($input->getOption('docker')) { if ($input->getOption('docker')) {
$writeConfigFile( $changed += (int) $writeConfigFile(
'docker-compose.yml', 'docker-compose.yml',
$this->createDockerComposerConfiguration( $this->createDockerComposerConfiguration(
realpath(__DIR__ . '/../..'), realpath(__DIR__ . '/../..'),
...@@ -75,6 +99,23 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -75,6 +99,23 @@ final class Compile extends \Symfony\Component\Console\Command\Command
); );
} }
if ($changed === 0) {
$io->comment('No changes');
}
if ($input->getOption('build')) {
$this->getApplication()->doRun(
new ArrayInput(['command' => 'docker:build']),
$output
);
if ($input->getOption('start')) {
$this->getApplication()->doRun(
new ArrayInput(['command' => 'docker:start']),
$output
);
}
}
return Command::SUCCESS; return Command::SUCCESS;
} }
...@@ -89,8 +130,8 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -89,8 +130,8 @@ final class Compile extends \Symfony\Component\Console\Command\Command
// Declare volumes // Declare volumes
$volumes = [ $volumes = [
[realpath(__DIR__ . '/../../web'), '/var/www/html/studip-dockerized-config.app', 'ro'], [realpath($cwd . '/web'), '/var/www/html/studip-dockerized-config.app', 'ro'],
[realpath(__DIR__ . '/../../config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'], [realpath($cwd . '/config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'],
...array_map( ...array_map(
fn(string $path, array $definition) => [$definition['source'], '/var/www/html/' . $path], fn(string $path, array $definition) => [$definition['source'], '/var/www/html/' . $path],
array_keys(Config::getInstance()->get('sites') ?? []), array_keys(Config::getInstance()->get('sites') ?? []),
...@@ -110,15 +151,18 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -110,15 +151,18 @@ final class Compile extends \Symfony\Component\Console\Command\Command
), ),
]); ]);
$creator->addServiceVolume( $mounts = [
'nginx', "{$compiledPath}/nginx.conf" => 'conf.d/nginx.conf',
"{$compiledPath}/nginx.conf", "{$compiledPath}/nginx-sites.conf" => 'sites.conf',
'/etc/nginx/conf.d/default.conf', "{$cwd}/config/nginx-php.conf" => 'php.conf',
'ro' "{$compiledPath}/ssl.crt" => "ssl.crt",
); "{$compiledPath}/ssl.key" => "ssl.key",
];
foreach ($mounts as $source => $target) {
$creator->addServiceVolume('nginx', $source, "/etc/nginx/{$target}", 'ro');
}
$creator->addServiceVolume('nginx', realpath("{$cwd}/logs/nginx"), '/var/log/nginx'); $creator->addServiceVolume('nginx', realpath("{$cwd}/logs/nginx"), '/var/log/nginx');
foreach ($volumes as $volume) { foreach ($volumes as $volume) {
$creator->addServiceVolume('nginx', ...$volume); $creator->addServiceVolume('nginx', ...$volume);
} }
...@@ -176,26 +220,40 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -176,26 +220,40 @@ final class Compile extends \Symfony\Component\Console\Command\Command
{ {
$creator = new NginxConfiguration(); $creator = new NginxConfiguration();
// Register event handler to add the nginx-php configuration to each location // Add upstreams and servers
$creator->on(NginxConfiguration::EVENT_LOCATION_ADD_BEFORE, function ($location, &$config) use ($creator) { foreach (Config::getInstance()->get('php') as $version => $port) {
$config[] = $creator->write('location ~ \.php(?:$|/)', [ $index = $this->getPHPVersionIndex($version, 'php');
'include fastcgi_params',
'fastcgi_split_path_info ^(.+?\.php)(/.*)$', $creator->addUpstream("{$index}_backend", [
'fastcgi_intercept_errors on', "server {$index}:9000"
'fastcgi_pass $fastcgi_backend', ]);
'fastcgi_index index.php',
'fastcgi_param SERVER_NAME $http_host', $creator->addServer([
'fastcgi_param DOCUMENT_ROOT $site_document_root', 'listen ' . $port . ' default_server ssl',
'fastcgi_param PATH_INFO $fastcgi_path_info', 'server_name _',
'fastcgi_param SCRIPT_FILENAME $request_filename',
'ssl_certificate /etc/nginx/ssl.crt',
'ssl_certificate_key /etc/nginx/ssl.key',
'root /var/www/html',
'set $fastcgi_backend ' . $index . '_backend',
'include sites.conf',
]); ]);
}); }
return $creator->dump();
}
private function createNginxSites(): string
{
$creator = new NginxConfiguration();
// Add default location // Add default location
$creator->addLocation('/sites', [ $creator->addLocation('/sites', [
'alias /var/www/html/studip-dockerized-config.app', 'alias /var/www/html/studip-dockerized-config.app',
'index index.php index.html index.html', 'index index.php index.html index.html',
'set $site_document_root /var/www/html/studip-dockerized-config.app', 'set $site_document_root /var/www/html/studip-dockerized-config.app',
'include php.conf',
]); ]);
// Add configured locations // Add configured locations
...@@ -209,22 +267,7 @@ final class Compile extends \Symfony\Component\Console\Command\Command ...@@ -209,22 +267,7 @@ final class Compile extends \Symfony\Component\Console\Command\Command
'alias ' . $p, 'alias ' . $p,
'index index.php index.html index.html', 'index index.php index.html index.html',
'set $site_document_root ' . $p, 'set $site_document_root ' . $p,
]); 'include php.conf',
}
// Add upstreams and servers
foreach (Config::getInstance()->get('php') as $version => $port) {
$index = $this->getPHPVersionIndex($version, 'php');
$creator->addUpstream("{$index}_backend", [
"server {$index}:9000"
]);
$creator->addServer([
'listen ' . $port . ' default_server',
'server_name _',
'root /var/www/html',
'set $fastcgi_backend ' . $index . '_backend',
]); ]);
} }
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
namespace Studip\Dockerized\Commands\Docker; namespace Studip\Dockerized\Commands\Docker;
use Studip\Dockerized\DockerComposeCommand; use Studip\Dockerized\DockerComposeCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class Build extends DockerComposeCommand final class Build extends DockerComposeCommand
{ {
...@@ -12,6 +16,34 @@ final class Build extends DockerComposeCommand ...@@ -12,6 +16,34 @@ final class Build extends DockerComposeCommand
} }
protected function getDockerComposeCommand(): array protected function getDockerComposeCommand(): array
{ {
$dockerRunning = $this->isDockerRunning();
if ($dockerRunning) {
$this->createDockerComposeCommand(['down']);
}
return ['up', '--no-start']; return ['up', '--no-start'];
} }
protected function execute(InputInterface $input, OutputInterface $output)
{
$dockerRunning = $this->isDockerRunning();
if ($dockerRunning) {
$this->getApplication()->doRun(
new ArrayInput(['command' => 'docker:stop']),
$output
);
}
$result = parent::execute($input, $output);
if ($result !== Command::SUCCESS || !$dockerRunning) {
return $result;
}
$this->getApplication()->doRun(
new ArrayInput(['command' => 'docker:start']),
$output
);
return Command::SUCCESS;
}
} }
\ No newline at end of file
<?php /** @noinspection PhpExpressionResultUnusedInspection */
namespace Studip\Dockerized\Commands\Keys;
use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Process\Process;
final class Create extends Command
{
use GetCompiledPath;
protected function configure()
{
$this->setName('keys:create');
$this->setDescription('Creates necessary keys for ssl');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$process = new Process([
'openssl', 'req',
'-batch',
'-x509',
'-nodes',
'-days', '365',
'-newkey', 'rsa:2048',
'-keyout', $this->getCompiledPath('ssl.key'),
'-out', $this->getCompiledPath('ssl.crt'),
]);
$process->run();
if (!$process->isSuccessful()) {
$io->error(['Could not create keys', $process->getErrorOutput()]);
return Command::FAILURE;
}
$io->success('Keys created successfully');
return Command::SUCCESS;
}
}
...@@ -75,7 +75,7 @@ final class Config ...@@ -75,7 +75,7 @@ final class Config
public function store(): void public function store(): void
{ {
if (!is_writable($this->filename)) { if (file_exists($this->filename) && !is_writable($this->filename)) {
throw new \Exception("Config file '{$this->filename}' is not writable"); throw new \Exception("Config file '{$this->filename}' is not writable");
} }
......
...@@ -70,18 +70,16 @@ final class NginxConfiguration ...@@ -70,18 +70,16 @@ final class NginxConfiguration
...array_map( ...array_map(
fn($server) => $this->write( fn($server) => $this->write(
'server', 'server',
array_merge( $server['config']
$server['config'], ),
array_map( $this->servers
),
...array_map(
fn($location) => $this->write( fn($location) => $this->write(
"location {$location['modifier']} {$location['location']}", "location {$location['modifier']} {$location['location']}",
$location['config'] $location['config']
), ),
$this->locations $this->locations
)
)
),
$this->servers
), ),
])); ]));
$result = (string) Scope::fromFile($tempName); $result = (string) Scope::fromFile($tempName);
......
<?php <?php
namespace Studip\Dockerized; namespace Studip\Dockerized;
use Studip\Dockerized\Traits\GetCompiledDockerComposeYMLPath; use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
...@@ -9,17 +9,29 @@ use Symfony\Component\Process\Process; ...@@ -9,17 +9,29 @@ use Symfony\Component\Process\Process;
abstract class DockerComposeCommand extends Command abstract class DockerComposeCommand extends Command
{ {
use GetCompiledDockerComposeYMLPath; use GetCompiledPath;
abstract protected function getDockerComposeCommand(): array; abstract protected function getDockerComposeCommand(): array;
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$process = new Process([ $process = $this->createDockerComposeCommand($this->getDockerComposeCommand());
$this->streamProcess($process, $output);
return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE;
}
protected function createDockerComposeCommand(array $command): Process
{
return new Process([
'docker', 'compose', 'docker', 'compose',
'-f', self::GetCompiledDockerComposeYMLPath(), '-f', self::GetCompiledPath() . '/docker-compose.yml',
...$this->getDockerComposeCommand(), ...$command,
]); ]);
}
protected function streamProcess(Process $process, OutputInterface $output): void
{
$process->setTty(true); $process->setTty(true);
$process->mustRun(function ($type, $buffer) use ($output) { $process->mustRun(function ($type, $buffer) use ($output) {
if (Process::OUT === $type) { if (Process::OUT === $type) {
...@@ -28,7 +40,19 @@ abstract class DockerComposeCommand extends Command ...@@ -28,7 +40,19 @@ abstract class DockerComposeCommand extends Command
$output->write('<error>' . $buffer . '</error>'); $output->write('<error>' . $buffer . '</error>');
} }
}); });
}
return $process->isSuccessful() ? Command::SUCCESS : Command::FAILURE; protected function isDockerRunning(): bool
{
$process = $this->createDockerComposeCommand(['ps']);
$process->run();
if (!$process->isSuccessful()) {
return false;
}
$output = trim($process->getOutput());
return count(explode("\n", $output)) > 1;
} }
} }
\ No newline at end of file
<?php
namespace Studip\Dockerized\Traits;
trait GetCompiledDockerComposeYMLPath
{
protected static function GetCompiledDockerComposeYMLPath(): string
{
$filename = realpath(__DIR__ . '/../../compiled/docker-compose.yml');
if (!file_exists($filename)) {
throw new \Exception('docker-compose.yml was not yet compiled');
}
if (!is_readable($filename)) {
throw new \Exception('docker-compose.yml is not readable');
}
return $filename;
}
}
\ No newline at end of file
<?php
namespace Studip\Dockerized\Traits;
trait GetCompiledPath
{
protected static function GetCompiledPath(?string $filename = null): string
{
return realpath(__DIR__ . '/../../compiled') . ($filename ? '/' . $filename : '');
}
}
\ No newline at end of file
...@@ -7,13 +7,13 @@ use Symfony\Component\Console\Application; ...@@ -7,13 +7,13 @@ use Symfony\Component\Console\Application;
const CONFIG_FILE = __DIR__ . '/config.json'; const CONFIG_FILE = __DIR__ . '/config.json';
\Studip\Dockerized\Config::load(CONFIG_FILE);
$application = new Application('Stud.IP Dockerized', '1.0'); $application = new Application('Stud.IP Dockerized', '1.0');
$application->add(new Commands\Init()); $application->add(new Commands\Init());
if (file_exists(CONFIG_FILE)) { if (file_exists(CONFIG_FILE)) {
\Studip\Dockerized\Config::load(CONFIG_FILE);
$application->add(new Commands\Compile()); $application->add(new Commands\Compile());
$application->add(new Commands\Sites\Add()); $application->add(new Commands\Sites\Add());
...@@ -27,6 +27,8 @@ if (file_exists(CONFIG_FILE)) { ...@@ -27,6 +27,8 @@ if (file_exists(CONFIG_FILE)) {
$application->add(new Commands\Docker\Build()); $application->add(new Commands\Docker\Build());
$application->add(new Commands\Docker\Start()); $application->add(new Commands\Docker\Start());
$application->add(new Commands\Docker\Stop()); $application->add(new Commands\Docker\Stop());
$application->add(new Commands\Keys\Create());
} }
$application->run(); $application->run();
...@@ -34,7 +34,7 @@ th { ...@@ -34,7 +34,7 @@ th {
<td><?= htmlentities($path) ?></td> <td><?= htmlentities($path) ?></td>
<?php foreach ($config['php'] as $version => $port): ?> <?php foreach ($config['php'] as $version => $port): ?>
<td> <td>
<a href="http://localhost:<?= $port ?>/<?= htmlentities($path) ?>"> <a href="https://localhost:<?= $port ?>/<?= htmlentities($path) ?>">
<?= htmlentities($version) ?> <?= htmlentities($version) ?>
</a> </a>
</td> </td>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment