cob-cli 2.54.0 → 2.55.0-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,7 +27,7 @@ async function updateFromServer(args) {
27
27
 
28
28
  console.log("\nOk to proceed. Getting files from server's live directories...");
29
29
  await cmdEnv.applyCurrentCommandEnvironmentChanges()
30
- let changes = await copyFiles(cmdEnv, "serverLive", "localCopy", args)
30
+ let changes = (await copyFiles(cmdEnv, "serverLive", "localCopy", args)).changes
31
31
  await cmdEnv.unApplyCurrentCommandEnvironmentChanges()
32
32
 
33
33
  if (changes.length == 0) {
@@ -91,7 +91,7 @@ async function updateFromServer(args) {
91
91
  const dataPath = `others/solutionsData/${solution.name}`
92
92
  fs.mkdir(dataPath, { recursive: true })
93
93
 
94
- if (solution.permissions) {
94
+ if (solution.groups) {
95
95
  await getGroupsFromServer(
96
96
  cmdEnv.servername,
97
97
  solution.groups,
@@ -49,7 +49,7 @@ async function registerRelease(cmdEnv) {
49
49
  // Add deploy tags to repo
50
50
  await git().fetch(["--tags","-f"]) // Apenas para ter certeza que temos todas as tags
51
51
  try {
52
- await git().silent(true).tag([deploySignature, "-d"])
52
+ await git().tag([deploySignature, "-d"])
53
53
  } catch {}
54
54
 
55
55
  await git().addAnnotatedTag(deploySignature, newDeployText )
@@ -86,3 +86,11 @@ function getCurrentBranch() {
86
86
  return git().revparse(["--abbrev-ref", "HEAD"]);
87
87
  }
88
88
  exports.getCurrentBranch = getCurrentBranch;
89
+
90
+ /* ************************************ */
91
+ async function appendToDeployLog(server, output) {
92
+ await execa('ssh', [...getSshArgs(server),
93
+ `{ echo $'${output.replace(/'/g, "\\'")}' | sed "s/^/\\t /"; } >> ${SERVER_CHANGELOG}`
94
+ ]);
95
+ }
96
+ exports.appendToDeployLog = appendToDeployLog;
@@ -2,7 +2,7 @@ const execa = require('execa');
2
2
  const path = require('path');
3
3
  const fs = require('fs-extra');
4
4
  const { getKeypress } = require("../task_lists/common_helpers");
5
- const { getRsyncSsh, getServerAddress } = require("./common_helpers");
5
+ const { getRsyncSsh, getServerAddress, getSshArgs } = require("./common_helpers");
6
6
 
7
7
  /* ************************************ */
8
8
  const COB_PRODUCTS = ["recordm", "integrationm", "recordm-importer", "reportm", "userm", "confm", "others", "authm", "public"];
@@ -19,13 +19,14 @@ exports.copyFiles = copyFiles;
19
19
 
20
20
  /* ************************************ */
21
21
  async function testEquality(cmdEnv, from, to, args) {
22
- return (await _syncFiles(_syncFiles.TEST, cmdEnv, from, to, [], args)).filter( f => !f.endsWith("/"))
22
+ return (await _syncFiles(_syncFiles.TEST, cmdEnv, from, to, [], args)).changes.filter( f => !f.endsWith("/"))
23
23
  }
24
24
  exports.testEquality = testEquality;
25
25
 
26
26
  /* ************************************ */
27
27
  async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], args = []) {
28
28
  let changes = [];
29
+ let encFiles = [];
29
30
  let products = cmdEnv.products.length ? cmdEnv.products : COB_PRODUCTS;
30
31
  // we need to recheck, because it can exist in head but not in the commit we're testing
31
32
  let hasRsyncFilter = cmdEnv.rsyncFilter && fs.lstatSync(cmdEnv.rsyncFilter, {throwIfNoEntry: false})?.isFile();
@@ -46,6 +47,7 @@ async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], ar
46
47
  while (count < RSYNC_RETRIES) {
47
48
  if (args.verbose > 2)
48
49
  console.log("rsync".bold +" '" + (product).blue + "': attempt " + (count + 1));
50
+ const protectFilters = _getDecryptedEncFilePaths(product).map(p => `--filter='P ${p}'`);
49
51
  const rsyncArgs = [
50
52
  fromPath,
51
53
  toPath,
@@ -58,6 +60,7 @@ async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], ar
58
60
  "--filter='merge " + path.resolve(__dirname,"rsyncFilter-pre.txt") + "'",
59
61
  hasRsyncFilter ? "--filter='merge " + cmdEnv.rsyncFilter + "'" : "",
60
62
  "--filter='merge " + path.resolve(__dirname,"rsyncFilter-post.txt") + "'",
63
+ ...protectFilters,
61
64
  executionType == _syncFiles.COPY ? "-v" : "--dry-run"
62
65
  ].concat(productExtraOptions)
63
66
  if (args.verbose > 3) {
@@ -71,12 +74,17 @@ async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], ar
71
74
  if (args.verbose > 3)
72
75
  console.log("rsync".bold +" '" + (product).blue + "': " + "success".green + " " + value.stdout);
73
76
 
74
- let result = _formatRsyncOutput(product, value.stdout);
77
+ let rsyncOutput = value.stdout;
78
+ if (executionType == _syncFiles.TEST || executionType == _syncFiles.DIFF)
79
+ rsyncOutput = _filterDecryptedEncFiles(product, rsyncOutput);
80
+ let result = _formatRsyncOutput(product, rsyncOutput);
75
81
  if (args.verbose > 2)
76
82
  console.log("rsync '" + product + "': success " + result);
77
83
  else if (args.verbose > 1)
78
84
  console.log("rsync '" + product + "': success ");
79
85
  changes = changes.concat(result);
86
+ if (executionType == _syncFiles.COPY)
87
+ encFiles = encFiles.concat(_extractEncFilePaths(product, value.stdout));
80
88
  count = RSYNC_RETRIES;
81
89
 
82
90
  resolve();
@@ -120,7 +128,41 @@ async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], ar
120
128
  });
121
129
  });
