@eliware/vyops 1.0.11 → 1.0.13
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/RELEASE_NOTES.md +7 -0
- package/package.json +4 -4
- package/src/deploy.mjs +13 -7
- package/src/git.mjs +2 -2
- package/src/ssh.mjs +2 -0
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Close SFTP channels after uploads and downloads to prevent SSH channel exhaustion during script synchronization.
|
|
6
|
+
- Reconnect after interactive deployments before downloading live configuration and cleaning up remote files.
|
|
7
|
+
- Make Git path handling and the `test:gaps` coverage command portable across Windows and Linux.
|
|
8
|
+
- Expand Windows and real-VyOS integration coverage to maintain 100% statements, branches, functions, and lines coverage.
|
|
9
|
+
|
|
3
10
|
## 1.0.11
|
|
4
11
|
|
|
5
12
|
- Fix VyOS 1.5 `commit-confirm` handling by answering confirmation prompts
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliware/vyops",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Deploy native VyOS configuration files over SSH.",
|
|
5
5
|
"main": "vyops.mjs",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "npx oxlint .",
|
|
8
8
|
"test": "npx --node-options=\"--experimental-vm-modules --no-warnings\" jest --detectOpenHandles --silent --coverage",
|
|
9
9
|
"deploy": "./vyops.mjs",
|
|
10
|
-
"test:gaps": "
|
|
10
|
+
"test:gaps": "node scripts/test-gaps.mjs",
|
|
11
11
|
"pack": "npm pack --dry-run",
|
|
12
12
|
"validate:package": "npm pack --dry-run --json | node scripts/validate-package.mjs",
|
|
13
|
-
"audit": "
|
|
13
|
+
"audit": "node scripts/audit.mjs"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"vyos",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"LICENSE",
|
|
49
49
|
"RELEASE_NOTES.md"
|
|
50
50
|
]
|
|
51
|
-
}
|
|
51
|
+
}
|
package/src/deploy.mjs
CHANGED
|
@@ -67,19 +67,19 @@ async function installScripts(client, config, log, runId) {
|
|
|
67
67
|
await exec(client, `sudo rm -f -- ${names.map(name => JSON.stringify(`${installDir}/${name}`)).join(' ')}; sudo cp -a ${JSON.stringify(`${backupDir}/.`)} ${JSON.stringify(`${installDir}/`)} 2>/dev/null || true; rm -rf -- ${JSON.stringify(remoteDir)} ${JSON.stringify(backupDir)}`).catch(cleanupError => log(`script rollback failed: ${cleanupError.message}`));
|
|
68
68
|
throw error;
|
|
69
69
|
}
|
|
70
|
-
return async committed => {
|
|
70
|
+
return async (committed, activeClient) => {
|
|
71
71
|
if (committed) {
|
|
72
|
-
await exec(
|
|
72
|
+
await exec(activeClient, `sudo rm -rf -- ${JSON.stringify(backupDir)}; rm -rf -- ${JSON.stringify(remoteDir)}`).catch(error => log(`hook cleanup failed: ${error.message}`));
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
|
-
await exec(
|
|
75
|
+
await exec(activeClient, `sudo rm -f -- ${names.map(name => JSON.stringify(`${installDir}/${name}`)).join(' ')}; sudo cp -a ${JSON.stringify(`${backupDir}/.`)} ${JSON.stringify(`${installDir}/`)} 2>/dev/null || true; rm -rf -- ${JSON.stringify(remoteDir)} ${JSON.stringify(backupDir)}`).catch(error => log(`script rollback failed: ${error.message}`));
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export async function deploy({ target, config, password }) {
|
|
80
80
|
const debugLog = message => log.debug(`[vyops] ${message}`);
|
|
81
81
|
debugLog(`connecting: ${target}`);
|
|
82
|
-
|
|
82
|
+
let client = password === undefined ? await connect(target) : await connect(target, { password });
|
|
83
83
|
debugLog('SSH connected');
|
|
84
84
|
let finalizeHooks;
|
|
85
85
|
let hooksFinalized = false;
|
|
@@ -91,6 +91,9 @@ export async function deploy({ target, config, password }) {
|
|
|
91
91
|
await upload(client, config, remote);
|
|
92
92
|
debugLog(`upload complete: ${remote}`);
|
|
93
93
|
finalizeHooks = await installScripts(client, config, debugLog, runId);
|
|
94
|
+
debugLog('reconnecting before interactive deployment sequence');
|
|
95
|
+
await close(client);
|
|
96
|
+
client = password === undefined ? await connect(target) : await connect(target, { password });
|
|
94
97
|
debugLog('starting interactive deployment sequence');
|
|
95
98
|
const output = await interactive(client, [
|
|
96
99
|
'configure',
|
|
@@ -99,7 +102,7 @@ export async function deploy({ target, config, password }) {
|
|
|
99
102
|
"printf '%s\\n' '--- compare ---'",
|
|
100
103
|
'compare',
|
|
101
104
|
"printf '%s\\n' '--- end compare ---'",
|
|
102
|
-
{ command: 'commit-confirm 5', reject: /(?:commit failed|commit aborted|cannot commit|configuration (?:commit )?failed|invalid configuration
|
|
105
|
+
{ command: 'commit-confirm 5', reject: /(?:commit failed|commit aborted|cannot commit|configuration (?:commit )?failed|invalid configuration)/i },
|
|
103
106
|
{ command: 'confirm', reject: /(?:confirm failed|error|invalid)/i },
|
|
104
107
|
{ command: 'save', reject: /(?:save failed|error|invalid)/i },
|
|
105
108
|
'exit',
|
|
@@ -109,15 +112,18 @@ export async function deploy({ target, config, password }) {
|
|
|
109
112
|
deploymentCommitted = true;
|
|
110
113
|
const compare = extractCompare(output);
|
|
111
114
|
if (compare) log.info(compare);
|
|
115
|
+
debugLog('reconnecting after interactive deployment sequence');
|
|
116
|
+
await close(client);
|
|
117
|
+
client = password === undefined ? await connect(target) : await connect(target, { password });
|
|
112
118
|
debugLog(`syncing live config: /config/config.boot -> ${config}`);
|
|
113
119
|
await download(client, '/config/config.boot', config);
|
|
114
120
|
if (finalizeHooks) {
|
|
115
121
|
hooksFinalized = true;
|
|
116
|
-
await finalizeHooks(true);
|
|
122
|
+
await finalizeHooks(true, client);
|
|
117
123
|
}
|
|
118
124
|
return 0;
|
|
119
125
|
} catch (error) {
|
|
120
|
-
if (finalizeHooks && !hooksFinalized && !deploymentCommitted) await finalizeHooks(false);
|
|
126
|
+
if (finalizeHooks && !hooksFinalized && !deploymentCommitted) await finalizeHooks(false, client);
|
|
121
127
|
throw error;
|
|
122
128
|
} finally {
|
|
123
129
|
debugLog('cleaning up remote file');
|
package/src/git.mjs
CHANGED
|
@@ -72,7 +72,7 @@ async function repositoryRoot(config) {
|
|
|
72
72
|
export async function shouldSkip(config) {
|
|
73
73
|
const repo = await repositoryRoot(config);
|
|
74
74
|
if (!repo) return false;
|
|
75
|
-
const relative = config.startsWith(
|
|
75
|
+
const relative = config.startsWith(repo) ? config.slice(repo.length).replace(/^[/\\]/, '') : config;
|
|
76
76
|
const { stdout: status } = await git(['status', '--porcelain', '--', relative], repo);
|
|
77
77
|
if (status.trim()) return false;
|
|
78
78
|
const { stdout: subject } = await git(['log', '-1', '--format=%s'], repo);
|
|
@@ -83,7 +83,7 @@ export async function pushBack(config, { force = false } = {}) {
|
|
|
83
83
|
const repo = await repositoryRoot(config);
|
|
84
84
|
if (!repo) return false;
|
|
85
85
|
return withRepositoryLock(repo, async () => {
|
|
86
|
-
const relative = config.startsWith(
|
|
86
|
+
const relative = config.startsWith(repo) ? config.slice(repo.length).replace(/^[/\\]/, '') : config;
|
|
87
87
|
const { stdout: diff } = await git(['diff', 'HEAD', '--', relative], repo);
|
|
88
88
|
if (!diff) return false;
|
|
89
89
|
await git(['add', '--', relative], repo);
|
package/src/ssh.mjs
CHANGED
|
@@ -122,6 +122,7 @@ export async function upload(client, local, remote, mode = 0o600) {
|
|
|
122
122
|
if (error) { clearTimeout(timer); return reject(error); }
|
|
123
123
|
sftp.writeFile(remote, data, { mode }, error2 => {
|
|
124
124
|
clearTimeout(timer);
|
|
125
|
+
sftp.end?.();
|
|
125
126
|
return error2 ? reject(error2) : resolve();
|
|
126
127
|
});
|
|
127
128
|
});
|
|
@@ -136,6 +137,7 @@ export function download(client, remote, local) {
|
|
|
136
137
|
if (error) { clearTimeout(timer); return reject(error); }
|
|
137
138
|
sftp.fastGet(remote, local, error2 => {
|
|
138
139
|
clearTimeout(timer);
|
|
140
|
+
sftp.end?.();
|
|
139
141
|
return error2 ? reject(error2) : resolve();
|
|
140
142
|
});
|
|
141
143
|
});
|