openvibers/openvibe.host Commits
deploy --install-units on a service already at its target installs unit files that differ, and restarts nothing (#146)
Changes
@@@ -233,6 +233,7 @@ Counts only, never a row about a person. A database that does not answer prints1. Take the per-service lock. A `release-layout` service goes to its own engine ([docs/deploy-strategies.md](docs/deploy-strategies.md#release-layout-release-layout)); an in-place strategy refuses a checkout that has a `releases/current` layout.2. `git fetch` as the owner, then work out the from/to shas, changed files, per-package install need and whether a restart is needed (`noRestartPaths`).++ Already at the target: nothing happens, except that `--install-units` still installs unit files that differ from the checkout (result `units-installed`, nothing restarted; in-place strategies).3. Refuse a tracked local change or a wrong branch.4. **Protected sessions** (only if a restart is needed): refuse, wait (`--wait-idle` or drain policy `wait`), report (drain policy `report`, Events SSE), or `--force`.5. `git merge --ff-only` as the owner (rollback uses `git reset --hard <sha>`).@@@ -470,6 +470,14 @@ async function apply(ctx, svc, mode, opts = {}) {if (plan.branch !== svc.branch) throw new OpError(`${svc.repo} is on "${plan.branch}", expected "${svc.branch}"`, EXIT.USAGE);if (plan.dirty.length) throw new OpError(`${svc.repo} has tracked local changes (${plan.dirty.slice(0, 5).join(', ')}${plan.dirty.length > 5 ? ', …' : ''}); commit or discard them first`, EXIT.VALIDATION);if (plan.upToDate && !opts.restart) {++ // --install-units still installs unit files that differ (say a drop-in the inventory gained after the++ // commit that carries it was deployed); nothing restarts for it.++ if (opts.installUnits && plan.unitDrift.length && mode === 'deploy') {++ await installUnits(ctx, svc, record);++ log(`already at ${plan.to.slice(0, 12)}; installed unit files only, nothing restarted`);++ record.result = 'units-installed';++ return { exitCode: EXIT.OK, record, plan };++ }log(`already at ${plan.to.slice(0, 12)}; nothing to do`);record.result = 'unchanged';writeRecord = false;@@@ -508,6 +508,22 @@ runTests([assert.strictEqual((await lastRecord(host, 'network')).strategy, 'git-checkout');}),++ test('--install-units on a service already at its target installs drifted unit files and restarts nothing', async () => {++ const host = networkHost();++ host.put('/etc/systemd/system/openvibe-network.service', '[Service]\nExecStart=node old.js\n');++ const r = await host.cli('deploy', 'network', '--install-units');++ assert.strictEqual(r.code, 0, r.out);++ assert.match(r.out, /installed unit files only, nothing restarted/);++ assert.strictEqual(host.read('/etc/systemd/system/openvibe-network.service'), '[Service]\nExecStart=node server/index.js\n');++ assert.deepStrictEqual(host.restarts(), []);++ const rec = await lastRecord(host, 'network');++ assert.strictEqual(rec.result, 'units-installed');++ assert.deepStrictEqual(rec.unitsInstalled, ['openvibe-network.service']);++ // Without the flag, or with nothing to install, an up-to-date deploy still does nothing.++ const again = await host.cli('deploy', 'network', '--install-units');++ assert.match(again.out, /nothing to do/);++ }),++test('unit bootstrap enables an installed service before its first restart', async () => {const host = networkHost();const raw = JSON.parse(host.read('/etc/openvibe/host.json'));
@@ -233,6 +233,7 @@ Counts only, never a row about a person. A database that does not answer prints
@@ -233,6 +233,7 @@ Counts only, never a row about a person. A database that does not answer prints
1. Take the per-service lock. A `release-layout` service goes to its own engine ([docs/deploy-strategies.md](docs/deploy-strategies.md#release-layout-release-layout)); an in-place strategy refuses a checkout that has a `releases/current` layout.
1. Take the per-service lock. A `release-layout` service goes to its own engine ([docs/deploy-strategies.md](docs/deploy-strategies.md#release-layout-release-layout)); an in-place strategy refuses a checkout that has a `releases/current` layout.
2. `git fetch` as the owner, then work out the from/to shas, changed files, per-package install need and whether a restart is needed (`noRestartPaths`).
2. `git fetch` as the owner, then work out the from/to shas, changed files, per-package install need and whether a restart is needed (`noRestartPaths`).
+ Already at the target: nothing happens, except that `--install-units` still installs unit files that differ from the checkout (result `units-installed`, nothing restarted; in-place strategies).
3. Refuse a tracked local change or a wrong branch.
3. Refuse a tracked local change or a wrong branch.
4. **Protected sessions** (only if a restart is needed): refuse, wait (`--wait-idle` or drain policy `wait`), report (drain policy `report`, Events SSE), or `--force`.
4. **Protected sessions** (only if a restart is needed): refuse, wait (`--wait-idle` or drain policy `wait`), report (drain policy `report`, Events SSE), or `--force`.
5. `git merge --ff-only` as the owner (rollback uses `git reset --hard <sha>`).
5. `git merge --ff-only` as the owner (rollback uses `git reset --hard <sha>`).
@@ -470,6 +470,14 @@ async function apply(ctx, svc, mode, opts = {}) {
@@ -470,6 +470,14 @@ async function apply(ctx, svc, mode, opts = {}) {
if (plan.branch !== svc.branch) throw new OpError(`${svc.repo} is on "${plan.branch}", expected "${svc.branch}"`, EXIT.USAGE);
if (plan.branch !== svc.branch) throw new OpError(`${svc.repo} is on "${plan.branch}", expected "${svc.branch}"`, EXIT.USAGE);
if (plan.dirty.length) throw new OpError(`${svc.repo} has tracked local changes (${plan.dirty.slice(0, 5).join(', ')}${plan.dirty.length > 5 ? ', …' : ''}); commit or discard them first`, EXIT.VALIDATION);
if (plan.dirty.length) throw new OpError(`${svc.repo} has tracked local changes (${plan.dirty.slice(0, 5).join(', ')}${plan.dirty.length > 5 ? ', …' : ''}); commit or discard them first`, EXIT.VALIDATION);
if (plan.upToDate && !opts.restart) {
if (plan.upToDate && !opts.restart) {
+ // --install-units still installs unit files that differ (say a drop-in the inventory gained after the
+ // commit that carries it was deployed); nothing restarts for it.
+ if (opts.installUnits && plan.unitDrift.length && mode === 'deploy') {
+ await installUnits(ctx, svc, record);
+ log(`already at ${plan.to.slice(0, 12)}; installed unit files only, nothing restarted`);
+ record.result = 'units-installed';
+ return { exitCode: EXIT.OK, record, plan };
+ }
log(`already at ${plan.to.slice(0, 12)}; nothing to do`);
log(`already at ${plan.to.slice(0, 12)}; nothing to do`);
record.result = 'unchanged';
record.result = 'unchanged';
writeRecord = false;
writeRecord = false;
@@ -508,6 +508,22 @@ runTests([
@@ -508,6 +508,22 @@ runTests([
assert.strictEqual((await lastRecord(host, 'network')).strategy, 'git-checkout');
assert.strictEqual((await lastRecord(host, 'network')).strategy, 'git-checkout');
}),
}),
+ test('--install-units on a service already at its target installs drifted unit files and restarts nothing', async () => {
+ const host = networkHost();
+ host.put('/etc/systemd/system/openvibe-network.service', '[Service]\nExecStart=node old.js\n');
+ const r = await host.cli('deploy', 'network', '--install-units');
+ assert.strictEqual(r.code, 0, r.out);
+ assert.match(r.out, /installed unit files only, nothing restarted/);
+ assert.strictEqual(host.read('/etc/systemd/system/openvibe-network.service'), '[Service]\nExecStart=node server/index.js\n');
+ assert.deepStrictEqual(host.restarts(), []);
+ const rec = await lastRecord(host, 'network');
+ assert.strictEqual(rec.result, 'units-installed');
+ assert.deepStrictEqual(rec.unitsInstalled, ['openvibe-network.service']);
+ // Without the flag, or with nothing to install, an up-to-date deploy still does nothing.
+ const again = await host.cli('deploy', 'network', '--install-units');
+ assert.match(again.out, /nothing to do/);
+ }),
+
test('unit bootstrap enables an installed service before its first restart', async () => {
test('unit bootstrap enables an installed service before its first restart', async () => {
const host = networkHost();
const host = networkHost();
const raw = JSON.parse(host.read('/etc/openvibe/host.json'));
const raw = JSON.parse(host.read('/etc/openvibe/host.json'));