Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Messenger/Kernel/CommandBusDependencies.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ enum CommandBusDependencies: string {
case Logger = self::class.'::Logger';
case EventDispatcher = self::class.'::EventDispatcher';
case Serializer = self::class.'::Serializer';
case Worker = self::class.'::Worker';
case SupervisorConfigDir = self::class.'::SupervisorConfigDir';
}
31 changes: 30 additions & 1 deletion src/Messenger/Kernel/MessengerServiceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Console\Application;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Messenger\Command\ConsumeMessagesCommand;
use Symfony\Component\Messenger\Command\StopWorkersCommand;
Expand All @@ -31,6 +32,8 @@
use WonderNetwork\SlimKernel\Messenger\QueryBus;
use WonderNetwork\SlimKernel\ServiceFactory;
use WonderNetwork\SlimKernel\ServicesBuilder;
use WonderNetwork\SlimKernel\Supervisor\GenerateSupervisorConfigCommand;
use WonderNetwork\SlimKernel\Supervisor\SupervisorConfiguration;
use function DI\autowire;
use function DI\create;
use function DI\factory;
Expand All @@ -41,10 +44,12 @@
public function __construct(
private string $commandPath = '/src/Application/Command/**/*Handler.php',
private string $queryPath = '/src/Application/Query/**/*Handler.php',
private string $supervisorConfigDir = 'app/supervisor',
private null|Closure|Reference|DefinitionHelper|TransportLocatorBuilder $transports = null,
private null|Closure|Reference|DefinitionHelper|EventDispatcher $eventDispatcher = null,
private null|Closure|Reference|DefinitionHelper|LoggerInterface $logger = null,
private null|Closure|Reference|DefinitionHelper|CacheItemPoolInterface $cachePool = null,
private null|Closure|Reference|DefinitionHelper|SupervisorConfiguration $programs = null,
) {
}

Expand All @@ -66,7 +71,7 @@ public function __invoke(ServicesBuilder $builder): iterable {
// region utilities
yield CommandBusDependencies::Serializer->value => factory(fn () => Serializer::create());
yield SerializerInterface::class => get(CommandBusDependencies::Serializer->value);
yield CommandBusDependencies::EventDispatcher->value => $this->eventDispatcher ?? new EventDispatcher();
yield CommandBusDependencies::EventDispatcher->value => $this->eventDispatcher ?? get(EventDispatcher::class);
yield CommandBusDependencies::Logger->value => $this->logger ?? new NullLogger();
yield CommandBusDependencies::CachePool->value => $this->cachePool ?? new ArrayAdapter();
// endregion
Expand Down Expand Up @@ -126,5 +131,29 @@ public function __invoke(ServicesBuilder $builder): iterable {
yield StopWorkersCommand::class => autowire()->constructor(
restartSignalCachePool: get(CommandBusDependencies::CachePool->value),
);

yield CommandBusDependencies::SupervisorConfigDir->value => $this->supervisorConfigDir;
yield SupervisorConfiguration::class => $this->programs ?? SupervisorConfiguration::empty();
yield GenerateSupervisorConfigCommand::class => autowire()->constructor(
configDir: get(CommandBusDependencies::SupervisorConfigDir->value),
);

yield CommandBusDependencies::Worker->value => function (ContainerInterface $container) {
$app = new Application('worker');
/** @var EventDispatcher $eventDispatcher */
$eventDispatcher = $container->get(CommandBusDependencies::EventDispatcher->value);

$app->setDispatcher($eventDispatcher);
$app->addCommands(
[
$container->get(StopWorkersCommand::class),
$container->get(ConsumeMessagesCommand::class),
$container->get(GenerateSupervisorConfigCommand::class),
],
);
$app->setAutoExit(false);

return $app;
};
}
}
2 changes: 2 additions & 0 deletions src/ServiceFactory/EventDispatcherServiceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Psr\EventDispatcher as Psr;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\EventDispatcher as Contracts;
use WonderNetwork\SlimKernel\ServiceFactory;
use WonderNetwork\SlimKernel\ServicesBuilder;
use function DI\autowire;
Expand All @@ -17,5 +18,6 @@ public function __invoke(ServicesBuilder $builder): iterable {
yield EventDispatcher::class => autowire();
yield EventDispatcherInterface::class => get(EventDispatcher::class);
yield Psr\EventDispatcherInterface::class => get(EventDispatcher::class);
yield Contracts\EventDispatcherInterface::class => get(EventDispatcher::class);
}
}
130 changes: 130 additions & 0 deletions src/Supervisor/GenerateSupervisorConfigCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Supervisor;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use WonderNetwork\SlimKernel\Cli\InitializeInputParamsTrait;

