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