122
130
  await Promise.all(requests).catch(err => { throw new Error(err.message); });
123
- return changes;
131
+ return { changes, encFiles };
132
+ }
133
+
134
+ /* ************************************ */
135
+ function _filterDecryptedEncFiles(product, rsyncOutput) {
136
+ return rsyncOutput
137
+ .split("\n")
138
+ .filter(line => {
139
+ // ignore decrypted files:
140
+ // * marked as deleted on local -> server
141
+ // * marked as added on server -> local
142
+ if (line.startsWith("*deleting ") || /^>f\+/.test(line)) {
143
+ const relativePath = line.split(/\s+/)[1];
144
+ if (!relativePath) return true;
145
+ return !fs.existsSync("./" + product + "/" + relativePath + ".enc");
146
+ }
147
+ return true;
148
+ })
149
+ .join("\n");
150
+ }
151
+
152
+ /* ************************************ */
153
+ function _getDecryptedEncFilePaths(product) {
154
+ const productDir = "./" + product;
155
+ if (!fs.existsSync(productDir)) return [];
156
+ const paths = [];
157
+ const walk = (dir, rel) => {
158
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
159
+ const entryRel = rel ? rel + "/" + entry.name : entry.name;
160
+ if (entry.isDirectory()) walk(path.join(dir, entry.name), entryRel);
161
+ else if (entry.name.endsWith(".enc")) paths.push("/" + entryRel.slice(0, -4));
162
+ }
163
+ };
164
+ walk(productDir, "");
165
+ return paths;
124
166
  }
125
167
 
126
168
  /* ************************************ */
