diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index d7744bcc3549ccf1d51660ddb90b988f34fa8bc9..d7c7f2dcd10a75170ff4ac677123c256674b7b82 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -763,7 +763,8 @@ Configuration added here applies for all Go-related updates, however currently t For self-hosted users, `GOPROXY`, `GONOPROXY` and `GOPRIVATE` environment variables are supported ([reference](https://golang.org/ref/mod#module-proxy)). -But when you use the `direct` or `off` keywords Renovate will fallback to its own fetching strategy (i.e. directly from GitHub, etc). +Usage of `direct` will fallback to Renovate-native release fetching mechanism. +Also we support `off` keyword which immediately will stop any fetching. ## group diff --git a/lib/datasource/go/__snapshots__/releases-goproxy.spec.ts.snap b/lib/datasource/go/__snapshots__/releases-goproxy.spec.ts.snap index 4f5aea2112414d9ae1f5dd5b7d8ef565832560d7..bddd8aa02a4b080a6b1a647b72f5a9442a49d3b7 100644 --- a/lib/datasource/go/__snapshots__/releases-goproxy.spec.ts.snap +++ b/lib/datasource/go/__snapshots__/releases-goproxy.spec.ts.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`datasource/go/releases-goproxy GOPROXY fetches release data from goproxy 1`] = ` +exports[`datasource/go/releases-goproxy getReleases fetches release data from goproxy 1`] = ` Array [ Object { "headers": Object { @@ -34,7 +34,7 @@ Array [ ] `; -exports[`datasource/go/releases-goproxy GOPROXY handles comma fallback 1`] = ` +exports[`datasource/go/releases-goproxy getReleases handles comma fallback 1`] = ` Array [ Object { "headers": Object { @@ -86,7 +86,7 @@ Array [ ] `; -exports[`datasource/go/releases-goproxy GOPROXY handles pipe fallback 1`] = ` +exports[`datasource/go/releases-goproxy getReleases handles pipe fallback 1`] = ` Array [ Object { "headers": Object { @@ -129,7 +129,7 @@ Array [ ] `; -exports[`datasource/go/releases-goproxy GOPROXY handles timestamp fetch errors 1`] = ` +exports[`datasource/go/releases-goproxy getReleases handles timestamp fetch errors 1`] = ` Array [ Object { "headers": Object { @@ -163,34 +163,125 @@ Array [ ] `; -exports[`datasource/go/releases-goproxy GOPROXY short-circuits with comma fallback 1`] = ` +exports[`datasource/go/releases-goproxy getReleases short-circuits for errors other than 404 or 410 1`] = ` Array [ Object { "headers": Object { "accept-encoding": "gzip, deflate, br", - "host": "foo.example.com", + "host": "foo.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://foo.example.com/github.com/google/btree/@v/list", + "url": "https://foo.com/github.com/foo/bar/@v/list", }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", - "host": "bar.example.com", + "host": "bar.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://bar.example.com/github.com/google/btree/@v/list", + "url": "https://bar.com/github.com/foo/bar/@v/list", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "baz.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://baz.com/github.com/foo/bar/@v/list", + }, +] +`; + +exports[`datasource/go/releases-goproxy getReleases skips GONOPROXY and GOPRIVATE packages 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/google/btree/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/google/btree/releases?per_page=100", + }, +] +`; + +exports[`datasource/go/releases-goproxy getReleases supports "direct" keyword 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "foo.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://foo.com/github.com/foo/bar/@v/list", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "bar.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://bar.com/github.com/foo/bar/@v/list", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/foo/bar/tags?per_page=100", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/foo/bar/releases?per_page=100", + }, +] +`; + +exports[`datasource/go/releases-goproxy getReleases supports "off" keyword 1`] = ` +Array [ + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "host": "foo.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://foo.com/github.com/foo/bar/@v/list", }, Object { "headers": Object { "accept-encoding": "gzip, deflate, br", - "host": "baz.example.com", + "host": "bar.com", "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", }, "method": "GET", - "url": "https://baz.example.com/github.com/google/btree/@v/list", + "url": "https://bar.com/github.com/foo/bar/@v/list", }, ] `; diff --git a/lib/datasource/go/index.spec.ts b/lib/datasource/go/index.spec.ts index 67fb7750d3d2232db5614a1acfe12d9133b43d37..2ae259dae7112d57dde48097ad001cae238f9840 100644 --- a/lib/datasource/go/index.spec.ts +++ b/lib/datasource/go/index.spec.ts @@ -19,7 +19,6 @@ describe('datasource/go/index', () => { jest.resetAllMocks(); hostRules.find.mockReturnValue({}); hostRules.hosts.mockReturnValue([]); - process.env.GOPROXY = 'https://proxy.golang.org,direct'; }); afterEach(() => { @@ -32,10 +31,10 @@ describe('datasource/go/index', () => { goproxy.getReleases.mockResolvedValue(null); direct.getReleases.mockResolvedValue(expected); - const res = await getReleases({ lookupName: 'golang.org/foo/something' }); + const res = await getReleases({ lookupName: 'golang.org/foo/bar' }); expect(res).toBe(expected); - expect(goproxy.getReleases).toHaveBeenCalled(); + expect(goproxy.getReleases).not.toHaveBeenCalled(); expect(direct.getReleases).toHaveBeenCalled(); }); @@ -43,8 +42,9 @@ describe('datasource/go/index', () => { const expected = { releases: [{ version: '0.0.1' }] }; goproxy.getReleases.mockResolvedValue(expected); direct.getReleases.mockResolvedValue(null); + process.env.GOPROXY = 'https://proxy.golang.org,direct'; - const res = await getReleases({ lookupName: 'golang.org/foo/something' }); + const res = await getReleases({ lookupName: 'golang.org/foo/bar' }); expect(res).toBe(expected); expect(goproxy.getReleases).toHaveBeenCalled(); diff --git a/lib/datasource/go/index.ts b/lib/datasource/go/index.ts index 271fdc4282320506a6e13269d61887a8cdfb32f8..dcc924c25ff9ca865dfea7be808194edd1bf5928 100644 --- a/lib/datasource/go/index.ts +++ b/lib/datasource/go/index.ts @@ -1,18 +1,15 @@ import type { GetReleasesConfig, ReleaseResult } from '../types'; -import { getReleases as directReleases } from './releases-direct'; +import * as direct from './releases-direct'; import * as goproxy from './releases-goproxy'; export { id } from './common'; export const customRegistrySupport = false; -export async function getReleases( +export function getReleases( config: GetReleasesConfig ): Promise<ReleaseResult | null> { - const res = await goproxy.getReleases(config); - if (res) { - return res; - } - - return directReleases(config); + return process.env.GOPROXY + ? goproxy.getReleases(config) + : direct.getReleases(config); } diff --git a/lib/datasource/go/releases-goproxy.spec.ts b/lib/datasource/go/releases-goproxy.spec.ts index 1e17e7a946cc44f4cf4ff367a368d37bcd6ae524..9361be5a0955eda38c270ed1db4550bf6523d38d 100644 --- a/lib/datasource/go/releases-goproxy.spec.ts +++ b/lib/datasource/go/releases-goproxy.spec.ts @@ -78,10 +78,17 @@ describe('datasource/go/releases-goproxy', () => { expect(parseGoproxy(undefined)).toBeEmpty(); expect(parseGoproxy(null)).toBeEmpty(); expect(parseGoproxy('')).toBeEmpty(); - expect(parseGoproxy('off')).toBeEmpty(); - expect(parseGoproxy('direct')).toBeEmpty(); + expect(parseGoproxy('off')).toMatchObject([ + { url: 'off', fallback: '|' }, + ]); + expect(parseGoproxy('direct')).toMatchObject([ + { url: 'direct', fallback: '|' }, + ]); expect(parseGoproxy('foo,off|direct,qux')).toMatchObject([ { url: 'foo', fallback: ',' }, + { url: 'off', fallback: '|' }, + { url: 'direct', fallback: ',' }, + { url: 'qux', fallback: '|' }, ]); }); }); @@ -127,7 +134,7 @@ describe('datasource/go/releases-goproxy', () => { }); }); - describe('GOPROXY', () => { + describe('getReleases', () => { const baseUrl = 'https://proxy.golang.org'; afterEach(() => { @@ -140,11 +147,22 @@ describe('datasource/go/releases-goproxy', () => { process.env.GOPROXY = baseUrl; process.env.GOPRIVATE = 'github.com/google/*'; - httpMock.scope('https://api.github.com/'); + httpMock + .scope('https://api.github.com/') + .get('/repos/google/btree/tags?per_page=100') + .reply(200, [{ name: 'v1.0.0' }, { name: 'v1.0.1' }]) + .get('/repos/google/btree/releases?per_page=100') + .reply(200, []); const res = await getReleases({ lookupName: 'github.com/google/btree' }); - expect(httpMock.getTrace()).toBeEmpty(); - expect(res).toBeNull(); + expect(httpMock.getTrace()).toMatchSnapshot(); + expect(res).toEqual({ + releases: [ + { gitRef: 'v1.0.0', version: 'v1.0.0' }, + { gitRef: 'v1.0.1', version: 'v1.0.1' }, + ], + sourceUrl: 'https://github.com/google/btree', + }); }); it('fetches release data from goproxy', async () => { @@ -246,31 +264,96 @@ describe('datasource/go/releases-goproxy', () => { ]); }); - it('short-circuits with comma fallback', async () => { + it('short-circuits for errors other than 404 or 410', async () => { process.env.GOPROXY = [ - 'https://foo.example.com', - 'https://bar.example.com', - 'https://baz.example.com', - baseUrl, + 'https://foo.com', + 'https://bar.com', + 'https://baz.com', + 'direct', ].join(','); httpMock - .scope('https://foo.example.com/github.com/google/btree') + .scope('https://foo.com/github.com/foo/bar') .get('/@v/list') .reply(404); httpMock - .scope('https://bar.example.com/github.com/google/btree') + .scope('https://bar.com/github.com/foo/bar') .get('/@v/list') .reply(410); httpMock - .scope('https://baz.example.com/github.com/google/btree') + .scope('https://baz.com/github.com/foo/bar') .get('/@v/list') .replyWithError('unknown'); - const res = await getReleases({ lookupName: 'github.com/google/btree' }); + const res = await getReleases({ lookupName: 'github.com/foo/bar' }); + + expect(httpMock.getTrace()).toMatchSnapshot([ + { method: 'GET', url: 'https://foo.com/github.com/foo/bar/@v/list' }, + { method: 'GET', url: 'https://bar.com/github.com/foo/bar/@v/list' }, + { method: 'GET', url: 'https://baz.com/github.com/foo/bar/@v/list' }, + ]); + expect(res).toBeNull(); + }); + + it('supports "direct" keyword', async () => { + process.env.GOPROXY = [ + 'https://foo.com', + 'https://bar.com', + 'direct', + ].join(','); + + httpMock + .scope('https://foo.com/github.com/foo/bar') + .get('/@v/list') + .reply(404); + + httpMock + .scope('https://bar.com/github.com/foo/bar') + .get('/@v/list') + .reply(410); + + httpMock + .scope('https://api.github.com/') + .get('/repos/foo/bar/tags?per_page=100') + .reply(200, [{ name: 'v1.0.0' }, { name: 'v1.0.1' }]) + .get('/repos/foo/bar/releases?per_page=100') + .reply(200, []); + + const res = await getReleases({ lookupName: 'github.com/foo/bar' }); + expect(httpMock.getTrace()).toMatchSnapshot(); + expect(res).toEqual({ + releases: [ + { gitRef: 'v1.0.0', version: 'v1.0.0' }, + { gitRef: 'v1.0.1', version: 'v1.0.1' }, + ], + sourceUrl: 'https://github.com/foo/bar', + }); + }); + + it('supports "off" keyword', async () => { + process.env.GOPROXY = ['https://foo.com', 'https://bar.com', 'off'].join( + ',' + ); + + httpMock + .scope('https://foo.com/github.com/foo/bar') + .get('/@v/list') + .reply(404); + + httpMock + .scope('https://bar.com/github.com/foo/bar') + .get('/@v/list') + .reply(410); + + const res = await getReleases({ lookupName: 'github.com/foo/bar' }); + + expect(httpMock.getTrace()).toMatchSnapshot([ + { method: 'GET', url: 'https://foo.com/github.com/foo/bar/@v/list' }, + { method: 'GET', url: 'https://bar.com/github.com/foo/bar/@v/list' }, + ]); expect(res).toBeNull(); }); }); diff --git a/lib/datasource/go/releases-goproxy.ts b/lib/datasource/go/releases-goproxy.ts index 6eab2abde0f1c80b7387e58abaff011a601e33f7..d1baae1996a275670297e51f1d693e7477228b88 100644 --- a/lib/datasource/go/releases-goproxy.ts +++ b/lib/datasource/go/releases-goproxy.ts @@ -6,6 +6,7 @@ import * as packageCache from '../../util/cache/package'; import { regEx } from '../../util/regex'; import type { GetReleasesConfig, Release, ReleaseResult } from '../types'; import { GoproxyFallback, http } from './common'; +import * as direct from './releases-direct'; import type { GoproxyItem, VersionInfo } from './types'; const parsedGoproxy: Record<string, GoproxyItem[]> = {}; @@ -34,7 +35,7 @@ export function parseGoproxy( return parsedGoproxy[input]; } - let result: GoproxyItem[] = input + const result: GoproxyItem[] = input .split(/([^,|]*(?:,|\|))/) // TODO: #12070 .filter(Boolean) .map((s) => s.split(/(?=,|\|)/)) // TODO: #12070 @@ -46,15 +47,6 @@ export function parseGoproxy( : GoproxyFallback.Always, })); - // Ignore hosts after any keyword - for (let idx = 0; idx < result.length; idx += 1) { - const { url } = result[idx]; - if (['off', 'direct'].includes(url)) { - result = result.slice(0, idx); - break; - } - } - parsedGoproxy[input] = result; return result; } @@ -159,22 +151,18 @@ export async function versionInfo( return result; } -export async function getReleases({ - lookupName, -}: GetReleasesConfig): Promise<ReleaseResult | null> { +export async function getReleases( + config: GetReleasesConfig +): Promise<ReleaseResult | null> { + const { lookupName } = config; logger.trace(`goproxy.getReleases(${lookupName})`); - const noproxy = parseNoproxy(); - if (noproxy?.test(lookupName)) { - logger.debug(`Skipping ${lookupName} via GONOPROXY match`); - return null; - } - const goproxy = process.env.GOPROXY; const proxyList = parseGoproxy(goproxy); + const noproxy = parseNoproxy(); const cacheNamespaces = 'datasource-go-proxy'; - const cacheKey = `${lookupName}@@${goproxy}`; + const cacheKey = `${lookupName}@@${goproxy}@@${noproxy?.toString()}`; const cacheMinutes = 60; const cachedResult = await packageCache.get<ReleaseResult | null>( cacheNamespaces, @@ -187,8 +175,22 @@ export async function getReleases({ let result: ReleaseResult | null = null; + if (noproxy?.test(lookupName)) { + logger.debug(`Fetching ${lookupName} via GONOPROXY match`); + result = await direct.getReleases(config); + await packageCache.set(cacheNamespaces, cacheKey, result, cacheMinutes); + return result; + } + for (const { url, fallback } of proxyList) { try { + if (url === 'off') { + break; + } else if (url === 'direct') { + result = await direct.getReleases(config); + break; + } + const versions = await listVersions(url, lookupName); const queue = versions.map((version) => async (): Promise<Release> => { try {