Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: cr:list command #4900

Open
wants to merge 4 commits into
base: 9.0
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function processNode(Node $node, Scope $scope): array
$targetClassName,
$node->name->toString()
)
)->build(),
)->identifier('neos.internal')->build(),
];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class ContentStreamCommandController extends CommandController
*/
public function pruneCommand(string $contentRepository = 'default', bool $removeTemporary = false): void
{
if (!$this->output->askConfirmation(sprintf('> This operation in "%s" cannot be reverted. Are you sure to proceed? (y/n) ', $contentRepository), false)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mhsdesign What was the intention on adding this unskippable confirmation? I remember using the pruneCommand and pruneRemovedFromEventStreamCommand regularly to cleanup the database.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its a safety measurement we already have in place in other locations but forgot about this one ... like cr:prune and cr:projectionReplay etc

Copy link
Member

@ahaeslich ahaeslich Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. But do we really assume that this commands will only ever have the need to be triggered manually? Cleaning up the contentstream tables of junk data is a bit of a different use case then cr:prune and cr:projectionReplay.

And I'm worried about adding this change now in a feature PR without addressing it. This might be a change someone running an active Neos 9 project would like to see in the Logs.

Copy link
Member

@nezaniel nezaniel Sep 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imho this must be able to be part of a deployment process, so at least a confirmation parameter must be added to the CLI call.

$this->outputLine('<comment>Abort.</comment>');
return;
}

$contentRepositoryId = ContentRepositoryId::fromString($contentRepository);
$contentStreamPruner = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentStreamPrunerFactory());

