Skip to content

Commit

Permalink
feat: add support for NO_PROXY (#359)
Browse files Browse the repository at this point in the history
Co-authored-by: Florian Greinacher <florian@greinacher.de>
Co-authored-by: Julian Beisert <julianbeisert@googlemail.com>
  • Loading branch information
3 people committed Apr 11, 2022
1 parent 0ad008e commit 3051799
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 27 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -58,6 +58,7 @@ Create a [personal access token](https://docs.gitlab.com/ce/user/profile/persona
| `GL_URL` or `GITLAB_URL` | The GitLab endpoint. |
| `GL_PREFIX` or `GITLAB_PREFIX` | The GitLab API prefix. |
| `HTTP_PROXY` or `HTTPS_PROXY` | HTTP or HTTPS proxy to use. |
| `NO_PROXY` | Patterns for which the proxy should be ignored. See [details below](#proxy-configuration). |

#### Proxy configuration

Expand All @@ -69,6 +70,8 @@ If your proxy server requires authentication embed the username and password in

If your GitLab instance is exposed via plain HTTP (not recommended!) use `HTTP_PROXY` instead.

If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environment variable: `NO_PROXY=*.host.com, host.com`

### Options

| Option | Description | Default |
Expand Down
104 changes: 83 additions & 21 deletions lib/resolve-config.js
Expand Up @@ -18,6 +18,7 @@ module.exports = (
GITLAB_PREFIX,
HTTP_PROXY,
HTTPS_PROXY,
NO_PROXY,
},
}
) => {
Expand All @@ -44,15 +45,74 @@ module.exports = (
assets: assets ? castArray(assets) : assets,
milestones: milestones ? castArray(milestones) : milestones,
successComment,
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY),
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle,
failComment,
labels: isNil(labels) ? 'semantic-release' : labels === false ? false : labels,
assignee,
};
};

function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY) {
// Copied from Rob Wu's great proxy-from-env library: https://github.com/Rob--W/proxy-from-env/blob/96d01f8fcfdccfb776735751132930bbf79c4a3a/index.js#L62
function shouldProxy(gitlabUrl, NO_PROXY) {
const DEFAULT_PORTS = {
ftp: 21,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443,
};
const parsedUrl = typeof gitlabUrl === 'string' ? new URL(gitlabUrl) : gitlabUrl || {};
let proto = parsedUrl.protocol;
let hostname = parsedUrl.host;
let {port} = parsedUrl;
if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') {
return ''; // Don't proxy URLs without a valid scheme or host.
}

proto = proto.split(':', 1)[0];
// Stripping ports in this way instead of using parsedUrl.hostname to make
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, '');
port = parseInt(port, 10) || DEFAULT_PORTS[proto] || 0;

if (!NO_PROXY) {
return true; // Always proxy if NO_PROXY is not set.
}

if (NO_PROXY === '*') {
return false; // Never proxy if wildcard is set.
}

return NO_PROXY.split(/[,\s]/).every(function(proxy) {
if (!proxy) {
return true; // Skip zero-length hosts.
}

const parsedProxy = proxy.match(/^(.+):(\d+)$/);
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2], 10) : 0;
if (parsedProxyPort && parsedProxyPort !== port) {
return true; // Skip if ports don't match.
}

if (!/^[.*]/.test(parsedProxyHostname)) {
// No wildcards, so stop proxying if there is an exact match.
return hostname !== parsedProxyHostname;
}

if (parsedProxyHostname.charAt(0) === '*') {
// Remove leading wildcard.
parsedProxyHostname = parsedProxyHostname.slice(1);
}

// Stop proxying if the hostname ends with the no_proxy host.
return !hostname.endsWith(parsedProxyHostname);
});
}

