From fbceff1397bff1eedbd9089b45cf588f3bfec466 Mon Sep 17 00:00:00 2001
From: Alexander Birkner <alex.birkner@gmail.com>
Date: Tue, 7 Feb 2023 14:00:16 +0100
Subject: [PATCH] feat(gomod): support -e option (#20199)

Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com>
---
 docs/usage/configuration-options.md         |   1 +
 docs/usage/golang.md                        |   3 +-
 lib/config/options/index.ts                 |   1 +
 lib/modules/manager/gomod/artifacts.spec.ts | 126 ++++++++++++++++++++
 lib/modules/manager/gomod/artifacts.ts      |  12 +-
 lib/modules/manager/gomod/readme.md         |   9 +-
 6 files changed, 143 insertions(+), 9 deletions(-)

diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md
index 7264ca790f..5b6716525c 100644
--- a/docs/usage/configuration-options.md
+++ b/docs/usage/configuration-options.md
@@ -2134,6 +2134,7 @@ Table with options:
 | `gomodMassage`           | Enable massaging `replace` directives before calling `go` commands.                                                                                        |
 | `gomodTidy`              | Run `go mod tidy` after Go module updates. This is implicitly enabled for major module updates when `gomodUpdateImportPaths` is enabled.                   |
 | `gomodTidy1.17`          | Run `go mod tidy -compat=1.17` after Go module updates.                                                                                                    |
+| `gomodTidyE`             | Run `go mod tidy -e` after Go module updates.                                                                                                              |
 | `gomodUpdateImportPaths` | Update source import paths on major module updates, using [mod](https://github.com/marwan-at-work/mod).                                                    |
 | `npmDedupe`              | Run `npm dedupe` after `package-lock.json` updates.                                                                                                        |
 | `yarnDedupeFewer`        | Run `yarn-deduplicate --strategy fewer` after `yarn.lock` updates.                                                                                         |
diff --git a/docs/usage/golang.md b/docs/usage/golang.md
index 7671f0038b..ed75cbd47e 100644
--- a/docs/usage/golang.md
+++ b/docs/usage/golang.md
@@ -15,8 +15,7 @@ Renovate supports upgrading dependencies in `go.mod` files and their accompanyin
 1. If Renovate finds an update, Renovate will update `go.mod` to the new value
 1. Renovate runs `go get` to update the `go.sum` files
 1. If the user has enabled the option `gomodUpdateImportPaths` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array, then Renovate uses [mod](https://github.com/marwan-at-work/mod) to update import paths on major updates, which can update any Go source file
-1. If the user has enabled the option `gomodTidy` or `gomodTidy1.17` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array, then Renovate runs `go mod tidy` or `go mod tidy -compat=1.17` respectively, which itself can update `go.mod` and `go.sum`.
-   1. `gomodTidy` is implicitly enabled for major updates if the user has enabled the option `gomodUpdateImportPaths` in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions) array. If go modules 1.17 compatibility is needed you need to explicitly set the option `gomodTidy1.17`.
+1. If the user has any of the available `gomodTidy` options (e.g. `gomodTidy1.17`) in the [`postUpdateOptions`](https://docs.renovatebot.com/configuration-options/#postupdateoptions), then Renovate runs `go mod tidy` with the respective options (multiple options are allowed).
 1. `go mod vendor` is run if vendored modules are detected
 1. A PR will be created with `go.mod`,`go.sum`, and any updated vendored files updated in the one commit
 1. If the source repository has either a "changelog" file or uses GitHub releases, then Release Notes for each version will be embedded in the generated PR
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 6a2aed7a40..3fbac96d6a 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -1979,6 +1979,7 @@ const options: RenovateOptions[] = [
       'gomodUpdateImportPaths',
       'gomodTidy',
       'gomodTidy1.17',
+      'gomodTidyE',
       'npmDedupe',
       'yarnDedupeFewer',
       'yarnDedupeHighest',
diff --git a/lib/modules/manager/gomod/artifacts.spec.ts b/lib/modules/manager/gomod/artifacts.spec.ts
index 33fae85137..b55699ecf4 100644
--- a/lib/modules/manager/gomod/artifacts.spec.ts
+++ b/lib/modules/manager/gomod/artifacts.spec.ts
@@ -1077,6 +1077,132 @@ describe('modules/manager/gomod/artifacts', () => {
     ]);
   });
 
+  it('supports docker mode with gomodTidyE and gomodTidy1.17', async () => {
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
+    hostRules.find.mockReturnValueOnce({});
+    fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
+    fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename
+    const execSnapshots = mockExecAll();
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['go.sum'],
+    } as StatusResult);
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 1');
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 2');
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 3');
+    fs.readLocalFile.mockResolvedValueOnce('New go.mod');
+    datasource.getPkgReleases.mockResolvedValueOnce({
+      releases: [{ version: '1.17.0' }, { version: '1.14.0' }],
+    });
+    expect(
+      await gomod.updateArtifacts({
+        packageFileName: 'go.mod',
+        updatedDeps: [],
+        newPackageFileContent: gomod1,
+        config: {
+          ...config,
+          postUpdateOptions: ['gomodTidyE', 'gomodTidy1.17'],
+        },
+      })
+    ).toEqual([
+      { file: { contents: 'New go.sum 1', path: 'go.sum', type: 'addition' } },
+      { file: { contents: 'New go.sum 2', path: 'go.mod', type: 'addition' } },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'docker pull renovate/sidecar' },
+      {},
+      {
+        cmd:
+          'docker run --rm --name=renovate_sidecar --label=renovate_child ' +
+          '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' +
+          '-v "/tmp/renovate/cache":"/tmp/renovate/cache" ' +
+          '-e GOPROXY ' +
+          '-e GOPRIVATE ' +
+          '-e GONOPROXY ' +
+          '-e GONOSUMDB ' +
+          '-e GOINSECURE ' +
+          '-e GOFLAGS ' +
+          '-e CGO_ENABLED ' +
+          '-e BUILDPACK_CACHE_DIR ' +
+          '-e CONTAINERBASE_CACHE_DIR ' +
+          '-w "/tmp/github/some/repo" ' +
+          'renovate/sidecar' +
+          ' bash -l -c "' +
+          'install-tool golang 1.14.0' +
+          ' && ' +
+          'go get -d -t ./...' +
+          ' && ' +
+          'go mod tidy -compat=1.17 -e' +
+          ' && ' +
+          'go mod tidy -compat=1.17 -e' +
+          '"',
+        options: { cwd: '/tmp/github/some/repo' },
+      },
+    ]);
+  });
+
+  it('supports docker mode with gomodTidyE', async () => {
+    GlobalConfig.set({ ...adminConfig, binarySource: 'docker' });
+    hostRules.find.mockReturnValueOnce({});
+    fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
+    fs.readLocalFile.mockResolvedValueOnce(null); // vendor modules filename
+    const execSnapshots = mockExecAll();
+    git.getRepoStatus.mockResolvedValueOnce({
+      modified: ['go.sum'],
+    } as StatusResult);
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 1');
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 2');
+    fs.readLocalFile.mockResolvedValueOnce('New go.sum 3');
+    fs.readLocalFile.mockResolvedValueOnce('New go.mod');
+    datasource.getPkgReleases.mockResolvedValueOnce({
+      releases: [{ version: '1.17.0' }, { version: '1.14.0' }],
+    });
+    expect(
+      await gomod.updateArtifacts({
+        packageFileName: 'go.mod',
+        updatedDeps: [],
+        newPackageFileContent: gomod1,
+        config: {
+          ...config,
+          postUpdateOptions: ['gomodTidyE'],
+        },
+      })
+    ).toEqual([
+      { file: { contents: 'New go.sum 1', path: 'go.sum', type: 'addition' } },
+      { file: { contents: 'New go.sum 2', path: 'go.mod', type: 'addition' } },
+    ]);
+    expect(execSnapshots).toMatchObject([
+      { cmd: 'docker pull renovate/sidecar' },
+      {},
+      {
+        cmd:
+          'docker run --rm --name=renovate_sidecar --label=renovate_child ' +
+          '-v "/tmp/github/some/repo":"/tmp/github/some/repo" ' +
+          '-v "/tmp/renovate/cache":"/tmp/renovate/cache" ' +
+          '-e GOPROXY ' +
+          '-e GOPRIVATE ' +
+          '-e GONOPROXY ' +
+          '-e GONOSUMDB ' +
+          '-e GOINSECURE ' +
+          '-e GOFLAGS ' +
+          '-e CGO_ENABLED ' +
+          '-e BUILDPACK_CACHE_DIR ' +
+          '-e CONTAINERBASE_CACHE_DIR ' +
+          '-w "/tmp/github/some/repo" ' +
+          'renovate/sidecar' +
+          ' bash -l -c "' +
+          'install-tool golang 1.14.0' +
+          ' && ' +
+          'go get -d -t ./...' +
+          ' && ' +
+          'go mod tidy -e' +
+          ' && ' +
+          'go mod tidy -e' +
+          '"',
+        options: { cwd: '/tmp/github/some/repo' },
+      },
+    ]);
+  });
+
   it('catches errors', async () => {
     const execSnapshots = mockExecAll();
     fs.readLocalFile.mockResolvedValueOnce('Current go.sum');
diff --git a/lib/modules/manager/gomod/artifacts.ts b/lib/modules/manager/gomod/artifacts.ts
index 715e834d5d..102d85d03c 100644
--- a/lib/modules/manager/gomod/artifacts.ts
+++ b/lib/modules/manager/gomod/artifacts.ts
@@ -296,13 +296,19 @@ export async function updateArtifacts({
       logger.debug('go mod tidy command skipped');
     }
 
-    const tidyOpts = config.postUpdateOptions?.includes('gomodTidy1.17')
-      ? ' -compat=1.17'
-      : '';
+    let tidyOpts = '';
+    if (config.postUpdateOptions?.includes('gomodTidy1.17')) {
+      tidyOpts += ' -compat=1.17';
+    }
+    if (config.postUpdateOptions?.includes('gomodTidyE')) {
+      tidyOpts += ' -e';
+    }
+
     const isGoModTidyRequired =
       !mustSkipGoModTidy &&
       (config.postUpdateOptions?.includes('gomodTidy') ||
         config.postUpdateOptions?.includes('gomodTidy1.17') ||
+        config.postUpdateOptions?.includes('gomodTidyE') ||
         (config.updateType === 'major' && isImportPathUpdateRequired));
     if (isGoModTidyRequired) {
       args = 'mod tidy' + tidyOpts;
diff --git a/lib/modules/manager/gomod/readme.md b/lib/modules/manager/gomod/readme.md
index b8ad489899..e17e507cc1 100644
--- a/lib/modules/manager/gomod/readme.md
+++ b/lib/modules/manager/gomod/readme.md
@@ -1,10 +1,11 @@
 You might be interested in the following `postUpdateOptions`:
 
-1. `gomodTidy` - if you'd like Renovate to run `go mod tidy` after every update before raising the PR.
+1. `gomodTidy` - if you'd like Renovate to run `go mod tidy` after every update before raising the PR
    1. This is implicitly enabled for major updates if the user has enabled the option `gomodUpdateImportPaths`
-1. `gomodTidy1.17` - if you'd like Renovate to run `go mod tidy -compat=1.17` after every update before raising the PR.
-1. `gomodUpdateImportPaths` - if you'd like Renovate to update your source import paths on major updates before raising the PR.
-1. `gomodMassage` - to enable massaging of all `replace` statements prior to running `go` so that they will be ignored.
+1. `gomodTidy1.17` - if you'd like Renovate to run `go mod tidy -compat=1.17` after every update before raising the PR
+1. `gomodTidyE` - if you'd like Renovate to run `go mod tidy -e` after every update before raising the PR
+1. `gomodUpdateImportPaths` - if you'd like Renovate to update your source import paths on major updates before raising the PR
+1. `gomodMassage` - to enable massaging of all `replace` statements prior to running `go` so that they will be ignored
 
 When Renovate is running using `binarySource=docker` (such as in the hosted Mend Renovate app) then it will pick the latest compatible version of Go to run, i.e. the latest `1.x` release.
 Even if the `go.mod` has a version like `go 1.14`, Renovate will treat it as a `^1.14` constraint and not `=1.14`.
-- 
GitLab