cob-cli 2.49.0 → 2.50.0-beta
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.
- package/bin/cob-cli.js +31 -0
- package/lib/commands/cleanup.js +106 -0
- package/lib/commands/deploy.js +9 -0
- package/lib/commands/package.js +20 -0
- package/lib/commands/tagDefs.js +67 -0
- package/lib/commands/test.js +27 -9
- package/lib/commands/updateFromServer.js +149 -13
- package/lib/task_lists/common_definitions.js +26 -0
- package/lib/task_lists/common_enviromentHandler.js +5 -5
- package/lib/task_lists/common_helpers.js +59 -13
- package/lib/task_lists/common_operationState.js +28 -0
- package/lib/task_lists/common_syncFiles.js +4 -0
- package/lib/task_lists/package_execute.js +45 -0
- package/lib/task_lists/tagDefs_execute.js +121 -0
- package/lib/task_lists/test_customUIsContinuosReload.js +36 -7
- package/lib/task_lists/test_otherFilesContiousReload.js +37 -16
- package/lib/task_lists/test_syncFile.js +1 -0
- package/lib/task_lists/updateFromServer_definitions.js +135 -0
- package/lib/task_lists/updateFromServer_domains.js +50 -0
- package/lib/task_lists/updateFromServer_groups.js +111 -0
- package/lib/task_lists/updateFromServer_instances.js +76 -0
- package/lib/task_lists/updateFromServer_kibana.js +88 -0
- package/lib/task_lists/updateFromServer_validate.js +3 -3
- package/lib/webpack/webpack.config.js +1 -1
- package/package.json +5 -1
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
const inquirer = require("inquirer")
|
|
2
|
+
const readline = require('readline');
|
|
3
|
+
const yaml = require('js-yaml');
|
|
4
|
+
const fs = require("fs/promises");
|
|
5
|
+
|
|
1
6
|
const SERVER_COB_CLI_DIRECTORY = "/opt/cob-cli/";
|
|
2
7
|
|
|
3
8
|
/* **************************************************************************************** */
|
|
@@ -41,25 +46,64 @@ function checkConnectivity(server) {
|
|
|
41
46
|
])
|
|
42
47
|
}
|
|
43
48
|
|
|
49
|
+
|
|
44
50
|
/* ************************************ */
|
|
51
|
+
let readlineSetup = false
|
|
45
52
|
function getKeypress() {
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
if (!readlineSetup) {
|
|
54
|
+
readline.emitKeypressEvents(process.stdin);
|
|
55
|
+
readlineSetup = true
|
|
56
|
+
}
|
|
48
57
|
process.stdin.setRawMode(true);
|
|
49
|
-
readline.emitKeypressEvents(process.stdin);
|
|
50
58
|
const rl = readline.createInterface({
|
|
51
59
|
input: process.stdin
|
|
52
60
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
|
|
62
|
+
return new Promise(resolve => {
|
|
63
|
+
const listener = function(letter,key){
|
|
64
|
+
if(key.name == "return") {
|
|
65
|
+
console.log("")
|
|
66
|
+
|
|
67
|
+
} else {
|
|
68
|
+
if(key.ctrl && key.name == 'c') letter = "ctrl+c"
|
|
69
|
+
rl.input.removeListener('keypress', listener)
|
|
70
|
+
rl.close();
|
|
71
|
+
process.stdin.setRawMode(false);
|
|
72
|
+
resolve(letter && letter.toLowerCase() || key.name);
|
|
73
|
+
}
|
|
61
74
|
}
|
|
62
|
-
|
|
75
|
+
rl.input.on("keypress", listener)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* ************************************ */
|
|
80
|
+
async function promptCredentials() {
|
|
81
|
+
const answers = await inquirer.prompt([
|
|
82
|
+
{
|
|
83
|
+
type: 'text',
|
|
84
|
+
message: 'Enter username:',
|
|
85
|
+
name: 'username',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
type: 'password',
|
|
89
|
+
message: 'Enter password:',
|
|
90
|
+
name: 'password',
|
|
91
|
+
mask: '*',
|
|
92
|
+
},
|
|
93
|
+
])
|
|
94
|
+
|
|
95
|
+
return answers
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/* ************************************ */
|
|
99
|
+
async function readYamlFile(filename) {
|
|
100
|
+
try {
|
|
101
|
+
const data = await fs.readFile(filename, 'utf-8');
|
|
102
|
+
const config = yaml.load(data);
|
|
103
|
+
return config
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Error reading YAML file:', error.message);
|
|
106
|
+
}
|
|
63
107
|
}
|
|
64
108
|
|
|
65
109
|
/* ************************************ */
|
|
@@ -67,5 +111,7 @@ module.exports = {
|
|
|
67
111
|
SERVER_COB_CLI_DIRECTORY : SERVER_COB_CLI_DIRECTORY,
|
|
68
112
|
checkConnectivity: checkConnectivity,
|
|
69
113
|
checkWorkingCopyCleanliness: checkWorkingCopyCleanliness,
|
|
70
|
-
getKeypress: getKeypress
|
|
114
|
+
getKeypress: getKeypress,
|
|
115
|
+
promptCredentials: promptCredentials,
|
|
116
|
+
readYamlFile: readYamlFile
|
|
71
117
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const fg = require('fast-glob');
|
|
3
|
+
const STATE_FILE = ".git/cob-cli-state";
|
|
4
|
+
|
|
5
|
+
function saveOperationState(environment, servername) {
|
|
6
|
+
try { fs.writeJsonSync(STATE_FILE, { environment, servername }); } catch {}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function loadOperationState() {
|
|
10
|
+
try { return fs.readJsonSync(STATE_FILE); } catch { return null; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function clearOperationState() {
|
|
14
|
+
try { fs.unlinkSync(STATE_FILE); } catch {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function checkNoInterruptedRun() {
|
|
18
|
+
const stateExists = !!loadOperationState();
|
|
19
|
+
const envBackupFiles = await fg(['**/*.ENV__ORIGINAL_BACKUP__.*', '**/*.ENV__DELETE__.*'], { onlyFiles: false, dot: true });
|
|
20
|
+
if (stateExists || envBackupFiles.length > 0) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"\nAborted:".red + " found traces of a previous interrupted test/deploy.\n"
|
|
23
|
+
+ "Run " + "cob-cli cleanup".yellow + " to restore the repo to a clean state first.\n"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { saveOperationState, loadOperationState, clearOperationState, checkNoInterruptedRun };
|
|
@@ -32,6 +32,7 @@ async function _syncFiles(executionType, cmdEnv, from, to, extraOptions = [], ar
|
|
|
32
32
|
return new Promise(async (resolve, reject) => {
|
|
33
33
|
let fromPath = resolveCobPath(cmdEnv.server, from, product);
|
|
34
34
|
let toPath = resolveCobPath(cmdEnv.server, to, product);
|
|
35
|
+
if (toPath.startsWith('.') && !fs.existsSync(toPath)) fs.mkdirSync(toPath)
|
|
35
36
|
let productExtraOptions = extraOptions.map( option => option.replace(/=.*\|/,"="))
|
|
36
37
|
productExtraOptions = productExtraOptions.filter( option => option.indexOf("|") < 0 )
|
|
37
38
|
|
|
@@ -179,6 +180,9 @@ function resolveCobPath(server, type, product) {
|
|
|
179
180
|
default:
|
|
180
181
|
return server + ':' + "/etc/" + product + "/";
|
|
181
182
|
}
|
|
183
|
+
|
|
184
|
+
case "staging":
|
|
185
|
+
return ".staging/" + product + "/";
|
|
182
186
|
}
|
|
183
187
|
}
|
|
184
188
|
exports.resolveCobPath = resolveCobPath;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const Listr = require('listr');
|
|
2
|
+
const UpdaterRenderer = require('listr-update-renderer');
|
|
3
|
+
const verboseRenderer = require('listr-verbose-renderer');
|
|
4
|
+
const { copyFiles } = require("./common_syncFiles");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const tar = require('tar');
|
|
7
|
+
const { format } = require('date-fns');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function executeTasks(cmdEnv, args) {
|
|
11
|
+
console.log("\nPackaging ...");
|
|
12
|
+
|
|
13
|
+
return new Listr([
|
|
14
|
+
{title: "Apply new enviroment specifics", task: () => cmdEnv.applyCurrentCommandEnvironmentChanges() },
|
|
15
|
+
{title: "Create staging dir", task: () => fs.existsSync('.staging') || fs.mkdirSync('.staging') },
|
|
16
|
+
{title: "Copy files to staging dir", task: () => copyFiles(cmdEnv, "localCopy", "staging")},
|
|
17
|
+
{title: "Create package", task: async () => { const filename = await createPackage(cmdEnv, args); console.log("created " + filename) }},
|
|
18
|
+
{title: "Sign package", task: () => console.log("lololo")},
|
|
19
|
+
{title: "Delete staging dir", task: () => fs.rmSync('.staging', {recursive: true, force: true})},
|
|
20
|
+
{title: "Undo new enviroment specifics", task: () => cmdEnv.unApplyCurrentCommandEnvironmentChanges() },
|
|
21
|
+
], {
|
|
22
|
+
renderer: args.verbose ? verboseRenderer : UpdaterRenderer,
|
|
23
|
+
collapse: false
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
exports.executeTasks = executeTasks;
|
|
27
|
+
|
|
28
|
+
const createPackage = async function(cmdEnv, args){
|
|
29
|
+
const filename = `${cmdEnv.servername}-${cmdEnv.name}-${format(new Date(), 'yyyy-MM-dd_HH_mm')}.tar.gz`;
|
|
30
|
+
const files = fs.readdirSync('.staging')
|
|
31
|
+
if (args.verbose > 0){
|
|
32
|
+
console.log("packaging " + files)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await tar.create({
|
|
36
|
+
gzip: true,
|
|
37
|
+
file: filename,
|
|
38
|
+
cwd: '.staging',
|
|
39
|
+
onWriteEntry(entry) {
|
|
40
|
+
if (args.verbose > 2) console.log('adding', entry.path, entry.stat.mode.toString(8))
|
|
41
|
+
}
|
|
42
|
+
}, files)
|
|
43
|
+
|
|
44
|
+
return filename;
|
|
45
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require('colors');
|
|
2
|
+
const Listr = require('listr');
|
|
3
|
+
const UpdaterRenderer = require('listr-update-renderer');
|
|
4
|
+
const VerboseRenderer = require('listr-verbose-renderer');
|
|
5
|
+
const { getFilteredDefinitions } = require('./common_definitions.js')
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const inquirer = require("inquirer")
|
|
8
|
+
|
|
9
|
+
async function getUntaggedChanges(servername, defId) {
|
|
10
|
+
const uri = `https://${servername}.cultofbits.com/recordm/recordm/definitions/${defId}/changes?tagged=false`
|
|
11
|
+
|
|
12
|
+
const response = await axios.get(uri, {params: {tagged: false}});
|
|
13
|
+
|
|
14
|
+
if(response.status % 200 > 100){
|
|
15
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return ( response.data || []).filter(c => !c.tag)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function tagDefinition(servername, defId, until) {
|
|
22
|
+
const uri = `https://${servername}.cultofbits.com/recordm/recordm/definitions/${defId}/tag`
|
|
23
|
+
|
|
24
|
+
const params = {}
|
|
25
|
+
if(until) {
|
|
26
|
+
params.until = until
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const response = await axios.put(uri, "", {headers: {'Content-Type': 'application/json'}, params: params});
|
|
30
|
+
|
|
31
|
+
if(response.status != 200){
|
|
32
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return response.data
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function tagDefinitions(servername, customization, defs, args) {
|
|
39
|
+
return new Listr([
|
|
40
|
+
{
|
|
41
|
+
title: `(${customization}) Definitions: `.bold + "searching for definitions matching queries",
|
|
42
|
+
task: async ctx => ctx.defs = (await getFilteredDefinitions(servername, defs))
|
|
43
|
+
.map(d => ({id: d.id, name: d.name}))
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: `(${customization}) Definitions: `.bold + "enriching metadata",
|
|
47
|
+
task: async ctx => {
|
|
48
|
+
for (const def of ctx.defs){
|
|
49
|
+
def.changes = await getUntaggedChanges(servername, def.id)
|
|
50
|
+
if(args.verbose) console.log(`definition ${def.name}: ${def.changes.length} untagged changes`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
title: `(${customization}) Definitions: `.bold + "tagging un-tagged changes",
|
|
56
|
+
task: async ctx => {
|
|
57
|
+
const subtasks = ctx.defs.map(d => ({
|
|
58
|
+
title: "Tagging changes to " + d.name.bold,
|
|
59
|
+
task: async (_, task) => {
|
|
60
|
+
if (d.changes.length == 0) {
|
|
61
|
+
task.skip("no untagged changes")
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if(args.all) {
|
|
66
|
+
await tagDefinition(servername, d.id)
|
|
67
|
+
task.output = 'Tagged by all'
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
d.changes.forEach(c => console.log(JSON.stringify(c)))
|
|
72
|
+
|
|
73
|
+
const answer = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'rawlist',
|
|
76
|
+
name: 'action',
|
|
77
|
+
message: `Do you wish to tag the ${d.changes.length} changes? (^c to abort)`,
|
|
78
|
+
choices: [
|
|
79
|
+
'Tag Changes',
|
|
80
|
+
'Choose Changes',
|
|
81
|
+
'Skip',
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
])
|
|
85
|
+
if (answer.action == 'Skip') {
|
|
86
|
+
task.skip()
|
|
87
|
+
|
|
88
|
+
} else if (answer.action == 'Tag Changes') {
|
|
89
|
+
await tagDefinition(servername, d.id)
|
|
90
|
+
task.output = 'Tagged'
|
|
91
|
+
|
|
92
|
+
} else if (answer.action == 'Choose Changes') {
|
|
93
|
+
const dates = d.changes.reduce((acc, cur) => {
|
|
94
|
+
if(!acc.includes(cur.date)) acc.push(cur.date)
|
|
95
|
+
return acc
|
|
96
|
+
}, [])
|
|
97
|
+
const answerDate = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'rawlist',
|
|
100
|
+
name: 'date',
|
|
101
|
+
message: `Choose the oldest date to tag`,
|
|
102
|
+
choices: dates
|
|
103
|
+
}
|
|
104
|
+
])
|
|
105
|
+
await tagDefinition(servername, d.id, answerDate.date)
|
|
106
|
+
task.output = 'Tagged until ' + answerDate.date
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}))
|
|
110
|
+
return new Listr(subtasks)
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
],{
|
|
114
|
+
// we need to list changes and read user input, the VerboseRenderer is needed
|
|
115
|
+
renderer: VerboseRenderer,
|
|
116
|
+
collapse: false
|
|
117
|
+
}).run()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
exports.tagDefinitions = tagDefinitions
|
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
const execa = require('execa');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const net = require('net');
|
|
3
4
|
const concurrently = require('concurrently');
|
|
4
5
|
const fs= require('fs');
|
|
5
6
|
const util = require('util');
|
|
6
7
|
const child_process = require('child_process');
|
|
7
8
|
const exec = util.promisify(child_process.exec);
|
|
8
9
|
|
|
10
|
+
function findPort(preferred = 8040) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const server = net.createServer();
|
|
13
|
+
server.listen(preferred, '0.0.0.0', () => {
|
|
14
|
+
server.close(() => resolve(preferred));
|
|
15
|
+
});
|
|
16
|
+
server.on('error', () => {
|
|
17
|
+
const fallback = net.createServer();
|
|
18
|
+
fallback.listen(0, '0.0.0.0', () => {
|
|
19
|
+
const port = fallback.address().port;
|
|
20
|
+
fallback.close(() => resolve(port));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
9
26
|
|
|
10
27
|
async function customUIsContinuosReload(cmdEnv, dashboard) {
|
|
11
28
|
process.env.dash_dir = dashboard
|
|
@@ -22,6 +39,9 @@ async function customUIsContinuosReload(cmdEnv, dashboard) {
|
|
|
22
39
|
throw new Error("Aborted: ".red + " dashboard " + dashboard.bold + " does not exist.\n ")
|
|
23
40
|
}
|
|
24
41
|
|
|
42
|
+
const dashPort = await findPort(8041);
|
|
43
|
+
process.env.dash_port = dashPort;
|
|
44
|
+
|
|
25
45
|
if(dashboardPathIsDirectory) {
|
|
26
46
|
// If we have a dashboard to serve run it from local dashboard directory on 8081 with vue-cli-service
|
|
27
47
|
// it will use its vue.config.js
|
|
@@ -31,13 +51,15 @@ async function customUIsContinuosReload(cmdEnv, dashboard) {
|
|
|
31
51
|
commands.push( {
|
|
32
52
|
command: [
|
|
33
53
|
"cd " + dashboardPath + ";",
|
|
34
|
-
"node_modules/.bin/vue-cli-service serve --port
|
|
35
|
-
].join(" "),
|
|
54
|
+
"node_modules/.bin/vue-cli-service serve --port " + dashPort + " 2>/dev/null"
|
|
55
|
+
].join(" "),
|
|
36
56
|
name: 'dashboard server',
|
|
37
|
-
prefixColor: "red"
|
|
57
|
+
prefixColor: "red"
|
|
38
58
|
})
|
|
39
59
|
}
|
|
40
60
|
|
|
61
|
+
const port = await findPort(8040);
|
|
62
|
+
|
|
41
63
|
console.log( "\n" + (" NOTE: Press " + "O".bold.red + " to open default browser, " + "CTRL+C".bold.red + " or " + "Q".bold.red + " to stop the tests... ").yellow.bold + "\n\n" )
|
|
42
64
|
|
|
43
65
|
const insideCobCliPath = path.resolve(__dirname, '../../node_modules/.bin/webpack-dev-server');
|
|
@@ -50,8 +72,9 @@ async function customUIsContinuosReload(cmdEnv, dashboard) {
|
|
|
50
72
|
webpackDevServerPath,
|
|
51
73
|
"--config",
|
|
52
74
|
path.resolve(__dirname, "../webpack/webpack.config.js"),
|
|
53
|
-
"--mode=development"
|
|
54
|
-
|
|
75
|
+
"--mode=development",
|
|
76
|
+
"--port", port
|
|
77
|
+
].join(" "),
|
|
55
78
|
name: 'customUIs server',
|
|
56
79
|
prefixColor: "blue"
|
|
57
80
|
})
|
|
@@ -82,14 +105,20 @@ async function customUIsContinuosReload(cmdEnv, dashboard) {
|
|
|
82
105
|
}
|
|
83
106
|
}
|
|
84
107
|
|
|
85
|
-
|
|
108
|
+
let notifyFailure;
|
|
109
|
+
const failurePromise = new Promise((_, reject) => { notifyFailure = reject; });
|
|
110
|
+
|
|
111
|
+
conc.result.catch( _err => {
|
|
86
112
|
if(isStopping) return;
|
|
87
113
|
|
|
88
114
|
stopEverything();
|
|
115
|
+
notifyFailure(new Error('processes-failed'));
|
|
89
116
|
});
|
|
90
117
|
|
|
91
118
|
return {
|
|
92
|
-
stop: stopEverything
|
|
119
|
+
stop: stopEverything,
|
|
120
|
+
failed: failurePromise,
|
|
121
|
+
port
|
|
93
122
|
};
|
|
94
123
|
}
|
|
95
124
|
exports.customUIsContinuosReload = customUIsContinuosReload;
|
|
@@ -29,22 +29,7 @@ async function otherFilesContiousReload(cmdEnv) {
|
|
|
29
29
|
let restoreChanges = async () => {
|
|
30
30
|
if(watcher.process) watcher.process.close();
|
|
31
31
|
if(changedFiles.size > 0) {
|
|
32
|
-
|
|
33
|
-
let stash = true;
|
|
34
|
-
await git().env('LC_ALL', 'C').stash(["--include-untracked"]).then( value => value.indexOf("Saved") == 0 || (stash = false))
|
|
35
|
-
let lastSha = await getLastDeployedSha(cmdEnv.server)
|
|
36
|
-
await git().checkout(lastSha)
|
|
37
|
-
|
|
38
|
-
await cmdEnv.applyLastEnvironmentDeployedToServerChanges()
|
|
39
|
-
for(let changedFile of changedFiles) {
|
|
40
|
-
await removeFileFromCurrentTest(cmdEnv.server, changedFile);
|
|
41
|
-
}
|
|
42
|
-
await cmdEnv.unApplyLastEnvironmentDeployedToServerChanges()
|
|
43
|
-
|
|
44
|
-
await execa('ssh', [cmdEnv.server,"find " + SERVER_IN_PROGRESS_TEST_FILE + " -size 0 -delete "]) //Remove file if empty
|
|
45
|
-
|
|
46
|
-
await git().checkout("-")
|
|
47
|
-
if (stash) await git().stash(["pop"])
|
|
32
|
+
await restoreServerFilesToLastDeployed(cmdEnv, changedFiles);
|
|
48
33
|
}
|
|
49
34
|
fs.unlinkSync("."+IN_PROGRESS_TEST_FILE)
|
|
50
35
|
}
|
|
@@ -114,6 +99,41 @@ async function otherFilesContiousReload(cmdEnv) {
|
|
|
114
99
|
}
|
|
115
100
|
exports.otherFilesContiousReload = otherFilesContiousReload
|
|
116
101
|
|
|
102
|
+
/* ************************************ */
|
|
103
|
+
async function restoreServerFilesToLastDeployed(cmdEnv, changedFiles) {
|
|
104
|
+
console.log("\nRestoring changed files...".yellow);
|
|
105
|
+
|
|
106
|
+
// Clean up any env-swap markers left by applyCurrentCommandEnvironmentChanges before
|
|
107
|
+
// stashing. These files are gitignored, so git stash --include-untracked ignores them.
|
|
108
|
+
// If they survive into _undoSpecific(lastEnv) they create ghost env files that are
|
|
109
|
+
// untracked at lastSha, causing "git checkout -" to abort.
|
|
110
|
+
await cmdEnv.unApplyCurrentCommandEnvironmentChanges();
|
|
111
|
+
|
|
112
|
+
let stashed = false;
|
|
113
|
+
await git().env('LC_ALL', 'C').stash(["--include-untracked"])
|
|
114
|
+
.then(value => { stashed = value.indexOf("Saved") === 0; })
|
|
115
|
+
.catch(() => {});
|
|
116
|
+
|
|
117
|
+
const lastSha = await getLastDeployedSha(cmdEnv.server);
|
|
118
|
+
if (!lastSha) throw new Error("Cannot restore server files: no previous deploy found on server.");
|
|
119
|
+
await git().checkout(lastSha);
|
|
120
|
+
|
|
121
|
+
await cmdEnv.applyLastEnvironmentDeployedToServerChanges();
|
|
122
|
+
|
|
123
|
+
for (const changedFile of changedFiles) {
|
|
124
|
+
await removeFileFromCurrentTest(cmdEnv.server, changedFile);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await cmdEnv.unApplyLastEnvironmentDeployedToServerChanges();
|
|
128
|
+
|
|
129
|
+
await execa('ssh', [cmdEnv.server, "find " + SERVER_IN_PROGRESS_TEST_FILE + " -size 0 -delete"])
|
|
130
|
+
.catch(() => {});
|
|
131
|
+
|
|
132
|
+
await git().checkout("-");
|
|
133
|
+
if (stashed) await git().stash(["pop"]);
|
|
134
|
+
}
|
|
135
|
+
exports.restoreServerFilesToLastDeployed = restoreServerFilesToLastDeployed
|
|
136
|
+
|
|
117
137
|
/* ************************************ */
|
|
118
138
|
async function addFileToCurrentTest(server, changedFile, changedFiles, changeType, restoreChanges) {
|
|
119
139
|
let inUse = "";
|
|
@@ -146,6 +166,7 @@ async function removeFileFromCurrentTest(server, changedFile) {
|
|
|
146
166
|
await execa('ssh', [server, "sed -i '/" + changedFile.split("/").join("\\/") + "/d' " + SERVER_IN_PROGRESS_TEST_FILE]).catch( () => {/* ignore files removed by user */} );
|
|
147
167
|
console.log(" reset ".brightGreen + changedFile);
|
|
148
168
|
}
|
|
169
|
+
exports.removeFileFromCurrentTest = removeFileFromCurrentTest
|
|
149
170
|
|
|
150
171
|
/* ************************************ */
|
|
151
172
|
function checkNoTestsRunningOnServer(server) {
|
|
@@ -24,6 +24,7 @@ async function syncFile(server, localFile) {
|
|
|
24
24
|
"--prune-empty-dirs",
|
|
25
25
|
"--chmod=Fg+w",
|
|
26
26
|
"--no-perms",
|
|
27
|
+
"--no-group",
|
|
27
28
|
"--no-t",
|
|
28
29
|
"--filter='merge " + path.resolve(__dirname,"rsyncFilter-pre.txt") + "'",
|
|
29
30
|
"--filter='merge " + path.resolve(__dirname,"rsyncFilter-post.txt") + "'"],
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require('colors');
|
|
2
|
+
const Listr = require('listr');
|
|
3
|
+
const UpdaterRenderer = require('listr-update-renderer');
|
|
4
|
+
const VerboseRenderer = require('listr-verbose-renderer');
|
|
5
|
+
const { getFilteredDefinitions } = require('./common_definitions.js')
|
|
6
|
+
const axios = require("axios");
|
|
7
|
+
const fs = require("fs/promises");
|
|
8
|
+
|
|
9
|
+
async function getRefs(servername, def) {
|
|
10
|
+
const uri = `https://${servername}.cultofbits.com/recordm/recordm/definitions/${def.id}`
|
|
11
|
+
const response = await axios.get(uri);
|
|
12
|
+
|
|
13
|
+
if(response.status != 200){
|
|
14
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fields = response.data.fieldDefinitions;
|
|
18
|
+
return [...new Set(get$Refs(fields))];
|
|
19
|
+
}
|
|
20
|
+
function get$Refs(fields){
|
|
21
|
+
let refs = [];
|
|
22
|
+
for(f of fields){
|
|
23
|
+
if(f.configuration.keys.Reference){
|
|
24
|
+
refs.push(f.configuration.keys.Reference.args.definition);
|
|
25
|
+
}
|
|
26
|
+
if(f.fields) {
|
|
27
|
+
const childRefs = get$Refs(f.fields);
|
|
28
|
+
if(childRefs.length > 0) refs.push(...childRefs)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return refs
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function exportDefinitionChanges(servername, def, dataPath) {
|
|
35
|
+
|
|
36
|
+
const uri = `https://${servername}.cultofbits.com/recordm/recordm/definitions/${def.id}/changes`
|
|
37
|
+
const response = await axios.get(uri);
|
|
38
|
+
|
|
39
|
+
if(response.status == 204){
|
|
40
|
+
// this is probably an error
|
|
41
|
+
throw new Error(`Definition "${def.name}" [${def.id}] has no tagged Changes.`);
|
|
42
|
+
|
|
43
|
+
} else if(response.status != 200){
|
|
44
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// write every change on a single line
|
|
48
|
+
const stringified = "[\n" + response.data.map(change => " " + JSON.stringify(change)).join(',\n') + "\n]\n";
|
|
49
|
+
|
|
50
|
+
await fs.writeFile(dataPath, stringified, 'utf-8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function exportDefinitionIdx(defs, dataPath) {
|
|
54
|
+
await fs.writeFile(dataPath, defs.map(d => d.name).join('\n'), 'utf-8')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function getViewsForDef(servername, definition, dataPath) {
|
|
58
|
+
const definitionId = definition.id
|
|
59
|
+
const uri = `https://${servername}.cultofbits.com/recordm/user/settings/${definition.views}/definitions-${definitionId}/views?`
|
|
60
|
+
|
|
61
|
+
const response = await axios.get(uri);
|
|
62
|
+
|
|
63
|
+
if(response.status != 200){
|
|
64
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let userViews = response.data.filter(v => v.isShared)
|
|
68
|
+
|
|
69
|
+
if(response.data.length == 0)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
userViews = response.data.map(v => {
|
|
73
|
+
v.value = JSON.stringify( JSON.parse(v.value).filter(f => f.visible == true) )
|
|
74
|
+
return v
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const jsonLines = userViews.map(v => JSON.stringify({
|
|
78
|
+
key: v.key,
|
|
79
|
+
value: v.value,
|
|
80
|
+
user: v.user,
|
|
81
|
+
})) .join('\n');
|
|
82
|
+
|
|
83
|
+
await fs.writeFile(dataPath, jsonLines)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getDefinitionsFromServer(servername, defs, definitionsPath, viewsPath, customization, args) {
|
|
87
|
+
|
|
88
|
+
return new Listr(
|
|
89
|
+
[
|
|
90
|
+
{
|
|
91
|
+
title: `(${customization}) Definitions: `.bold + "searching for definitions matching queries",
|
|
92
|
+
task: async ctx => ctx.defs = await getFilteredDefinitions(servername, defs)
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
title: `(${customization}) Definitions: `.bold + "enriching metadata",
|
|
96
|
+
task: async ctx => {
|
|
97
|
+
const definitions = await Promise.all(ctx.defs
|
|
98
|
+
.map(async d => ({id: d.id, name: d.name, views: d.views, refs: await getRefs(servername, d)}))
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
// we'll sort by refs, Defs that reference others must come after them
|
|
102
|
+
definitions.sort((a, b) => {
|
|
103
|
+
if (a.refs.includes(b.name)){
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if(b.refs.includes(a.name)){
|
|
108
|
+
return -1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return a.refs.length - b.refs.length;
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
ctx.defs = definitions
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: `(${customization}) Definitions: `.bold + "exporting changes",
|
|
119
|
+
task: async ctx => {
|
|
120
|
+
for( d of ctx.defs ){ await exportDefinitionChanges(servername, d, definitionsPath + `${d.name}.json`)}
|
|
121
|
+
await exportDefinitionIdx(ctx.defs, definitionsPath + "_index")
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
title: `(${customization}) Definitions: `.bold + "exporting views",
|
|
126
|
+
task: async ctx => { for (d of ctx.defs.filter(d => d.views )){ await getViewsForDef(servername, d, viewsPath + `${d.name}.jsonl`)} }
|
|
127
|
+
},
|
|
128
|
+
],{
|
|
129
|
+
renderer: args.verbose ? VerboseRenderer : UpdaterRenderer,
|
|
130
|
+
collapse: false
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
exports.getDefinitionsFromServer = getDefinitionsFromServer
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require('colors');
|
|
2
|
+
const Listr = require('listr');
|
|
3
|
+
const UpdaterRenderer = require('listr-update-renderer');
|
|
4
|
+
const VerboseRenderer = require('listr-verbose-renderer');
|
|
5
|
+
const axios = require("axios");
|
|
6
|
+
const fs = require("fs/promises");
|
|
7
|
+
|
|
8
|
+
async function domainsFromServer(servername, domainsFilter, exportPath, customization, args) {
|
|
9
|
+
await new Listr(
|
|
10
|
+
[
|
|
11
|
+
{ title: `(${customization}) Domains: `.bold + "searching for domains matching the queries" ,
|
|
12
|
+
task: async ctx => ctx.domains = await getDomains(servername, domainsFilter, exportPath) },
|
|
13
|
+
{ title: `(${customization}) Domains: `.bold + "exporting domains " ,
|
|
14
|
+
task: ctx => ctx.domains.forEach( d => exportDomain(d, exportPath)) }
|
|
15
|
+
],{
|
|
16
|
+
renderer: args.verbose ? VerboseRenderer : UpdaterRenderer,
|
|
17
|
+
collapse: false
|
|
18
|
+
})
|
|
19
|
+
.run()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function match(query, domain) {
|
|
23
|
+
// simple matching for now
|
|
24
|
+
return (domain.name && domain.name.includes(query)) ||
|
|
25
|
+
(domain.description && domain.description.includes(query) )
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function getDomains(servername, filters, exportPath) {
|
|
29
|
+
|
|
30
|
+
const uri = `https://${servername}.cultofbits.com/recordm/recordm/domains`
|
|
31
|
+
const response = await axios.get( uri );
|
|
32
|
+
|
|
33
|
+
if(response.status != 200){
|
|
34
|
+
throw new Error(`HTTP Error Response: ${response.status} ${response.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const matchesAny = (domain) => filters.filter(q => match(q, domain)).length > 0;
|
|
38
|
+
|
|
39
|
+
return response.data.filter( matchesAny )
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function exportDomain(domain, rootPath) {
|
|
43
|
+
const d = {
|
|
44
|
+
name: domain.name,
|
|
45
|
+
definitions: domain.definitions.map(d => d.name).sort()
|
|
46
|
+
}
|
|
47
|
+
await fs.writeFile(rootPath + domain.name + ".json", JSON.stringify(d, null, 2) )
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
exports.getDomainsFromServer = domainsFromServer
|