Merge branch 'master' into release
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
**/vendor/
|
**/vendor/
|
||||||
**/logs/
|
**/logs/
|
||||||
**/*.env
|
**/*.env
|
||||||
|
**/output/
|
||||||
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM php:8-cli
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY ./app /app
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/app/bin/manager" ]
|
2
app/.env.sample
Normal file
2
app/.env.sample
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ROOT=/opt/docks
|
||||||
|
OUTPUT=/output
|
12
app/bin/console.php
Normal file
12
app/bin/console.php
Normal 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
3
app/bin/manager
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
php /app/bin/console.php $@
|
50
app/bootstrap/app.php
Normal file
50
app/bootstrap/app.php
Normal 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();
|
2
app/bootstrap/commands/loader.php
Normal file
2
app/bootstrap/commands/loader.php
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
$app->setCommandLoader($app->getContainer()->get(Symfony\Component\Console\CommandLoader\CommandLoaderInterface::class));
|
6
app/bootstrap/composer.php
Normal file
6
app/bootstrap/composer.php
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
require_once implode(DIRECTORY_SEPARATOR, [
|
||||||
|
dirname(__DIR__),
|
||||||
|
'vendor',
|
||||||
|
'autoload.php'
|
||||||
|
]);
|
9
app/bootstrap/configs/commands.php
Normal file
9
app/bootstrap/configs/commands.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'commands' => function() {
|
||||||
|
return [
|
||||||
|
'create' => ProVM\ComposeManager\Command\Create::class
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
2
app/bootstrap/configs/env.php
Normal file
2
app/bootstrap/configs/env.php
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
return $_ENV;
|
14
app/bootstrap/setups/commands.php
Normal file
14
app/bootstrap/setups/commands.php
Normal 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')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
];
|
18
app/bootstrap/setups/logs.php
Normal file
18
app/bootstrap/setups/logs.php
Normal 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
24
app/composer.json
Normal 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
2693
app/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
118
app/src/Command/Create.php
Normal file
118
app/src/Command/Create.php
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\ComposeManager\Command;
|
||||||
|
|
||||||
|
use Symfony\Component\Console;
|
||||||
|
|
||||||
|
#[Console\Attribute\AsCommand(
|
||||||
|
name: 'create',
|
||||||
|
description: 'Create a systemd service file for a docker-compose project',
|
||||||
|
)]
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
19
app/src/Wrapper/Application.php
Normal file
19
app/src/Wrapper/Application.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
8
compose.yml
Normal file
8
compose.yml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
env_file:
|
||||||
|
- ./app/.env
|
||||||
|
volumes:
|
||||||
|
- ./app:/app
|
||||||
|
- ./output:/output
|
Reference in New Issue
Block a user