Skip to content

Commit

Permalink
combines new console logger and circuit breaker on make pipeline command
Browse files Browse the repository at this point in the history
  • Loading branch information
llaville committed Sep 3, 2024
1 parent 2230e6e commit 266e0ff
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 127 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ stub.php
###### PHPUnit ######
.phpunit.result.cache
.phpunit.cache
phpunit.xml

###### Mega-Linter ######
report/
megalinter-reports/

######################################
Expand Down
10 changes: 8 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
"cyclonedx/cyclonedx-library": "^3.0",
"humbug/box": "^4.6",
"league/pipeline": "^1.0",
"psr/log": "^3.0",
"symfony/console": "^6.4 || ^7.0",
"symfony/filesystem": "^6.4 || ^7.0",
"symfony/process": "^6.4 || ^7.0",
"symfony/serializer": "^6.4 || ^7.0"
"symfony/serializer": "^6.4 || ^7.0",
"symfony/stopwatch": "^6.4 || ^7.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8"
Expand Down Expand Up @@ -67,12 +69,16 @@
"code:check": "vendor/bin/phpstan analyse --configuration .github/linters/phpstan.neon.dist --ansi",
"code:lint": "vendor/bin/phplint --configuration .github/linters/.phplint.yml --verbose --progress=indicator --ansi",
"style:check": "vendor/bin/phpcs --standard=.github/linters/.phpcs.xml.dist --warning-severity=0 --colors",
"tests:unit": ["@putenv TERM_PROGRAM=Hyper", "vendor/bin/phpunit --configuration phpunit.xml.dist"]
"tests:all": ["@putenv TERM_PROGRAM=Hyper", "vendor/bin/phpunit --configuration phpunit.xml.dist"],
"tests:e2e": ["@putenv TERM_PROGRAM=Hyper", "vendor/bin/phpunit --configuration phpunit.xml.dist tests/e2e"],
"tests:unit": ["@putenv TERM_PROGRAM=Hyper", "vendor/bin/phpunit --configuration phpunit.xml.dist tests/unit"]
},
"scripts-descriptions" : {
"code:check": "Run PHPStan code analysis on project source code",
"code:lint": "Run PHPLint on project source code",
"style:check": "Run PHP CodeSniffer on project source code",
"tests:all": "Run PHPUnit (all tests) on project source code",
"tests:e2e": "Run PHPUnit (End-To-End tests) on project source code",
"tests:unit": "Run PHPUnit (unit tests) on project source code"
}
}
11 changes: 8 additions & 3 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
<text outputFile="php://stdout" showUncoveredFiles="true" showOnlySummary="false"/>
</report>
</coverage>
<testsuite name="unit">
<directory suffix="Test.php">tests/unit/</directory>
</testsuite>
<testsuites>
<testsuite name="e2e">
<directory suffix="Test.php">tests/e2e/</directory>
</testsuite>
<testsuite name="unit">
<directory suffix="Test.php">tests/unit/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src/</directory>
Expand Down
23 changes: 23 additions & 0 deletions src/Composer/ManifestOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

use Fidry\Console\IO;

use RuntimeException;
use function file_exists;
use function is_dir;
use function sprintf;

