mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-06-20 16:50:15 +00:00
Fix proxy support and add proxy exclusion feature (Node.js >= 10.16.0 only)
See README for more details on proxy configuration and Node.js compatibility. Resolves: #1579 Resolves: #1335 Connects-to: #1580 Change-type: minor Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
*Please note that this issue tracker is used for specific bug reports and feature requests.
|
*Please note that this issue tracker is used for specific bug reports and feature requests.
|
||||||
General and troubleshooting questions are encouraged to be posted to the [balena
|
General and troubleshooting questions are encouraged to be posted to the [balena
|
||||||
forums](https://forums.balena.io), which are monitored by balena's support team and where
|
forums](https://forums.balena.io), which are monitored by balena's support team and
|
||||||
the community can both contribute and benefit from the answers.*
|
where the community can both contribute and benefit from the answers.*
|
||||||
|
|
||||||
*Before submitting this issue please check that this issue is not a duplicate. If there is another
|
*Before submitting this issue please check that this issue is not a duplicate. If there is another
|
||||||
issue describing the same problem or feature please add your information to the existing issue's
|
issue describing the same problem or feature please add your information to the existing issue's
|
||||||
|
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -11,14 +11,14 @@ on Windows, macOS and Linux. Tests will be automatically run by balena CI on
|
|||||||
all three operating systems, but this will only help if you have added test
|
all three operating systems, but this will only help if you have added test
|
||||||
code that exercises the modified or added feature code.
|
code that exercises the modified or added feature code.
|
||||||
|
|
||||||
Note that each commit's message (currently only the first line) will be
|
Note that each commit message (currently only the first line) will be
|
||||||
automatically copied to the CHANGELOG.md file, so try writing it in a way
|
automatically copied to the CHANGELOG.md file, so try writing it in a way
|
||||||
that describes the feature or fix for CLI users.
|
that describes the feature or fix for CLI users.
|
||||||
|
|
||||||
If there isn't a linked issue or if the linked issue doesn't quite match the
|
If there isn't a linked issue or if the linked issue doesn't quite match the
|
||||||
PR, please add a description to explain the PR's motivation and the features
|
PR, please add a PR description to explain its purpose or the features that it
|
||||||
that it adds. Adding comments to blocks of code that aren't self explanatory
|
implements. Adding PR comments to blocks of code that aren't self explanatory
|
||||||
usually helps with the review process.
|
usually helps with the review process.
|
||||||
|
|
||||||
If he PR introduces security considerations or affects the development, build
|
If the PR introduces security considerations or affects the development, build
|
||||||
or release process, please be sure to add a description and highlight this.
|
or release process, please be sure to highlight this in the PR description.
|
||||||
|
12
INSTALL.md
12
INSTALL.md
@ -141,11 +141,13 @@ especially if you're using a user-managed node install such as [nvm](https://git
|
|||||||
([more information](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse)).
|
([more information](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse)).
|
||||||
For other versions of Windows, there are several ssh/OpenSSH clients provided by 3rd parties.
|
For other versions of Windows, there are several ssh/OpenSSH clients provided by 3rd parties.
|
||||||
|
|
||||||
* If you need SSH to work behind a proxy, you will also need to install
|
* The [`proxytunnel`](http://proxytunnel.sourceforge.net/) package (command-line tool) is needed
|
||||||
[`proxytunnel`](http://proxytunnel.sourceforge.net/) (available as a `proxytunnel` package
|
for the `balena ssh` command to work behind a proxy. It is available for Linux distributions
|
||||||
for Ubuntu, for example).
|
like Ubuntu/Debian (`apt install proxytunnel`), and for macOS through
|
||||||
Check the [README](https://github.com/balena-io/balena-cli/blob/master/README.md) file
|
[Homebrew](https://brew.sh/). Windows support is limited to the Windows Subsystem for Linux
|
||||||
for proxy configuration instructions.
|
(e.g., by installing Ubuntu through the Microsoft App Store). Check the
|
||||||
|
[README](https://github.com/balena-io/balena-cli/blob/master/README.md) file for proxy
|
||||||
|
configuration instructions.
|
||||||
|
|
||||||
* The `balena preload`, `balena build` and `balena deploy --build` commands require
|
* The `balena preload`, `balena build` and `balena deploy --build` commands require
|
||||||
[Docker](https://docs.docker.com/install/overview/) or [balenaEngine](https://www.balena.io/engine/)
|
[Docker](https://docs.docker.com/install/overview/) or [balenaEngine](https://www.balena.io/engine/)
|
||||||
|
68
README.md
68
README.md
@ -64,19 +64,65 @@ $ balena login
|
|||||||
|
|
||||||
### Proxy support
|
### Proxy support
|
||||||
|
|
||||||
HTTP(S) proxies can be configured through any of the following methods, in order of preference:
|
HTTP(S) proxies can be configured through any of the following methods, in precedence order
|
||||||
|
(from higher to lower):
|
||||||
|
|
||||||
* Set the `BALENARC_PROXY` environment variable in URL format (with protocol, host, port, and
|
* The `BALENARC_PROXY` environment variable in URL format, with protocol (`http` or `https`),
|
||||||
optionally basic auth).
|
host, port and optionally basic auth. Examples:
|
||||||
* Alternatively, use the [balena config file](https://www.npmjs.com/package/balena-settings-client#documentation)
|
* `export BALENARC_PROXY='https://bob:secret@proxy.company.com:12345'`
|
||||||
(project-specific or user-level) and set the `proxy` setting. It can be:
|
* `export BALENARC_PROXY='http://localhost:8000'`
|
||||||
* A string in URL format, or
|
|
||||||
* An object in the [global-tunnel-ng options format](https://www.npmjs.com/package/global-tunnel-ng#options) (which allows more control).
|
|
||||||
* Alternatively, set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
|
|
||||||
environment variable (in the same standard URL format).
|
|
||||||
|
|
||||||
To get a proxy to work with the `balena ssh` command, check the
|
* The `proxy` setting in the [CLI config
|
||||||
[installation instructions](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
|
file](https://www.npmjs.com/package/balena-settings-client#documentation). It may be:
|
||||||
|
* A string in URL format, e.g. `proxy: 'http://localhost:8000'`
|
||||||
|
* An object in the format:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxy:
|
||||||
|
protocol: 'http'
|
||||||
|
host: 'proxy.company.com'
|
||||||
|
port: 12345
|
||||||
|
proxyAuth: 'bob:secret'
|
||||||
|
```
|
||||||
|
|
||||||
|
* The `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables, in the same URL format as
|
||||||
|
`BALENARC_PROXY`.
|
||||||
|
|
||||||
|
> Note: The `balena ssh` command has additional setup requirements to work behind a proxy.
|
||||||
|
> Check the [installation instructions](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
|
||||||
|
|
||||||
|
Some installations of the balena CLI also include support for the `BALENARC_NO_PROXY` environment
|
||||||
|
variable, which allows proxy exclusion patterns to be defined. The current support status is listed
|
||||||
|
below. Eventually, all installation types will have support for it.
|
||||||
|
|
||||||
|
OS | Installation type | BALENARC_NO_PROXY environment variable support
|
||||||
|
-- | ----------------- | ----------------------------------------------
|
||||||
|
Windows | standalone zip | Supported with CLI v11.24.0 and later
|
||||||
|
Windows | native/GUI | Not supported
|
||||||
|
macOS | standalone zip | Not supported
|
||||||
|
macOS | native/GUI | Supported with CLI v11.24.0 and later
|
||||||
|
Linux | standalone zip | Not supported
|
||||||
|
Any | npm | Supported with Node.js >= v10.16.0 and CLI >= v11.24.0
|
||||||
|
|
||||||
|
The format of the `BALENARC_NO_PROXY` environment variable is a comma-separated list of patterns
|
||||||
|
that are matched against hostnames or IP addresses. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
export BALENARC_NO_PROXY='*.local,dev*.mycompany.com,192.168.*'
|
||||||
|
```
|
||||||
|
|
||||||
|
Matched patterns are excluded from proxying. Matching takes place _before_ name resolution, so a
|
||||||
|
pattern like `'192.168.*'` will **not** match a hostname like `proxy.company.com` even if the
|
||||||
|
hostname resolves to an IP address like `192.168.1.2`. Pattern matching expressions are documented
|
||||||
|
at [matcher](https://www.npmjs.com/package/matcher#usage).
|
||||||
|
|
||||||
|
By default, if BALENARC_NO_PROXY is not defined, all [private IPv4
|
||||||
|
addresses](https://en.wikipedia.org/wiki/Private_network) and `'*.local'` are excluded from
|
||||||
|
proxying. Other hostnames that may resolve to private IPv4 addresses are **not** excluded by
|
||||||
|
default, as matching takes place _before_ name resolution. In addition, `localhost` and `127.0.0.1`
|
||||||
|
are always excluded from proxying, regardless of the value of BALENARC_NO_PROXY. These default
|
||||||
|
exclusions only apply to the CLI installations where BALENARC_NO_PROXY is supported, as listed in
|
||||||
|
the table above.
|
||||||
|
|
||||||
## Command reference documentation
|
## Command reference documentation
|
||||||
|
|
||||||
|
@ -57,19 +57,65 @@ $ balena login
|
|||||||
|
|
||||||
### Proxy support
|
### Proxy support
|
||||||
|
|
||||||
HTTP(S) proxies can be configured through any of the following methods, in order of preference:
|
HTTP(S) proxies can be configured through any of the following methods, in precedence order
|
||||||
|
(from higher to lower):
|
||||||
|
|
||||||
* Set the `BALENARC_PROXY` environment variable in URL format (with protocol, host, port, and
|
* The `BALENARC_PROXY` environment variable in URL format, with protocol (`http` or `https`),
|
||||||
optionally basic auth).
|
host, port and optionally basic auth. Examples:
|
||||||
* Alternatively, use the [balena config file](https://www.npmjs.com/package/balena-settings-client#documentation)
|
* `export BALENARC_PROXY='https://bob:secret@proxy.company.com:12345'`
|
||||||
(project-specific or user-level) and set the `proxy` setting. It can be:
|
* `export BALENARC_PROXY='http://localhost:8000'`
|
||||||
* A string in URL format, or
|
|
||||||
* An object in the [global-tunnel-ng options format](https://www.npmjs.com/package/global-tunnel-ng#options) (which allows more control).
|
|
||||||
* Alternatively, set the conventional `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY`
|
|
||||||
environment variable (in the same standard URL format).
|
|
||||||
|
|
||||||
To get a proxy to work with the `balena ssh` command, check the
|
* The `proxy` setting in the [CLI config
|
||||||
[installation instructions](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
|
file](https://www.npmjs.com/package/balena-settings-client#documentation). It may be:
|
||||||
|
* A string in URL format, e.g. `proxy: 'http://localhost:8000'`
|
||||||
|
* An object in the format:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
proxy:
|
||||||
|
protocol: 'http'
|
||||||
|
host: 'proxy.company.com'
|
||||||
|
port: 12345
|
||||||
|
proxyAuth: 'bob:secret'
|
||||||
|
```
|
||||||
|
|
||||||
|
* The `HTTPS_PROXY` and/or `HTTP_PROXY` environment variables, in the same URL format as
|
||||||
|
`BALENARC_PROXY`.
|
||||||
|
|
||||||
|
> Note: The `balena ssh` command has additional setup requirements to work behind a proxy.
|
||||||
|
> Check the [installation instructions](https://github.com/balena-io/balena-cli/blob/master/INSTALL.md).
|
||||||
|
|
||||||
|
Some installations of the balena CLI also include support for the `BALENARC_NO_PROXY` environment
|
||||||
|
variable, which allows proxy exclusion patterns to be defined. The current support status is listed
|
||||||
|
below. Eventually, all installation types will have support for it.
|
||||||
|
|
||||||
|
OS | Installation type | BALENARC_NO_PROXY environment variable support
|
||||||
|
-- | ----------------- | ----------------------------------------------
|
||||||
|
Windows | standalone zip | Supported with CLI v11.24.0 and later
|
||||||
|
Windows | native/GUI | Not supported
|
||||||
|
macOS | standalone zip | Not supported
|
||||||
|
macOS | native/GUI | Supported with CLI v11.24.0 and later
|
||||||
|
Linux | standalone zip | Not supported
|
||||||
|
Any | npm | Supported with Node.js >= v10.16.0 and CLI >= v11.24.0
|
||||||
|
|
||||||
|
The format of the `BALENARC_NO_PROXY` environment variable is a comma-separated list of patterns
|
||||||
|
that are matched against hostnames or IP addresses. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
export BALENARC_NO_PROXY='*.local,dev*.mycompany.com,192.168.*'
|
||||||
|
```
|
||||||
|
|
||||||
|
Matched patterns are excluded from proxying. Matching takes place _before_ name resolution, so a
|
||||||
|
pattern like `'192.168.*'` will **not** match a hostname like `proxy.company.com` even if the
|
||||||
|
hostname resolves to an IP address like `192.168.1.2`. Pattern matching expressions are documented
|
||||||
|
at [matcher](https://www.npmjs.com/package/matcher#usage).
|
||||||
|
|
||||||
|
By default, if BALENARC_NO_PROXY is not defined, all [private IPv4
|
||||||
|
addresses](https://en.wikipedia.org/wiki/Private_network) and `'*.local'` are excluded from
|
||||||
|
proxying. Other hostnames that may resolve to private IPv4 addresses are **not** excluded by
|
||||||
|
default, as matching takes place _before_ name resolution. In addition, `localhost` and `127.0.0.1`
|
||||||
|
are always excluded from proxying, regardless of the value of BALENARC_NO_PROXY. These default
|
||||||
|
exclusions only apply to the CLI installations where BALENARC_NO_PROXY is supported, as listed in
|
||||||
|
the table above.
|
||||||
|
|
||||||
## Support, FAQ and troubleshooting
|
## Support, FAQ and troubleshooting
|
||||||
|
|
||||||
|
@ -227,9 +227,9 @@ export const ssh: CommandDefinition<
|
|||||||
const applicationOrDevice =
|
const applicationOrDevice =
|
||||||
params.applicationOrDevice_raw || params.applicationOrDevice;
|
params.applicationOrDevice_raw || params.applicationOrDevice;
|
||||||
const bash = await import('bash');
|
const bash = await import('bash');
|
||||||
// TODO: Make this typed
|
const { getProxyConfig, getSubShellCommand, which } = await import(
|
||||||
const hasbin = require('hasbin');
|
'../utils/helpers'
|
||||||
const { getSubShellCommand } = await import('../utils/helpers');
|
);
|
||||||
const { child_process } = await import('mz');
|
const { child_process } = await import('mz');
|
||||||
const {
|
const {
|
||||||
exitIfNotLoggedIn,
|
exitIfNotLoggedIn,
|
||||||
@ -239,8 +239,7 @@ export const ssh: CommandDefinition<
|
|||||||
const sdk = BalenaSdk.fromSharedOptions();
|
const sdk = BalenaSdk.fromSharedOptions();
|
||||||
|
|
||||||
const verbose = options.verbose === true;
|
const verbose = options.verbose === true;
|
||||||
// ugh TODO: Fix this
|
const proxyConfig = getProxyConfig();
|
||||||
const proxyConfig = (global as any).PROXY_CONFIG;
|
|
||||||
const useProxy = !!proxyConfig && !options.noProxy;
|
const useProxy = !!proxyConfig && !options.noProxy;
|
||||||
const port = options.port != null ? parseInt(options.port, 10) : undefined;
|
const port = options.port != null ? parseInt(options.port, 10) : undefined;
|
||||||
|
|
||||||
@ -276,8 +275,8 @@ export const ssh: CommandDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const [hasTunnelBin, username, proxyUrl] = await Promise.all([
|
const [whichProxytunnel, username, proxyUrl] = await Promise.all([
|
||||||
useProxy ? await hasbin('proxytunnel') : undefined,
|
useProxy ? which('proxytunnel', false) : undefined,
|
||||||
sdk.auth.whoami(),
|
sdk.auth.whoami(),
|
||||||
sdk.settings.get('proxyUrl'),
|
sdk.settings.get('proxyUrl'),
|
||||||
]);
|
]);
|
||||||
@ -287,7 +286,7 @@ export const ssh: CommandDefinition<
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTunnelBin) {
|
if (!whichProxytunnel) {
|
||||||
console.warn(stripIndent`
|
console.warn(stripIndent`
|
||||||
Proxy is enabled but the \`proxytunnel\` binary cannot be found.
|
Proxy is enabled but the \`proxytunnel\` binary cannot be found.
|
||||||
Please install it if you want to route the \`balena ssh\` requests through the proxy.
|
Please install it if you want to route the \`balena ssh\` requests through the proxy.
|
||||||
@ -298,18 +297,14 @@ export const ssh: CommandDefinition<
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let tunnelOptions: Dictionary<string> = {
|
const p = proxyConfig!;
|
||||||
proxy: `${proxyConfig.host}:${proxyConfig.port}`,
|
const tunnelOptions: Dictionary<string> = {
|
||||||
|
proxy: `${p.host}:${p.port}`,
|
||||||
dest: '%h:%p',
|
dest: '%h:%p',
|
||||||
};
|
};
|
||||||
const { proxyAuth } = proxyConfig;
|
if (p.username && p.password) {
|
||||||
if (proxyAuth) {
|
tunnelOptions.user = p.username;
|
||||||
const i = proxyAuth.indexOf(':');
|
tunnelOptions.pass = p.password;
|
||||||
tunnelOptions = {
|
|
||||||
user: proxyAuth.substring(0, i),
|
|
||||||
pass: proxyAuth.substring(i + 1),
|
|
||||||
...tunnelOptions,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProxyCommand = `proxytunnel ${bash.args(tunnelOptions, '--', '=')}`;
|
const ProxyCommand = `proxytunnel ${bash.args(tunnelOptions, '--', '=')}`;
|
||||||
@ -335,7 +330,7 @@ export const ssh: CommandDefinition<
|
|||||||
{
|
{
|
||||||
port,
|
port,
|
||||||
proxyCommand,
|
proxyCommand,
|
||||||
proxyUrl,
|
proxyUrl: proxyUrl || '',
|
||||||
username: username!,
|
username: username!,
|
||||||
},
|
},
|
||||||
version,
|
version,
|
||||||
@ -356,7 +351,7 @@ export const ssh: CommandDefinition<
|
|||||||
verbose,
|
verbose,
|
||||||
port,
|
port,
|
||||||
proxyCommand,
|
proxyCommand,
|
||||||
proxyUrl,
|
proxyUrl: proxyUrl || '',
|
||||||
username: username!,
|
username: username!,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,6 +15,36 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
class CliSettings {
|
||||||
|
public readonly settings: any;
|
||||||
|
constructor() {
|
||||||
|
// TODO figure out why the typescript compiler attempts to type-check
|
||||||
|
// the `balena-settings-client` module (and then fails with errors) if
|
||||||
|
// a straighforward `require('balena-settings-client')` statement is
|
||||||
|
// used here. It may even be a compiler bug, because `tsconfig.json`
|
||||||
|
// has a `"skipLibCheck": true` setting.
|
||||||
|
this.settings = require(['balena', 'settings', 'client'].join('-'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public get<T>(name: string): T {
|
||||||
|
return this.settings.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like settings.get(), but return `undefined` instead of throwing an
|
||||||
|
* error if the setting is not found / not defined.
|
||||||
|
*/
|
||||||
|
public getCatch<T>(name: string): T | undefined {
|
||||||
|
try {
|
||||||
|
return this.settings.get(name);
|
||||||
|
} catch (err) {
|
||||||
|
if (!/Setting not found/i.test(err.message)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sentry.io setup
|
* Sentry.io setup
|
||||||
* @see https://docs.sentry.io/clients/node/
|
* @see https://docs.sentry.io/clients/node/
|
||||||
@ -53,37 +83,136 @@ function checkNodeVersion() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupGlobalHttpProxy() {
|
export interface GlobalTunnelNgConfig {
|
||||||
// Doing this before requiring any other modules,
|
host: string;
|
||||||
// including the 'balena-sdk', to prevent any module from reading the http proxy config
|
port: number;
|
||||||
// before us
|
protocol: string;
|
||||||
const globalTunnel = require('global-tunnel-ng');
|
proxyAuth?: string;
|
||||||
const settings = require('balena-settings-client');
|
connect?: string;
|
||||||
let proxy;
|
sockets?: number;
|
||||||
try {
|
}
|
||||||
proxy = settings.get('proxy') || null;
|
|
||||||
} catch (error1) {
|
/**
|
||||||
proxy = null;
|
* Global proxy setup. Originally, `global-tunnel-ng` was used, but it only
|
||||||
|
* supports Node.js versions older than 10.16.0. For v10.16.0 and later,
|
||||||
|
* we use `global-agent` (which only supports Node.js v10.0.0 and later).
|
||||||
|
*
|
||||||
|
* For backwards compatibility reasons, in either case we still accept a
|
||||||
|
* 'proxy' setting in `.balenarc.yml` that follows the
|
||||||
|
* `global-tunnel-ng` object configuration format:
|
||||||
|
* https://www.npmjs.com/package/global-tunnel-ng#options
|
||||||
|
*
|
||||||
|
* The proxy may also be configured with the environment variables:
|
||||||
|
* BALENARC_PROXY, HTTP_PROXY, HTTPS_PROXY, http_proxy, and https_proxy,
|
||||||
|
* any of which should contain a URL in the usual format (authentication
|
||||||
|
* details are optional): http://username:password@domain.com:1234
|
||||||
|
*
|
||||||
|
* A proxy exclusion list in the NO_PROXY variable is only supported when
|
||||||
|
* `global-agent` is used, i.e. with Node.js v10.16.0 or later. The format
|
||||||
|
* is specified at: https://www.npmjs.com/package/global-agent#exclude-urls
|
||||||
|
* Patterns are matched with matcher: https://www.npmjs.com/package/matcher
|
||||||
|
* 'localhost' and '127.0.0.1' are always excluded. If NO_PROXY is not defined,
|
||||||
|
* default exclusion patterns are added for all private IPv4 address ranges.
|
||||||
|
*/
|
||||||
|
async function setupGlobalHttpProxy(settings: CliSettings) {
|
||||||
|
const semver = await import('semver');
|
||||||
|
if (semver.lt(process.version, '10.16.0')) {
|
||||||
|
setupGlobalTunnelNgProxy(settings);
|
||||||
|
} else {
|
||||||
|
// use global-agent instead of global-tunnel-ng
|
||||||
|
await setupGlobalAgentProxy(settings);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Init the tunnel even if the proxy is not configured
|
/**
|
||||||
// because it can also get the proxy from the http(s)_proxy env var
|
* `global-tunnel-ng` proxy setup.
|
||||||
// If that is not set as well the initialize will do nothing
|
* See docs for setupGlobalHttpProxy() above.
|
||||||
|
*/
|
||||||
|
function setupGlobalTunnelNgProxy(settings: CliSettings) {
|
||||||
|
const proxy = settings.getCatch<string | GlobalTunnelNgConfig>('proxy');
|
||||||
|
const globalTunnel = require('global-tunnel-ng');
|
||||||
|
// Init the tunnel even if BALENARC_PROXY is not defined, because
|
||||||
|
// other env vars may be defined. If no proxy configuration exists,
|
||||||
|
// initialize() does nothing.
|
||||||
globalTunnel.initialize(proxy);
|
globalTunnel.initialize(proxy);
|
||||||
|
|
||||||
// TODO: make this a feature of capitano https://github.com/balena-io/capitano/issues/48
|
|
||||||
(global as any).PROXY_CONFIG = globalTunnel.proxyConfig;
|
(global as any).PROXY_CONFIG = globalTunnel.proxyConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupBalenaSdkSharedOptions() {
|
/**
|
||||||
|
* `global-agent` proxy setup.
|
||||||
|
* See docs for setupGlobalHttpProxy() above, and also the README file
|
||||||
|
* (Proxy Support section).
|
||||||
|
*/
|
||||||
|
async function setupGlobalAgentProxy(settings: CliSettings) {
|
||||||
|
const proxy = settings.getCatch<string | GlobalTunnelNgConfig>('proxy');
|
||||||
|
const noProxy = settings.getCatch<string>('noProxy');
|
||||||
|
// Always exclude localhost, even if NO_PROXY is set
|
||||||
|
const requiredNoProxy = ['localhost', '127.0.0.1'];
|
||||||
|
// Private IPv4 address patterns in `matcher` format: https://www.npmjs.com/package/matcher
|
||||||
|
const privateNoProxy = ['*.local', '10.*', '192.168.*'];
|
||||||
|
for (let i = 16; i <= 31; i++) {
|
||||||
|
privateNoProxy.push(`172.${i}.*`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const env = process.env;
|
||||||
|
env.GLOBAL_AGENT_ENVIRONMENT_VARIABLE_NAMESPACE = '';
|
||||||
|
|
||||||
|
if (proxy) {
|
||||||
|
const _ = await import('lodash');
|
||||||
|
const proxyUrl: string =
|
||||||
|
typeof proxy === 'string' ? proxy : makeUrlFromTunnelNgConfig(proxy);
|
||||||
|
|
||||||
|
env.HTTPS_PROXY = env.HTTP_PROXY = proxyUrl;
|
||||||
|
delete env.http_proxy;
|
||||||
|
delete env.https_proxy;
|
||||||
|
|
||||||
|
env.NO_PROXY = [
|
||||||
|
...requiredNoProxy,
|
||||||
|
...(noProxy ? _.filter((noProxy || '').split(',')) : privateNoProxy),
|
||||||
|
].join(',');
|
||||||
|
} else {
|
||||||
|
// `global-tunnel-ng` accepts lowercase variables with higher precedence
|
||||||
|
// than uppercase variables, but `global-agent` does not accept lowercase.
|
||||||
|
// Set uppercase versions for backwards compatibility.
|
||||||
|
if (env.http_proxy) {
|
||||||
|
env.HTTP_PROXY = env.http_proxy;
|
||||||
|
}
|
||||||
|
if (env.https_proxy) {
|
||||||
|
env.HTTPS_PROXY = env.https_proxy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { bootstrap } = require('global-agent');
|
||||||
|
bootstrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Make a URL in the format 'http://bob:secret@proxy.company.com:12345' */
|
||||||
|
export function makeUrlFromTunnelNgConfig(cfg: GlobalTunnelNgConfig): string {
|
||||||
|
let url: string = cfg.host;
|
||||||
|
if (cfg.proxyAuth) {
|
||||||
|
url = `${cfg.proxyAuth}@${url}`;
|
||||||
|
}
|
||||||
|
if (cfg.protocol) {
|
||||||
|
// accept 'http', 'http:', 'http://' and the like
|
||||||
|
const match = cfg.protocol.match(/^[^:/]+/);
|
||||||
|
if (match) {
|
||||||
|
url = `${match[0].toLowerCase()}://${url}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cfg.port) {
|
||||||
|
url = `${url}:${cfg.port}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupBalenaSdkSharedOptions(settings: CliSettings) {
|
||||||
// We don't yet use balena-sdk directly everywhere, but we set up shared
|
// We don't yet use balena-sdk directly everywhere, but we set up shared
|
||||||
// options correctly so we can do safely in submodules
|
// options correctly so we can do safely in submodules
|
||||||
const BalenaSdk = require('balena-sdk');
|
const BalenaSdk = require('balena-sdk');
|
||||||
const settings = require('balena-settings-client');
|
|
||||||
BalenaSdk.setSharedOptions({
|
BalenaSdk.setSharedOptions({
|
||||||
apiUrl: settings.get('apiUrl'),
|
apiUrl: settings.get<string>('apiUrl'),
|
||||||
imageMakerUrl: settings.get('imageMakerUrl'),
|
imageMakerUrl: settings.get<string>('imageMakerUrl'),
|
||||||
dataDirectory: settings.get('dataDirectory'),
|
dataDirectory: settings.get<string>('dataDirectory'),
|
||||||
retries: 2,
|
retries: 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -120,12 +249,16 @@ export function setMaxListeners(maxListeners: number) {
|
|||||||
require('events').EventEmitter.defaultMaxListeners = maxListeners;
|
require('events').EventEmitter.defaultMaxListeners = maxListeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function globalInit() {
|
export async function globalInit() {
|
||||||
setupRaven();
|
setupRaven();
|
||||||
checkNodeVersion();
|
checkNodeVersion();
|
||||||
configureBluebird();
|
configureBluebird();
|
||||||
setupGlobalHttpProxy();
|
|
||||||
setupBalenaSdkSharedOptions();
|
const settings = new CliSettings();
|
||||||
|
|
||||||
|
// Proxy setup should be done early on, before loading balena-sdk
|
||||||
|
await setupGlobalHttpProxy(settings);
|
||||||
|
setupBalenaSdkSharedOptions(settings);
|
||||||
|
|
||||||
// check for CLI updates once a day
|
// check for CLI updates once a day
|
||||||
require('./utils/update').notify();
|
require('./utils/update').notify();
|
||||||
|
@ -35,7 +35,7 @@ export async function run(
|
|||||||
// globalInit() must be called very early on (before other imports) because
|
// globalInit() must be called very early on (before other imports) because
|
||||||
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
// it sets up Sentry error reporting, global HTTP proxy settings, balena-sdk
|
||||||
// shared options, and performs node version requirement checks.
|
// shared options, and performs node version requirement checks.
|
||||||
globalInit();
|
await globalInit();
|
||||||
await routeCliFramework(cliArgs, options);
|
await routeCliFramework(cliArgs, options);
|
||||||
|
|
||||||
// Windows fix: reading from stdin prevents the process from exiting
|
// Windows fix: reading from stdin prevents the process from exiting
|
||||||
|
@ -379,20 +379,87 @@ export async function workaroundWindowsDnsIssue(ipOrHostname: string) {
|
|||||||
* so hash -r is not needed when the PATH changes."
|
* so hash -r is not needed when the PATH changes."
|
||||||
*
|
*
|
||||||
* @param program Basename of a program, for example 'ssh'
|
* @param program Basename of a program, for example 'ssh'
|
||||||
|
* @param rejectOnMissing If the program cannot be found, reject the promise
|
||||||
|
* with an ExpectedError instead of fulfilling it with an empty string.
|
||||||
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
* @returns The program's full path, e.g. 'C:\WINDOWS\System32\OpenSSH\ssh.EXE'
|
||||||
*/
|
*/
|
||||||
export async function which(program: string): Promise<string> {
|
export async function which(
|
||||||
|
program: string,
|
||||||
|
rejectOnMissing = true,
|
||||||
|
): Promise<string> {
|
||||||
const whichMod = await import('which');
|
const whichMod = await import('which');
|
||||||
let programPath: string;
|
let programPath: string;
|
||||||
try {
|
try {
|
||||||
programPath = await whichMod(program);
|
programPath = await whichMod(program);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') {
|
if (err.code === 'ENOENT') {
|
||||||
throw new ExpectedError(
|
if (rejectOnMissing) {
|
||||||
`'${program}' program not found. Is it installed?`,
|
throw new ExpectedError(
|
||||||
);
|
`'${program}' program not found. Is it installed?`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
return programPath;
|
return programPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ProxyConfig {
|
||||||
|
host: string;
|
||||||
|
port: string;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
proxyAuth?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a proxy has been configured (whether global-tunnel-ng or
|
||||||
|
* global-agent) and if so, return a ProxyConfig object.
|
||||||
|
*/
|
||||||
|
export function getProxyConfig(): ProxyConfig | undefined {
|
||||||
|
const tunnelNgConfig: any = (global as any).PROXY_CONFIG;
|
||||||
|
// global-tunnel-ng
|
||||||
|
if (tunnelNgConfig) {
|
||||||
|
let username: string | undefined;
|
||||||
|
let password: string | undefined;
|
||||||
|
const proxyAuth: string = tunnelNgConfig.proxyAuth;
|
||||||
|
if (proxyAuth) {
|
||||||
|
const i = proxyAuth.lastIndexOf(':');
|
||||||
|
if (i > 0) {
|
||||||
|
username = proxyAuth.substring(0, i);
|
||||||
|
password = proxyAuth.substring(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
host: tunnelNgConfig.host,
|
||||||
|
port: `${tunnelNgConfig.port}`,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
proxyAuth: tunnelNgConfig.proxyAuth,
|
||||||
|
};
|
||||||
|
// global-agent, or no proxy config
|
||||||
|
} else {
|
||||||
|
const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY;
|
||||||
|
if (proxyUrl) {
|
||||||
|
const { URL } = require('url') as typeof import('url');
|
||||||
|
let url: URL;
|
||||||
|
try {
|
||||||
|
url = new URL(proxyUrl);
|
||||||
|
} catch (_e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
host: url.hostname,
|
||||||
|
port: url.port,
|
||||||
|
username: url.username,
|
||||||
|
password: url.password,
|
||||||
|
proxyAuth:
|
||||||
|
url.username && url.password
|
||||||
|
? `${url.username}:${url.password}`
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
117
npm-shrinkwrap.json
generated
117
npm-shrinkwrap.json
generated
@ -2452,6 +2452,11 @@
|
|||||||
"multicast-dns-service-types": "^1.1.0"
|
"multicast-dns-service-types": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"boolean": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ=="
|
||||||
|
},
|
||||||
"bottleneck": {
|
"bottleneck": {
|
||||||
"version": "2.19.5",
|
"version": "2.19.5",
|
||||||
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
|
||||||
@ -4038,6 +4043,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||||
},
|
},
|
||||||
|
"detect-node": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw=="
|
||||||
|
},
|
||||||
"dev-null-stream": {
|
"dev-null-stream": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dev-null-stream/-/dev-null-stream-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dev-null-stream/-/dev-null-stream-0.0.1.tgz",
|
||||||
@ -4650,6 +4660,11 @@
|
|||||||
"next-tick": "^1.0.0"
|
"next-tick": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"es6-error": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
|
||||||
|
},
|
||||||
"es6-iterator": {
|
"es6-iterator": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||||
@ -6482,6 +6497,32 @@
|
|||||||
"object.defaults": "^1.1.0"
|
"object.defaults": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"global-agent": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-ooK7eqGYZku+LgnbfH/Iv0RJ74XfhrBZDlke1QSzcBt0bw1PmJcnRADPAQuFE+R45pKKDTynAr25SBasY2kvow==",
|
||||||
|
"requires": {
|
||||||
|
"boolean": "^3.0.0",
|
||||||
|
"core-js": "^3.4.1",
|
||||||
|
"es6-error": "^4.1.1",
|
||||||
|
"matcher": "^2.0.0",
|
||||||
|
"roarr": "^2.14.5",
|
||||||
|
"semver": "^6.3.0",
|
||||||
|
"serialize-error": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": {
|
||||||
|
"version": "3.6.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
|
||||||
|
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"global-dirs": {
|
"global-dirs": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz",
|
||||||
@ -6542,6 +6583,14 @@
|
|||||||
"integrity": "sha512-uNUtxIZpGyuaq+5BqGGQHsL4wUlJAXRqOm6g3Y48/CWNGTLONgBibI0lh6lGxjR2HljFYUfszb+mk4WkgMntsA==",
|
"integrity": "sha512-uNUtxIZpGyuaq+5BqGGQHsL4wUlJAXRqOm6g3Y48/CWNGTLONgBibI0lh6lGxjR2HljFYUfszb+mk4WkgMntsA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"globalthis": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw==",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "^1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"globby": {
|
"globby": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz",
|
||||||
@ -6957,21 +7006,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hasbin": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/hasbin/-/hasbin-1.2.3.tgz",
|
|
||||||
"integrity": "sha1-eMWSaJPIAhXCtWiuH9P8q3omlrA=",
|
|
||||||
"requires": {
|
|
||||||
"async": "~1.5"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"async": {
|
|
||||||
"version": "1.5.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
|
||||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"he": {
|
"he": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||||
@ -8576,6 +8610,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"matcher": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/matcher/-/matcher-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ==",
|
||||||
|
"requires": {
|
||||||
|
"escape-string-regexp": "^2.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"mbr": {
|
"mbr": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/mbr/-/mbr-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/mbr/-/mbr-1.1.3.tgz",
|
||||||
@ -16044,6 +16093,26 @@
|
|||||||
"string-to-stream": "^1.0.1"
|
"string-to-stream": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"roarr": {
|
||||||
|
"version": "2.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.14.6.tgz",
|
||||||
|
"integrity": "sha512-qjbw0BEesKA+3XFBPt+KVe1PC/Z6ShfJ4wPlx2XifqH5h2Lj8/KQT5XJTsy3n1Es5kai+BwKALaECW3F70B1cg==",
|
||||||
|
"requires": {
|
||||||
|
"boolean": "^3.0.0",
|
||||||
|
"detect-node": "^2.0.4",
|
||||||
|
"globalthis": "^1.0.0",
|
||||||
|
"json-stringify-safe": "^5.0.1",
|
||||||
|
"semver-compare": "^1.0.0",
|
||||||
|
"sprintf-js": "^1.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"rsync": {
|
"rsync": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/rsync/-/rsync-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/rsync/-/rsync-0.4.0.tgz",
|
||||||
@ -16139,8 +16208,7 @@
|
|||||||
"semver-compare": {
|
"semver-compare": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
|
||||||
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
|
"integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"semver-diff": {
|
"semver-diff": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
@ -16208,6 +16276,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"serialize-error": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA==",
|
||||||
|
"requires": {
|
||||||
|
"type-fest": "^0.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"serve-static": {
|
"serve-static": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
|
||||||
|
@ -189,8 +189,8 @@
|
|||||||
"event-stream": "3.3.4",
|
"event-stream": "3.3.4",
|
||||||
"express": "^4.13.3",
|
"express": "^4.13.3",
|
||||||
"fast-boot2": "^1.0.9",
|
"fast-boot2": "^1.0.9",
|
||||||
|
"global-agent": "^2.1.7",
|
||||||
"global-tunnel-ng": "^2.1.1",
|
"global-tunnel-ng": "^2.1.1",
|
||||||
"hasbin": "^1.2.3",
|
|
||||||
"humanize": "0.0.9",
|
"humanize": "0.0.9",
|
||||||
"ignore": "^5.1.4",
|
"ignore": "^5.1.4",
|
||||||
"inquirer": "^3.1.1",
|
"inquirer": "^3.1.1",
|
||||||
|
38
tests/app-common.spec.ts
Normal file
38
tests/app-common.spec.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2020 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GlobalTunnelNgConfig,
|
||||||
|
makeUrlFromTunnelNgConfig,
|
||||||
|
} from '../build/app-common';
|
||||||
|
|
||||||
|
describe('makeUrlFromTunnelNgConfig() function', function() {
|
||||||
|
it('should return a URL given a GlobalTunnelNgConfig object', () => {
|
||||||
|
const tunnelNgConfig: GlobalTunnelNgConfig = {
|
||||||
|
host: 'proxy.company.com',
|
||||||
|
port: 8080,
|
||||||
|
proxyAuth: 'bob:secret',
|
||||||
|
protocol: 'http:',
|
||||||
|
connect: 'https',
|
||||||
|
};
|
||||||
|
const expectedUrl = 'http://bob:secret@proxy.company.com:8080';
|
||||||
|
const url = makeUrlFromTunnelNgConfig(tunnelNgConfig);
|
||||||
|
expect(url).to.deep.equal(expectedUrl);
|
||||||
|
});
|
||||||
|
});
|
87
tests/utils/helpers.spec.ts
Normal file
87
tests/utils/helpers.spec.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2020 Balena Ltd.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
import { getProxyConfig } from '../../build/utils/helpers';
|
||||||
|
|
||||||
|
describe('getProxyConfig() function', function() {
|
||||||
|
let originalProxyConfig: [boolean, object | undefined];
|
||||||
|
let originalHttpProxy: [boolean, string | undefined];
|
||||||
|
let originalHttpsProxy: [boolean, string | undefined];
|
||||||
|
|
||||||
|
this.beforeEach(() => {
|
||||||
|
originalProxyConfig = [
|
||||||
|
global.hasOwnProperty('PROXY_CONFIG'),
|
||||||
|
(global as any).PROXY_CONFIG,
|
||||||
|
];
|
||||||
|
originalHttpProxy = [
|
||||||
|
process.env.hasOwnProperty('HTTP_PROXY'),
|
||||||
|
process.env.HTTP_PROXY,
|
||||||
|
];
|
||||||
|
originalHttpsProxy = [
|
||||||
|
process.env.hasOwnProperty('HTTPS_PROXY'),
|
||||||
|
process.env.HTTPS_PROXY,
|
||||||
|
];
|
||||||
|
delete (global as any).PROXY_CONFIG;
|
||||||
|
delete process.env.HTTP_PROXY;
|
||||||
|
delete process.env.HTTPS_PROXY;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.afterEach(() => {
|
||||||
|
if (originalProxyConfig[0]) {
|
||||||
|
(global as any).PROXY_CONFIG = originalProxyConfig[1];
|
||||||
|
}
|
||||||
|
if (originalHttpProxy[0]) {
|
||||||
|
process.env.HTTP_PROXY = originalHttpProxy[1];
|
||||||
|
}
|
||||||
|
if (originalHttpsProxy[0]) {
|
||||||
|
process.env.HTTPS_PROXY = originalHttpsProxy[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a ProxyConfig object when global-tunnel-ng is in use', () => {
|
||||||
|
(global as any).PROXY_CONFIG = {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 8080,
|
||||||
|
proxyAuth: 'bob:secret',
|
||||||
|
protocol: 'http:',
|
||||||
|
connect: 'https',
|
||||||
|
};
|
||||||
|
const expectedProxyConfig = {
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: '8080',
|
||||||
|
proxyAuth: 'bob:secret',
|
||||||
|
username: 'bob',
|
||||||
|
password: 'secret',
|
||||||
|
};
|
||||||
|
expect(getProxyConfig()).to.deep.equal(expectedProxyConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a ProxyConfig object when the HTTP(S)_PROXY env vars are defined', () => {
|
||||||
|
process.env.HTTPS_PROXY = 'http://bob:secret@proxy.company.com:12345';
|
||||||
|
process.env.HTTP_PROXY = 'http://my.net:8080';
|
||||||
|
const expectedProxyConfig = {
|
||||||
|
host: 'proxy.company.com',
|
||||||
|
port: '12345',
|
||||||
|
proxyAuth: 'bob:secret',
|
||||||
|
username: 'bob',
|
||||||
|
password: 'secret',
|
||||||
|
};
|
||||||
|
expect(getProxyConfig()).to.deep.equal(expectedProxyConfig);
|
||||||
|
});
|
||||||
|
});
|
Reference in New Issue
Block a user