This commit is contained in:
2024-08-22 19:49:44 -04:00
parent a617bcf040
commit d251d8bd7d
14 changed files with 2971 additions and 0 deletions

2
app/.env.sample Normal file
View File

@ -0,0 +1,2 @@
ROOT=/opt/docks
OUTPUT=/output

12
app/bin/console.php Normal file
View File

@ -0,0 +1,12 @@
<?php
$app = require_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'bootstrap',
'app.php'
]);
try {
$app->run();
} catch (Exception $e) {
$app->getContainer()->get(Psr\Log\LoggerInterface::class)->error($e);
exit(1);
}

3
app/bin/manager Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
php /app/bin/console.php $@

50
app/bootstrap/app.php Normal file
View File

@ -0,0 +1,50 @@
<?php
use DI\ContainerBuilder;
use ProVM\ComposeManager\Wrapper\Application;
require_once 'composer.php';
function buildApp(): Application
{
$containerBuilder = new ContainerBuilder();
$folders = [
'configs',
'setups'
];
foreach ($folders as $folderName) {
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
$folderName
]);
if (!file_exists($folder)) {
continue;
}
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
$containerBuilder->addDefinitions($file->getPathname());
}
}
$app = (new Application)->setContainer($containerBuilder->build());
$folder = implode(DIRECTORY_SEPARATOR, [
__DIR__,
'commands'
]);
if (file_exists($folder)) {
$files = new FilesystemIterator($folder);
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
require_once $file->getPathname();
}
}
return $app;
}
return buildApp();

View File

@ -0,0 +1,2 @@
<?php
$app->setCommandLoader($app->getContainer()->get(Symfony\Component\Console\CommandLoader\CommandLoaderInterface::class));

View File

@ -0,0 +1,6 @@
<?php
require_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'vendor',
'autoload.php'
]);

View File

@ -0,0 +1,9 @@
<?php
return [
'commands' => function() {
return [
'create' => ProVM\ComposeManager\Command\Create::class
];
}
];

View File

@ -0,0 +1,2 @@
<?php
return $_ENV;

View File

@ -0,0 +1,14 @@
<?php
use Psr\Container\ContainerInterface;
return [
ProVM\ComposeManager\Command\Create::class => function(ContainerInterface $container) {
return new ProVM\ComposeManager\Command\Create($container->get('ROOT'), $container->get('OUTPUT'));
},
Symfony\Component\Console\CommandLoader\CommandLoaderInterface::class => function(ContainerInterface $container) {
return new Symfony\Component\Console\CommandLoader\ContainerCommandLoader(
$container,
$container->get('commands')
);
}
];

View File

@ -0,0 +1,18 @@
<?php
use Psr\Container\ContainerInterface;
return [
Psr\Log\LoggerInterface::class => function(ContainerInterface $container) {
return new Monolog\Logger('app', [
(new Monolog\Handler\RotatingFileHandler('/logs/error.log'))
->setFormatter(new Monolog\Formatter\LineFormatter(null, null, true, true)),
], [
new Monolog\Processor\UidProcessor(),
new Monolog\Processor\MemoryUsageProcessor(),
new Monolog\Processor\MemoryPeakUsageProcessor(),
new Monolog\Processor\PsrLogMessageProcessor(),
new Monolog\Processor\IntrospectionProcessor(),
]);
}
];

24
app/composer.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "provm/compose-manager",
"description": "Manage docker compose projects",
"type": "project",
"require": {
"symfony/console": "^7.1",
"php-di/php-di": "^7.0",
"monolog/monolog": "^3.7"
},
"require-dev": {
"phpunit/phpunit": "^11.3"
},
"autoload": {
"psr-4": {
"ProVM\\ComposeManager\\": "src/"
}
},
"authors": [
{
"name": "Aldarien",
"email": "aldarien85@gmail.com"
}
]
}

2693
app/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

117
app/src/Command/Create.php Normal file
View File