final class GenerateSupervisorConfigCommand extends Command {
use InitializeInputParamsTrait;

public function __construct(
private readonly string $configDir,
private readonly SupervisorConfiguration $configuration,
) {
parent::__construct('supervisor:generate-config');
}

protected function configure(): void {
$this->addArgument(
name: 'current-directory',
mode: InputArgument::REQUIRED,
description: 'The absolute directory to the script',
);

$this->addOption(
name: 'stdio',
mode: InputOption::VALUE_NONE,
description: "Log to stdout/stderr instead of files",
);

$this->addOption(
name: 'purge',
mode: InputOption::VALUE_NONE | InputOption::VALUE_NEGATABLE,
description: 'Remove all files before generating new ones',
);

$this->addOption(
name: 'logfile',
mode: InputOption::VALUE_REQUIRED,
default: '/var/log/supervisor/{processName}.{suffix}.log',
);

$this->addOption(
name: 'logfile-maxbytes',
mode: InputOption::VALUE_REQUIRED,
default: "50MB",
);

$this->addOption(
name: 'config-dir',
mode: InputOption::VALUE_REQUIRED,
default: $this->configDir,
);
}

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

$currentDirectory = $this->params->arguments->string('current-directory');
$stdio = $this->params->options->bool('stdio');
$noPurge = $this->params->options->bool('no-purge');
$logfile = $this->params->options->string('logfile');
$maxBytes = $this->params->options->string('logfile-maxbytes');
$configDir = $this->params->options->string('config-dir');

if ("" === $configDir) {
$io->error("Config directory can't be empty");

return self::FAILURE;
}

if (false === $noPurge) {
foreach (glob(sprintf('%s/*.conf', $configDir)) ?: [] as $preExistingConfig) {
unlink($preExistingConfig);
}
}

if ($stdio) {
$maxBytes = "0";
$logfile = '/dev/std{suffix}';
}

foreach ($this->configuration->programs as $program) {
$concurrency = $program->concurrency;
$processName = $program->name;

if ($concurrency > 1) {
$processName = '%(program_name)s_%(process_num)02d';
}

$errorLog = strtr($logfile, ['{processName}' => $processName, '{suffix}' => 'err']);
$standardLog = strtr($logfile, ['{processName}' => $processName, '{suffix}' => 'out']);

$fullCommand = sprintf('%s/%s', rtrim($currentDirectory, '/'), $program->command);
$supervisorConfigPath = sprintf('%s/%s.conf', $configDir, $program->name);

$supervisorConfig = <<<EOF
[program:$program->name]
command=$fullCommand
process_name=$processName
numprocs=$concurrency
user=www-data
autostart=true
autorestart=true
stderr_logfile=$errorLog
stderr_logfile_maxbytes=$maxBytes
stdout_logfile=$standardLog
stdout_logfile_maxbytes=$maxBytes
EOF;

file_put_contents($supervisorConfigPath, $supervisorConfig);
$io->writeln(
sprintf(
'Writing <comment>%s</comment> configuration file to <info>%s</info>',
$program->name,
$supervisorConfigPath,
),
);
}

return self::SUCCESS;
}
}
29 changes: 29 additions & 0 deletions src/Supervisor/SupervisorConfiguration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Supervisor;

final readonly class SupervisorConfiguration {
public static function empty(): self {
return new self([]);
}

public static function start(): self {
return new self([]);
}

/**
* @param list<SupervisorProgram> $programs
*/
public function __construct(public array $programs) {
}

public function withPrograms(SupervisorProgram ...$programs): self {
return new self([...$this->programs, ...array_values($programs)]);
}

public function withSimpleCommand(string $name, string $command): self {
return new self([...$this->programs, SupervisorProgram::single($name, $command)]);
}
}
25 changes: 25 additions & 0 deletions src/Supervisor/SupervisorProgram.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace WonderNetwork\SlimKernel\Supervisor;

final readonly class SupervisorProgram {
public static function single(string $name, string $command): self {
return new self(
name: $name,
command: $command,
concurrency: 1,
);
}

/**
* @param positive-int $concurrency
*/
public function __construct(
public string $name,
public string $command,
public int $concurrency,
) {
}
}
1 change: 1 addition & 0 deletions tests/Resources/Supervisor/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.conf
Loading