Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion deployer/dev/task/sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
if ($target !== 'prod' && $target !== 'stage') {
$dbSyncToolSync = get('dev_db_sync_tool_default_sync');

$dbSyncToolOriginPath = str_replace('<feature>', $target, get('dev_db_sync_tool_origin_path'));
// the remote path carries the instance name, not the raw branch name
$dbSyncToolOriginPath = str_replace('<feature>', getFeatureName($target), get('dev_db_sync_tool_origin_path'));
$additionalOptions = "--origin-path $dbSyncToolOriginPath";
} else {
$dbSyncToolSync = $target === 'prod' ? get('dev_db_sync_tool_prod_sync') : get('dev_db_sync_tool_default_sync');
Expand Down
2 changes: 2 additions & 0 deletions deployer/feature/task/feature_init.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ function initFeature(?string $feature = null): ?string
return $array[2];
}, listFeatureInstances()));
}
// branch names may contain path separators ("feature/ABC-12"), the instance name must stay flat
$feature = getFeatureName($feature);
set('feature', $feature);

if (isUrlShortener()) {
Expand Down
13 changes: 2 additions & 11 deletions deployer/feature/task/feature_setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ function renderRemoteTemplates(): void
{
debug('Rendering remote template');
$databaseName = DbUtility::getDatabaseManager()->getDatabaseName();
$feature = input()->getOption('feature');
// the normalized name, matching the instance directory and url segment
$feature = get('feature');
$templates = get('feature_templates');

if (!$templates) {
Expand Down Expand Up @@ -144,13 +145,3 @@ function uploadTemplate($localTemplate, $remoteTarget, $arguments): void {
upload($temporaryFileName,get('deploy_path') . $remoteTarget);
unlink($temporaryFileName);
}

/**
* @param ?string $feature
* @return array|string|string[]|null
*/
function getFeatureName(?string $feature = null) {
$feature = $feature ?: input()->getOption('feature');

return preg_replace('/[^A-Za-z0-9\_\-.]/', '', $feature);
}
2 changes: 1 addition & 1 deletion deployer/feature/task/feature_stop.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
*/
function deleteFeature(?string $feature = null, $needConfirmation = false): void
{
$feature = $feature ?: input()->getOption('feature');
$feature = getFeatureName($feature);

$filesRemoveCommand = "rm -rf " . get('deploy_path');

Expand Down
21 changes: 21 additions & 0 deletions deployer/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Deployer;

use Deployer\Exception\RunException;
use MoveElevator\DeployerTools\Utility\FeatureUtility;
use Symfony\Component\Console\Output\OutputInterface;

/**
Expand Down Expand Up @@ -55,6 +56,26 @@ function featureRequested(): bool
return null !== $feature && '' !== trim((string)$feature);
}

/**
* Normalize a feature identifier into the flat instance name used for the deploy
* directory, the public url segment, the url shortener symlink and the database name.
*
* Branch names may carry path separators ("feature/ABC-12"), which would otherwise
* nest the instance directory and break listing, cleanup and deletion. Identifiers
* that already consist of allowed characters only are returned unchanged.
*
* @param ?string $feature
* @return string
*/
function getFeatureName(?string $feature = null): string
{
if (null === $feature || '' === trim($feature)) {
$feature = featureRequested() ? (string)input()->getOption('feature') : '';
}

return FeatureUtility::normalize($feature);
}

/**
* Extend the deployer configuration with available environment variables (starting with "DEPLOYER_CONFIG_"):
*
Expand Down
13 changes: 12 additions & 1 deletion docs/FEATURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ The `feature:setup` command represent the initialization of a new feature branch
$ vendor/bin/dep feature:setup stage --feature=TEST-01
```

The `--feature=` value may be a full branch name. Path separators are replaced by a hyphen so the instance stays a single flat directory, url segment and database suffix:

| `--feature=` | instance name |
|----------------------|----------------------|
| `TEST-01` | `TEST-01` |
| `feature/TEST-01` | `feature-TEST-01` |
| `bugfix/TEST-01` | `bugfix-TEST-01` |
| `release/1.2.0` | `release-1.2.0` |

Names that already consist of letters, digits, `_`, `-` and `.` are used unchanged, so existing instances and their databases stay reachable. Since the branch prefix is kept, `feature/TEST-01` and `bugfix/TEST-01` remain two separate instances. The same normalization is applied by `feature:cleanup` when it compares remote git branches with the deployed instances.

The recipe already wires this task into the deploy flow, together with a `feature:init` before `deploy:info`. That ordering matters: `deploy:info` resolves `{{release_name}}` and deployer caches the result for the rest of the run, so the feature instance has to be known before it runs. Do not hook `feature:setup` any earlier yourself.

> Upgrading: if your `deploy.php` carries a `before('deploy:info', 'feature:init')` (or an equivalent `feature:setup` hook) as a workaround for that ordering, remove it — the recipe registers it now and the hook would otherwise run twice.
Expand All @@ -75,7 +86,7 @@ This configuration defines the local template file as well as the remote target
| `DEPLOYER_CONFIG_DATABASE_PORT` | default is `3306`, overwrite with deployer `set('database_port', '3306');` |
| `DEPLOYER_CONFIG_DATABASE_USER` | should be defined with `database_user` in the host configuration |
| `DEPLOYER_CONFIG_DATABASE_NAME` | will be dynamically generated |
| `DEPLOYER_CONFIG_FEATURE_NAME` | will be provide with the `--feature=` command line argument |
| `DEPLOYER_CONFIG_FEATURE_NAME` | the normalized instance name derived from the `--feature=` command line argument |
| `DEPLOYER_CONFIG_FEATURE_URL` | will be dynamically generated |
| `DEPLOYER_CONFIG_FEATURE_PATH` | will be dynamically generated |

Expand Down
11 changes: 7 additions & 4 deletions src/Database/Manager/AbstractManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MoveElevator\DeployerTools\Database\Manager;

use MoveElevator\DeployerTools\Database\Exception\DatabaseException;
use MoveElevator\DeployerTools\Utility\FeatureUtility;
use MoveElevator\DeployerTools\Utility\VarUtility;

use function Deployer\get;
Expand Down Expand Up @@ -41,16 +42,18 @@ public function run(string $command, bool $useDoubleQuotes = true): string
*/
public function getDatabaseName(?string $feature = null): string
{
$feature = $feature ?: input()->getOption('feature');
$project = get('project');
return substr($this->getFeatureName("{$project}--{$feature}"), 0, 63);
// both parts are normalized separately, so the "--" separator survives
$project = FeatureUtility::normalize((string) get('project'));
$feature = $this->getFeatureName($feature);

return substr($project . '--' . $feature, 0, 63);
}


public function getFeatureName(?string $feature = null): string
{
$feature = $feature ?: input()->getOption('feature');

return preg_replace('/[^A-Za-z0-9\_\-.]/', '', (string) $feature);
return FeatureUtility::normalize((string) $feature);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
}
8 changes: 3 additions & 5 deletions src/Database/Manager/Simple.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use function Deployer\set;
use function Deployer\has;
use function Deployer\run;
use function Deployer\input;
use function Deployer\upload;
use function Deployer\runExtended;
use function Deployer\test;
Expand Down Expand Up @@ -50,7 +49,7 @@ public function delete(string $feature): void
debug('Deleting database');
$this->ensureDatabasePoolExists();
$this->initDatabaseConfiguration(feature: $feature);
$this->assignmentManager->removeAssignment($feature);
$this->assignmentManager->removeAssignment($this->getFeatureName($feature));
$this->run($this->generateDropTablesQuery($this->getDatabaseName($feature)));
}

Expand Down Expand Up @@ -78,8 +77,7 @@ public function exists(?string $feature = null): bool

public function getDatabaseName(?string $feature = null): string
{
$feature = $feature ?: input()->getOption('feature');
$databaseAssignment = $this->assignmentManager->getAssignment($feature);
$databaseAssignment = $this->assignmentManager->getAssignment($this->getFeatureName($feature));

if (!$databaseAssignment) {
return '';
Expand Down Expand Up @@ -115,7 +113,7 @@ private function initDatabaseConfiguration(?string $database = null, ?string $fe
{
$pool = get('database_pool');
if (!$database) {
$database = $this->assignmentManager->getAssignment($feature ?: $this->getFeatureName());
$database = $this->assignmentManager->getAssignment($this->getFeatureName($feature));
}

if (!isset($pool[$database])) {
Expand Down
44 changes: 44 additions & 0 deletions src/Utility/FeatureUtility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

declare(strict_types=1);

namespace MoveElevator\DeployerTools\Utility;

/**
* Normalizes feature identifiers into a name that is safe to use as a directory,
* URL segment, symlink and database name suffix.
*/
final class FeatureUtility
{
/**
* Turn a feature identifier (typically a git branch name) into a flat instance name.
*
* Path separators become hyphens instead of being dropped, so "feature/ABC-12" and
* "bugfix/ABC-12" stay distinct instances. Names that already consist of allowed
* characters only are returned unchanged, which keeps existing instances and their
* databases reachable.
*
* @throws \InvalidArgumentException if a non-blank identifier normalizes to an empty
* name, which would silently address the base instance
*/
public static function normalize(?string $feature): string
{
$feature = trim((string) $feature);

if ('' === $feature) {
return '';
}

$normalized = str_replace(['/', '\\'], '-', $feature);
$normalized = (string) preg_replace('/[^A-Za-z0-9_\-.]/', '', $normalized);
$normalized = trim($normalized, '-.');
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

if ('' === $normalized) {
throw new \InvalidArgumentException(
sprintf('The feature name "%s" contains no usable characters.', $feature)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
);
}

return $normalized;
}
}