deploy-remote-script.test.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import assert from 'node:assert/strict';
  2. import {
  3. chmodSync,
  4. mkdirSync,
  5. mkdtempSync,
  6. readFileSync,
  7. rmSync,
  8. writeFileSync
  9. } from 'node:fs';
  10. import os from 'node:os';
  11. import path from 'node:path';
  12. import { spawnSync } from 'node:child_process';
  13. import { test } from 'node:test';
  14. import { fileURLToPath } from 'node:url';
  15. const scriptPath = fileURLToPath(new URL('../scripts/deploy-remote.sh', import.meta.url));
  16. const canRun = process.platform !== 'win32';
  17. test('waits for app and postfix health before and after certificate synchronization', { skip: !canRun }, (t) => {
  18. const fixture = createFixture(t);
  19. const result = runDeploy(fixture);
  20. assert.equal(result.status, 0, result.stderr);
  21. const events = readEvents(fixture.logFile);
  22. assert.deepEqual(
  23. events.filter((event) => event.startsWith('health:') || event.startsWith('sync:')),
  24. [
  25. 'health:app-container',
  26. 'health:postfix-container',
  27. 'sync:1',
  28. 'health:app-container',
  29. 'health:postfix-container'
  30. ]
  31. );
  32. });
  33. test('reports the previous revision when deployment fails', { skip: !canRun }, (t) => {
  34. const fixture = createFixture(t);
  35. const result = runDeploy(fixture, { syncStatus: '19' });
  36. assert.equal(result.status, 19);
  37. assert.match(
  38. result.stderr,
  39. /Deployment failed\. Previous revision was previous-revision; inspect the running containers before rollback\./
  40. );
  41. const events = readEvents(fixture.logFile);
  42. assert.equal(events.filter((event) => event === 'sync:1').length, 1);
  43. assert.equal(events.filter((event) => event === 'health:app-container').length, 1);
  44. assert.equal(events.filter((event) => event === 'health:postfix-container').length, 1);
  45. });
  46. function createFixture(t) {
  47. const root = mkdtempSync(path.join(os.tmpdir(), 'mailhub-deploy-script-'));
  48. const fakeBin = path.join(root, 'bin');
  49. const remoteDir = path.join(root, 'remote');
  50. const remoteScriptsDir = path.join(remoteDir, 'scripts');
  51. const logFile = path.join(root, 'events.log');
  52. mkdirSync(fakeBin, { recursive: true });
  53. mkdirSync(remoteScriptsDir, { recursive: true });
  54. writeFileSync(logFile, '');
  55. writeExecutable(path.join(fakeBin, 'git'), `#!/bin/sh
  56. if [ "$1" = "remote" ] && [ "$2" = "get-url" ]; then
  57. printf '%s\\n' 'ssh://git.example.test/mailhub.git'
  58. elif [ "$1" = "status" ]; then
  59. :
  60. elif [ "$1" = "rev-parse" ] && [ "$2" = "HEAD" ]; then
  61. if [ "$PWD" = "$MAILHUB_DEPLOY_TEST_REMOTE_DIR" ]; then
  62. printf '%s\\n' 'previous-revision'
  63. else
  64. printf '%s\\n' 'pushed-revision'
  65. fi
  66. elif [ "$1" = "rev-parse" ]; then
  67. printf '%s\\n' 'pushed-revision'
  68. fi
  69. `);
  70. writeExecutable(path.join(fakeBin, 'ssh'), `#!/bin/sh
  71. while [ "$#" -gt 0 ]; do
  72. case "$1" in
  73. -o) shift 2 ;;
  74. *) break ;;
  75. esac
  76. done
  77. [ "$#" -gt 0 ] && shift
  78. [ "$#" -gt 0 ] && shift
  79. [ "\${1:-}" = "--" ] && shift
  80. exec bash -s -- "$@"
  81. `);
  82. writeExecutable(path.join(fakeBin, 'docker'), `#!/bin/sh
  83. if [ "$1" = "compose" ] && [ "$2" = "ps" ] && [ "\${3:-}" = "--all" ]; then
  84. printf '%s-container\\n' "$5"
  85. exit 0
  86. fi
  87. if [ "$1" = "inspect" ]; then
  88. container=''
  89. for argument in "$@"; do
  90. container="$argument"
  91. done
  92. printf 'health:%s\\n' "$container" >> "$MAILHUB_DEPLOY_TEST_LOG"
  93. printf '%s\\n' 'running healthy'
  94. fi
  95. `);
  96. writeExecutable(path.join(remoteScriptsDir, 'sync-tls-certificate.sh'), `#!/bin/sh
  97. printf 'sync:%s\\n' "\${MAILHUB_CERT_RESTART:-}" >> "$MAILHUB_DEPLOY_TEST_LOG"
  98. exit "\${MAILHUB_DEPLOY_TEST_SYNC_STATUS:-0}"
  99. `);
  100. t.after(() => rmSync(root, { recursive: true, force: true }));
  101. return { root, fakeBin, remoteDir, logFile };
  102. }
  103. function runDeploy(fixture, { syncStatus = '0' } = {}) {
  104. return spawnSync('bash', [scriptPath], {
  105. cwd: fixture.root,
  106. encoding: 'utf8',
  107. env: {
  108. ...process.env,
  109. PATH: `${fixture.fakeBin}${path.delimiter}${process.env.PATH ?? ''}`,
  110. MAILHUB_DEPLOY_REMOTE: 'deploy@example.test',
  111. MAILHUB_DEPLOY_DIR: fixture.remoteDir,
  112. MAILHUB_DEPLOY_BRANCH: 'main',
  113. MAILHUB_DEPLOY_GIT_URL: 'ssh://git.example.test/mailhub.git',
  114. MAILHUB_DEPLOY_TEST_LOG: fixture.logFile,
  115. MAILHUB_DEPLOY_TEST_REMOTE_DIR: fixture.remoteDir,
  116. MAILHUB_DEPLOY_TEST_SYNC_STATUS: syncStatus
  117. }
  118. });
  119. }
  120. function writeExecutable(filePath, contents) {
  121. writeFileSync(filePath, contents);
  122. chmodSync(filePath, 0o755);
  123. }
  124. function readEvents(logFile) {
  125. return readFileSync(logFile, 'utf8').trim().split('\n').filter(Boolean);
  126. }