Skip to content

Commit 3051799

Browse files
feat: add support for NO_PROXY (#359)
Co-authored-by: Florian Greinacher <[email protected]> Co-authored-by: Julian Beisert <[email protected]>
1 parent 0ad008e commit 3051799

File tree

4 files changed

+186
-27
lines changed

4 files changed

+186
-27
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ Create a [personal access token](https://docs.gitlab.com/ce/user/profile/persona
5858
| `GL_URL` or `GITLAB_URL` | The GitLab endpoint. |
5959
| `GL_PREFIX` or `GITLAB_PREFIX` | The GitLab API prefix. |
6060
| `HTTP_PROXY` or `HTTPS_PROXY` | HTTP or HTTPS proxy to use. |
61+
| `NO_PROXY` | Patterns for which the proxy should be ignored. See [details below](#proxy-configuration). |
6162

6263
#### Proxy configuration
6364

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

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

73+
If you need to bypass the proxy for some hosts, configure the `NO_PROXY` environment variable: `NO_PROXY=*.host.com, host.com`
74+
7275
### Options
7376

7477
| Option | Description | Default |

lib/resolve-config.js

Lines changed: 83 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = (
1818
GITLAB_PREFIX,
1919
HTTP_PROXY,
2020
HTTPS_PROXY,
21+
NO_PROXY,
2122
},
2223
}
2324
) => {
@@ -44,15 +45,74 @@ module.exports = (
4445
assets: assets ? castArray(assets) : assets,
4546
milestones: milestones ? castArray(milestones) : milestones,
4647
successComment,
47-
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY),
48+
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
4849
failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle,
4950
failComment,
5051
labels: isNil(labels) ? 'semantic-release' : labels === false ? false : labels,
5152
assignee,
5253
};
5354
};
5455

