diff --git a/docs/usage/golang.md b/docs/usage/golang.md index fc3e1d59b14578a7911923495d77bdb47a82a154..8c860e7997b2e354d495f10d85f8b91363a502cf 100644 --- a/docs/usage/golang.md +++ b/docs/usage/golang.md @@ -45,3 +45,22 @@ By default, Renovate will keep up with the very latest version of `go`. You can "pin" the `go` version that Renovate uses. Say you want Renovate to use Go version 1.14, you can do this by adding `go 1.14` to your `go.mod` file. We do not support pinning Go versions to a specific patch level, so you cannot use `go 1.14.12`, but you can use `go 1.14` in your `go.mod` file. + +### Custom registry support, and authentication + +This example shows how you can use a `config.js` file to configure Renovate for use with a custom private Go module source using Git to pull the modules. +We're using environment variables to pass the Git token to Renovate bot. + +```js +module.exports = { + hostRules: [ + { + matchHost: 'github.enterprise.com', + token: process.env.GO_GIT_TOKEN, + }, + ], + golang: { + registryUrls: ['github.enterprise.com'], + }, +}; +``` diff --git a/docs/usage/private-modules.md b/docs/usage/private-modules.md index 87159f65ba7acf442ecb3eeac384a060a86b7bf5..ec245b912af6b2a7e029f60a2f05facd62a7548d 100644 --- a/docs/usage/private-modules.md +++ b/docs/usage/private-modules.md @@ -128,8 +128,11 @@ Any `hostRules` with `hostType=packagist` are also included. ### gomod -If a `github.com` token is found in `hostRules`, then it is written out to local git config prior to running `go` commands. -The command run is `git config --global url."https://${token}@github.com/".insteadOf "https://github.com/"`. +The `GOPRIVATE` environment variable is used to decide if a package should be fetched from the `GOPROXY` or directly from the source. +Most private modules will need to be fetched from the source directly, Renovate currently supports only Git for this. +For each comma-separated module path in `GOPRIVATE` or `golang.registryUrls` Renovate will look for credentials in `hostRules`. +Credentials are passed to Git via `GIT_CONFIG_KEY_x=url."https://${token}@${GOPRIVATE}/".insteadOf` and `GIT_CONFIG_VALUE_x=https://${GOPRIVATE}/` when running `go` commands. +Glob syntax for `GOPRIVATE` is not supported. ### npm diff --git a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap index 6e0ee06a4a9eb53fb7fcef28528b144b8ce1f9c0..a4ab89395c3458bc9228d92618da8267fbcfa2bf 100644 --- a/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap +++ b/lib/manager/gomod/__snapshots__/artifacts.spec.ts.snap @@ -13,6 +13,48 @@ Array [ exports[`.updateArtifacts() catches errors 2`] = `Array []`; +exports[`.updateArtifacts() ignore docker mode passthrough of invalid GIT_CONFIG_COUNT 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/go:latest", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_go -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "CGO_ENABLED": "1", + "GOFLAGS": "-modcacherw", + "GONOPROXY": "noproxy.example.com/*", + "GONOSUMDB": "1", + "GOPATH": "/tmp/renovate/cache/others/go", + "GOPRIVATE": "private.example.com/*", + "GOPROXY": "proxy.example.com", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + exports[`.updateArtifacts() returns if no go.sum found 1`] = `Array []`; exports[`.updateArtifacts() returns null if unchanged 1`] = ` @@ -269,7 +311,7 @@ Array [ ] `; -exports[`.updateArtifacts() supports docker mode with credentials 1`] = ` +exports[`.updateArtifacts() supports docker mode passthrough of GIT_CONFIG_COUNT 1`] = ` Array [ Object { "cmd": "docker pull renovate/go:latest", @@ -284,12 +326,13 @@ Array [ }, }, Object { - "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"git config --global url.\\\\\\"https://some-token@github.com/\\\\\\".insteadOf \\\\\\"https://github.com/\\\\\\" && go get -d ./...\\"", + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -e GIT_CONFIG_COUNT -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", "options": Object { "cwd": "/tmp/github/some/repo", "encoding": "utf-8", "env": Object { "CGO_ENABLED": "1", + "GIT_CONFIG_COUNT": "2", "GOFLAGS": "-modcacherw", "GONOPROXY": "noproxy.example.com/*", "GONOSUMDB": "1", @@ -311,6 +354,149 @@ Array [ ] `; +exports[`.updateArtifacts() supports docker mode with credentials 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/go:latest", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_go -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -e GIT_CONFIG_KEY_0 -e GIT_CONFIG_VALUE_0 -e GIT_CONFIG_COUNT -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "CGO_ENABLED": "1", + "GIT_CONFIG_COUNT": "1", + "GIT_CONFIG_KEY_0": "url.https://some-token@github.com/.insteadOf", + "GIT_CONFIG_VALUE_0": "https://github.com/", + "GOFLAGS": "-modcacherw", + "GONOPROXY": "noproxy.example.com/*", + "GONOSUMDB": "1", + "GOPATH": "/tmp/renovate/cache/others/go", + "GOPRIVATE": "github.com", + "GOPROXY": "proxy.example.com", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`.updateArtifacts() supports docker mode with credentials with golang.registryUrls 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/go:latest", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_go -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -e GIT_CONFIG_KEY_0 -e GIT_CONFIG_VALUE_0 -e GIT_CONFIG_COUNT -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "CGO_ENABLED": "1", + "GIT_CONFIG_COUNT": "1", + "GIT_CONFIG_KEY_0": "url.https://some-token@github.com/.insteadOf", + "GIT_CONFIG_VALUE_0": "https://github.com/", + "GOFLAGS": "-modcacherw", + "GONOPROXY": "noproxy.example.com/*", + "GONOSUMDB": "1", + "GOPATH": "/tmp/renovate/cache/others/go", + "GOPRIVATE": "github.com,github.enterprise.com/test,github2.enterprise.com", + "GOPROXY": "proxy.example.com", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + +exports[`.updateArtifacts() supports docker mode with custom credentials 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/go:latest", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_go -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GOPRIVATE -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -e GIT_CONFIG_KEY_0 -e GIT_CONFIG_VALUE_0 -e GIT_CONFIG_COUNT -e GIT_CONFIG_KEY_1 -e GIT_CONFIG_VALUE_1 -e GIT_CONFIG_KEY_2 -e GIT_CONFIG_VALUE_2 -e GIT_CONFIG_KEY_3 -e GIT_CONFIG_VALUE_3 -e GIT_CONFIG_KEY_4 -e GIT_CONFIG_VALUE_4 -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "CGO_ENABLED": "1", + "GIT_CONFIG_COUNT": "5", + "GIT_CONFIG_KEY_0": "url.https://some-github-enterprise-token@github.enterprise.com/.insteadOf", + "GIT_CONFIG_KEY_1": "url.https://some-git-private-token@git.private.com/.insteadOf", + "GIT_CONFIG_KEY_2": "url.https://some-git2-private-token@git2.private.com/.insteadOf", + "GIT_CONFIG_KEY_3": "url.https://some-git3-private-token@git3.private.com/test-org.insteadOf", + "GIT_CONFIG_KEY_4": "url.https://username:password@git4.private.com/.insteadOf", + "GIT_CONFIG_VALUE_0": "https://github.enterprise.com/", + "GIT_CONFIG_VALUE_1": "https://git.private.com/", + "GIT_CONFIG_VALUE_2": "https://git2.private.com/", + "GIT_CONFIG_VALUE_3": "https://git3.private.com/test-org", + "GIT_CONFIG_VALUE_4": "https://git4.private.com/", + "GOFLAGS": "-modcacherw", + "GONOPROXY": "noproxy.example.com/*", + "GONOSUMDB": "1", + "GOPATH": "/tmp/renovate/cache/others/go", + "GOPRIVATE": "github.enterprise.com,git.private.com,git2.private.com,git3.private.com/test-org,git4.private.com,git5.private.com", + "GOPROXY": "proxy.example.com", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + exports[`.updateArtifacts() supports docker mode with goModTidy 1`] = ` Array [ Object { @@ -353,6 +539,47 @@ Array [ ] `; +exports[`.updateArtifacts() supports docker mode without GORPIVATE 1`] = ` +Array [ + Object { + "cmd": "docker pull renovate/go:latest", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker ps --filter name=renovate_go -aq", + "options": Object { + "encoding": "utf-8", + }, + }, + Object { + "cmd": "docker run --rm --name=renovate_go --label=renovate_child -v \\"/tmp/github/some/repo\\":\\"/tmp/github/some/repo\\" -v \\"/tmp/renovate/cache\\":\\"/tmp/renovate/cache\\" -v \\"/tmp/renovate/cache/others/go\\":\\"/tmp/renovate/cache/others/go\\" -e GOPATH -e GOPROXY -e GONOPROXY -e GONOSUMDB -e GOFLAGS -e CGO_ENABLED -w \\"/tmp/github/some/repo\\" renovate/go:latest bash -l -c \\"go get -d ./...\\"", + "options": Object { + "cwd": "/tmp/github/some/repo", + "encoding": "utf-8", + "env": Object { + "CGO_ENABLED": "1", + "GOFLAGS": "-modcacherw", + "GONOPROXY": "noproxy.example.com/*", + "GONOSUMDB": "1", + "GOPATH": "/tmp/renovate/cache/others/go", + "GOPROXY": "proxy.example.com", + "HOME": "/home/user", + "HTTPS_PROXY": "https://example.com", + "HTTP_PROXY": "http://example.com", + "LANG": "en_US.UTF-8", + "LC_ALL": "en_US", + "NO_PROXY": "localhost", + "PATH": "/tmp/path", + }, + "maxBuffer": 10485760, + "timeout": 900000, + }, + }, +] +`; + exports[`.updateArtifacts() supports docker mode without credentials 1`] = ` Array [ Object { diff --git a/lib/manager/gomod/artifacts.spec.ts b/lib/manager/gomod/artifacts.spec.ts index f9b5e52e0eb887ab53d28eadc85739c8738b1e4d..f1b5d0a3f4315349e8acdc11041b063d8aa28f09 100644 --- a/lib/manager/gomod/artifacts.spec.ts +++ b/lib/manager/gomod/artifacts.spec.ts @@ -49,7 +49,6 @@ const config: UpdateArtifactsConfig = { const goEnv = { GONOSUMDB: '1', GOPROXY: 'proxy.example.com', - GOPRIVATE: 'private.example.com/*', GONOPROXY: 'noproxy.example.com/*', CGO_ENABLED: '1', }; @@ -60,13 +59,17 @@ describe('.updateArtifacts()', () => { jest.resetModules(); delete process.env.GOPATH; + delete process.env.GIT_CONFIG_COUNT; + process.env.GOPRIVATE = 'private.example.com/*'; env.getChildProcessEnv.mockReturnValue({ ...envMock.basic, ...goEnv }); setAdminConfig(adminConfig); docker.resetPrefetchedImages(); }); + afterEach(() => { setAdminConfig(); }); + it('returns if no go.sum found', async () => { const execSnapshots = mockExecAll(exec); expect( @@ -79,6 +82,7 @@ describe('.updateArtifacts()', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns null if unchanged', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -96,6 +100,7 @@ describe('.updateArtifacts()', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns updated go.sum', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -114,6 +119,7 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('supports vendor directory update', async () => { const foo = join('vendor/github.com/foo/foo/go.mod'); const bar = join('vendor/github.com/bar/bar/go.mod'); @@ -150,6 +156,7 @@ describe('.updateArtifacts()', () => { ]); expect(execSnapshots).toMatchSnapshot(); }); + it('supports docker mode without credentials', async () => { setAdminConfig({ ...adminConfig, binarySource: 'docker' }); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); @@ -169,6 +176,70 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + + it('supports docker mode passthrough of GIT_CONFIG_COUNT', async () => { + process.env.GIT_CONFIG_COUNT = '2'; + setAdminConfig({ ...adminConfig, binarySource: 'docker' }); + fs.readFile.mockResolvedValueOnce('Current go.sum' as any); + fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['go.sum'], + } as StatusResult); + fs.readFile.mockResolvedValueOnce('New go.sum' as any); + expect( + await gomod.updateArtifacts({ + packageFileName: 'go.mod', + updatedDeps: [], + newPackageFileContent: gomod1, + config, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('supports docker mode without GORPIVATE', async () => { + delete process.env.GOPRIVATE; + setAdminConfig({ ...adminConfig, binarySource: 'docker' }); + fs.readFile.mockResolvedValueOnce('Current go.sum' as any); + fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['go.sum'], + } as StatusResult); + fs.readFile.mockResolvedValueOnce('New go.sum' as any); + expect( + await gomod.updateArtifacts({ + packageFileName: 'go.mod', + updatedDeps: [], + newPackageFileContent: gomod1, + config, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('ignore docker mode passthrough of invalid GIT_CONFIG_COUNT', async () => { + process.env.GIT_CONFIG_COUNT = 'not-a-number'; + setAdminConfig({ ...adminConfig, binarySource: 'docker' }); + fs.readFile.mockResolvedValueOnce('Current go.sum' as any); + fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['go.sum'], + } as StatusResult); + fs.readFile.mockResolvedValueOnce('New go.sum' as any); + expect( + await gomod.updateArtifacts({ + packageFileName: 'go.mod', + updatedDeps: [], + newPackageFileContent: gomod1, + config, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + it('supports global mode', async () => { setAdminConfig({ ...adminConfig, binarySource: 'global' }); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); @@ -188,7 +259,33 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('supports docker mode with credentials', async () => { + setAdminConfig({ ...adminConfig, binarySource: 'docker' }); + process.env.GOPRIVATE = 'github.com'; + hostRules.find.mockReturnValueOnce({ + token: 'some-token', + }); + fs.readFile.mockResolvedValueOnce('Current go.sum' as any); + fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['go.sum'], + } as StatusResult); + fs.readFile.mockResolvedValueOnce('New go.sum' as any); + expect( + await gomod.updateArtifacts({ + packageFileName: 'go.mod', + updatedDeps: [], + newPackageFileContent: gomod1, + config, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('supports docker mode with credentials with golang.registryUrls', async () => { + delete process.env.GOPRIVATE; setAdminConfig({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({ token: 'some-token', @@ -200,6 +297,66 @@ describe('.updateArtifacts()', () => { modified: ['go.sum'], } as StatusResult); fs.readFile.mockResolvedValueOnce('New go.sum' as any); + expect( + await gomod.updateArtifacts({ + packageFileName: 'go.mod', + updatedDeps: [], + newPackageFileContent: gomod1, + config: { + ...config, + registryUrls: [ + 'github.com', + 'github.enterprise.com/test', + 'https://github2.enterprise.com', + 'ftp://this-is-invalid.git.enterprise.com', + ], + }, + }) + ).not.toBeNull(); + expect(execSnapshots).toMatchSnapshot(); + }); + + it('supports docker mode with custom credentials', async () => { + setAdminConfig({ ...adminConfig, binarySource: 'docker' }); + const mockValues = [ + { + token: 'some-github-enterprise-token', + matchHost: 'github.enterprise.com', + }, + { + token: 'some-git-private-token', + matchHost: 'git.private.com', + }, + { + token: 'some-git2-private-token', + matchHost: 'https://git2.private.com', + }, + { + token: 'some-git3-private-token', + matchHost: 'git3.private.com/test-org', + }, + { + username: 'username', + password: 'password', + matchHost: 'git4.private.com', + }, + { + username: 'username-is-not-enough', + matchHost: 'git5.private.com', + }, + ]; + process.env.GOPRIVATE = + 'github.enterprise.com,git.private.com,git2.private.com,git3.private.com/test-org,git4.private.com,git5.private.com'; + for (const mockValue of mockValues) { + hostRules.find.mockReturnValueOnce(mockValue); + } + fs.readFile.mockResolvedValueOnce('Current go.sum' as any); + fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename + const execSnapshots = mockExecAll(exec); + git.getRepoStatus.mockResolvedValueOnce({ + modified: ['go.sum'], + } as StatusResult); + fs.readFile.mockResolvedValueOnce('New go.sum' as any); expect( await gomod.updateArtifacts({ packageFileName: 'go.mod', @@ -210,6 +367,7 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('supports docker mode with goModTidy', async () => { setAdminConfig({ ...adminConfig, binarySource: 'docker' }); hostRules.find.mockReturnValueOnce({}); @@ -236,6 +394,7 @@ describe('.updateArtifacts()', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('catches errors', async () => { const execSnapshots = mockExecAll(exec); fs.readFile.mockResolvedValueOnce('Current go.sum' as any); @@ -253,6 +412,7 @@ describe('.updateArtifacts()', () => { ).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('updates import paths with gomodUpdateImportPaths', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -279,6 +439,7 @@ describe('.updateArtifacts()', () => { ).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('skips updating import paths with gomodUpdateImportPaths on v0 to v1', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -304,6 +465,7 @@ describe('.updateArtifacts()', () => { ).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('updates import paths with specific tool version from constraint', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -333,6 +495,7 @@ describe('.updateArtifacts()', () => { ).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('updates import paths with latest tool version on invalid version constraint', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename @@ -362,6 +525,7 @@ describe('.updateArtifacts()', () => { ).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('skips updating import paths for gopkg.in dependencies', async () => { fs.readFile.mockResolvedValueOnce('Current go.sum' as any); fs.readFile.mockResolvedValueOnce(null as any); // vendor modules filename diff --git a/lib/manager/gomod/artifacts.ts b/lib/manager/gomod/artifacts.ts index 36f05c0983457cf7063a5c10c1c540c4be055172..6e611233dfb815ed12c6f036942316d63b6eb71d 100644 --- a/lib/manager/gomod/artifacts.ts +++ b/lib/manager/gomod/artifacts.ts @@ -1,14 +1,14 @@ import is from '@sindresorhus/is'; -import { quote } from 'shlex'; import { dirname, join } from 'upath'; import { getAdminConfig } from '../../config/admin'; import { TEMPORARY_ERROR } from '../../constants/error-messages'; -import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms'; import { logger } from '../../logger'; import { ExecOptions, exec } from '../../util/exec'; import { ensureCacheDir, readLocalFile, writeLocalFile } from '../../util/fs'; import { getRepoStatus } from '../../util/git'; -import { find } from '../../util/host-rules'; +import { getGitAuthenticatedEnvironmentVariables } from '../../util/git/auth'; +import { getHttpUrl } from '../../util/git/url'; +import { parseUrl } from '../../util/url'; import { isValid } from '../../versioning/semver'; import type { PackageDependency, @@ -17,19 +17,18 @@ import type { UpdateArtifactsResult, } from '../types'; -function getPreCommands(): string[] | null { - const credentials = find({ - hostType: PLATFORM_TYPE_GITHUB, - url: 'https://api.github.com/', - }); - let preCommands = null; - if (credentials?.token) { - const token = quote(credentials.token); - preCommands = [ - `git config --global url.\"https://${token}@github.com/\".insteadOf \"https://github.com/\"`, // eslint-disable-line no-useless-escape - ]; +function getGoEnvironmentVariables(registryUrls: string[]): NodeJS.ProcessEnv { + let goEnvVariables: NodeJS.ProcessEnv = {}; + + for (const registryUrl of registryUrls) { + const goPrivateWithProtocol = getHttpUrl(registryUrl); + goEnvVariables = getGitAuthenticatedEnvironmentVariables( + goPrivateWithProtocol, + goEnvVariables + ); } - return preCommands; + + return goEnvVariables; } function getUpdateImportPathCmds( @@ -116,6 +115,51 @@ export async function updateArtifacts({ logger.debug('Removed some relative replace statements from go.mod'); } await writeLocalFile(goModFileName, massagedGoMod); + // array of GOMODULE registries which should be fetched from + // passed to go as GOPRIVATE + let registryUrls: string[] = []; + let goPrivates: string[] = []; + + // add GOPRIVATE environment variables + if (process.env.GOPRIVATE) { + goPrivates = process.env.GOPRIVATE.split(','); + // GOPRIVATE is without protocol so we add the https protocol before adding it to the registryUrls + const goPrivatesWithProtocol = goPrivates.map( + (goPrivate) => `https://${goPrivate}` + ); + registryUrls = registryUrls.concat(goPrivatesWithProtocol); + } + + // add explicit registryUrls + if (config.registryUrls) { + for (const registryUrl of config.registryUrls) { + // if the registryUrl could not be parsed, retry with a protocol + const parsedRegistryUrl = + parseUrl(registryUrl) || parseUrl(`https://${registryUrl}`); + + // Only allow http(s) + if (parsedRegistryUrl?.protocol?.startsWith('http')) { + // Add the full URL to the registryUrls + registryUrls.push(parsedRegistryUrl.toString()); + + // Add only the domain + path to the goPrivates + let goPrivate = parsedRegistryUrl.host; + // only add the pathname if it is not just the base path + if (parsedRegistryUrl.pathname !== '/') { + goPrivate += parsedRegistryUrl.pathname; + } + goPrivates.push(goPrivate); + } else { + // ignore if registryUrl could not be parsed + logger.warn( + `Could not parse registryUrl ${registryUrl} or not using http(s). Ignoring` + ); + } + } + } + + // create comma-separated GOPRIVATE environment variable if any goPrivates exist + const goPrivate = goPrivates.length > 0 ? goPrivates.join(',') : undefined; const cmd = 'go'; const execOptions: ExecOptions = { @@ -123,18 +167,18 @@ export async function updateArtifacts({ extraEnv: { GOPATH: goPath, GOPROXY: process.env.GOPROXY, - GOPRIVATE: process.env.GOPRIVATE, + GOPRIVATE: goPrivate, GONOPROXY: process.env.GONOPROXY, GONOSUMDB: process.env.GONOSUMDB, GOFLAGS: useModcacherw(config.constraints?.go) ? '-modcacherw' : null, CGO_ENABLED: getAdminConfig().binarySource === 'docker' ? '0' : null, + ...getGoEnvironmentVariables(registryUrls), }, docker: { image: 'go', tagConstraint: config.constraints?.go, tagScheme: 'npm', volumes: [goPath], - preCommands: getPreCommands(), }, }; diff --git a/lib/util/git/auth.ts b/lib/util/git/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..4cfba38e4ffaae8dd21f97794331062ca1b4845b --- /dev/null +++ b/lib/util/git/auth.ts @@ -0,0 +1,43 @@ +import { logger } from '../../logger'; +import { getRemoteUrlWithToken } from './url'; + +/* + Add authorization to a Git Url and returns the updated environment variables +*/ +export function getGitAuthenticatedEnvironmentVariables( + gitUrl: string, + environmentVariables: NodeJS.ProcessEnv +): NodeJS.ProcessEnv { + // check if the environmentVariables already contain a GIT_CONFIG_COUNT or if the process has one + const gitConfigCountEnvVariable = + environmentVariables.GIT_CONFIG_COUNT || process.env.GIT_CONFIG_COUNT; + let gitConfigCount = 0; + if (gitConfigCountEnvVariable) { + // passthrough the gitConfigCountEnvVariable environment variable as start value of the index count + gitConfigCount = parseInt(gitConfigCountEnvVariable, 10); + if (Number.isNaN(gitConfigCount)) { + logger.warn( + `Found GIT_CONFIG_COUNT env variable, but couldn't parse the value to an integer: ${process.env.GIT_CONFIG_COUNT}. Ignoring it.` + ); + gitConfigCount = 0; + } + } + + const gitUrlWithToken = getRemoteUrlWithToken(gitUrl); + const returnEnvironmentVariables = { ...environmentVariables }; + + // only if credentials got injected and thus the urls are no longer equal + if (gitUrlWithToken !== gitUrl) { + // prettier-ignore + returnEnvironmentVariables[`GIT_CONFIG_KEY_${gitConfigCount}`] = `url.${gitUrlWithToken}.insteadOf`; + // prettier-ignore + returnEnvironmentVariables[`GIT_CONFIG_VALUE_${gitConfigCount}`] = gitUrl; + gitConfigCount += 1; + } + + if (gitConfigCount > 0) { + returnEnvironmentVariables.GIT_CONFIG_COUNT = gitConfigCount.toString(); + } + + return returnEnvironmentVariables; +}