From c47bf9358bd0f483a93efbf1dd76f0f6612db18c Mon Sep 17 00:00:00 2001 From: Ian Johnson Date: Fri, 3 Feb 2023 22:09:27 -0500 Subject: [PATCH] feat: support the no_proxy environment variable Closes #563 BREAKING CHANGE: The `no_proxy` and `NO_PROXY` environment variables are now respected when obtaining proxy configuration from the environment, bypassing the proxy when they match the GitHub host. This does not apply when [`proxy`](https://github.com/semantic-release/github#proxy) is explicitly provided in the plugin configuration. --- lib/resolve-config.js | 41 +++++++++++++++++++---------------- lib/resolve-proxy.js | 24 +++++++++++++++++++++ test/resolve-proxy.test.js | 44 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 18 deletions(-) create mode 100644 lib/resolve-proxy.js create mode 100644 test/resolve-proxy.test.js diff --git a/lib/resolve-config.js b/lib/resolve-config.js index 91b85a84c..eed1928c4 100644 --- a/lib/resolve-config.js +++ b/lib/resolve-config.js @@ -1,4 +1,5 @@ const {isNil, castArray} = require('lodash'); +const resolveProxy = require('./resolve-proxy'); module.exports = ( { @@ -15,21 +16,25 @@ module.exports = ( addReleases, }, {env} -) => ({ - githubToken: env.GH_TOKEN || env.GITHUB_TOKEN, - githubUrl: githubUrl || env.GITHUB_API_URL || env.GH_URL || env.GITHUB_URL, - githubApiPathPrefix: githubApiPathPrefix || env.GH_PREFIX || env.GITHUB_PREFIX || '', - proxy: isNil(proxy) ? env.http_proxy || env.HTTP_PROXY || false : proxy, - assets: assets ? castArray(assets) : assets, - successComment, - failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle, - failComment, - labels: isNil(labels) ? ['semantic-release'] : labels === false ? false : castArray(labels), - assignees: assignees ? castArray(assignees) : assignees, - releasedLabels: isNil(releasedLabels) - ? [`released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>`] - : releasedLabels === false - ? false - : castArray(releasedLabels), - addReleases: isNil(addReleases) ? false : addReleases, -}); +) => { + githubUrl ||= env.GITHUB_API_URL || env.GH_URL || env.GITHUB_URL; + + return { + githubToken: env.GH_TOKEN || env.GITHUB_TOKEN, + githubUrl, + githubApiPathPrefix: githubApiPathPrefix || env.GH_PREFIX || env.GITHUB_PREFIX || '', + proxy: isNil(proxy) ? resolveProxy(githubUrl, env) : proxy, + assets: assets ? castArray(assets) : assets, + successComment, + failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle, + failComment, + labels: isNil(labels) ? ['semantic-release'] : labels === false ? false : castArray(labels), + assignees: assignees ? castArray(assignees) : assignees, + releasedLabels: isNil(releasedLabels) + ? [`released<%= nextRelease.channel ? \` on @\${nextRelease.channel}\` : "" %>`] + : releasedLabels === false + ? false + : castArray(releasedLabels), + addReleases: isNil(addReleases) ? false : addReleases, + }; +}; diff --git a/lib/resolve-proxy.js b/lib/resolve-proxy.js new file mode 100644 index 000000000..25c05ab3f --- /dev/null +++ b/lib/resolve-proxy.js @@ -0,0 +1,24 @@ +module.exports = (githubUrl, env) => { + githubUrl ||= 'https://api.github.com'; + const proxy = env.http_proxy || env.HTTP_PROXY || false; + const noProxy = env.no_proxy || env.NO_PROXY; + + if (proxy && noProxy) { + const {hostname} = new URL(githubUrl); + for (let noProxyHost of noProxy.split(',')) { + if (noProxyHost === '*') { + return false; + } + + if (noProxyHost.startsWith('.')) { + noProxyHost = noProxyHost.slice(1); + } + + if (hostname === noProxyHost || hostname.endsWith('.' + noProxyHost)) { + return false; + } + } + } + + return proxy; +}; diff --git a/test/resolve-proxy.test.js b/test/resolve-proxy.test.js new file mode 100644 index 000000000..0f6f8c73b --- /dev/null +++ b/test/resolve-proxy.test.js @@ -0,0 +1,44 @@ +const test = require('ava'); +const resolveProxy = require('../lib/resolve-proxy'); + +test('Resolve proxy with no proxy configuration', (t) => { + t.is(resolveProxy(undefined, {}), false); +}); + +test('Resolve proxy with no exclusions', (t) => { + t.is(resolveProxy(undefined, {http_proxy: 'proxy.example.com'}), 'proxy.example.com'); +}); + +test('Resolve proxy with no matching exclusion', (t) => { + t.is( + resolveProxy(undefined, {http_proxy: 'proxy.example.com', no_proxy: 'notgithub.com,.example.org,example.net'}), + 'proxy.example.com' + ); +}); + +test('Resolve proxy with matching exclusion', (t) => { + t.is(resolveProxy(undefined, {http_proxy: 'proxy.example.com', no_proxy: 'github.com'}), false); +}); + +test('Resolve proxy with matching exclusion (leading .)', (t) => { + t.is(resolveProxy(undefined, {http_proxy: 'proxy.example.com', no_proxy: '.github.com'}), false); +}); + +test('Resolve proxy with global exclusion', (t) => { + t.is(resolveProxy(undefined, {http_proxy: 'proxy.example.com', no_proxy: '*'}), false); +}); + +test('Resolve proxy with matching GitHub Enterprise exclusion', (t) => { + t.is( + resolveProxy('https://github.example.com/api/v3', {http_proxy: 'proxy.example.com', no_proxy: 'example.com'}), + false + ); +}); + +test('Resolve proxy with uppercase environment variables', (t) => { + t.is(resolveProxy(undefined, {HTTP_PROXY: 'proxy.example.com', NO_PROXY: 'github.com'}), false); + t.is( + resolveProxy(undefined, {HTTP_PROXY: 'proxy.example.com', NO_PROXY: 'subdomain.github.com'}), + 'proxy.example.com' + ); +});