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
Branches
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;
use Studip\Dockerized\Config;
use Studip\Dockerized\Creators\DockerComposeConfiguration;
use Studip\Dockerized\Creators\NginxConfiguration;
use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
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()
{
$this->setName('compile');
......@@ -21,10 +25,14 @@ final class Compile extends \Symfony\Component\Console\Command\Command
p',
InputOption::VALUE_OPTIONAL,
'Path to store the compiled files',
realpath(__DIR__ . '/../../compiled')
$this->getCompiledPath()
);
$this->addOption('nginx', null, InputOption::VALUE_NEGATABLE, 'Compile nginx 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)
......@@ -48,25 +56,41 @@ final class Compile extends \Symfony\Component\Console\Command\Command
protected function execute(InputInterface $input, OutputInterface $output)
{
$compiledPath = realpath($input->getOption('path'));
$force = $input->getOption('force');
$io = new SymfonyStyle($input, $output);
$writeConfigFile = function (string $filename, string $content) use ($compiledPath, $io): void {
file_put_contents(
"{$compiledPath}/{$filename}",
$content
);
$writeConfigFile = function (string $filename, string $content) use ($compiledPath, $force, $io): bool {
$filepath = "{$compiledPath}/{$filename}";
if (
!file_exists($filepath)
|| $content !== file_get_contents($filepath)
|| $force
) {
file_put_contents($filepath, $content);
$io->success('Config file ' . $filename . ' written');
return true;
}
return false;
};
$changed = 0;
if ($input->getOption('nginx')) {
$writeConfigFile(
$changed += (int) $writeConfigFile(
'nginx.conf',
$this->createNginxConfiguration()
);
$changed += (int) $writeConfigFile(
'nginx-sites.conf',
$this->createNginxSites()
);
}
if ($input->getOption('docker')) {
$writeConfigFile(
$changed += (int) $writeConfigFile(
'docker-compose.yml',
$this->createDockerComposerConfiguration(
realpath(__DIR__ . '/../..'),
......@@ -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;
}
......@@ -89,8 +130,8 @@ final class Compile extends \Symfony\Component\Console\Command\Command
// Declare volumes
$volumes = [
[realpath(__DIR__ . '/../../web'), '/var/www/html/studip-dockerized-config.app', 'ro'],
[realpath(__DIR__ . '/../../config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'],
[realpath($cwd . '/web'), '/var/www/html/studip-dockerized-config.app', 'ro'],
[realpath($cwd . '/config.json'), '/var/www/html/studip-dockerized-config.json', 'ro'],
...array_map(
fn(string $path, array $definition) => [$definition['source'], '/var/www/html/' . $path],
array_keys(Config::getInstance()->get('sites') ?? []),
......@@ -110,15 +151,18 @@ final class Compile extends \Symfony\Component\Console\Command\Command
),
]);
$creator->addServiceVolume(
'nginx',
"{$compiledPath}/nginx.conf",
'/etc/nginx/conf.d/default.conf',
'ro'
);
$mounts = [
"{$compiledPath}/nginx.conf" => 'conf.d/nginx.conf',
"{$compiledPath}/nginx-sites.conf" => 'sites.conf',
"{$cwd}/config/nginx-php.conf" => 'php.conf',
"{$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');
foreach ($volumes as $volume) {
$creator->addServiceVolume('nginx', ...$volume);
}
......@@ -176,26 +220,40 @@ final class Compile extends \Symfony\Component\Console\Command\Command
{
$creator = new NginxConfiguration();
// Register event handler to add the nginx-php configuration to each location
$creator->on(NginxConfiguration::EVENT_LOCATION_ADD_BEFORE, function ($location, &$config) use ($creator) {
$config[] = $creator->write('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',
// 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 ssl',
'server_name _',
'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
$creator->addLocation('/sites', [
'alias /var/www/html/studip-dockerized-config.app',
'index index.php index.html index.html',
'set $site_document_root /var/www/html/studip-dockerized-config.app',
'include php.conf',
]);
// Add configured locations
......@@ -209,22 +267,7 @@ final class Compile extends \Symfony\Component\Console\Command\Command
'alias ' . $p,
'index index.php index.html index.html',
'set $site_document_root ' . $p,
]);
}
// 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',
'include php.conf',
]);
}
......
......@@ -2,6 +2,10 @@
namespace Studip\Dockerized\Commands\Docker;
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
{
......@@ -12,6 +16,34 @@ final class Build extends DockerComposeCommand
}
protected function getDockerComposeCommand(): array
{
$dockerRunning = $this->isDockerRunning();
if ($dockerRunning) {
$this->createDockerComposeCommand(['down']);
}
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
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");
}
......
......@@ -70,18 +70,16 @@ final class NginxConfiguration
...array_map(
fn($server) => $this->write(
'server',
array_merge(
$server['config'],
array_map(
$server['config']
),
$this->servers
),
...array_map(
fn($location) => $this->write(
"location {$location['modifier']} {$location['location']}",
$location['config']
),
$this->locations
)
)
),
$this->servers
),
]));
$result = (string) Scope::fromFile($tempName);
......
<?php
namespace Studip\Dockerized;
use Studip\Dockerized\Traits\GetCompiledDockerComposeYMLPath;
use Studip\Dockerized\Traits\GetCompiledPath;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
......@@ -9,17 +9,29 @@ use Symfony\Component\Process\Process;
abstract class DockerComposeCommand extends Command
{
use GetCompiledDockerComposeYMLPath;
use GetCompiledPath;
abstract protected function getDockerComposeCommand(): array;
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',
'-f', self::GetCompiledDockerComposeYMLPath(),
...$this->getDockerComposeCommand(),
'-f', self::GetCompiledPath() . '/docker-compose.yml',
...$command,
]);
}
protected function streamProcess(Process $process, OutputInterface $output): void
{
$process->setTty(true);
$process->mustRun(function ($type, $buffer) use ($output) {
if (Process::OUT === $type) {
......@@ -28,7 +40,19 @@ abstract class DockerComposeCommand extends Command
$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;
const CONFIG_FILE = __DIR__ . '/config.json';
\Studip\Dockerized\Config::load(CONFIG_FILE);
$application = new Application('Stud.IP Dockerized', '1.0');
$application->add(new Commands\Init());
if (file_exists(CONFIG_FILE)) {
\Studip\Dockerized\Config::load(CONFIG_FILE);
$application->add(new Commands\Compile());
$application->add(new Commands\Sites\Add());
......@@ -27,6 +27,8 @@ if (file_exists(CONFIG_FILE)) {
$application->add(new Commands\Docker\Build());
$application->add(new Commands\Docker\Start());
$application->add(new Commands\Docker\Stop());
$application->add(new Commands\Keys\Create());
}
$application->run();
......@@ -34,7 +34,7 @@ th {
<td><?= htmlentities($path) ?></td>
<?php foreach ($config['php'] as $version => $port): ?>
<td>
<a href="http://localhost:<?= $port ?>/<?= htmlentities($path) ?>">
<a href="https://localhost:<?= $port ?>/<?= htmlentities($path) ?>">
<?= htmlentities($version) ?>
</a>
</td>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment