diff --git a/lib/config/common.ts b/lib/config/common.ts index e01092a96301291f7ba2fe4b4becbcfe107a3869..86032544dde66530a0ed9c794ad6a71e5b8e9e3e 100644 --- a/lib/config/common.ts +++ b/lib/config/common.ts @@ -170,6 +170,9 @@ export interface RenovateConfig packageRules?: PackageRule[]; prConcurrentLimit?: number; prHourlyLimit?: number; + + registryUrls?: string[]; + repoIsOnboarded?: boolean; updateType?: UpdateType; diff --git a/lib/datasource/github-releases/index.ts b/lib/datasource/github-releases/index.ts index a5b882b30c03bc26ff46a547870acf0ded270ed5..10359bf41b865af52b30239111932774c5f53477 100644 --- a/lib/datasource/github-releases/index.ts +++ b/lib/datasource/github-releases/index.ts @@ -1,14 +1,21 @@ -import URL from 'url'; import * as packageCache from '../../util/cache/package'; import { GithubHttp } from '../../util/http/github'; +import { ensureTrailingSlash } from '../../util/url'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'github-releases'; +export const defaultRegistryUrls = ['https://github.com']; +export const registryStrategy = 'first'; const cacheNamespace = 'datasource-github-releases'; const http = new GithubHttp(); +function getCacheKey(depHost: string, repo: string): string { + const type = 'tags'; + return `${depHost}:${repo}:${type}`; +} + type GithubRelease = { tag_name: string; published_at: string; @@ -27,27 +34,32 @@ type GithubRelease = { */ export async function getReleases({ lookupName: repo, - registryUrl: depHost, + registryUrl, }: GetReleasesConfig): Promise<ReleaseResult | null> { const cachedResult = await packageCache.get<ReleaseResult>( cacheNamespace, - repo + getCacheKey(registryUrl, repo) ); // istanbul ignore if if (cachedResult) { return cachedResult; } - const sourceUrlBase = depHost ?? `https://github.com/`; - const apiBaseUrl = depHost - ? URL.resolve(depHost, 'api/v3/') - : `https://api.github.com/`; - const url = URL.resolve(apiBaseUrl, `repos/${repo}/releases?per_page=100`); + // default to GitHub.com if no GHE host is specified. + const sourceUrlBase = ensureTrailingSlash( + registryUrl ?? 'https://github.com/' + ); + const apiBaseUrl = + sourceUrlBase !== 'https://github.com/' + ? `${sourceUrlBase}api/v3/` + : `https://api.github.com/`; + + const url = `${apiBaseUrl}repos/${repo}/releases?per_page=100`; const res = await http.getJson<GithubRelease[]>(url, { paginate: true, }); const githubReleases = res.body; const dependency: ReleaseResult = { - sourceUrl: URL.resolve(sourceUrlBase, repo), + sourceUrl: `${sourceUrlBase}${repo}`, releases: null, }; dependency.releases = githubReleases.map( @@ -59,6 +71,11 @@ export async function getReleases({ }) ); const cacheMinutes = 10; - await packageCache.set(cacheNamespace, repo, dependency, cacheMinutes); + await packageCache.set( + cacheNamespace, + getCacheKey(registryUrl, repo), + dependency, + cacheMinutes + ); return dependency; } diff --git a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap index 48cbabcbbfda162881e21fbe7ce3a2f1d914a58d..380e0f5ed2487ab748b340805f114e59f349badd 100644 --- a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap @@ -91,6 +91,33 @@ Array [ ] `; +exports[`datasource/github-tags getDigest supports ghe 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "git.enterprise.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://git.enterprise.com/api/v3/repos/some/dep/commits?per_page=1", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate", + "authorization": "token some-token", + "host": "git.enterprise.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://git.enterprise.com/api/v3/repos/some/dep/git/refs/tags/v1.2.0", + }, +] +`; + exports[`datasource/github-tags getDigest warns if unknown ref 1`] = ` Array [ Object { diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts index 106718400554a999a743b353956fe1a58c8208d4..2bb236b19cd5e33d2236abc1f5909a169e37777e 100644 --- a/lib/datasource/github-tags/index.spec.ts +++ b/lib/datasource/github-tags/index.spec.ts @@ -82,6 +82,27 @@ describe('datasource/github-tags', () => { expect(res).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('supports ghe', async () => { + httpMock + .scope(githubEnterpriseApiHost) + .get(`/api/v3/repos/${lookupName}/git/refs/tags/${tag}`) + .reply(200, { object: { type: 'commit', sha: 'ddd111' } }) + .get(`/api/v3/repos/${lookupName}/commits?per_page=1`) + .reply(200, [{ sha: 'abcdef' }]); + + const sha1 = await github.getDigest( + { lookupName, registryUrl: githubEnterpriseApiHost }, + null + ); + const sha2 = await github.getDigest( + { lookupName: 'some/dep', registryUrl: githubEnterpriseApiHost }, + 'v1.2.0' + ); + expect(httpMock.getTrace()).toMatchSnapshot(); + expect(sha1).toBe('abcdef'); + expect(sha2).toBe('ddd111'); + }); }); describe('getReleases', () => { beforeEach(() => { diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts index 050b0a7613cdae3694f84f11a97309f54d48728b..5940685d51c5f61dc479a66f6e7d9765f3285621 100644 --- a/lib/datasource/github-tags/index.ts +++ b/lib/datasource/github-tags/index.ts @@ -1,17 +1,19 @@ -import URL from 'url'; import { logger } from '../../logger'; import * as packageCache from '../../util/cache/package'; import { GithubHttp } from '../../util/http/github'; +import { ensureTrailingSlash } from '../../util/url'; import { DigestConfig, GetReleasesConfig, ReleaseResult } from '../common'; import * as githubReleases from '../github-releases'; export const id = 'github-tags'; +export const defaultRegistryUrls = ['https://github.com']; +export const registryStrategy = 'first'; const http = new GithubHttp(); const cacheNamespace = 'datasource-github-tags'; -function getCacheKey(repo: string, type: string): string { - return `${repo}:${type}`; +function getCacheKey(registryUrl: string, repo: string, type: string): string { + return `${registryUrl}:${repo}:${type}`; } interface TagResponse { @@ -23,20 +25,30 @@ interface TagResponse { } async function getTagCommit( + registryUrl: string, githubRepo: string, tag: string ): Promise<string | null> { const cachedResult = await packageCache.get<string>( cacheNamespace, - getCacheKey(githubRepo, `tag-${tag}`) + getCacheKey(registryUrl, githubRepo, `tag-${tag}`) ); // istanbul ignore if if (cachedResult) { return cachedResult; } + + // default to GitHub.com if no GHE host is specified. + const sourceUrlBase = ensureTrailingSlash( + registryUrl ?? 'https://github.com/' + ); + const apiBaseUrl = + sourceUrlBase !== 'https://github.com/' + ? `${sourceUrlBase}api/v3/` + : `https://api.github.com/`; let digest: string; try { - const url = `https://api.github.com/repos/${githubRepo}/git/refs/tags/${tag}`; + const url = `${apiBaseUrl}repos/${githubRepo}/git/refs/tags/${tag}`; const res = (await http.getJson<TagResponse>(url)).body.object; if (res.type === 'commit') { digest = res.sha; @@ -57,7 +69,7 @@ async function getTagCommit( const cacheMinutes = 120; await packageCache.set( cacheNamespace, - getCacheKey(githubRepo, `tag-${tag}`), + getCacheKey(registryUrl, githubRepo, `tag-${tag}`), digest, cacheMinutes ); @@ -72,28 +84,36 @@ async function getTagCommit( * This function will simply return the latest commit hash for the configured repository. */ export async function getDigest( - { lookupName: githubRepo }: Partial<DigestConfig>, + { lookupName: repo, registryUrl }: Partial<DigestConfig>, newValue?: string ): Promise<string | null> { if (newValue?.length) { - return getTagCommit(githubRepo, newValue); + return getTagCommit(registryUrl, repo, newValue); } const cachedResult = await packageCache.get<string>( cacheNamespace, - getCacheKey(githubRepo, 'commit') + getCacheKey(registryUrl, repo, 'commit') ); // istanbul ignore if if (cachedResult) { return cachedResult; } + // default to GitHub.com if no GHE host is specified. + const sourceUrlBase = ensureTrailingSlash( + registryUrl ?? 'https://github.com/' + ); + const apiBaseUrl = + sourceUrlBase !== 'https://github.com/' + ? `${sourceUrlBase}api/v3/` + : `https://api.github.com/`; let digest: string; try { - const url = `https://api.github.com/repos/${githubRepo}/commits?per_page=1`; + const url = `${apiBaseUrl}repos/${repo}/commits?per_page=1`; const res = await http.getJson<{ sha: string }[]>(url); digest = res.body[0].sha; } catch (err) { logger.debug( - { githubRepo, err }, + { githubRepo: repo, err, registryUrl }, 'Error getting latest commit from GitHub repo' ); } @@ -103,7 +123,7 @@ export async function getDigest( const cacheMinutes = 10; await packageCache.set( cacheNamespace, - getCacheKey(githubRepo, 'commit'), + getCacheKey(registryUrl, repo, 'commit'), digest, cacheMinutes ); @@ -111,12 +131,12 @@ export async function getDigest( } async function getTags({ - registryUrl: depHost, + registryUrl, lookupName: repo, }: GetReleasesConfig): Promise<ReleaseResult | null> { const cachedResult = await packageCache.get<ReleaseResult>( cacheNamespace, - getCacheKey(repo, 'tags') + getCacheKey(registryUrl, repo, 'tags') ); // istanbul ignore if if (cachedResult) { @@ -124,13 +144,16 @@ async function getTags({ } // default to GitHub.com if no GHE host is specified. - const sourceUrlBase = depHost ?? `https://github.com/`; - const apiBaseUrl = depHost - ? URL.resolve(depHost, 'api/v3/') - : `https://api.github.com/`; + const sourceUrlBase = ensureTrailingSlash( + registryUrl ?? 'https://github.com/' + ); + const apiBaseUrl = + sourceUrlBase !== 'https://github.com/' + ? `${sourceUrlBase}api/v3/` + : `https://api.github.com/`; // tag - const url = URL.resolve(apiBaseUrl, `repos/${repo}/tags?per_page=100`); + const url = `${apiBaseUrl}repos/${repo}/tags?per_page=100`; type GitHubTag = { name: string; }[]; @@ -141,7 +164,7 @@ async function getTags({ }) ).body.map((o) => o.name); const dependency: ReleaseResult = { - sourceUrl: URL.resolve(sourceUrlBase, repo), + sourceUrl: `${sourceUrlBase}${repo}`, releases: null, }; dependency.releases = versions.map((version) => ({ @@ -151,7 +174,7 @@ async function getTags({ const cacheMinutes = 10; await packageCache.set( cacheNamespace, - getCacheKey(repo, 'tags'), + getCacheKey(registryUrl, repo, 'tags'), dependency, cacheMinutes ); @@ -164,25 +187,23 @@ export async function getReleases( const tagsResult = await getTags(config); try { - if (tagsResult?.releases) { - // Fetch additional data from releases endpoint when possible - const releasesResult = await githubReleases.getReleases(config); - const releaseByVersion = {}; - releasesResult?.releases?.forEach((release) => { - const key = release.version; - const value = { ...release }; - delete value.version; - releaseByVersion[key] = value; - }); - - const mergedReleases = []; - tagsResult.releases.forEach((tag) => { - const release = releaseByVersion[tag.version]; - mergedReleases.push({ ...release, ...tag }); - }); - - tagsResult.releases = mergedReleases; - } + // Fetch additional data from releases endpoint when possible + const releasesResult = await githubReleases.getReleases(config); + const releaseByVersion = {}; + releasesResult?.releases?.forEach((release) => { + const key = release.version; + const value = { ...release }; + delete value.version; + releaseByVersion[key] = value; + }); + + const mergedReleases = []; + tagsResult.releases.forEach((tag) => { + const release = releaseByVersion[tag.version]; + mergedReleases.push({ ...release, ...tag }); + }); + + tagsResult.releases = mergedReleases; } catch (e) { // no-op } diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index 47e706e95cd54e8b556ad263a55963095d17f4be..34f169598608bd8994e0d8901a7cd6a4471e3eb5 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -35,6 +35,8 @@ docker.defaultRegistryUrls = ['https://index.docker.io']; const gitSubmodules = mocked(datasourceGitSubmodules); const githubReleases = mocked(datasourceGithubReleases); +Object.assign(githubReleases, { defaultRegistryUrls: ['https://github.com'] }); + let config: lookup.LookupUpdateConfig; describe('workers/repository/process/lookup', () => {