From 2932e8859870972937b8478907e3f8a55f549a9d Mon Sep 17 00:00:00 2001 From: Matt Stevens <matt@mattstevens.co.uk> Date: Tue, 22 Sep 2020 16:18:35 +0100 Subject: [PATCH] feat(go): add support for github enterprise in go datasource (#7252) --- .../__snapshots__/index.spec.ts.snap | 32 +++++++++++++ lib/datasource/github-tags/index.spec.ts | 15 ++++++ lib/datasource/github-tags/index.ts | 13 ++++- .../go/__snapshots__/index.spec.ts.snap | 27 +++++++++++ lib/datasource/go/index.spec.ts | 48 +++++++++++++++++++ lib/datasource/go/index.ts | 30 +++++++++++- 6 files changed, 162 insertions(+), 3 deletions(-) diff --git a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap index f1aae20671..dc29fe0500 100644 --- a/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/github-tags/__snapshots__/index.spec.ts.snap @@ -138,3 +138,35 @@ Array [ }, ] `; + +exports[`datasource/github-tags getReleases supports ghe 1`] = ` +Object { + "releases": Array [ + Object { + "gitRef": "v1.0.0", + "version": "v1.0.0", + }, + Object { + "gitRef": "v1.1.0", + "version": "v1.1.0", + }, + ], + "sourceUrl": "https://git.enterprise.com/some/dep2", +} +`; + +exports[`datasource/github-tags getReleases supports ghe 2`] = ` +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/dep2/tags?per_page=100", + }, +] +`; \ No newline at end of file diff --git a/lib/datasource/github-tags/index.spec.ts b/lib/datasource/github-tags/index.spec.ts index 672126c8bc..e605046033 100644 --- a/lib/datasource/github-tags/index.spec.ts +++ b/lib/datasource/github-tags/index.spec.ts @@ -7,6 +7,7 @@ jest.mock('../../util/host-rules'); const hostRules: any = _hostRules; const githubApiHost = 'https://api.github.com'; +const githubEnterpriseApiHost = 'https://git.enterprise.com'; describe('datasource/github-tags', () => { beforeEach(() => { @@ -102,5 +103,19 @@ describe('datasource/github-tags', () => { expect(res.releases).toHaveLength(2); expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('supports ghe', async () => { + const body = [{ name: 'v1.0.0' }, { name: 'v1.1.0' }]; + httpMock + .scope(githubEnterpriseApiHost) + .get(`/api/v3/repos/${depName}/tags?per_page=100`) + .reply(200, body); + const res = await github.getReleases({ + registryUrl: 'https://git.enterprise.com', + lookupName: depName, + }); + expect(res).toMatchSnapshot(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); }); }); diff --git a/lib/datasource/github-tags/index.ts b/lib/datasource/github-tags/index.ts index 281dd34c3a..921d3f6587 100644 --- a/lib/datasource/github-tags/index.ts +++ b/lib/datasource/github-tags/index.ts @@ -1,3 +1,4 @@ +import URL from 'url'; import { logger } from '../../logger'; import * as packageCache from '../../util/cache/package'; import { GithubHttp } from '../../util/http/github'; @@ -119,6 +120,7 @@ export async function getDigest( * - Return a dependency object containing sourceUrl string and releases array */ export async function getReleases({ + registryUrl: depHost, lookupName: repo, }: GetReleasesConfig): Promise<ReleaseResult | null> { const cachedResult = await packageCache.get<ReleaseResult>( @@ -129,8 +131,15 @@ export async function getReleases({ if (cachedResult) { return cachedResult; } + + // 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/`; + // tag - const url = `https://api.github.com/repos/${repo}/tags?per_page=100`; + const url = URL.resolve(apiBaseUrl, `repos/${repo}/tags?per_page=100`); type GitHubTag = { name: string; }[]; @@ -141,7 +150,7 @@ export async function getReleases({ }) ).body.map((o) => o.name); const dependency: ReleaseResult = { - sourceUrl: 'https://github.com/' + repo, + sourceUrl: URL.resolve(sourceUrlBase, repo), releases: null, }; dependency.releases = versions.map((version) => ({ diff --git a/lib/datasource/go/__snapshots__/index.spec.ts.snap b/lib/datasource/go/__snapshots__/index.spec.ts.snap index 9789204307..9614386770 100644 --- a/lib/datasource/go/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/go/__snapshots__/index.spec.ts.snap @@ -166,6 +166,33 @@ Array [ ] `; +exports[`datasource/go getReleases support ghe 1`] = ` +Object { + "releases": Array [ + Object { + "version": "v1.0.0", + }, + Object { + "version": "v2.0.0", + }, + ], +} +`; + +exports[`datasource/go getReleases support ghe 2`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate", + "host": "git.enterprise.com", + "user-agent": "https://github.com/renovatebot/renovate", + }, + "method": "GET", + "url": "https://git.enterprise.com/example/module?go-get=1", + }, +] +`; + exports[`datasource/go getReleases works for known servers 1`] = ` Array [ Array [ diff --git a/lib/datasource/go/index.spec.ts b/lib/datasource/go/index.spec.ts index eacac1a4f1..0f07bec49f 100644 --- a/lib/datasource/go/index.spec.ts +++ b/lib/datasource/go/index.spec.ts @@ -24,6 +24,20 @@ Nothing to see here; <a href="https://godoc.org/golang.org/x/text">move along</a </body> </html>`; +const resGitHubEnterprise = `<!DOCTYPE html> +<html lang="en"> +<head> +<meta charset="utf-8"> + +<title>Go remote import path metadata</title> +<meta name="go-import" content="git.enterprise.com/example/module git https://git.enterprise.com/example/module.git"> +</head> + +<body> +<!-- Metadata for Go remote import path --> +</body> +</html>`; + describe('datasource/go', () => { beforeEach(() => { httpMock.setup(); @@ -145,6 +159,40 @@ describe('datasource/go', () => { expect(res).toBeDefined(); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('support ghe', async () => { + httpMock + .scope('https://git.enterprise.com/') + .get('/example/module?go-get=1') + .reply(200, resGitHubEnterprise); + github.getReleases.mockResolvedValueOnce({ + releases: [{ version: 'v1.0.0' }, { version: 'v2.0.0' }], + }); + const res = await getPkgReleases({ + datasource, + depName: 'git.enterprise.com/example/module', + }); + expect(res).toMatchSnapshot(); + expect(res).not.toBeNull(); + expect(res).toBeDefined(); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); + it('returns null for go-import prefix mismatch', async () => { + httpMock + .scope('https://git.enterprise.com/') + .get('/example/module?go-get=1') + .reply( + 200, + resGitHubEnterprise.replace( + 'git.enterprise.com/example/module', + 'git.enterprise.com/badexample/badmodule' + ) + ); + const res = await getPkgReleases({ + datasource, + depName: 'git.enterprise.com/example/module', + }); + expect(res).toBeNull(); + }); it('skips wrong package', async () => { httpMock .scope('https://golang.org/') diff --git a/lib/datasource/go/index.ts b/lib/datasource/go/index.ts index 75348d3634..e249819088 100644 --- a/lib/datasource/go/index.ts +++ b/lib/datasource/go/index.ts @@ -1,3 +1,4 @@ +import URL from 'url'; import { logger } from '../../logger'; import { Http } from '../../util/http'; import { regEx } from '../../util/regex'; @@ -35,6 +36,7 @@ async function getDatasource(goModule: string): Promise<DataSource | null> { lookupName, }; } + const pkgUrl = `https://${goModule}?go-get=1`; const res = (await http.get(pkgUrl)).body; const sourceMatch = regEx( @@ -64,7 +66,33 @@ async function getDatasource(goModule: string): Promise<DataSource | null> { }; } } else { - logger.trace({ goModule }, 'No go-source header found'); + // GitHub Enterprise only returns a go-import meta + const importMatch = regEx( + `<meta\\s+name="go-import"\\s+content="([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)">` + ).exec(res); + if (importMatch) { + const [, prefix, , goImportURL] = importMatch; + if (!goModule.startsWith(prefix)) { + logger.trace({ goModule }, 'go-import header prefix not match'); + return null; + } + logger.debug({ goModule, goImportURL }, 'Go lookup import url'); + + // get server base url from import url + const parsedUrl = URL.parse(goImportURL); + + // split the go module from the URL: host/go/module -> go/module + const split = goModule.split('/'); + const lookupName = split[1] + '/' + split[2]; + + return { + datasource: github.id, + registryUrl: `${parsedUrl.protocol}//${parsedUrl.host}`, + lookupName, + }; + } + + logger.trace({ goModule }, 'No go-source or go-import header found'); } return null; } -- GitLab