/**
* @author Laurent Laville
* @since Release 3.5.0
Expand All @@ -28,6 +33,7 @@ final class ManifestOptions
public const RESOURCE_OPTION = 'resource';
public const RESOURCE_DIR_OPTION = 'resource-dir';
public const IMMUTABLE_OPTION = 'immutable';
public const WORKING_DIR_OPTION = 'working-dir';

public function __construct(private readonly IO $io)
{
Expand Down Expand Up @@ -86,6 +92,23 @@ public function getResourceDir(): string
return $this->io->getTypedOption(self::RESOURCE_DIR_OPTION)->asString();
}

public function getWorkingDir(): ?string
{
$workingDir = $this->io->getTypedOption(ManifestOptions::WORKING_DIR_OPTION)->asNullableNonEmptyString();

if ($workingDir === null) {
return null;
}

if (!file_exists($workingDir) || !is_dir($workingDir)) {
throw new RuntimeException(
sprintf('Invalid working directory specified, "%s" does not exist or is not a directory.', $workingDir)
);
}

return $workingDir;
}

public function isImmutable(): bool
{
return $this->io->getTypedOption(self::IMMUTABLE_OPTION)->asBoolean();
Expand Down
156 changes: 109 additions & 47 deletions src/Console/Command/Make.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Bartlett\BoxManifest\Console\Command;

use Bartlett\BoxManifest\Composer\ManifestOptions;
use Bartlett\BoxManifest\Console\Logger;
use Bartlett\BoxManifest\Helper\BoxHelper;
use Bartlett\BoxManifest\Helper\ManifestFormat;
use Bartlett\BoxManifest\Pipeline\AbstractStage;
Expand All @@ -16,13 +17,17 @@
use Bartlett\BoxManifest\Pipeline\ConfigureStage;
use Bartlett\BoxManifest\Pipeline\StageInterface;
use Bartlett\BoxManifest\Pipeline\StubStage;
use Bartlett\BoxManifest\Pipeline\InterruptibleTimedProcessor;

use CycloneDX\Core\Spec\Version;

use Fidry\Console\IO;

use League\Pipeline\FingersCrossedProcessor;
use League\Pipeline\PipelineBuilder;

use Psr\Log\LoggerInterface;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\Helper;
Expand All @@ -34,8 +39,13 @@
use Symfony\Component\Console\Output\OutputInterface;

use InvalidArgumentException;
use Throwable;
use UnexpectedValueException;
use function array_unique;
use function chdir;
use function class_exists;
use function count;
use function getcwd;
use function implode;
use function is_file;
use function is_string;
Expand Down Expand Up @@ -127,6 +137,13 @@ protected function configure(): void
InputOption::VALUE_NONE,
'Generates immutable version of a manifest file',
),
new InputOption(
ManifestOptions::WORKING_DIR_OPTION,
'd',
InputOption::VALUE_REQUIRED,
'If specified, use the given directory as working directory',
null
),
];

$this->setName(self::NAME)
Expand All @@ -142,6 +159,13 @@ protected function configure(): void

protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var DebugFormatterHelper $debugFormatter */
$debugFormatter = $this->getHelper('debug_formatter');

$logger = new Logger($debugFormatter, $output);

$pid = uniqid();

$startTime = microtime(true);

/** @var BoxHelper $boxHelper */
Expand All @@ -155,57 +179,73 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$bootstrap = $options->getBootstrap() ?? '';

/** @var DebugFormatterHelper $debugFormatter */
$debugFormatter = $this->getHelper('debug_formatter');
$resourceCount = count($resources);

$pid = uniqid();
$stages = array_unique($io->getTypedArgument(ManifestOptions::STAGES_OPTION)->asNonEmptyStringList());

if ($io->isDebug()) {
$resourceCount = count($resources);
$io->write(
$debugFormatter->start(
$pid,
sprintf('Making manifests for %d resource%s', $resourceCount, $resourceCount > 1 ? 's' : ''),
'STARTED'
)
);
}
$logger->notice(
sprintf('Workflow with following stages >> %s', implode(', ', $stages)),
['status' => Logger::STATUS_STARTED, 'id' => $pid]
);
$context = ['status' => Logger::STATUS_STOPPED, 'id' => $pid, 'error' => false];

// 1. (optional) but should be tried first before any other environment changes
$bootstrap = realpath($bootstrap);

