From 219950399eecd14eed40999e6fca09528899cc5f Mon Sep 17 00:00:00 2001
From: Ayoub Kaanich <kayoub5@live.com>
Date: Fri, 6 Jul 2018 07:26:36 +0200
Subject: [PATCH] feat: endpoints credentials handling

---
 lib/config/env.js                             | 33 ++++++-
 lib/config/index.js                           | 44 ++++++----
 lib/datasource/github.js                      | 20 -----
 lib/datasource/npm.js                         | 13 ++-
 lib/platform/github/gh-got-wrapper.js         | 12 ++-
 lib/platform/github/index.js                  | 28 +++---
 lib/platform/gitlab/gl-got-wrapper.js         | 13 ++-
 lib/platform/gitlab/index.js                  | 22 ++---
 lib/platform/vsts/index.js                    |  7 +-
 lib/platform/vsts/vsts-got-wrapper.js         | 15 ++--
 lib/platform/vsts/vsts-helper.js              | 21 -----
 lib/util/endpoints.js                         | 85 +++++++++++++++++++
 lib/workers/global/index.js                   |  7 ++
 lib/workers/pr/changelog/index.js             | 35 +-------
 lib/workers/pr/changelog/source-github.js     | 30 ++++---
 test/config/__snapshots__/env.spec.js.snap    | 73 ++++++++++++++++
 test/config/env.spec.js                       | 45 ++++++++--
 test/config/index.spec.js                     |  1 +
 test/platform/github/index.spec.js            |  6 --
 test/platform/gitlab/index.spec.js            |  9 +-
 test/platform/vsts/index.spec.js              | 20 ++++-
 test/platform/vsts/vsts-got-wrapper.spec.js   | 38 +++------
 test/platform/vsts/vsts-helper.spec.js        | 44 ----------
 .../util/__snapshots__/endpoints.spec.js.snap | 43 ++++++++++
 test/util/endpoints.spec.js                   | 62 ++++++++++++++
 test/workers/pr/changelog.spec.js             | 37 ++++----
 test/workers/repository/init/apis.spec.js     |  2 +-
 27 files changed, 489 insertions(+), 276 deletions(-)
 create mode 100644 lib/util/endpoints.js
 create mode 100644 test/config/__snapshots__/env.spec.js.snap
 create mode 100644 test/util/__snapshots__/endpoints.spec.js.snap
 create mode 100644 test/util/endpoints.spec.js

diff --git a/lib/config/env.js b/lib/config/env.js
index 68e0dea667..c8262067f3 100644
--- a/lib/config/env.js
+++ b/lib/config/env.js
@@ -19,7 +19,7 @@ function getEnvName(option) {
 function getConfig(env) {
   const options = configDefinitions.getOptions();
 
-  const config = {};
+  const config = { endpoints: [] };
 
   const coersions = {
     boolean: val => val === 'true',
@@ -39,5 +39,36 @@ function getConfig(env) {
     }
   });
 
+  if (env.GITHUB_COM_TOKEN) {
+    config.endpoints.push({
+      platform: 'github',
+      token: env.GITHUB_COM_TOKEN,
+    });
+  }
+  if (env.GITHUB_TOKEN) {
+    config.endpoints.push({
+      platform: 'github',
+      endpoint: env.GITHUB_ENDPOINT,
+      token: env.GITHUB_TOKEN,
+      default: true,
+    });
+  }
+
+  if (env.GITLAB_TOKEN) {
+    config.endpoints.push({
+      platform: 'gitlab',
+      endpoint: env.GITLAB_ENDPOINT,
+      token: env.GITLAB_TOKEN,
+    });
+  }
+
+  if (env.VSTS_ENDPOINT || env.VSTS_TOKEN) {
+    config.endpoints.push({
+      platform: 'vsts',
+      endpoint: env.VSTS_ENDPOINT,
+      token: env.GITLAB_TOKEN,
+    });
+  }
+
   return config;
 }
diff --git a/lib/config/index.js b/lib/config/index.js
index 186394e3bc..1c7f0403d3 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -12,6 +12,8 @@ const { getPlatformApi } = require('../platform');
 const { resolveConfigPresets } = require('./presets');
 const { get, getLanguageList, getManagerList } = require('../manager');
 
+const endpoints = require('../util/endpoints');
+
 exports.parseConfigs = parseConfigs;
 exports.mergeChildConfig = mergeChildConfig;
 exports.filterConfig = filterConfig;