function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY) {
const sharedParameters = {
keepAlive: true,
keepAliveMsecs: 1000,
Expand All @@ -61,26 +121,28 @@ function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY) {
scheduling: 'lifo',
};

if (HTTP_PROXY && gitlabUrl.startsWith('http://')) {
return {
agent: {
http: new HttpProxyAgent({
...sharedParameters,
proxy: HTTP_PROXY,
}),
},
};
}
if (shouldProxy(gitlabUrl, NO_PROXY)) {
if (HTTP_PROXY && gitlabUrl.startsWith('http://')) {
return {
agent: {
http: new HttpProxyAgent({
...sharedParameters,
proxy: HTTP_PROXY,
}),
},
};
}

if (HTTPS_PROXY && gitlabUrl.startsWith('https://')) {
return {
agent: {
https: new HttpsProxyAgent({
...sharedParameters,
proxy: HTTPS_PROXY,
}),
},
};
if (HTTPS_PROXY && gitlabUrl.startsWith('https://')) {
return {
agent: {
https: new HttpsProxyAgent({
...sharedParameters,
proxy: HTTPS_PROXY,
}),
},
};
}
}

return {};
Expand Down
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -75,7 +75,9 @@
},
"prettier": {
"printWidth": 120,
"trailingComma": "es5"
"trailingComma": "es5",
"singleQuote": true,
"bracketSpacing": false
},
"publishConfig": {
"access": "public"
Expand Down
102 changes: 97 additions & 5 deletions test/resolve-config.test.js
Expand Up @@ -121,7 +121,7 @@ test('Returns user config via alternative environment variables with https proxy
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8443 port because HttpsProxyAgent ignores 443 port with https protocol
const proxyUrl = 'https://proxy.test:8443';
const proxyUrl = 'http://proxy.test:8443';

const result = resolveConfig(
{assets},
Expand All @@ -146,8 +146,6 @@ test('Returns user config via alternative environment variables with mismatching
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8443 port because HttpsProxyAgent ignores 443 port with https protocol
const httpProxyUrl = 'http://proxy.test:8443';
const proxyUrl = 'https://proxy.test:8443';

// HTTP GitLab URL and HTTPS_PROXY set
t.deepEqual(
Expand All @@ -158,7 +156,7 @@ test('Returns user config via alternative environment variables with mismatching
GL_TOKEN: gitlabToken,
GL_URL: httpGitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTPS_PROXY: proxyUrl,
HTTPS_PROXY: 'https://proxy.test:8443',
},
}
),
Expand All @@ -180,7 +178,7 @@ test('Returns user config via alternative environment variables with mismatching
GL_TOKEN: gitlabToken,
GL_URL: gitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTP_PROXY: httpProxyUrl,
HTTP_PROXY: 'http://proxy.test:8443',
},
}
),
Expand All @@ -193,6 +191,100 @@ test('Returns user config via alternative environment variables with mismatching
}
);
});
test('Returns user config via environment variables with HTTP_PROXY and NO_PROXY set', t => {
const gitlabToken = 'TOKEN';
const gitlabUrl = 'http://host.com';
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
const proxyUrl = 'http://proxy.test:8080';

const result = resolveConfig(
{assets},
{
env: {
GL_TOKEN: gitlabToken,
GL_URL: gitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTP_PROXY: proxyUrl,
NO_PROXY: '*.host.com, host.com',
},
}
);

t.deepEqual(result.proxy, {});
});

test('Returns user config via environment variables with HTTPS_PROXY and NO_PROXY set', t => {
const gitlabToken = 'TOKEN';
const gitlabUrl = 'https://host.com';
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
const proxyUrl = 'http://proxy.test:8080';

const result = resolveConfig(
{assets},
{
env: {
GL_TOKEN: gitlabToken,
GL_URL: gitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTPS_PROXY: proxyUrl,
NO_PROXY: '*.host.com, host.com',
},
}
);
t.deepEqual(result.proxy, {});
});

test('Returns user config via environment variables with HTTPS_PROXY and non-matching NO_PROXY set', t => {
const gitlabToken = 'TOKEN';
const gitlabUrl = 'https://host.com';
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8443 port because HttpsProxyAgent ignores 443 port with http protocol
const proxyUrl = 'https://proxy.test:8443';
process.env.HTTPS_PROXY = proxyUrl;
process.env.NO_PROXY = '*.differenthost.com, differenthost.com';

const result = resolveConfig(
{assets},
{
env: {
GL_TOKEN: gitlabToken,
GL_URL: gitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTPS_PROXY: proxyUrl,
NO_PROXY: '*.differenthost.com, differenthost.com',
},
}
);
t.assert(result.proxy.agent.https instanceof HttpsProxyAgent);
});

test('Returns user config via environment variables with HTTP_PROXY and non-matching NO_PROXY set', t => {
const gitlabToken = 'TOKEN';
const gitlabUrl = 'http://host.com';
const gitlabApiPathPrefix = '/api/prefix';
const assets = ['file.js'];
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
const proxyUrl = 'http://proxy.test:8080';

const result = resolveConfig(
{assets},
{
env: {
GL_TOKEN: gitlabToken,
GL_URL: gitlabUrl,
GL_PREFIX: gitlabApiPathPrefix,
HTTP_PROXY: proxyUrl,
NO_PROXY: '*.differenthost.com, differenthost.com',
},
}
);
t.assert(result.proxy.agent.http instanceof HttpProxyAgent);
});

test('Returns default config', t => {
const gitlabToken = 'TOKEN';
Expand Down

0 comments on commit 3051799

Please sign in to comment.