@@ -147,7 +189,7 @@ function _formatRsyncOutput(product, rsyncOutput) {
147
189
  /* ************************************ */
148
190
  async function confirmExecutionOfChanges(cmdEnv, extraOptions = []) {
149
191
  console.log("\nChecking changes... ")
150
- let changes = (await _syncFiles(_syncFiles.DIFF, cmdEnv, "localCopy", "serverLive", extraOptions)).filter( f => !f.endsWith("/") )
192
+ let changes = (await _syncFiles(_syncFiles.DIFF, cmdEnv, "localCopy", "serverLive", extraOptions)).changes.filter( f => !f.endsWith("/") )
151
193
  if (changes.length) {
152
194
  console.log("\nChanges that will be done in " + cmdEnv.serverStr + ":");
153
195
  console.log(" " + changes.join("\n "));
@@ -163,6 +205,19 @@ async function confirmExecutionOfChanges(cmdEnv, extraOptions = []) {
163
205
  }
164
206
  exports.confirmExecutionOfChanges = confirmExecutionOfChanges;
165
207
 
208
+ /* ************************************ */
209
+ function _getServerLiveBasePath(product) {
210
+ switch (product) {
211
+ case "public":
212
+ return "/usr/share/cob/"
213
+ case "recordm-importer":
214
+ case "others":
215
+ return "/opt/" + product + "/";
216
+ default:
217
+ return "/etc/" + product + "/";
218
+ }
219
+ }
220
+
166
221
  /* ************************************ */
167
222
  function resolveCobPath(server, type, product) {
168
223
  switch (type) {
@@ -173,18 +228,47 @@ function resolveCobPath(server, type, product) {
173
228
  return getServerAddress(server) + ":/opt/" + product + "/etc.default/";
174
229
 
175
230
  case "serverLive":
176
- switch (product) {
177
- case "public":
178
- return getServerAddress(server) + ":/usr/share/cob/"
179
- case "recordm-importer":
180
- case "others":
181
- return getServerAddress(server) + ':' + "/opt/" + product + "/";
182
- default:
183
- return getServerAddress(server) + ':' + "/etc/" + product + "/";
184
- }
231
+ return getServerAddress(server) + ':' + _getServerLiveBasePath(product);
185
232
 
186
233
  case "staging":
187
234
  return ".staging/" + product + "/";
188
235
  }
189
236
  }
190
237
  exports.resolveCobPath = resolveCobPath;
238
+
239
+ /* ************************************ */
240
+ function _extractEncFilePaths(product, rsyncOutput) {
241
+ const basePath = _getServerLiveBasePath(product);
242
+ return rsyncOutput
243
+ .split("\n")
244
+ .slice(1, -3)
245
+ .filter(line => /^[<>]f/.test(line))
246
+ .map(line => line.split(/\s+/)[1])
247
+ .filter(filePath => filePath && filePath.endsWith(".enc"))
248
+ .map(filePath => basePath + filePath);
249
+ }
250
+
251
+ /* ************************************ */
252
+ async function decryptEncFile(server, serverFilePath) {
253
+ await execa('ssh', [...getSshArgs(server), `sudo -u cob-secrets /usr/local/bin/cob-decrypt '${serverFilePath}'`]);
254
+ }
255
+ exports.decryptEncFile = decryptEncFile;
256
+
257
+ /* ************************************ */
258
+ async function decryptEncFiles(cmdEnv, encFiles) {
259
+ for (const f of encFiles) {
260
+ await decryptEncFile(cmdEnv.server, f);
261
+ }
262
+ }
263
+ exports.decryptEncFiles = decryptEncFiles;
264
+
265
+ /* ************************************ */
266
+ async function deploySolutions(server) {
267
+ const result = await execa('ssh', [...getSshArgs(server),
268
+ '/usr/local/bin/cc-install solutions'
269
+ ], { reject: false, all: true });
270
+
271
+ return { output: result.all, exitCode: result.exitCode };
272
+ }
273
+ exports.deploySolutions = deploySolutions;
274
+
@@ -1,21 +1,30 @@
1
1
  const Listr = require('listr');
2
2
  const UpdaterRenderer = require('listr-update-renderer');
3
3
  const verboseRenderer = require('listr-verbose-renderer');
4
+ const fs = require('fs');
4
5
  const git = require('simple-git');
5
- const { registerRelease } = require("./common_releaseManager");
6
- const { copyFiles } = require("./common_syncFiles");
6
+ const { registerRelease, appendToDeployLog } = require("./common_releaseManager");
7
+ const { copyFiles, decryptEncFiles, deploySolutions } = require("./common_syncFiles");
7
8
 
8
9
 
9
10
  function executeTasks(cmdEnv, args) {
10
11
  console.log("\nDeploying ...");
12
+ let encFiles = [];
13
+ let ccInstallResult = {};
11
14
 
12
15
  return new Listr([
13
16
  {title: "Register release in DEPLOYLOG.md", task: () => registerRelease(cmdEnv)},
14
17
  {title: `git push (branch: ${cmdEnv.currentBranch})`, task: () => git().push("origin", cmdEnv.currentBranch, {"--set-upstream": null})},
15
- {title: "Apply new enviroment specifics", task: () => cmdEnv.applyCurrentCommandEnvironmentChanges() },
16
- {title: "Deploy files to server's live directories", task: () => copyFiles(cmdEnv, "localCopy", "serverLive")},
18
+ {title: "Apply new enviroment specifics", task: () => cmdEnv.applyCurrentCommandEnvironmentChanges() },
19
+ {title: "Deploy files to server's live directories", task: async () => { const result = await copyFiles(cmdEnv, "localCopy", "serverLive"); encFiles = result.encFiles; }},
20
+ {title: "Decrypt .enc files", skip: () => !encFiles.length, task: () => decryptEncFiles(cmdEnv, encFiles)},
21
+ {title: "Deploy data", skip: () => !fs.existsSync('solutions.yml'), task: async () => {
22
+ ccInstallResult = await deploySolutions(cmdEnv.server);
23
+ if (ccInstallResult.exitCode !== 0) throw new Error(`Deploying data failed:\n${ccInstallResult.output}`);
24
+ }},
25
+ {title: "Register solutions deploy output", skip: () => !fs.existsSync('solutions.yml'), task: () => appendToDeployLog(cmdEnv.server, ccInstallResult.output)},
17
26
  {title: "Set last environment deployed", task: () => cmdEnv.setLastEnvironmentDeployed()},
18
- {title: "Undo new enviroment specifics", task: () => cmdEnv.unApplyCurrentCommandEnvironmentChanges() },
27
+ {title: "Undo new enviroment specifics", task: () => cmdEnv.unApplyCurrentCommandEnvironmentChanges() },
19
28
  ], {
20
29
  renderer: args.verbose ? verboseRenderer : UpdaterRenderer,
21
30
  collapse: false
@@ -14,11 +14,11 @@ function validateDeployConditions(cmdEnv, args) {
14
14
  return new Listr([
15
15
  { title: "Check no previous interrupted run".bold, task: () => checkNoInterruptedRun() },
16
16
  { title: "Check connectivity and permissions".bold, task: () => checkConnectivity(cmdEnv.server) },
17
- { title: "Check git status".bold, task: () => checkWorkingCopyCleanliness(false) },
17
+ { title: "Check git status".bold, task: () => checkWorkingCopyCleanliness(false) },
18
18
  { title: "Check there's no 'cob-cli test' running on server".bold, task: () => checkNoTestsRunningOnServer(cmdEnv.server) },
19
- { title: "find out branch-for-HEAD", skip: () => args.force, task: (ctx) => getCurrentBranch().then( currentBranch => ctx.currentBranch = currentBranch ) },
20
- { title: "find out SHA for last-deploy on specified server", skip: () => args.force, task: (ctx) => getLastDeployedSha(cmdEnv.server).then(lastSha => _handleGetLastDeployedSha(ctx, cmdEnv, lastSha)) },
21
- { title: "git checkout SHA-for-last-deploy", skip: ctx => args.force || !ctx.lastSha, task: (ctx) => git().checkout(ctx.lastSha) },
19
+ { title: "find out branch-for-HEAD", skip: () => args.force, task: (ctx) => getCurrentBranch().then( currentBranch => ctx.currentBranch = currentBranch ) },
20
+ { title: "find out SHA for last-deploy on specified server", skip: () => args.force, task: (ctx) => getLastDeployedSha(cmdEnv.server).then(lastSha => _handleGetLastDeployedSha(ctx, cmdEnv, lastSha)) },
21
+ { title: "git checkout SHA-for-last-deploy", skip: ctx => args.force || !ctx.lastSha, task: (ctx) => git().checkout(ctx.lastSha) },
22
22
  { title: "Apply last enviroment specifics".bold, skip: ctx => args.force || !ctx.lastSha, task: () => cmdEnv.applyLastEnvironmentDeployedToServerChanges() },
23
23
  { title: "Check last-deploy == serverLive".bold, skip: ctx => args.force || !ctx.lastSha, task: (ctx) => testEquality(cmdEnv, "localCopy", "serverLive", args).then( changes => _handleTestEquality(ctx, changes)) },
24
24
  { title: "Undo last enviroment specifics".bold, skip: ctx => args.force || !ctx.lastSha, task: () => cmdEnv.unApplyLastEnvironmentDeployedToServerChanges() },
@@ -34,18 +34,18 @@ exports.validateDeployConditions = validateDeployConditions;
34
34
 
35
35
  /* ************************************ */
36
36
  function _handleGetLastDeployedSha(ctx, cmdEnv, lastSha) {
37
- if (lastSha) {
38
- ctx.lastSha = lastSha
39
- } else {
40
- ctx.err =
41
- "No previous deploy to "
42
- + cmdEnv.server.bold
43
- + " found.\n If it's a new server do "
44
- + ("cob-cli deploy -s " + cmdEnv.servername + " --force").yellow
45
- + " first, to initialize the server\n\n "
46
- + "Aborted:".bgRed + " no previous deploy detected on this server."
37
+ if (lastSha) {
38
+ ctx.lastSha = lastSha
39
+ } else {
40
+ ctx.err =
41
+ "No previous deploy to "
42
+ + cmdEnv.server.bold
43
+ + " found.\n If it's a new server do "
44
+ + ("cob-cli deploy -s " + cmdEnv.servername + " --force").yellow
45
+ + " first, to initialize the server\n\n "
46
+ + "Aborted:".bgRed + " no previous deploy detected on this server."
47
47
  + "\n"
48
- }
48
+ }
49
49
  }
50
50
 
51
51
  /* ************************************ */
@@ -1,11 +1,11 @@
1
- + /services/com.cultofbits.web.integration.properties
2
- + /services/com.cultofbits.genesis.comm.properties
3
- + /services/com.cultofbits.confm.interact.properties
4
- + /services/com.cultofbits.integrationm.service.properties
1
+ + /services/com.cultofbits.web.integration.properties*
2
+ + /services/com.cultofbits.genesis.comm.properties*
3
+ + /services/com.cultofbits.confm.interact.properties*
4
+ + /services/com.cultofbits.integrationm.service.properties*
5
5
  - /services/**
6
6
 
7
7
  - /security
8
8
  - /hornetq
9
9
  - /elasticsearch
10
10
  - /db
11
- - /reports
11
+ - /reports
@@ -36,7 +36,7 @@ async function otherFilesContiousReload(cmdEnv) {
36
36
  }
37
37
  /* ************************************ */
38
38
 
39
- fs.writeFileSync('.'+IN_PROGRESS_TEST_FILE, "in progress")
39
+ fs.writeFileSync('.'+IN_PROGRESS_TEST_FILE, "starting")
40
40
 
41
41
  changes.length && console.log("\n Making the changes...".yellow);
42
42
  for(let change of changes) {
@@ -44,6 +44,9 @@ async function otherFilesContiousReload(cmdEnv) {
44
44
  const changedFile = change.split(" ")[1];
45
45
  await addFileToCurrentTest(cmdEnv.server, changedFile, changedFiles, changeType, restoreChanges);
46
46
  }
47
+
48
+ fs.writeFileSync('.'+IN_PROGRESS_TEST_FILE, "in progress")
49
+
47
50
  watcher.process = chokidar.watch('.', {
48
51
  //ignored: /(^|[\/\\])\../, // ignore dotfiles
49
52
  persistent: true,
@@ -123,6 +126,16 @@ async function restoreServerFilesToLastDeployed(cmdEnv, changedFiles) {
123
126
 
124
127
  for (const changedFile of changedFiles) {
125
128
  await removeFileFromCurrentTest(cmdEnv.server, changedFile);
129
+ // A .enc file is decrypted onto its sibling path on the server. When the .enc did not
130
+ // exist at the last deploy (it's new to this test), that decrypted output either
131
+ // overwrote a previously deployed cleartext file (e.g. a file being "encrypted":
132
+ // cleartext removed, .enc added) or is brand new. The diff that seeds the test only
133
+ // tracks the .enc, never the overwritten/removed sibling, so roll the sibling back to
134
+ // the last deployed state explicitly: restores the cleartext if there was one, or
135
+ // removes the stray decrypted file if there wasn't.
136
+ if (changedFile.endsWith(".enc") && !fs.existsSync(changedFile)) {
137
+ await removeFileFromCurrentTest(cmdEnv.server, changedFile.slice(0, -4));
138
+ }
126
139
  }
127
140
 
128
141
  await cmdEnv.unApplyLastEnvironmentDeployedToServerChanges();
@@ -1,7 +1,7 @@
1
1
  const execa = require('execa');
2
2
  const fs = require('fs-extra');
3
3
  const path = require('path');
4
- const { resolveCobPath } = require("./common_syncFiles");
4
+ const { resolveCobPath, decryptEncFile } = require("./common_syncFiles");
5
5
  const { getSshArgs, getRsyncSsh } = require("./common_helpers");
6
6
 
7
7
  const DEBUG = false;
@@ -17,6 +17,7 @@ async function syncFile(server, localFile) {
17
17
  let remoteProductPath = resolveCobPath(server, "serverLive", product);
18
18
  let remoteFileDir = remoteProductPath.split(":")[1] + productScopeFilePath.substring(0, productScopeFilePath.lastIndexOf("/"));
19
19
 
20
+ let fileWasSynced = true;
20
21
  let count = 0;
21
22
  while (count < RSYNC_RETRIES) {
22
23
  await execa('ssh', [...getSshArgs(server), "mkdir -p " + remoteFileDir]).catch({});
@@ -34,7 +35,15 @@ async function syncFile(server, localFile) {
34
35
  .catch(async (err) => {
35
36
  // exitCode 23 indicam que o ficheiro não existe na origem. Temod de apagar no destino.
36
37
  if (err.exitCode == 23) {
37
- await execa('ssh', [...getSshArgs(server), "rm -fd " + remoteProductPath.split(":")[1] + productScopeFilePath]).catch({});
38
+ fileWasSynced = false;
39
+ await execa('ssh', [...getSshArgs(server),
40
+ "rm -fd " + remoteProductPath.split(":")[1] + productScopeFilePath
41
+ ]).catch({});
42
+ if (localFile.endsWith(".enc")) {
43
+ await execa('ssh', [ ...getSshArgs(server),
44
+ "rm -fd " + remoteProductPath.split(":")[1] + productScopeFilePath.slice(0, -4)
45
+ ]).catch({});
46
+ }
38
47
 
39
48
  let localFileDir = localFile.substring(0, localFile.lastIndexOf("/"));
40
49
  if (!fs.existsSync(localFileDir)) {
@@ -51,5 +60,9 @@ async function syncFile(server, localFile) {
51
60
  });
52
61
  count++;
53
62
  }
63
+
64
+ if (fileWasSynced && localFile.endsWith(".enc")) {
65
+ await decryptEncFile(server, remoteProductPath.split(":")[1] + productScopeFilePath);
66
+ }
54
67
  }
55
68
  exports.syncFile = syncFile;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cob-cli",
3
- "version": "2.54.0",
3
+ "version": "2.55.0-beta-2",
4
4
  "description": "A command line utility to help Cult of Bits partners develop with higher speed and reusing common code and best practices.",
5
5
  "preferGlobal": true,
6
6
  "repository": {