if (is_string($bootstrap) && is_file($bootstrap)) {
if ($io->isDebug()) {
$io->write(
$debugFormatter->progress(
$pid,
sprintf('Bootstrapped file "<info>%s</info>"', $bootstrap),
false,
'IN'
)
$logger->notice(
sprintf('Bootstrapped file "%s"', $bootstrap),
['status' => Logger::STATUS_RUNNING, 'id' => $pid, 'prefix' => 'IN']
);
include $bootstrap;
}

// 2. (optional) changes current working directory
try {
$workingDir = $this->changeWorkingDir($options->getWorkingDir());
if (null !== $workingDir) {
$logger->notice(
sprintf('Changed working directory to "%s"', getcwd()),
['status' => Logger::STATUS_RUNNING, 'id' => $pid, 'prefix' => 'IN']
);
}
include $bootstrap;
} catch (UnexpectedValueException $e) {
$context['error'] = true;
$logger->error($e->getMessage(), $context);
$logger->error('Aborting workflow ... none operation was executed', $context);
return Command::FAILURE;
}

// 3. creates the Pipeline with stages asked
$pipelineBuilder = new PipelineBuilder();

foreach ($io->getTypedArgument(ManifestOptions::STAGES_OPTION)->asNonEmptyStringList() as $stageName) {
foreach ($stages as $stageName) {
try {
$arguments = [$io, $this, $logger, ['pid' => $pid]];
$stage = match ($stageName) {
StageInterface::BUILD_STAGE => new BuildStage($io, $this, ['pid' => $pid]),
StageInterface::STUB_STAGE => new StubStage($io, $this, ['pid' => $pid]),
StageInterface::CONFIGURE_STAGE => new ConfigureStage($io, $this, ['pid' => $pid]),
StageInterface::COMPILE_STAGE => new CompileStage($io, $this, ['pid' => $pid]),
default => $this->getCustomStage($stageName, $io),
StageInterface::BUILD_STAGE => new BuildStage(...$arguments),
StageInterface::STUB_STAGE => new StubStage(...$arguments),
StageInterface::CONFIGURE_STAGE => new ConfigureStage(...$arguments),
StageInterface::COMPILE_STAGE => new CompileStage(...$arguments),
default => $this->getCustomStage($stageName, ...$arguments),
};
} catch (InvalidArgumentException $e) {
$io->error($e->getMessage());
$context['error'] = true;
$logger->error($e->getMessage(), $context);
$logger->error('Aborting workflow ... none operation was executed', $context);
return Command::FAILURE;
}

$pipelineBuilder->add($stage);
}

$processor = $io->isDebug()
? new InterruptibleTimedProcessor($logger)
: new FingersCrossedProcessor()
;
$pipeline = $pipelineBuilder->build($processor);

// 4. prepares payload to transmit to all stages of the pipeline
$config = $boxHelper->getBoxConfiguration(
$io->withOutput(new NullOutput()),
true,
Expand All @@ -219,7 +259,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
;

$payload = [
'pid' => $pid,
'configuration' => $config,
'config' => $config->getConfigurationFile(),
'ansiSupport' => $output->isDecorated(),
'immutableCopy' => $io->getTypedOption(ManifestOptions::IMMUTABLE_OPTION)->asBoolean(),
'versions' => [
Expand All @@ -238,37 +280,57 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'configurationFile' => $config->getConfigurationFile(),
];

$pipeline = $pipelineBuilder->build();
$finalPayload = $pipeline($payload);

if ($io->isDebug()) {
foreach ($finalPayload['response']['artifacts'] ?? [] as $responseArtifact => $mimeType) {
$io->write(
$debugFormatter->stop($pid, sprintf('%s: %s', $responseArtifact, $mimeType), true, 'RESPONSE')
// 5. runs the workflow (pipeline)
try {
$pipeline($payload);
$isSuccessful = true;
} catch (Throwable $e) {
$context['error'] = true;
$logger->error('Workflow has failed', $context);
$isSuccessful = false;
} finally {
if ($isSuccessful) {
$logger->notice(
sprintf(
'Workflow has finished. Elapsed time %s',
Helper::formatTime(microtime(true) - $startTime)
),
$context
);
return Command::SUCCESS;
}
$io->write(
$debugFormatter->stop(
$pid,
'Process elapsed time ' . Helper::formatTime(microtime(true) - $startTime),
true,
'FINISHED'
)
);
return Command::FAILURE;
}
}

return Command::SUCCESS;
private function changeWorkingDir(?string $newWorkingDir): ?string
{
if (null !== $newWorkingDir) {
$oldWorkingDir = getcwd();
if (false !== $oldWorkingDir && $newWorkingDir !== $oldWorkingDir) {
if (false === chdir($newWorkingDir)) {
throw new UnexpectedValueException(
sprintf(
'Failed to change the working directory from "%s" to "%s".',
$oldWorkingDir,
$newWorkingDir,
)
);
}
}
}
return $newWorkingDir;
}

private function getCustomStage(string $stageClass, IO $io): StageInterface
private function getCustomStage(string $stageClass, IO $io, Command $command, LoggerInterface $logger, array $context): StageInterface
{
if (!class_exists($stageClass)) {
throw new InvalidArgumentException(
sprintf('No stage found for "%s".', $stageClass)
);
}

$stage = new $stageClass($io, $this);
$stage = $stageClass::create($io, $command, $logger, $context);

if (!$stage instanceof StageInterface) {
throw new InvalidArgumentException(
Expand Down
Loading

0 comments on commit 266e0ff

Please sign in to comment.