@ -0,0 +1,117 @@
<?php
namespace ProVM\ComposeManager\Command;
use Symfony\Component\Console;
#[Console\Attribute\AsCommand(
name: 'create'
)]
class Create extends Console\Command\Command
{
public function __construct(protected string $root, protected string $output, ?string $name = null)
{
parent::__construct($name);
}
protected function configure(): void
{
$this->addArgument('name', Console\Input\InputArgument::REQUIRED, 'Name of the project, same as project folder where docker compose file is located');
$this->addOption('group', 'g', Console\Input\InputOption::VALUE_REQUIRED, "Sub-path to the project folder relative to the root {$this->root}");
$this->addOption('description', 'd', Console\Input\InputOption::VALUE_REQUIRED, 'Description of the project');
$this->addOption('remove-orphans', 'r', Console\Input\InputOption::VALUE_NONE, 'Remove containers for services not defined in the Compose file');
}
protected function execute(Console\Input\InputInterface $input, Console\Output\OutputInterface $output): int
{
$filename = implode(DIRECTORY_SEPARATOR, [rtrim($this->output, '/'), "docker-{$input->getArgument('name')}.service"]);
$after = 'docker.service';
$sourceParts = [rtrim($this->root, '/')];
if ($input->hasOption('group') and $input->getOption('group')) {
$sourceParts[] = $input->getOption('group');
$after = $this->getGroupServiceName($input->getOption('group'));
if (!$this->checkGroupFile($input->getOption('group'))) {
$this->buildGroupFile($input->getOption('group'));
}
}
$sourceParts[] = $input->getArgument('name');
$source = implode(DIRECTORY_SEPARATOR, $sourceParts);
if ($input->hasOption('description') and $input->getOption('description')) {
$description = $input->getOption('description');
} else {
$descriptionParts = [];
if ($input->hasOption('group') and $input->getOption('group')) {
$descriptionParts[] = $input->getOption('group');
}
$descriptionParts[] = $input->getArgument('name');
$description = "Docker Compose service for " . implode(DIRECTORY_SEPARATOR, $descriptionParts);
}
$content = $this->buildServiceFile($source, $description, $after, $input->getOption('remove-orphans'));
file_put_contents($filename, $content);
return Console\Command\Command::SUCCESS;
}
protected function buildServiceFile(string $source, string $description, string $after = 'docker.service', bool $removeOrphans = false): string
{
$removeOrphans = $removeOrphans ? ' --remove-orphans' : '';
return <<<SERVICE
[Unit]
Description={$description}
PartOf={$after}
After={$after}
Before=docker-services.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={$source}
ExecStart=/usr/local/bin/docker-compose up -d
ExecStop=/usr/local/bin/docker-compose down{$removeOrphans}
[Install]
RequiredBy=docker-services.target
SERVICE;
}
protected function getGroupServiceName(string $groupName): string
{
return "docker-{$groupName}.service";
}
protected function checkGroupFile(string $groupName): bool
{
$filename = implode(DIRECTORY_SEPARATOR, [rtrim($this->output, '/'), $this->getGroupServiceName($groupName)]);
return file_exists($filename);
}
protected function buildGroupFile(string $groupName): void
{
$filename = implode(DIRECTORY_SEPARATOR, [rtrim($this->output, '/'), $this->getGroupServiceName($groupName)]);
$content = $this->buildGroupServiceFile($groupName);
file_put_contents($filename, $content);
}
protected function buildGroupServiceFile(string $groupName): string
{
return <<<SERVICE
[Unit]
Description=Docker Compose service for group {$groupName}
PartOf=docker.service
After=docker.service
Before=docker-services.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={$this->root}/{$groupName}
ExecStart=/bin/true
[Install]
RequiredBy=docker-services.target
SERVICE;
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace ProVM\ComposeManager\Wrapper;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console;
class Application extends Console\Application
{
protected ContainerInterface $container;
public function getContainer(): ContainerInterface
{
return $this->container;
}
public function setContainer(ContainerInterface $container): Application
{
$this->container = $container;
return $this;
}
}