@@ -78,27 +80,39 @@ async function parseConfigs(env, argv) {
   logger.trace({ config }, 'Raw config');
 
   // Check platforms and tokens
-  if (config.platform === 'github') {
-    if (!config.token && !env.GITHUB_TOKEN) {
-      throw new Error('You need to supply a GitHub token.');
-    }
-  } else if (config.platform === 'gitlab') {
-    if (!config.token && !env.GITLAB_TOKEN) {
-      throw new Error('You need to supply a GitLab token.');
-    }
-  } else if (config.platform === 'vsts') {
-    if (!config.token && !env.VSTS_TOKEN) {
-      throw new Error('You need to supply a VSTS token.');
-    }
-  } else {
+  const { platform, endpoint, token } = config;
+  const platformInfo = endpoints.defaults[platform];
+  if (!platformInfo) {
     throw new Error(`Unsupported platform: ${config.platform}.`);
   }
+  config.endpoints.forEach(endpoints.update);
+  delete config.endpoints;
+  delete config.token;
+
+  const credentials = endpoints.find(
+    { platform },
+    {
+      platform,
+      endpoint: endpoint || platformInfo.endpoint,
+      token,
+    }
+  );
+
+  // we don't need to check endpoint, endpoints.update({}) will do that
+  if (!credentials.token) {
+    throw new Error(`You need to supply a ${platformInfo.name} token.`);
+  }
+
+  endpoints.update({
+    ...credentials,
+    default: true,
+  });
 
   if (config.autodiscover) {
     // Autodiscover list of repositories
     const discovered = await getPlatformApi(config.platform).getRepos(
-      config.token,
-      config.endpoint
+      credentials.token,
+      credentials.endpoint
     );
     if (!(discovered && discovered.length)) {
       // Soft fail (no error thrown) if no accessible repositories
diff --git a/lib/datasource/github.js b/lib/datasource/github.js
index 4418198c11..365470b61f 100644
--- a/lib/datasource/github.js
+++ b/lib/datasource/github.js
@@ -9,19 +9,6 @@ async function getDependency(purl, config) {
   const { versionScheme } = config || {};
   const { fullname: repo, qualifiers: options } = purl;
   let versions;
-  let endpoint;
-  let token;
-  // istanbul ignore if
-  if (
-    process.env.GITHUB_ENDPOINT &&
-    !process.env.GITHUB_ENDPOINT.startsWith('https://api.github.com')
-  ) {
-    logger.debug('Removing GHE token before retrieving node releases');
-    endpoint = process.env.GITHUB_ENDPOINT;
-    delete process.env.GITHUB_ENDPOINT;
-    token = process.env.GITHUB_TOKEN;
-    process.env.GITHUB_TOKEN = process.env.GITHUB_COM_TOKEN;
-  }
   try {
     if (options.ref === 'release') {
       const url = `repos/${repo}/releases?per_page=100`;
@@ -38,13 +25,6 @@ async function getDependency(purl, config) {
       { repo, err, message: err.message },
       'Error retrieving from github'
     );
-  } finally {
-    // istanbul ignore if
-    if (endpoint) {
-      logger.debug('Restoring GHE token and endpoint');
-      process.env.GITHUB_TOKEN = token;
-      process.env.GITHUB_ENDPOINT = endpoint;
-    }
   }
   if (!versions) {
     return null;
diff --git a/lib/datasource/npm.js b/lib/datasource/npm.js
index 7ecf1e4127..00b34b300a 100644
--- a/lib/datasource/npm.js
+++ b/lib/datasource/npm.js
@@ -9,6 +9,7 @@ const registryAuthToken = require('registry-auth-token');
 const parse = require('github-url-from-git');
 const { isBase64 } = require('validator');
 const { isVersion, sortVersions } = require('../versioning')('semver');
+const endpoints = require('../util/endpoints');
 
 module.exports = {
   maskToken,
@@ -174,14 +175,10 @@ async function getDependencyInner(name, retries = 5) {
 
     if (res.repository && res.repository.url) {
       const extraBaseUrls = [];
-      // istanbul ignore if
-      if (process.env.GITHUB_ENDPOINT) {
-        const parsedEndpoint = url.parse(process.env.GITHUB_ENDPOINT);
-        extraBaseUrls.push(
-          parsedEndpoint.hostname,
-          `gist.${parsedEndpoint.hostname}`
-        );
-      }
+      // istanbul ignore next
+      endpoints.hosts({ platform: 'github' }).forEach(host => {
+        extraBaseUrls.push(host, `gist.${host}`);
+      });
       // Massage www out of github URL
       res.repository.url = res.repository.url.replace(
         'www.github.com',
diff --git a/lib/platform/github/gh-got-wrapper.js b/lib/platform/github/gh-got-wrapper.js
index 9ac5d79e6a..ca8c82de86 100644
--- a/lib/platform/github/gh-got-wrapper.js
+++ b/lib/platform/github/gh-got-wrapper.js
@@ -2,13 +2,17 @@ const URL = require('url');
 const ghGot = require('gh-got');
 const delay = require('delay');
 const parseLinkHeader = require('parse-link-header');
+const endpoints = require('../../util/endpoints');
 
 let cache = {};
 
-async function get(path, opts, retries = 5) {
-  /* eslint-disable no-param-reassign */
-  opts = Object.assign({}, opts);
-  const method = opts.method ? opts.method : 'get';
+async function get(path, options, retries = 5) {
+  const { host } = URL.parse(path);
+  const opts = {
+    ...endpoints.find({ platform: 'github', host }),
+    ...options,
+  };
+  const method = opts.method || 'get';
   const useCache = opts.useCache || true;
   if (method === 'get' && useCache && cache[path]) {
     logger.trace({ path }, 'Returning cached result');
diff --git a/lib/platform/github/index.js b/lib/platform/github/index.js
index e27e4dbe89..3955ba03a6 100644
--- a/lib/platform/github/index.js
+++ b/lib/platform/github/index.js
@@ -5,6 +5,7 @@ const moment = require('moment');
 const openpgp = require('openpgp');
 const delay = require('delay');
 const path = require('path');
+const endpoints = require('../../util/endpoints');
 
 let config = {};
 
@@ -54,15 +55,11 @@ module.exports = {
 // Get all repositories that the user has access to
 async function getRepos(token, endpoint) {
   logger.info('Autodiscovering GitHub repositories');
-  logger.debug('getRepos(token, endpoint)');
-  if (token) {
-    process.env.GITHUB_TOKEN = token;
-  } else if (!process.env.GITHUB_TOKEN) {
+  const opts = endpoints.find({ platform: 'github' }, { token, endpoint });
+  if (!opts.token) {
     throw new Error('No token found for getRepos');
   }
-  if (endpoint) {
-    process.env.GITHUB_ENDPOINT = endpoint;
-  }
+  endpoints.update({ ...opts, platform: 'github', default: true });
   try {
     const res = await get('user/repos', { paginate: true });
     return res.body.map(repo => repo.full_name);
@@ -100,16 +97,11 @@ async function initRepo({
   gitPrivateKey,
 }) {
   logger.debug(`initRepo("${repository}")`);
-  if (token) {
-    logger.debug('Setting token in env for use by gh-got');
-    process.env.GITHUB_TOKEN = token;
-  } else if (!process.env.GITHUB_TOKEN) {
+  const opts = endpoints.find({ platform: 'github' }, { token, endpoint });
+  if (!opts.token) {
     throw new Error(`No token found for GitHub repository ${repository}`);
   }
-  if (endpoint) {
-    logger.debug('Setting endpoint in env for use by gh-got');
-    process.env.GITHUB_ENDPOINT = endpoint;
-  }
+  endpoints.update({ ...opts, platform: 'github', default: true });
   logger.debug('Resetting platform config');
   // config is used by the platform api itself, not necessary for the app layer to know
   cleanRepo();
@@ -208,11 +200,11 @@ async function initRepo({
     config.repository = null;
     // Get list of existing repos
     const existingRepos = (await get('user/repos?per_page=100', {
-      token: forkToken || process.env.GITHUB_TOKEN,
+      token: forkToken || opts.token,
       paginate: true,
     })).body.map(r => r.full_name);
     config.repository = (await get.post(`repos/${repository}/forks`, {
-      token: forkToken || process.env.GITHUB_TOKEN,
+      token: forkToken || opts.token,
     })).body.full_name;
     if (existingRepos.includes(config.repository)) {
       logger.info(
@@ -232,7 +224,7 @@ async function initRepo({
           body: {
             sha: config.parentSha,
           },
-          token: forkToken || process.env.GITHUB_TOKEN,
+          token: forkToken || opts.token,
         }
       );
     } else {
diff --git a/lib/platform/gitlab/gl-got-wrapper.js b/lib/platform/gitlab/gl-got-wrapper.js
index 4e3b0a0db6..daae254205 100644
--- a/lib/platform/gitlab/gl-got-wrapper.js
+++ b/lib/platform/gitlab/gl-got-wrapper.js
@@ -1,12 +1,17 @@
+const URL = require('url');
 const glGot = require('gl-got');
 const parseLinkHeader = require('parse-link-header');
+const endpoints = require('../../util/endpoints');
 
 let cache = {};
 
-async function get(path, opts, retries = 5) {
-  /* eslint-disable no-param-reassign */
-  opts = Object.assign({}, opts);
-  const method = opts.method ? opts.method : 'get';
+async function get(path, options, retries = 5) {
+  const { host } = URL.parse(path);
+  const opts = {
+    ...endpoints.find({ platform: 'gitlab', host }),
+    ...options,
+  };
+  const method = opts.method || 'get';
   const useCache = opts.useCache || true;
   if (method === 'get' && useCache && cache[path]) {
     logger.debug({ path }, 'Returning cached result');
diff --git a/lib/platform/gitlab/index.js b/lib/platform/gitlab/index.js
index b5ac0303b8..21f1dd769e 100644
--- a/lib/platform/gitlab/index.js
+++ b/lib/platform/gitlab/index.js
@@ -1,6 +1,7 @@
 const is = require('@sindresorhus/is');
 const get = require('./gl-got-wrapper');
 const addrs = require('email-addresses');
+const endpoints = require('../../util/endpoints');
 
 let config = {};
 
@@ -50,14 +51,11 @@ module.exports = {
 async function getRepos(token, endpoint) {
   logger.info('Autodiscovering GitLab repositories');
   logger.debug('getRepos(token, endpoint)');
-  if (token) {
-    process.env.GITLAB_TOKEN = token;
-  } else if (!process.env.GITLAB_TOKEN) {
+  const opts = endpoints.find({ platform: 'gitlab' }, { token, endpoint });
+  if (!opts.token) {
     throw new Error('No token found for getRepos');
   }
-  if (endpoint) {
-    process.env.GITLAB_ENDPOINT = endpoint;
-  }
+  endpoints.update({ ...opts, platform: 'gitlab', default: true });
   try {
     const url = `projects?membership=true&per_page=100`;
     const res = await get(url, { paginate: true });
@@ -75,17 +73,11 @@ function urlEscape(str) {
 
 // Initialize GitLab by getting base branch
 async function initRepo({ repository, token, endpoint, gitAuthor }) {
-  if (token) {
-    process.env.GITLAB_TOKEN = token;
-  } else if (!process.env.GITLAB_TOKEN) {
+  const opts = endpoints.find({ platform: 'gitlab' }, { token, endpoint });
+  if (!opts.token) {
     throw new Error(`No token found for GitLab repository ${repository}`);
   }
-  if (token) {
-    process.env.GITLAB_TOKEN = token;
-  }
-  if (endpoint) {
-    process.env.GITLAB_ENDPOINT = endpoint;
-  }
+  endpoints.update({ ...opts, platform: 'gitlab', default: true });
   config = {};
   get.reset();
   config.repository = urlEscape(repository);
diff --git a/lib/platform/vsts/index.js b/lib/platform/vsts/index.js
index f16c69959c..d0be6f6f7d 100644
--- a/lib/platform/vsts/index.js
+++ b/lib/platform/vsts/index.js
@@ -1,6 +1,7 @@
 // @ts-nocheck //because of logger, we can't ts-check
 const vstsHelper = require('./vsts-helper');
 const vstsApi = require('./vsts-got-wrapper');
+const endpoints = require('../../util/endpoints');
 
 const config = {};
 
@@ -50,7 +51,8 @@ module.exports = {
 async function getRepos(token, endpoint) {
   logger.info('Autodiscovering vsts repositories');
   logger.debug('getRepos(token, endpoint)');
-  vstsHelper.setTokenAndEndpoint(token, endpoint);
+  const opts = endpoints.find({ platform: 'vsts' }, { token, endpoint });
+  endpoints.update({ ...opts, platform: 'vsts', default: true });
   const vstsApiGit = await vstsApi.gitApi();
   const repos = await vstsApiGit.getRepositories();
   return repos.map(repo => `${repo.project.name}/${repo.name}`);
@@ -58,7 +60,8 @@ async function getRepos(token, endpoint) {
 
 async function initRepo({ repository, token, endpoint }) {
   logger.debug(`initRepo("${repository}")`);
-  vstsHelper.setTokenAndEndpoint(token, endpoint);
+  const opts = endpoints.find({ platform: 'vsts' }, { token, endpoint });
+  endpoints.update({ ...opts, platform: 'vsts', default: true });
   config.repository = repository;
   config.fileList = null;
   config.prList = null;
diff --git a/lib/platform/vsts/vsts-got-wrapper.js b/lib/platform/vsts/vsts-got-wrapper.js
index 0deb855996..bb49e3c5aa 100644
--- a/lib/platform/vsts/vsts-got-wrapper.js
+++ b/lib/platform/vsts/vsts-got-wrapper.js
@@ -1,4 +1,5 @@
 const vsts = require('vso-node-api');
+const endpoints = require('../../util/endpoints');
 
 module.exports = {
   vstsObj,
@@ -7,18 +8,12 @@ module.exports = {
 };
 
 function vstsObj() {
-  if (!process.env.VSTS_TOKEN) {
+  const config = endpoints.find({ platform: 'vsts' }, {});
+  if (!config.token) {
     throw new Error(`No token found for vsts`);
   }
-  if (!process.env.VSTS_ENDPOINT) {
-    throw new Error(
-      `You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)`
-    );
-  }
-  const authHandler = vsts.getPersonalAccessTokenHandler(
-    process.env.VSTS_TOKEN
-  );
-  return new vsts.WebApi(process.env.VSTS_ENDPOINT, authHandler);
+  const authHandler = vsts.getPersonalAccessTokenHandler(config.token);
+  return new vsts.WebApi(config.endpoint, authHandler);
 }
 
 function gitApi() {
diff --git a/lib/platform/vsts/vsts-helper.js b/lib/platform/vsts/vsts-helper.js
index 90f06992d9..5d83064593 100644
--- a/lib/platform/vsts/vsts-helper.js
+++ b/lib/platform/vsts/vsts-helper.js
@@ -3,7 +3,6 @@
 const vstsApi = require('./vsts-got-wrapper');
 
 module.exports = {
-  setTokenAndEndpoint,
   getBranchNameWithoutRefsheadsPrefix,
   getRefs,
   getVSTSBranchObj,
@@ -16,26 +15,6 @@ module.exports = {
   getProjectAndRepo,
 };
 
-/**
- *
- * @param {string} token
- * @param {string} endpoint
- */
-function setTokenAndEndpoint(token, endpoint) {
-  if (token) {
-    process.env.VSTS_TOKEN = token;
-  } else if (!process.env.VSTS_TOKEN) {
-    throw new Error(`No token found for vsts`);
-  }
-  if (endpoint) {
-    process.env.VSTS_ENDPOINT = endpoint;
-  } else {
-    throw new Error(
-      `You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)`
-    );
-  }
-}
-
 /**
  *
  * @param {string} branchName
diff --git a/lib/util/endpoints.js b/lib/util/endpoints.js
new file mode 100644
index 0000000000..b70c731374
--- /dev/null
+++ b/lib/util/endpoints.js
@@ -0,0 +1,85 @@
+const URL = require('url');
+
+const defaults = {
+  github: { name: 'GitHub', endpoint: 'https://api.github.com/' },
+  gitlab: { name: 'GitLab', endpoint: 'https://gitlab.com/api/v4/' },
+  vsts: { name: 'VSTS' },
+};
+
+module.exports = {
+  update,
+  find,
+  clear,
+  defaults,
+  hosts,
+};
+
+const platforms = {};
+
+function update(params) {
+  const { platform } = params;
+  if (!platform) {
+    throw new Error('Failed to set configuration: no platform specified');
+  }
+  const config = { ...defaults[platform], ...params };
+  const { endpoint } = config;
+  if (!endpoint) {
+    throw new Error(
+      `Failed to configure platform '${platform}': no endpoint defined`
+    );
+  }
+  let { host } = config;
+  // extract host from endpoint
+  host = host || (endpoint && URL.parse(endpoint).host);
+  // endpoint is in the format host/path (protocol missing)
+  host = host || (endpoint && URL.parse('http://' + endpoint).host);
+  if (!host) {
+    throw new Error(
+      `Failed to configure platform '${platform}': no host for endpoint '${endpoint}'`
+    );
+  }
+  platforms[platform] = { ...platforms[platform] };
+  if (config.default) {
+    for (const conf of Object.values(platforms[platform])) {
+      delete conf.default;
+    }
+  }
+  platforms[platform][host] = { ...platforms[platform][host], ...config };
+  return true;
+}
+
+function find({ platform, host }, overrides) {
+  if (!platforms[platform]) {
+    return merge(null, overrides);
+  }
+  if (host) {
+    return merge(platforms[platform][host], overrides);
+  }
+  const configs = Object.values(platforms[platform]);
+  let config = configs.find(c => c.default);
+  if (!config && configs.length === 1) {
+    [config] = configs;
+  }
+  return merge(config, overrides);
+}
+
+function hosts({ platform }) {
+  return Object.keys({ ...platforms[platform] });
+}
+
+function merge(config, overrides) {
+  if (!overrides) {
+    return config || null;
+  }
+  const locals = { ...overrides };
+  Object.keys(locals).forEach(key => {
+    if (locals[key] === undefined || locals[key] === null) {
+      delete locals[key];
+    }
+  });
+  return { ...config, ...locals };
+}
+
+function clear() {
+  Object.keys(platforms).forEach(key => delete platforms[key]);
+}
diff --git a/lib/workers/global/index.js b/lib/workers/global/index.js
index 8c5eef62ac..86eb4d9d03 100644
--- a/lib/workers/global/index.js
+++ b/lib/workers/global/index.js
@@ -12,6 +12,13 @@ async function start() {
   initLogger();
   try {
     const config = await configParser.parseConfigs(process.env, process.argv);
+    delete process.env.GITHUB_TOKEN;
+    delete process.env.GITHUB_ENDPOINT;
+    delete process.env.GITHUB_COM_TOKEN;
+    delete process.env.GITLAB_TOKEN;
+    delete process.env.GITLAB_ENDPOINT;
+    delete process.env.VSTS_TOKEN;
+    delete process.env.VSTS_ENDPOINT;
     if (config.repositories.length === 0) {
       logger.warn(
         'No repositories found - did you want to run with flag --autodiscover?'
diff --git a/lib/workers/pr/changelog/index.js b/lib/workers/pr/changelog/index.js
index 4815ac7b51..11a6953ef6 100644
--- a/lib/workers/pr/changelog/index.js
+++ b/lib/workers/pr/changelog/index.js
@@ -1,5 +1,3 @@
-const url = require('url');
-
 const versioning = require('../../../versioning');
 const { addReleaseNotes } = require('../release-notes');
 
@@ -27,32 +25,9 @@ async function getChangeLogJSON(args) {
     logger.debug('Returning cached changelog');
     return cachedResult;
   }
-  let token;
-  let endpoint;
-  let gheBaseURL;
-  let githubBaseURL = 'https://github.com/';
 
-  if (
-    process.env.GITHUB_ENDPOINT &&
-    !process.env.GITHUB_ENDPOINT.startsWith('https://api.github.com')
-  ) {
-    const parsedEndpoint = url.parse(process.env.GITHUB_ENDPOINT);
-    gheBaseURL = `${parsedEndpoint.protocol}//${parsedEndpoint.hostname}/`;
-    if (repositoryUrl.startsWith(gheBaseURL)) {
-      githubBaseURL = gheBaseURL;
-    } else {
-      // Switch tokens
-      token = process.env.GITHUB_TOKEN;
-      endpoint = process.env.GITHUB_ENDPOINT;
-      delete process.env.GITHUB_ENDPOINT;
-      process.env.GITHUB_TOKEN = process.env.GITHUB_COM_TOKEN;
-    }
-  }
   try {
-    const res = await sourceGithub.getChangeLogJSON({
-      ...args,
-      githubBaseURL,
-    });
+    const res = await sourceGithub.getChangeLogJSON({ ...args });
     const output = await addReleaseNotes(res);
     await sourceCache.setChangeLogJSON(args, output);
     return output;
@@ -62,13 +37,5 @@ async function getChangeLogJSON(args) {
       'getChangeLogJSON error'
     );
     return null;
-  } finally {
-    // wrap everything in a try/finally to ensure process.env.GITHUB_TOKEN is restored no matter if
-    // getChangeLogJSON and addReleaseNotes succed or fails
-    if (token) {
-      logger.debug('Restoring GHE token and endpoint');
-      process.env.GITHUB_TOKEN = token;
-      process.env.GITHUB_ENDPOINT = endpoint;
-    }
   }
 }
diff --git a/lib/workers/pr/changelog/source-github.js b/lib/workers/pr/changelog/source-github.js
index afdddf38c0..6bf4ec9334 100644
--- a/lib/workers/pr/changelog/source-github.js
+++ b/lib/workers/pr/changelog/source-github.js
@@ -1,3 +1,5 @@
+const URL = require('url');
+const endpoints = require('../../../util/endpoints');
 const versioning = require('../../../versioning');
 const ghGot = require('../../../platform/github/gh-got-wrapper');
 
@@ -5,10 +7,11 @@ module.exports = {
   getChangeLogJSON,
 };
 
-async function getTags(versionScheme, repository) {
+async function getTags(endpoint, versionScheme, repository) {
   const { isVersion } = versioning(versionScheme);
   try {
     const res = await ghGot(`repos/${repository}/tags?per_page=100`, {
+      endpoint,
       paginate: true,
     });
 
@@ -35,13 +38,15 @@ async function getTags(versionScheme, repository) {
   }
 }
 
-async function getDateRef(repository, timestamp) {
+async function getDateRef(endpoint, repository, timestamp) {
   if (!timestamp) {
     return null;
   }
   logger.trace({ repository, timestamp }, 'Looking for commit SHA by date');
   try {
-    const res = await ghGot(`repos/${repository}/commits/@{${timestamp}}`);
+    const res = await ghGot(`repos/${repository}/commits/@{${timestamp}}`, {
+      endpoint,
+    });
     const commit = res && res.body;
     return commit && commit.sha;
   } catch (err) {
@@ -52,7 +57,6 @@ async function getDateRef(repository, timestamp) {
 
 async function getChangeLogJSON({
   versionScheme,
-  githubBaseURL,
   fromVersion,
   toVersion,
   repositoryUrl,
@@ -61,13 +65,17 @@ async function getChangeLogJSON({
   const { isVersion, equals, isGreaterThan, sortVersions } = versioning(
     versionScheme
   );
-  if (!(repositoryUrl && repositoryUrl.startsWith(githubBaseURL))) {
-    logger.debug('Repository URL does not match base URL');
+  const { protocol, host, pathname } = URL.parse(repositoryUrl);
+  const githubBaseURL = `${protocol}//${host}/`;
+  const config = endpoints.find({
+    platform: 'github',
+    host: host === 'github.com' ? 'api.github.com' : host,
+  });
+  if (!config) {
+    logger.debug('Repository URL does not match any hnown hosts');
     return null;
   }
-  const repository = repositoryUrl
-    .replace(githubBaseURL, '')
-    .replace(/#.*/, '');
+  const repository = pathname.slice(1);
   if (repository.split('/').length !== 2) {
     logger.info('Invalid github URL found');
     return null;
@@ -86,7 +94,7 @@ async function getChangeLogJSON({
     return null;
   }
 
-  const tags = await getTags(versionScheme, repository);
+  const tags = await getTags(config.endpoint, versionScheme, repository);
 
   function getRef(release) {
     const tagName = tags.find(tag => equals(tag, release.version));
@@ -96,7 +104,7 @@ async function getChangeLogJSON({
     if (release.gitRef) {
       return release.gitRef;
     }
-    return getDateRef(repository, release.releaseTimestamp);
+    return getDateRef(config.endpoint, repository, release.releaseTimestamp);
   }
 
   const changelogReleases = [];
diff --git a/test/config/__snapshots__/env.spec.js.snap b/test/config/__snapshots__/env.spec.js.snap
new file mode 100644
index 0000000000..bc5ac148e3
--- /dev/null
+++ b/test/config/__snapshots__/env.spec.js.snap
@@ -0,0 +1,73 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`config/env .getConfig(env) supports GitHub custom endpoint 1`] = `
+Object {
+  "endpoints": Array [],
+}
+`;
+
+exports[`config/env .getConfig(env) supports GitHub custom endpoint and github.com 1`] = `
+Object {
+  "endpoints": Array [
+    Object {
+      "platform": "github",
+      "token": "public",
+    },
+    Object {
+      "default": true,
+      "endpoint": "endpoint",
+      "platform": "github",
+      "token": "token",
+    },
+  ],
+}
+`;
+
+exports[`config/env .getConfig(env) supports GitHub token 1`] = `
+Object {
+  "endpoints": Array [
+    Object {
+      "default": true,
+      "endpoint": undefined,
+      "platform": "github",
+      "token": "token",
+    },
+  ],
+}
+`;
+
+exports[`config/env .getConfig(env) supports GitLab custom endpoint 1`] = `
+Object {
+  "endpoints": Array [
+    Object {
+      "endpoint": "endpoint",
+      "platform": "gitlab",
+      "token": "token",
+    },
+  ],
+}
+`;
+
+exports[`config/env .getConfig(env) supports GitLab token 1`] = `
+Object {
+  "endpoints": Array [
+    Object {
+      "endpoint": undefined,
+      "platform": "gitlab",
+      "token": "token",
+    },
+  ],
+}
+`;
+
+exports[`config/env .getConfig(env) supports VSTS 1`] = `
+Object {
+  "endpoints": Array [
+    Object {
+      "endpoint": "endpoint",
+      "platform": "vsts",
+      "token": undefined,
+    },
+  ],
+}
+`;
diff --git a/test/config/env.spec.js b/test/config/env.spec.js
index 31e04ea905..c68cfac495 100644
--- a/test/config/env.spec.js
+++ b/test/config/env.spec.js
@@ -3,36 +3,65 @@ const env = require('../../lib/config/env.js');
 describe('config/env', () => {
   describe('.getConfig(env)', () => {
     it('returns empty env', () => {
-      env.getConfig({}).should.eql({});
+      expect(env.getConfig({})).toEqual({ endpoints: [] });
     });
     it('supports boolean true', () => {
       const envParam = { RENOVATE_RECREATE_CLOSED: 'true' };
-      env.getConfig(envParam).should.eql({ recreateClosed: true });
+      expect(env.getConfig(envParam).recreateClosed).toBe(true);
     });
     it('supports boolean false', () => {
       const envParam = { RENOVATE_RECREATE_CLOSED: 'false' };
-      env.getConfig(envParam).should.eql({ recreateClosed: false });
+      expect(env.getConfig(envParam).recreateClosed).toBe(false);
     });
     it('supports boolean nonsense as false', () => {
       const envParam = { RENOVATE_RECREATE_CLOSED: 'foo' };
-      env.getConfig(envParam).should.eql({ recreateClosed: false });
+      expect(env.getConfig(envParam).recreateClosed).toBe(false);
     });
     delete process.env.RENOVATE_RECREATE_CLOSED;
     it('supports list single', () => {
       const envParam = { RENOVATE_LABELS: 'a' };
-      env.getConfig(envParam).should.eql({ labels: ['a'] });
+      expect(env.getConfig(envParam).labels).toEqual(['a']);
     });
     it('supports list multiple', () => {
       const envParam = { RENOVATE_LABELS: 'a,b,c' };
-      env.getConfig(envParam).should.eql({ labels: ['a', 'b', 'c'] });
+      expect(env.getConfig(envParam).labels).toEqual(['a', 'b', 'c']);
     });
     it('supports string', () => {
       const envParam = { RENOVATE_TOKEN: 'a' };
-      env.getConfig(envParam).should.eql({ token: 'a' });
+      expect(env.getConfig(envParam).token).toBe('a');
     });
     it('supports json', () => {
       const envParam = { RENOVATE_LOCK_FILE_MAINTENANCE: '{}' };
-      expect(env.getConfig(envParam)).toEqual({ lockFileMaintenance: {} });
+      expect(env.getConfig(envParam).lockFileMaintenance).toEqual({});
+    });
+    it('supports GitHub token', () => {
+      const envParam = { GITHUB_TOKEN: 'token' };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
+    });
+    it('supports GitHub custom endpoint', () => {
+      const envParam = { GITHUB_ENDPOINT: 'endpoint' };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
+    });
+
+    it('supports GitHub custom endpoint and github.com', () => {
+      const envParam = {
+        GITHUB_COM_TOKEN: 'public',
+        GITHUB_ENDPOINT: 'endpoint',
+        GITHUB_TOKEN: 'token',
+      };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
+    });
+    it('supports GitLab token', () => {
+      const envParam = { GITLAB_TOKEN: 'token' };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
+    });
+    it('supports GitLab custom endpoint', () => {
+      const envParam = { GITLAB_TOKEN: 'token', GITLAB_ENDPOINT: 'endpoint' };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
+    });
+    it('supports VSTS', () => {
+      const envParam = { VSTS_TOKEN: 'token', VSTS_ENDPOINT: 'endpoint' };
+      expect(env.getConfig(envParam)).toMatchSnapshot();
     });
   });
   describe('.getEnvName(definition)', () => {
diff --git a/test/config/index.spec.js b/test/config/index.spec.js
index 1127dcc489..24b2d7b560 100644
--- a/test/config/index.spec.js
+++ b/test/config/index.spec.js
@@ -134,6 +134,7 @@ describe('config/index', () => {
       defaultArgv = defaultArgv.concat([
         '--autodiscover',
         '--platform=vsts',
+        '--endpoint=endpoint',
         '--token=abc',
       ]);
       vstsHelper.getFile.mockImplementationOnce(() => `Hello Renovate!`);
diff --git a/test/platform/github/index.spec.js b/test/platform/github/index.spec.js
index 7a238f598c..cf17a619ae 100644
--- a/test/platform/github/index.spec.js
+++ b/test/platform/github/index.spec.js
@@ -2,10 +2,6 @@ describe('platform/github', () => {
   let github;
   let get;
   beforeEach(() => {
-    // clean up env
-    delete process.env.GITHUB_TOKEN;
-    delete process.env.GITHUB_ENDPOINT;
-
     // reset module
     jest.resetModules();
     jest.mock('delay');
@@ -103,8 +99,6 @@ describe('platform/github', () => {
         });
         expect(get.mock.calls).toMatchSnapshot();
         expect(config).toMatchSnapshot();
-        expect(process.env.GITHUB_TOKEN).toBe(token);
-        expect(process.env.GITHUB_ENDPOINT).toBe(endpoint);
       });
     });
     it('should throw an error if no token is provided', async () => {
diff --git a/test/platform/gitlab/index.spec.js b/test/platform/gitlab/index.spec.js
index 55aa5adb51..8b255dae51 100644
--- a/test/platform/gitlab/index.spec.js
+++ b/test/platform/gitlab/index.spec.js
@@ -1,10 +1,11 @@
+const endpoints = require('../../../lib/util/endpoints');
+
 describe('platform/gitlab', () => {
   let gitlab;
   let get;
   beforeEach(() => {
-    // clean up env
-    delete process.env.GITLAB_TOKEN;
-    delete process.env.GITLAB_ENDPOINT;
+    // clean up endpoints
+    endpoints.clear();
 
     // reset module
     jest.resetModules();
@@ -111,8 +112,6 @@ describe('platform/gitlab', () => {
         });
         expect(get.mock.calls).toMatchSnapshot();
         expect(config).toMatchSnapshot();
-        expect(process.env.GITLAB_TOKEN).toBe(token);
-        expect(process.env.GITLAB_ENDPOINT).toBe(endpoint);
       });
     });
     it(`should escape all forward slashes in project names`, async () => {
diff --git a/test/platform/vsts/index.spec.js b/test/platform/vsts/index.spec.js
index 42bae97619..681c9acddb 100644
--- a/test/platform/vsts/index.spec.js
+++ b/test/platform/vsts/index.spec.js
@@ -1,11 +1,12 @@
+const endpoints = require('../../../lib/util/endpoints');
+
 describe('platform/vsts', () => {
   let vsts;
   let vstsApi;
   let vstsHelper;
   beforeEach(() => {
-    // clean up env
-    delete process.env.VSTS_TOKEN;
-    delete process.env.VSTS_ENDPOINT;
+    // clean up endpoints
+    endpoints.clear();
 
     // reset module
     jest.resetModules();
@@ -80,7 +81,18 @@ describe('platform/vsts', () => {
       repo: 'some-repo',
     }));
 
-    return vsts.initRepo(...args);
+    if (typeof args[0] === 'string') {
+      return vsts.initRepo({
+        repository: args[0],
+        token: args[1],
+        endpoint: 'https://my.custom.endpoint/',
+      });
+    }
+
+    return vsts.initRepo({
+      endpoint: 'https://my.custom.endpoint/',
+      ...args[0],
+    });
   }
 
   describe('initRepo', () => {
diff --git a/test/platform/vsts/vsts-got-wrapper.spec.js b/test/platform/vsts/vsts-got-wrapper.spec.js
index 5237465d55..f8d4abbf20 100644
--- a/test/platform/vsts/vsts-got-wrapper.spec.js
+++ b/test/platform/vsts/vsts-got-wrapper.spec.js
@@ -1,46 +1,28 @@
 describe('platform/vsts/vsts-got-wrapper', () => {
+  let endpoints;
   let vsts;
   beforeEach(() => {
-    // clean up env
-    delete process.env.VSTS_TOKEN;
-    delete process.env.VSTS_ENDPOINT;
-
     // reset module
     jest.resetModules();
+    endpoints = require('../../../lib/util/endpoints');
     vsts = require('../../../lib/platform/vsts/vsts-got-wrapper');
   });
 
   describe('gitApi', () => {
-    it('should throw an error if no token is provided', async () => {
-      let err;
-      try {
-        await vsts.gitApi();
-      } catch (e) {
-        err = e;
-      }
-      expect(err.message).toBe('No token found for vsts');
-    });
-    it('should throw an error if no endpoint is provided', async () => {
-      let err;
-      try {
-        process.env.VSTS_TOKEN = 'myToken';
-        await vsts.getCoreApi();
-      } catch (e) {
-        err = e;
-      }
-      expect(err.message).toBe(
-        `You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)`
-      );
+    it('should throw an error if no token is provided', () => {
+      expect(vsts.gitApi).toThrow('No token found for vsts');
+      expect(vsts.getCoreApi).toThrow('No token found for vsts');
     });
     it('should set token and endpoint', async () => {
-      process.env.VSTS_TOKEN = 'myToken';
-      process.env.VSTS_ENDPOINT = 'myEndpoint';
+      endpoints.update({
+        platform: 'vsts',
+        token: 'myToken',
+        endpoint: 'myEndpoint',
+      });
       const res = await vsts.vstsObj();
 
       // We will track if the lib vso-node-api change
       expect(res).toMatchSnapshot();
-      expect(process.env.VSTS_TOKEN).toBe(`myToken`);
-      expect(process.env.VSTS_ENDPOINT).toBe(`myEndpoint`);
     });
   });
 });
diff --git a/test/platform/vsts/vsts-helper.spec.js b/test/platform/vsts/vsts-helper.spec.js
index f11d28ea3f..1aa6848db1 100644
--- a/test/platform/vsts/vsts-helper.spec.js
+++ b/test/platform/vsts/vsts-helper.spec.js
@@ -5,10 +5,6 @@ describe('platform/vsts/helpers', () => {
   let vstsApi;
 
   beforeEach(() => {
-    // clean up env
-    delete process.env.VSTS_TOKEN;
-    delete process.env.VSTS_ENDPOINT;
-
     // reset module
     jest.resetModules();
     jest.mock('../../../lib/platform/vsts/vsts-got-wrapper');
@@ -16,46 +12,6 @@ describe('platform/vsts/helpers', () => {
     vstsApi = require('../../../lib/platform/vsts/vsts-got-wrapper');
   });
 
-  describe('getRepos', () => {
-    it('should throw an error if no token is provided', async () => {
-      let err;
-      try {
-        await vstsHelper.setTokenAndEndpoint();
-      } catch (e) {
-        err = e;
-      }
-      expect(err.message).toBe('No token found for vsts');
-    });
-    it('should throw an error if no endpoint provided (with env variable on token)', async () => {
-      let err;
-      process.env.VSTS_TOKEN = 'token123';
-      try {
-        await vstsHelper.setTokenAndEndpoint();
-      } catch (e) {
-        err = e;
-      }
-      expect(err.message).toBe(
-        'You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)'
-      );
-    });
-    it('should throw an error if no endpoint is provided', async () => {
-      let err;
-      try {
-        await vstsHelper.setTokenAndEndpoint('myToken');
-      } catch (e) {
-        err = e;
-      }
-      expect(err.message).toBe(
-        `You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)`
-      );
-    });
-    it('should set token and endpoint', async () => {
-      await vstsHelper.setTokenAndEndpoint('myToken', 'myEndpoint');
-      expect(process.env.VSTS_TOKEN).toBe(`myToken`);
-      expect(process.env.VSTS_ENDPOINT).toBe(`myEndpoint`);
-    });
-  });
-
   describe('getNewBranchName', () => {
     it('should add refs/heads', () => {
       const res = vstsHelper.getNewBranchName('testBB');
diff --git a/test/util/__snapshots__/endpoints.spec.js.snap b/test/util/__snapshots__/endpoints.spec.js.snap
new file mode 100644
index 0000000000..96c7b38024
--- /dev/null
+++ b/test/util/__snapshots__/endpoints.spec.js.snap
@@ -0,0 +1,43 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`util/endpoints find() allows overrides 1`] = `
+Object {
+  "endpoint": "endpoint",
+  "name": "GitHub",
+  "other": "data",
+  "platform": "github",
+  "token": "secret",
+}
+`;
+
+exports[`util/endpoints find() allows overrides 2`] = `
+Object {
+  "token": "secret",
+}
+`;
+
+exports[`util/endpoints find() allows overrides 3`] = `
+Object {
+  "token": "secret",
+}
+`;
+
+exports[`util/endpoints update() uses default endpoint 1`] = `
+Object {
+  "endpoint": "https://api.github.com/",
+  "name": "GitHub",
+  "other": "data",
+  "platform": "github",
+  "token": "token",
+}
+`;
+
+exports[`util/endpoints update() uses default endpoint 2`] = `
+Object {
+  "endpoint": "https://api.github.com/",
+  "name": "GitHub",
+  "other": "data",
+  "platform": "github",
+  "token": "token",
+}
+`;
diff --git a/test/util/endpoints.spec.js b/test/util/endpoints.spec.js
new file mode 100644
index 0000000000..d7c4913e15
--- /dev/null
+++ b/test/util/endpoints.spec.js
@@ -0,0 +1,62 @@
+const { update, find, clear } = require('../../lib/util/endpoints');
+
+describe('util/endpoints', () => {
+  beforeEach(() => {
+    clear();
+  });
+  describe('update()', () => {
+    it('throws if no platform ', () => {
+      expect(() => update({})).toThrow(
+        'Failed to set configuration: no platform specified'
+      );
+    });
+    it('throws if no endpoint ', () => {
+      expect(() => update({ platform: 'vsts' })).toThrow(
+        `Failed to configure platform 'vsts': no endpoint defined`
+      );
+    });
+
+    it('throws if invalid endpoint ', () => {
+      expect(() =>
+        update({ platform: 'vsts', endpoint: '/some/path' })
+      ).toThrow(
+        `Failed to configure platform 'vsts': no host for endpoint '/some/path'`
+      );
+    });
+
+    it('uses default endpoint', () => {
+      update({
+        platform: 'github',
+        token: 'token',
+        other: 'data',
+      });
+      expect(find({ platform: 'github' })).toMatchSnapshot();
+      expect(
+        find({ platform: 'github', host: 'api.github.com' })
+      ).toMatchSnapshot();
+      expect(find({ platform: 'github', host: 'example.com' })).toBe(null);
+    });
+  });
+  describe('find()', () => {
+    it('allows overrides', () => {
+      update({
+        platform: 'github',
+        endpoint: 'endpoint',
+        token: 'token',
+        other: 'data',
+      });
+      const overrides = {
+        token: 'secret',
+        other: null,
+        foo: undefined,
+      };
+      expect(find({ platform: 'github' }, overrides)).toMatchSnapshot();
+      expect(
+        find({ platform: 'github', host: 'api.github.com' }, overrides)
+      ).toMatchSnapshot();
+      expect(
+        find({ platform: 'github', host: 'example.com' }, overrides)
+      ).toMatchSnapshot();
+    });
+  });
+});
diff --git a/test/workers/pr/changelog.spec.js b/test/workers/pr/changelog.spec.js
index 07f1152e03..257181a379 100644
--- a/test/workers/pr/changelog.spec.js
+++ b/test/workers/pr/changelog.spec.js
@@ -2,6 +2,7 @@ jest.mock('../../../lib/platform/github/gh-got-wrapper');
 jest.mock('../../../lib/datasource/npm');
 jest.mock('got');
 
+const endpoints = require('../../../lib/util/endpoints');
 const ghGot = require('../../../lib/platform/github/gh-got-wrapper');
 
 const { getChangeLogJSON } = require('../../../lib/workers/pr/changelog');
@@ -33,7 +34,11 @@ describe('workers/pr/changelog', () => {
   describe('getChangeLogJSON', () => {
     beforeEach(async () => {
       ghGot.mockClear();
-
+      endpoints.clear();
+      endpoints.update({
+        platform: 'github',
+        endpoint: 'https://api.github.com/',
+      });
       await rmAllCache();
     });
     it('returns null if no fromVersion', async () => {
@@ -166,37 +171,36 @@ describe('workers/pr/changelog', () => {
       ).toBe(null);
     });
     it('supports github enterprise and github.com changelog', async () => {
-      const token = process.env.GITHUB_TOKEN;
-      const endpoint = process.env.GITHUB_ENDPOINT;
-      process.env.GITHUB_TOKEN = 'super_secret';
-      process.env.GITHUB_ENDPOINT = 'https://github-enterprise.example.com/';
-      const oldenv = { ...process.env };
+      endpoints.update({
+        platform: 'github',
+        token: 'super_secret',
+        endpoint: 'https://github-enterprise.example.com/',
+      });
       expect(
         await getChangeLogJSON({
           ...upgrade,
         })
       ).toMatchSnapshot();
-      // check that process env was restored
-      expect(process.env).toEqual(oldenv);
-      process.env.GITHUB_TOKEN = token;
-      process.env.GITHUB_ENDPOINT = endpoint;
     });
     it('supports github enterprise and github enterprise changelog', async () => {
-      const endpoint = process.env.GITHUB_ENDPOINT;
-      process.env.GITHUB_ENDPOINT = 'https://github-enterprise.example.com/';
+      endpoints.update({
+        platform: 'github',
+        endpoint: 'https://github-enterprise.example.com/',
+      });
+      process.env.GITHUB_ENDPOINT = '';
       expect(
         await getChangeLogJSON({
           ...upgrade,
           repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
         })
       ).toMatchSnapshot();
-
-      process.env.GITHUB_ENDPOINT = endpoint;
     });
 
     it('supports github enterprise alwo when retrieving data from cache', async () => {
-      const endpoint = process.env.GITHUB_ENDPOINT;
-      process.env.GITHUB_ENDPOINT = 'https://github-enterprise.example.com/';
+      endpoints.update({
+        platform: 'github',
+        endpoint: 'https://github-enterprise.example.com/',
+      });
       expect(
         await getChangeLogJSON({
           ...upgrade,
@@ -210,7 +214,6 @@ describe('workers/pr/changelog', () => {
           repositoryUrl: 'https://github-enterprise.example.com/chalk/chalk',
         })
       ).toMatchSnapshot();
-      process.env.GITHUB_ENDPOINT = endpoint;
     });
   });
 });
diff --git a/test/workers/repository/init/apis.spec.js b/test/workers/repository/init/apis.spec.js
index 7624bafe5e..4857b03f42 100644
--- a/test/workers/repository/init/apis.spec.js
+++ b/test/workers/repository/init/apis.spec.js
@@ -33,7 +33,7 @@ describe('workers/repository/init/apis', () => {
         await initApis(config);
       } catch (error) {
         expect(error.message).toBe(
-          'You need an endpoint with vsts. Something like this: https://{instance}.VisualStudio.com/{collection} (https://fabrikam.visualstudio.com/DefaultCollection)'
+          `Failed to configure platform 'vsts': no endpoint defined`
         );
       }
     });
-- 
GitLab