Skip to content

Commit 00389d4

Browse files
Merge pull request #35 from move-elevator/fix/feature-instance-deploy-path
fix: keep feature deployments off the base instance
2 parents 2a6f4f3 + 38d1801 commit 00389d4

9 files changed

Lines changed: 71 additions & 20 deletions

File tree

deployer/dev/task/sync.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
if (host('stage')->get('labels')['type'] === 'feature-branch-deployment') {
1212
on(host('stage'), function () {
1313
$currentBranch = runLocally('git branch --show-current');
14-
$target = !is_null(input()->getOption('feature')) ? input()->getOption('feature') : askChoice('Please select a sync origin', array_merge(
14+
$target = featureRequested() ? input()->getOption('feature') : askChoice('Please select a sync origin', array_merge(
1515
["[current] ($currentBranch)", "[prod]"],
1616
array_map(function ($array) {
1717
return $array[2];

deployer/feature/task/deploy.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,14 @@
66
* Default extensions for deploy tasks
77
*/
88

9-
before('rollback', 'feature:init');
9+
/*
10+
* deploy:info resolves {{release_name}} and deployer caches it for the rest of the
11+
* run. Without this hook the counter is read from the base path and the cached name
12+
* then collides in deploy:release, so the feature instance has to exist by now.
13+
*/
14+
before('deploy:info', 'feature:init');
15+
16+
before('rollback', 'feature:select');
1017
before('deploy:unlock', 'feature:init');
1118
before('feature:sync', 'feature:init');
1219
before('deploy:setup', 'feature:setup');
@@ -16,7 +23,6 @@
1623
before('feature:sync', 'feature:wait_for_database');
1724
before('deploy:database:update', 'feature:wait_for_database');
1825
after('deploy:symlink', 'feature:urlshortener');
19-
before('feature:sync', 'feature:init');
20-
before('debug:db', 'feature:init');
21-
before('debug:ssh', 'feature:init');
22-
before('debug:log:app', 'feature:init');
26+
before('debug:db', 'feature:select');
27+
before('debug:ssh', 'feature:select');
28+
before('debug:log:app', 'feature:select');

deployer/feature/task/feature_init.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,26 @@
88

99
task('feature:init', function () {
1010
checkVerbosity();
11+
if (!featureRequested()) {
12+
debug('No feature given, staying on the base instance');
13+
return;
14+
}
1115
// extend deploy path / public url
1216
initFeature();
1317
})
1418
->select('type=feature-branch-deployment')
1519
->once()
1620
->desc('Initialize a feature branch');
1721

22+
task('feature:select', function () {
23+
checkVerbosity();
24+
// extend deploy path / public url, asking for the feature if none was given
25+
initFeature();
26+
})
27+
->select('type=feature-branch-deployment')
28+
->once()
29+
->desc('Select a feature branch and initialize it');
30+
1831

1932
/**
2033
* Initialize a feature branch
@@ -36,9 +49,12 @@ function initFeature(?string $feature = null): ?string
3649

3750
prepareDeployerConfiguration();
3851
// use feature variable or feature input option or ask for feature branch
39-
$feature = $feature ?: (!is_null(input()->getOption('feature')) ? input()->getOption('feature') : askChoice('Please select a feature branch', array_map(function ($array) {
40-
return $array[2];
41-
}, listFeatureInstances())));
52+
// (?: would discard a caller-provided "0", which is a valid instance name)
53+
if (null === $feature || '' === trim($feature)) {
54+
$feature = featureRequested() ? input()->getOption('feature') : askChoice('Please select a feature branch', array_map(function ($array) {
55+
return $array[2];
56+
}, listFeatureInstances()));
57+
}
4258
set('feature', $feature);
4359

4460
if (isUrlShortener()) {

deployer/feature/task/feature_notify.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
task('feature:notify', function () {
99

10-
if ((has('feature_setup') && !get('feature_setup')) || !input()->getOption('feature')) return;
10+
if ((has('feature_setup') && !get('feature_setup')) || !featureRequested()) return;
1111
checkVerbosity();
1212

1313
set('public_url', get('public_urls')[0]);

deployer/feature/task/feature_setup.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
require_once('url_shortener.php');
99

1010
task('feature:setup', function () {
11-
if (!input()->hasOption('feature')) {
11+
// Without a feature we are deploying the base instance and must not touch any
12+
// feature scaffolding — renderRemoteTemplates() would overwrite its .env.
13+
if (!featureRequested()) {
1214
return;
1315
}
1416
checkVerbosity();

deployer/feature/task/feature_sync.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function resolveDatabaseHostToIp(string $hostname): void
102102

103103

104104
task('feature:wait_for_database', function () {
105-
if ((has('feature_setup') && !get('feature_setup')) || !input()->getOption('feature')) return;
105+
if ((has('feature_setup') && !get('feature_setup')) || !featureRequested()) return;
106106
waitForDatabaseHost();
107107
})
108108
->select('type=feature-branch-deployment')
@@ -113,7 +113,7 @@ function resolveDatabaseHostToIp(string $hostname): void
113113

114114
task('feature:sync', function () {
115115

116-
if ((has('feature_setup') && !get('feature_setup')) || !input()->getOption('feature')) return;
116+
if ((has('feature_setup') && !get('feature_setup')) || !featureRequested()) return;
117117

118118
$feature = initFeature();
119119
$synced = false;

deployer/feature/task/url_shortener.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
namespace Deployer;
44

5-
tasK('feature:urlshortener', function () {
5+
task('feature:urlshortener', function () {
66

7-
if (!input()->getOption('feature')) {
7+
if (!featureRequested()) {
88
return;
99
}
1010
if (!isUrlShortener()) {

deployer/functions.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,27 @@ function checkVerbosity(): void
3434
}
3535
}
3636

37+
/**
38+
* Whether a feature instance was addressed via --feature.
39+
*
40+
* A blank --feature= counts as absent: it would otherwise resolve to the base
41+
* instance path and let the feature scaffolding write into the reference stage.
42+
* Only null and blank strings qualify — empty() would also swallow the perfectly
43+
* valid feature name "0". The option itself only exists once the feature recipe
44+
* is loaded, hence hasOption().
45+
*
46+
* @return bool
47+
*/
48+
function featureRequested(): bool
49+
{
50+
if (!input()->hasOption('feature')) {
51+
return false;
52+
}
53+
$feature = input()->getOption('feature');
54+
55+
return null !== $feature && '' !== trim((string)$feature);
56+
}
57+
3758
/**
3859
* Extend the deployer configuration with available environment variables (starting with "DEPLOYER_CONFIG_"):
3960
*

docs/FEATURE.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,9 @@ The `feature:setup` command represent the initialization of a new feature branch
5555
$ vendor/bin/dep feature:setup stage --feature=TEST-01
5656
```
5757

58-
This task should be declared to run at first within your deploy task:
58+
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.
5959

60-
```php
61-
before('deploy:info', 'feature:setup');
62-
```
60+
> 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.
6361
6462
If the application needs to setup additional configuration files for e.g. storing the database credentials, use the feature templates to provide this kind of dynamic setup. For example the TYPO3 setup with a `.env` file:
6563

@@ -86,8 +84,16 @@ You can extend these list be providing more environment variables starting with
8684
> Hint: If you're using other deployer commands within the feature branch deployment context, you should use the `feature:init` task to extend the host definition with the necessary feature instance configuration:
8785
>
8886
> ```php
89-
> before('deploy:rollback', 'feature:init');
87+
> before('my:task', 'feature:init');
9088
> ```
89+
>
90+
> `feature:init` is a no-op without `--feature`, so the command keeps operating on the base instance. Use `feature:select` instead if the command should offer an interactive choice between the existing feature instances when `--feature` is omitted:
91+
>
92+
> ```php
93+
> before('my:task', 'feature:select');
94+
> ```
95+
>
96+
> `rollback` and the `debug:*` tasks are already wired to `feature:select` by the recipe.
9197
9298
### Deletion
9399

0 commit comments

Comments
 (0)