@automattic/vip 3.13.0 → 3.14.0

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.
@@ -8,6 +8,7 @@ var _readline = _interopRequireDefault(require("readline"));
8
8
  var _socket = _interopRequireDefault(require("socket.io-client"));
9
9
  var _socket2 = _interopRequireDefault(require("socket.io-stream"));
10
10
  var _stream = require("stream");
11
+ var _wpSsh = require("../commands/wp-ssh");
11
12
  var _api = _interopRequireWildcard(require("../lib/api"));
12
13
  var _command = _interopRequireWildcard(require("../lib/cli/command"));
13
14
  var exit = _interopRequireWildcard(require("../lib/cli/exit"));
@@ -30,6 +31,7 @@ const appQuery = `id, name,
30
31
  appId
31
32
  type
32
33
  name
34
+ wpcliStrategy
33
35
  primaryDomain {
34
36
  name
35
37
  }
@@ -330,6 +332,13 @@ const examples = [{
330
332
  (0, _api.disableGlobalGraphQLErrorHandling)();
331
333
  const promptIdentifier = `${appName}.${(0, _command.getEnvIdentifier)(opts.env)}`;
332
334
  let countSIGINT = 0;
335
+ if (opts.env.wpcliStrategy === 'ssh') {
336
+ const wpCommandRunner = new _wpSsh.WPCliCommandOverSSH(opts.app, opts.env);
337
+ await wpCommandRunner.run(cmd, {
338
+ command: commandForAnalytics
339
+ });
340
+ return;
341
+ }
333
342
  const mutableStdout = new _stream.Writable({
334
343
  write(chunk, encoding, callback) {
335
344
  if (!this.muted) {
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.WPCliCommandOverSSH = exports.NonZeroExitCodeError = void 0;
5
+ var _chalk = _interopRequireDefault(require("chalk"));
6
+ var _debug = _interopRequireDefault(require("debug"));
7
+ var _graphqlTag = _interopRequireDefault(require("graphql-tag"));
8
+ var ssh2 = _interopRequireWildcard(require("ssh2"));
9
+ var _package = _interopRequireDefault(require("../../package.json"));
10
+ var _api = _interopRequireDefault(require("../lib/api"));
11
+ var _tracker = require("../lib/tracker");
12
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
13
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
14
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
15
+ /**
16
+ * External dependencies
17
+ */
18
+
19
+ /**
20
+ * Internal dependencies
21
+ */
22
+
23
+ const debug = (0, _debug.default)('@automattic/vip:wp/ssh');
24
+ const NON_TTY_COLUMNS = 100;
25
+ const NON_TTY_ROWS = 15;
26
+ const SSH_HANDSHAKE_TIMEOUT_MS = 5000;
27
+ const TRIGGER_WP_CLI_COMMAND_MUTATION = (0, _graphqlTag.default)`
28
+ mutation TriggerWPCLICommandMutation($input: AppEnvironmentTriggerWPCLICommandInput) {
29
+ triggerWPCLICommandOnAppEnvironment(input: $input) {
30
+ inputToken
31
+ sshAuthentication {
32
+ host
33
+ port
34
+ username
35
+ privateKey
36
+ passphrase
37
+ }
38
+ command {
39
+ guid
40
+ }
41
+ }
42
+ }
43
+ `;
44
+ class NonZeroExitCodeError extends Error {
45
+ exitCode;
46
+ constructor(message, exitCode) {
47
+ super(message);
48
+ this.exitCode = exitCode;
49
+ }
50
+ }
51
+ exports.NonZeroExitCodeError = NonZeroExitCodeError;
52
+ class WPCliCommandOverSSH {
53
+ app;
54
+ env;
55
+ track;
56
+ constructor(app, env) {
57
+ this.app = app;
58
+ this.env = env;
59
+ this.track = (0, _tracker.makeCommandTracker)('wp', {
60
+ app: this.app.id,
61
+ env: this.env.id,
62
+ execution_type: 'ssh'
63
+ });
64
+ }
65
+ async run(command, extraTrackingInfo = {}) {
66
+ if (!this.app.id || !this.env.id) {
67
+ await this.track('error', {
68
+ error: 'no_app_env_id',
69
+ message: 'No app or env ID provided',
70
+ ...extraTrackingInfo
71
+ });
72
+ throw new Error('No app ID or environment ID provided');
73
+ }
74
+ debug("Requesting SSH authentication for command '%s'", command);
75
+ const sshAuth = await this.getSSHAuthForCommand(command, extraTrackingInfo);
76
+ const data = sshAuth.data?.triggerWPCLICommandOnAppEnvironment;
77
+ if (!data) {
78
+ await this.track('error', {
79
+ error: 'no_ssh_auth_data',
80
+ message: 'No SSH authentication data received',
81
+ ...extraTrackingInfo
82
+ });
83
+ throw new Error('WP-CLI SSH Authentication failed');
84
+ }
85
+ debug('Connecting to SSH', {
86
+ host: data.sshAuthentication.host,
87
+ port: data.sshAuthentication.port,
88
+ username: data.sshAuthentication.username,
89
+ guid: data.command.guid
90
+ });
91
+ try {
92
+ await this.executeCommandOverSSH({
93
+ host: data.sshAuthentication.host,
94
+ port: data.sshAuthentication.port,
95
+ username: data.sshAuthentication.username,
96
+ privateKey: data.sshAuthentication.privateKey,
97
+ passphrase: data.sshAuthentication.passphrase,
98
+ guid: data.command.guid,
99
+ inputToken: data.inputToken
100
+ });
101
+ await this.track('success', {
102
+ guid: data?.command.guid
103
+ });
104
+ } catch (err) {
105
+ if (err instanceof NonZeroExitCodeError) {
106
+ await this.track('error', {
107
+ guid: data?.command.guid,
108
+ error: 'non_zero_exit_code',
109
+ message: `Command failed with exit code ${err.exitCode}`,
110
+ ...extraTrackingInfo
111
+ });
112
+ process.exit(err.exitCode);
113
+ }
114
+ const message = err instanceof Error ? err.message : String(err);
115
+ console.error(`${_chalk.default.red('Error:')} ${message}`);
116
+ await this.track('error', {
117
+ guid: data?.command.guid,
118
+ error: 'ssh_command_failed',
119
+ message: 'Error executing command over SSH',
120
+ ...extraTrackingInfo
121
+ });
122
+ process.exit(1);
123
+ }
124
+ }
125
+ async executeCommandOverSSH({
126
+ host,
127
+ port,
128
+ username,
129
+ privateKey,
130
+ passphrase,
131
+ guid,
132
+ inputToken
133
+ }) {
134
+ return new Promise((resolve, reject) => {
135
+ const conn = new ssh2.Client();
136
+ const columns = process.stdout.columns || NON_TTY_COLUMNS;
137
+ const rows = process.stdout.rows || NON_TTY_ROWS;
138
+ const isTTY = process.stdout.isTTY ? 'true' : 'false';
139
+ let commandStarted = false;
140
+ conn.on('ready', () => {
141
+ conn.exec(`GUID=${guid} INPUT_TOKEN=${inputToken} VERSION=${_package.default.version} ROWS=${rows} COLUMNS=${columns} TTY=${isTTY}`, (err, stream) => {
142
+ if (err) {
143
+ reject(err);
144
+ return;
145
+ }
146
+ stream.on('exit', exitCode => {
147
+ if (exitCode !== 0) {
148
+ reject(new NonZeroExitCodeError(`Command failed`, exitCode));
149
+ }
150
+ });
151
+ commandStarted = true;
152
+ const handleSIGINT = () => {
153
+ process.removeListener('SIGINT', handleSIGINT);
154
+ console.log('SIGINT received. Canceling command...');
155
+ stream.end('\x03');
156
+ };
157
+ process.on('SIGINT', handleSIGINT);
158
+ const handleSIGTERM = () => {
159
+ process.removeListener('SIGTERM', handleSIGTERM);
160
+ console.log('SIGTERM received. Canceling command...');
161
+ stream.end('\x1F');
162
+ };
163
+ process.on('SIGTERM', handleSIGTERM);
164
+ stream.pipe(process.stdout);
165
+ process.stdin.pipe(stream);
166
+ stream.on('close', () => {
167
+ process.removeListener('SIGINT', handleSIGINT);
168
+ process.removeListener('SIGTERM', handleSIGTERM);
169
+ conn.end();
170
+ resolve();
171
+ });
172
+ });
173
+ }).on('error', err => {
174
+ reject(err);
175
+ }).on('close', () => {
176
+ if (!commandStarted) {
177
+ reject(new Error('Connection closed before command started'));
178
+ }
179
+ }).connect({
180
+ host,
181
+ port,
182
+ username,
183
+ privateKey,
184
+ passphrase,
185
+ readyTimeout: SSH_HANDSHAKE_TIMEOUT_MS
186
+ });
187
+ });
188
+ }
189
+ async getSSHAuthForCommand(command, extraTrackingInfo) {
190
+ const api = (0, _api.default)();
191
+ try {
192
+ return api.mutate({
193
+ mutation: TRIGGER_WP_CLI_COMMAND_MUTATION,
194
+ variables: {
195
+ input: {
196
+ id: this.app.id,
197
+ environmentId: this.env.id,
198
+ command
199
+ }
200
+ }
201
+ });
202
+ } catch (error) {
203
+ const message = error instanceof Error ? error.message : String(error);
204
+ await this.track('error', {
205
+ error: 'trigger_failed',
206
+ message,
207
+ ...extraTrackingInfo
208
+ });
209
+ throw new Error(`Unable to trigger the WP-CLI command`);
210
+ }
211
+ }
212
+ }
213
+ exports.WPCliCommandOverSSH = WPCliCommandOverSSH;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@automattic/vip",
9
- "version": "3.13.0",
9
+ "version": "3.14.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -41,6 +41,7 @@
41
41
  "socket.io-client": "^4.5.3",
42
42
  "socket.io-stream": "npm:@wearemothership/socket.io-stream@^0.9.1",
43
43
  "socks-proxy-agent": "^5.0.1",
44
+ "ssh2": "1.16.0",
44
45
  "tar": "^7.4.0",
45
46
  "update-notifier": "7.3.1",
46
47
  "uuid": "11.1.0",
@@ -127,6 +128,7 @@
127
128
  "@types/proxy-from-env": "^1.0.4",
128
129
  "@types/semver": "^7.5.5",
129
130
  "@types/shelljs": "^0.8.15",
131
+ "@types/ssh2": "^1.15.4",
130
132
  "@types/tar": "^6.1.13",
131
133
  "@types/update-notifier": "^6.0.8",
132
134
  "@types/xml2js": "^0.4.14",
@@ -3797,9 +3799,9 @@
3797
3799
  }
3798
3800
  },
3799
3801
  "node_modules/@types/ssh2": {
3800
- "version": "1.11.11",
3801
- "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.11.tgz",
3802
- "integrity": "sha512-LdnE7UBpvHCgUznvn2fwLt2hkaENcKPFqOyXGkvyTLfxCXBN6roc1RmECNYuzzbHePzD3PaAov5rri9hehzx9Q==",
3802
+ "version": "1.15.5",
3803
+ "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
3804
+ "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
3803
3805
  "dev": true,
3804
3806
  "dependencies": {
3805
3807
  "@types/node": "^18.11.18"
@@ -5823,9 +5825,9 @@
5823
5825
  }
5824
5826
  },
5825
5827
  "node_modules/dockerode": {
5826
- "version": "4.0.5",
5827
- "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.5.tgz",
5828
- "integrity": "sha512-ZPmKSr1k1571Mrh7oIBS/j0AqAccoecY2yH420ni5j1KyNMgnoTh4Nu4FWunh0HZIJmRSmSysJjBIpa/zyWUEA==",
5828
+ "version": "4.0.6",
5829
+ "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.6.tgz",
5830
+ "integrity": "sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w==",
5829
5831
  "license": "Apache-2.0",
5830
5832
  "dependencies": {
5831
5833
  "@balena/dockerignore": "^1.0.2",
@@ -16092,9 +16094,9 @@
16092
16094
  }
16093
16095
  },
16094
16096
  "@types/ssh2": {
16095
- "version": "1.11.11",
16096
- "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.11.11.tgz",
16097
- "integrity": "sha512-LdnE7UBpvHCgUznvn2fwLt2hkaENcKPFqOyXGkvyTLfxCXBN6roc1RmECNYuzzbHePzD3PaAov5rri9hehzx9Q==",
16097
+ "version": "1.15.5",
16098
+ "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
16099
+ "integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
16098
16100
  "dev": true,
16099
16101
  "requires": {
16100
16102
  "@types/node": "^18.11.18"
@@ -17533,9 +17535,9 @@
17533
17535
  }
17534
17536
  },
17535
17537
  "dockerode": {
17536
- "version": "4.0.5",
17537
- "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.5.tgz",
17538
- "integrity": "sha512-ZPmKSr1k1571Mrh7oIBS/j0AqAccoecY2yH420ni5j1KyNMgnoTh4Nu4FWunh0HZIJmRSmSysJjBIpa/zyWUEA==",
17538
+ "version": "4.0.6",
17539
+ "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.6.tgz",
17540
+ "integrity": "sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w==",
17539
17541
  "requires": {
17540
17542
  "@balena/dockerignore": "^1.0.2",
17541
17543
  "@grpc/grpc-js": "^1.11.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip",
3
- "version": "3.13.0",
3
+ "version": "3.14.0",
4
4
  "description": "The VIP Javascript library & CLI",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -127,6 +127,7 @@
127
127
  "@types/proxy-from-env": "^1.0.4",
128
128
  "@types/semver": "^7.5.5",
129
129
  "@types/shelljs": "^0.8.15",
130
+ "@types/ssh2": "^1.15.4",
130
131
  "@types/tar": "^6.1.13",
131
132
  "@types/update-notifier": "^6.0.8",
132
133
  "@types/xml2js": "^0.4.14",
@@ -170,6 +171,7 @@
170
171
  "socket.io-client": "^4.5.3",
171
172
  "socket.io-stream": "npm:@wearemothership/socket.io-stream@^0.9.1",
172
173
  "socks-proxy-agent": "^5.0.1",
174
+ "ssh2": "1.16.0",
173
175
  "tar": "^7.4.0",
174
176
  "update-notifier": "7.3.1",
175
177
  "uuid": "11.1.0",