Expand All @@ -54,6 +59,11 @@ public function pruneCommand(string $contentRepository = 'default', bool $remove
*/
public function pruneRemovedFromEventStreamCommand(string $contentRepository = 'default'): void
{
if (!$this->output->askConfirmation(sprintf('> This operation in "%s" cannot be reverted. Are you sure to proceed? (y/n) ', $contentRepository), false)) {
$this->outputLine('<comment>Abort.</comment>');
return;
}

$contentRepositoryId = ContentRepositoryId::fromString($contentRepository);
$contentStreamPruner = $this->contentRepositoryRegistry->buildService($contentRepositoryId, new ContentStreamPrunerFactory());

Expand Down
124 changes: 124 additions & 0 deletions Neos.ContentRepositoryRegistry/Classes/Command/CrCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@
use Neos\ContentRepository\Core\Service\ContentStreamPrunerFactory;
use Neos\ContentRepository\Core\Service\WorkspaceMaintenanceServiceFactory;
use Neos\ContentRepository\Core\SharedModel\ContentRepository\ContentRepositoryId;
use Neos\ContentRepository\Core\SharedModel\Exception\RootNodeAggregateDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Exception\WorkspaceDoesNotExist;
use Neos\ContentRepository\Core\SharedModel\Workspace\WorkspaceName;
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\ContentRepositoryRegistry\Exception\InvalidConfigurationException;
use Neos\ContentRepositoryRegistry\Service\ProjectionReplayServiceFactory;
use Neos\EventStore\Model\Event\SequenceNumber;
use Neos\EventStore\Model\EventStore\StatusType;
use Neos\Flow\Cli\CommandController;
use Neos\Neos\Domain\Service\NodeTypeNameFactory;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutput;
use Neos\Flow\Persistence\Doctrine\Exception\DatabaseException;
use Neos\Neos\Domain\Model\Site;
use Neos\Neos\Domain\Repository\SiteRepository;
use Symfony\Component\Console\Output\Output;

final class CrCommandController extends CommandController
Expand All @@ -23,6 +31,7 @@ final class CrCommandController extends CommandController
public function __construct(
private readonly ContentRepositoryRegistry $contentRepositoryRegistry,
private readonly ProjectionReplayServiceFactory $projectionServiceFactory,
private readonly SiteRepository $siteRepository,
) {
parent::__construct();
}
Expand Down Expand Up @@ -244,4 +253,119 @@ public function pruneCommand(string $contentRepository = 'default', bool $force

$this->outputLine('<success>Done.</success>');
}

/**
* @param bool $verbose shows additional internal output regarding content-streams and nodes in the projection
*/
public function listCommand(bool $verbose = false): void
{
/** @var list<Site> $neosSiteEntities */
$neosSiteEntities = [];
try {
$neosSiteEntities = iterator_to_array($this->siteRepository->findAll());
} catch (DatabaseException) {
// doctrine might have not been migrated yet or no database is connected.
$this->outputLine('<comment>Site repository is not accessible.</comment>');
}

$rows = [];
foreach ($this->contentRepositoryRegistry->getContentRepositoryIds() as $contentRepositoryId) {
$contentRepository = null;
try {
$contentRepository = $this->contentRepositoryRegistry->get($contentRepositoryId);
} catch (InvalidConfigurationException $exception) {
$this->outputLine('<comment>Content repository %s is not well configures: %s.</comment>', [$contentRepositoryId->value, $exception->getMessage()]);
}


$liveContentGraph = null;
try {
$liveContentGraph = $contentRepository?->getContentGraph(WorkspaceName::forLive());
} catch (WorkspaceDoesNotExist) {
$this->outputLine('<comment>Live workspace in content repository %s not existing.</comment>', [$contentRepositoryId->value]);
}

$currenSiteNodes = [];
// todo wrap in catch runtime exception
if ($liveContentGraph && $verbose) {
$sitesAggregate = null;
try {
$sitesAggregate = $liveContentGraph->findRootNodeAggregateByType(NodeTypeNameFactory::forSites());
} catch (RootNodeAggregateDoesNotExist) {
$this->outputLine('<comment>Sites root node does not exist in content repository %s.</comment>', [$contentRepositoryId->value]);
}

if ($sitesAggregate) {
$siteNodeAggregates = $liveContentGraph->findChildNodeAggregates($sitesAggregate->nodeAggregateId);
foreach ($siteNodeAggregates as $siteNodeAggregate) {
assert($siteNodeAggregate->nodeName !== null);
$currenSiteNodes[] = $siteNodeAggregate->nodeName->value;
}
}
}

$currentNeosSiteEntities = [];
foreach ($neosSiteEntities as $site) {
if (!$site->getConfiguration()->contentRepositoryId->equals($contentRepositoryId)) {
continue;
}
$currentNeosSiteEntities[] = $site->getNodeName()->value;
}

if ($verbose) {
$connectedWorkingSites = array_intersect($currentNeosSiteEntities, $currenSiteNodes);
$siteNodesWithoutMatchingNeosSiteEntity = array_diff($currenSiteNodes, $currentNeosSiteEntities);
$neosSiteEntitiesWithoutMatchingSiteNode = array_diff($currentNeosSiteEntities, $currenSiteNodes);
$sitesString = ltrim(
(join(', ', $connectedWorkingSites)
. ($siteNodesWithoutMatchingNeosSiteEntity ? (', (only node: ' . join(', ', $siteNodesWithoutMatchingNeosSiteEntity) . ')') : '')
. ($neosSiteEntitiesWithoutMatchingSiteNode ? (', (only neos site: ' . join(', ', $neosSiteEntitiesWithoutMatchingSiteNode) . ')') : '')
), ' ,') ?: '-';
} else {
$sitesString = join(', ', $currentNeosSiteEntities) ?: '-';
}

$statusString = '-';
$workspacesString = '-';
$contentStreamsString = '-';
$nodesString = '-';

if ($contentRepository) {
$statusString = $contentRepository->status()->isOk() ? 'okay' : 'not okay';

try {
$workspacesString = sprintf('%d found', count($contentRepository->getWorkspaceFinder()->findAll()));
} catch (\RuntimeException $e) {
$this->outputLine('<comment>WorkspaceFinder of %s not functional: %s.</comment>', [$contentRepositoryId->value, $e->getMessage()]);
}

if ($verbose) {
try {
/** @phpstan-ignore neos.internal */
$contentStreamsString = sprintf('%d found', iterator_count($contentRepository->getContentStreamFinder()->findAllIds()));
} catch (\RuntimeException $e) {
$this->outputLine('<comment>ContentStreamFinder of %s not functional: %s.</comment>', [$contentRepositoryId->value, $e->getMessage()]);
}

try {
if ($liveContentGraph) {
$nodesString = sprintf('%d found', $liveContentGraph->countNodes());
}
} catch (\RuntimeException $e) {
$this->outputLine('<comment>ContentGraph of %s not functional: %s.</comment>', [$contentRepositoryId->value, $e->getMessage()]);
}
}
}

$rows[] = [
$contentRepositoryId->value,
$statusString,
$sitesString,
$workspacesString,
...($verbose ? [$contentStreamsString, $nodesString] : [])
];
}

$this->output->outputTable($rows, ['Identifier', 'Status', 'Sites', 'Workspaces', ...($verbose ? ['Contentstreams', 'Nodes'] : [])]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ public function get(ContentRepositoryId $contentRepositoryId): ContentRepository
return $this->getFactory($contentRepositoryId)->getOrBuild();
}

/**
* @return iterable<ContentRepositoryId>
*/
public function getContentRepositoryIds(): iterable
{
/** @phpstan-ignore argument.type */
return array_map(ContentRepositoryId::fromString(...), array_keys($this->settings['contentRepositories'] ?? []));
}

/**
* @internal for test cases only
*/
Expand Down
2 changes: 2 additions & 0 deletions Neos.Neos/Classes/Command/SiteCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ public function listCommand()
}
}

// todo use outputTable

$this->outputLine();
$this->outputLine(' ' . str_pad('Name', $longestSiteName + 15)
. str_pad('Node name', $longestNodeName + 15)
Expand Down
Loading