55-
function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY) {
56+
// Copied from Rob Wu's great proxy-from-env library: https://github.com/Rob--W/proxy-from-env/blob/96d01f8fcfdccfb776735751132930bbf79c4a3a/index.js#L62
57+
function shouldProxy(gitlabUrl, NO_PROXY) {
58+
const DEFAULT_PORTS = {
59+
ftp: 21,
60+
gopher: 70,
61+
http: 80,
62+
https: 443,
63+
ws: 80,
64+
wss: 443,
65+
};
66+
const parsedUrl = typeof gitlabUrl === 'string' ? new URL(gitlabUrl) : gitlabUrl || {};
67+
let proto = parsedUrl.protocol;
68+
let hostname = parsedUrl.host;
69+
let {port} = parsedUrl;
70+
if (typeof hostname !== 'string' || !hostname || typeof proto !== 'string') {
71+
return ''; // Don't proxy URLs without a valid scheme or host.
72+
}
73+
74+
proto = proto.split(':', 1)[0];
75+
// Stripping ports in this way instead of using parsedUrl.hostname to make
76+
// sure that the brackets around IPv6 addresses are kept.
77+
hostname = hostname.replace(/:\d*$/, '');
78+
port = parseInt(port, 10) || DEFAULT_PORTS[proto] || 0;
79+
80+
if (!NO_PROXY) {
81+
return true; // Always proxy if NO_PROXY is not set.
82+
}
83+
84+
if (NO_PROXY === '*') {
85+
return false; // Never proxy if wildcard is set.
86+
}
87+
88+
return NO_PROXY.split(/[,\s]/).every(function(proxy) {
89+
if (!proxy) {
90+
return true; // Skip zero-length hosts.
91+
}
92+
93+
const parsedProxy = proxy.match(/^(.+):(\d+)$/);
94+
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
95+
const parsedProxyPort = parsedProxy ? parseInt(parsedProxy[2], 10) : 0;
96+
if (parsedProxyPort && parsedProxyPort !== port) {
97+
return true; // Skip if ports don't match.
98+
}
99+
100+
if (!/^[.*]/.test(parsedProxyHostname)) {
101+
// No wildcards, so stop proxying if there is an exact match.
102+
return hostname !== parsedProxyHostname;
103+
}
104+
105+
if (parsedProxyHostname.charAt(0) === '*') {
106+
// Remove leading wildcard.
107+
parsedProxyHostname = parsedProxyHostname.slice(1);
108+
}
109+
110+
// Stop proxying if the hostname ends with the no_proxy host.
111+
return !hostname.endsWith(parsedProxyHostname);
112+
});
113+
}
114+
115+
function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY) {
56116
const sharedParameters = {
57117
keepAlive: true,
58118
keepAliveMsecs: 1000,
@@ -61,26 +121,28 @@ function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY) {
61121
scheduling: 'lifo',
62122
};
63123

64-
if (HTTP_PROXY && gitlabUrl.startsWith('http://')) {
65-
return {
66-
agent: {
67-
http: new HttpProxyAgent({
68-
...sharedParameters,
69-
proxy: HTTP_PROXY,
70-
}),
71-
},
72-
};
73-
}
124+
if (shouldProxy(gitlabUrl, NO_PROXY)) {
125+
if (HTTP_PROXY && gitlabUrl.startsWith('http://')) {
126+
return {
127+
agent: {
128+
http: new HttpProxyAgent({
129+
...sharedParameters,
130+
proxy: HTTP_PROXY,
131+
}),
132+
},
133+
};
134+
}
74135

75-
if (HTTPS_PROXY && gitlabUrl.startsWith('https://')) {
76-
return {
77-
agent: {
78-
https: new HttpsProxyAgent({
79-
...sharedParameters,
80-
proxy: HTTPS_PROXY,
81-
}),
82-
},
83-
};
136+
if (HTTPS_PROXY && gitlabUrl.startsWith('https://')) {
137+
return {
138+
agent: {
139+
https: new HttpsProxyAgent({
140+
...sharedParameters,
141+
proxy: HTTPS_PROXY,
142+
}),
143+
},
144+
};
145+
}
84146
}
85147

86148
return {};

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@
7575
},
7676
"prettier": {
7777
"printWidth": 120,
78-
"trailingComma": "es5"
78+
"trailingComma": "es5",
79+
"singleQuote": true,
80+
"bracketSpacing": false
7981
},
8082
"publishConfig": {
8183
"access": "public"

test/resolve-config.test.js

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ test('Returns user config via alternative environment variables with https proxy
121121
const gitlabApiPathPrefix = '/api/prefix';
122122
const assets = ['file.js'];
123123
// Testing with 8443 port because HttpsProxyAgent ignores 443 port with https protocol
124-
const proxyUrl = 'https://proxy.test:8443';
124+
const proxyUrl = 'http://proxy.test:8443';
125125

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

152150
// HTTP GitLab URL and HTTPS_PROXY set
153151
t.deepEqual(
@@ -158,7 +156,7 @@ test('Returns user config via alternative environment variables with mismatching
158156
GL_TOKEN: gitlabToken,
159157
GL_URL: httpGitlabUrl,
160158
GL_PREFIX: gitlabApiPathPrefix,
161-
HTTPS_PROXY: proxyUrl,
159+
HTTPS_PROXY: 'https://proxy.test:8443',
162160
},
163161
}
164162
),
@@ -180,7 +178,7 @@ test('Returns user config via alternative environment variables with mismatching
180178
GL_TOKEN: gitlabToken,
181179
GL_URL: gitlabUrl,
182180
GL_PREFIX: gitlabApiPathPrefix,
183-
HTTP_PROXY: httpProxyUrl,
181+
HTTP_PROXY: 'http://proxy.test:8443',
184182
},
185183
}
186184
),
@@ -193,6 +191,100 @@ test('Returns user config via alternative environment variables with mismatching
193191
}
194192
);
195193
});
194+
test('Returns user config via environment variables with HTTP_PROXY and NO_PROXY set', t => {
195+
const gitlabToken = 'TOKEN';
196+
const gitlabUrl = 'http://host.com';
197+
const gitlabApiPathPrefix = '/api/prefix';
198+
const assets = ['file.js'];
199+
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
200+
const proxyUrl = 'http://proxy.test:8080';
201+
202+
const result = resolveConfig(
203+
{assets},
204+
{
205+
env: {
206+
GL_TOKEN: gitlabToken,
207+
GL_URL: gitlabUrl,
208+
GL_PREFIX: gitlabApiPathPrefix,
209+
HTTP_PROXY: proxyUrl,
210+
NO_PROXY: '*.host.com, host.com',
211+
},
212+
}
213+
);
214+
215+
t.deepEqual(result.proxy, {});
216+
});
217+
218+
test('Returns user config via environment variables with HTTPS_PROXY and NO_PROXY set', t => {
219+
const gitlabToken = 'TOKEN';
220+
const gitlabUrl = 'https://host.com';
221+
const gitlabApiPathPrefix = '/api/prefix';
222+
const assets = ['file.js'];
223+
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
224+
const proxyUrl = 'http://proxy.test:8080';
225+
226+
const result = resolveConfig(
227+
{assets},
228+
{
229+
env: {
230+
GL_TOKEN: gitlabToken,
231+
GL_URL: gitlabUrl,
232+
GL_PREFIX: gitlabApiPathPrefix,
233+
HTTPS_PROXY: proxyUrl,
234+
NO_PROXY: '*.host.com, host.com',
235+
},
236+
}
237+
);
238+
t.deepEqual(result.proxy, {});
239+
});
240+
241+
test('Returns user config via environment variables with HTTPS_PROXY and non-matching NO_PROXY set', t => {
242+
const gitlabToken = 'TOKEN';
243+
const gitlabUrl = 'https://host.com';
244+
const gitlabApiPathPrefix = '/api/prefix';
245+
const assets = ['file.js'];
246+
// Testing with 8443 port because HttpsProxyAgent ignores 443 port with http protocol
247+
const proxyUrl = 'https://proxy.test:8443';
248+
process.env.HTTPS_PROXY = proxyUrl;
249+
process.env.NO_PROXY = '*.differenthost.com, differenthost.com';
250+
251+
const result = resolveConfig(
252+
{assets},
253+
{
254+
env: {
255+
GL_TOKEN: gitlabToken,
256+
GL_URL: gitlabUrl,
257+
GL_PREFIX: gitlabApiPathPrefix,
258+
HTTPS_PROXY: proxyUrl,
259+
NO_PROXY: '*.differenthost.com, differenthost.com',
260+
},
261+
}
262+
);
263+
t.assert(result.proxy.agent.https instanceof HttpsProxyAgent);
264+
});
265+
266+
test('Returns user config via environment variables with HTTP_PROXY and non-matching NO_PROXY set', t => {
267+
const gitlabToken = 'TOKEN';
268+
const gitlabUrl = 'http://host.com';
269+
const gitlabApiPathPrefix = '/api/prefix';
270+
const assets = ['file.js'];
271+
// Testing with 8080 port because HttpsProxyAgent ignores 80 port with http protocol
272+
const proxyUrl = 'http://proxy.test:8080';
273+
274+
const result = resolveConfig(
275+
{assets},
276+
{
277+
env: {
278+
GL_TOKEN: gitlabToken,
279+
GL_URL: gitlabUrl,
280+
GL_PREFIX: gitlabApiPathPrefix,
281+
HTTP_PROXY: proxyUrl,
282+
NO_PROXY: '*.differenthost.com, differenthost.com',
283+
},
284+
}
285+
);
286+
t.assert(result.proxy.agent.http instanceof HttpProxyAgent);
287+
});
196288

197289
test('Returns default config', t => {
198290
const gitlabToken = 'TOKEN';

0 commit comments

Comments
 (0)