Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Go modules ignore conditions using go list -m -versions #3630

Closed
wants to merge 3 commits into from

Conversation

mctofu
Copy link
Contributor

@mctofu mctofu commented May 4, 2021

This is an initial exploration of moving go_module latest version selection out of the updatechecker helper and into the ruby code. The goal is to support the upcoming update_type ignore conditions.

Instead of modifying the updatechecker helper we started down the route of using go list -m -versions --json <module> to fetch the list of available versions and then added any extra filtering that was needed to make the tests pass.

% go list -m -versions --json github.com/dependabot-fixtures/go-modules-lib
{
	"Path": "github.com/dependabot-fixtures/go-modules-lib",
	"Version": "v1.1.0",
	"Versions": [
		"v1.0.0",
		"v1.0.1",
		"v1.1.0",
		"v1.2.0-pre1",
		"v1.2.0-pre2"
	],
	"Time": "2018-10-18T21:48:48Z"
}

This appeared to work pretty well and had some benefits:

  • The result omits major version upgrades (since they would have a different module path) so we didn't need to reimplement the major version filter
  • The command is aware of retracted go modules so we gained filtering of them for free
  • No longer need to maintain the updatechecker code

However, we hit one snag. While pairing with @jasonrudolph we discovered that his dev shell was out of date and using go 1.15. When I tried this under 1.16 the go list command started to report this error:

go: github.com/dependabot-fixtures/go-modules-lib@v1.0.0: missing go.sum entry; to add it:
    go mod download github.com/dependabot-fixtures/go-modules-lib

A workaround I came up with was to change the command to go list -m -mod=mod -versions --json github.com/dependabot-fixtures/go-modules-lib. This works but takes much more time as it ends up downloading the module into the module cache. For update_checker_spec.rb this increased the runtime from ~2s to ~10s when run with an empty module cache. In practice this change would mean we need to download every dependency we check for updates instead of only downloading dependencies that need to be updated.

Because of the potential performance impact I'm not feeling good about rolling out the go list change as part of supporting ignore conditions but wanted to put this out for feedback in case I missed something. I'm also going to put up a draft of an alternate approach which modifies the updatechecker helper to behave more like go list instead of switching to go list. That could make it easier to swap out the implementation in the future.

Alternate: #3631

jasonrudolph and others added 3 commits May 3, 2021 17:57
Instead of using our custom Go-based updatechecker
(go_modules/helpers/updatechecker/main.go), let's rely on the built-in
Go tooling to identify the latest resolvable version of a dependency
(i.e., `go list -m --versions <module>`).

This also signals a move to performing all of our Dependabot-specific
logic in Ruby (which is the norm for most ecosystems), as opposed to
splitting it between Ruby and Go.

Co-authored-by: David McIntosh <mctofu@github.com>
@mctofu mctofu requested a review from a team May 4, 2021 01:11
Comment on lines +70 to +75
json, stderr, status = Open3.capture3(env, SharedHelpers.escape_command("go list -m -mod=mod -versions --json #{dependency.name}"))
if !status.success?

# TODO: Should we populate an error_context here?
handle_subprocess_error(SharedHelpers::HelperSubprocessFailed.new(message: stderr, error_context: {}))
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
json, stderr, status = Open3.capture3(env, SharedHelpers.escape_command("go list -m -mod=mod -versions --json #{dependency.name}"))
if !status.success?
# TODO: Should we populate an error_context here?
handle_subprocess_error(SharedHelpers::HelperSubprocessFailed.new(message: stderr, error_context: {}))
end
json = SharedHelpers.run_shell_command("go list -m -mod=mod -versions --json #{dependency.name}")

@mctofu
Copy link
Contributor Author

mctofu commented May 4, 2021

Closing in favor of #3631.

We'd still like to make use of go list -m -versions some day but that should be introduced as its own change.

@mctofu mctofu closed this May 4, 2021
@thepwagner
Copy link
Contributor

This works but takes much more time as it ends up downloading the module into the module cache

To extend this: both approaches download the target module into the cache.

I setup the following sandbox:

[dependabot-core-dev] /tmp/asdf $ ls
go.mod
[dependabot-core-dev] /tmp/asdf $ cat go.mod
module foo

require github.com/dependabot-fixtures/go-modules-lib v1.0.0

go 1.15

When I wipe the package cache and invoke the helper, the dependency is cloned:

[dependabot-core-dev] /tmp/asdf $ rm -Rf /opt/go/gopath/pkg/mod/cache/; echo '{"function":"getUpdatedVersion","args":{"dependency":{"name":"github.com/dependabot-fixtures/go-modules-lib","version":"v1.0.0"}}}' | GOPRIVATE='*' /opt/go_modules/bin/helper ; echo ; find /opt/go/gopath/pkg/mod/cache/
{"result":"v1.1.0"}
/opt/go/gopath/pkg/mod/cache/
/opt/go/gopath/pkg/mod/cache/vcs
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/FETCH_HEAD
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/description
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/HEAD
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/info
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/info/exclude
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/pre-receive.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/fsmonitor-watchman.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/pre-push.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/commit-msg.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/pre-commit.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/update.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/pre-rebase.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/prepare-commit-msg.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/applypatch-msg.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/post-update.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/hooks/pre-applypatch.sample
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/pack
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/b6
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/b6/2be3f3427aee5c6df388d201549c0784910da7
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/46
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/46/08f198fc429d34efa88b0083a77472f91da721
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/8a
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/8a/42a529f3827ecff3107b4cdfd8eb14442d06bd
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/info
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/a1
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/a1/4197f437dd2a0c81045f524a916aec93596b94
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/3a
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/3a/2e301bf6c88a663a1bf4880591f848027f7341
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/8c
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/objects/8c/a1633b2d6b54a5c3ba889c167c4617d3ab0ef8
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/refs
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/refs/tags
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/refs/tags/v1.2.0-pre2
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/refs/heads
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/config
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/branches
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225/shallow
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225.info
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225.lock
[dependabot-core-dev] /tmp/asdf $ du -csh /opt/go/gopath/pkg/mod/cache/
176K	/opt/go/gopath/pkg/mod/cache/
176K	total

The difference is that module listing fetches the entire module graph (e.g. including transitive), not just the module being queried:

[dependabot-core-dev] /tmp/asdf $ rm -Rf /opt/go/gopath/pkg/mod/cache/; GOPRIVATE='*' go list -m -versions --json github.com/dependabot-fixtures/go-modules-lib ; find /opt/go/gopath/pkg/mod/cache/ -maxdepth 2 -type d
{
	"Path": "github.com/dependabot-fixtures/go-modules-lib",
	"Version": "v1.0.0",
	"Versions": [
		"v1.0.0",
		"v1.0.1",
		"v1.1.0",
		"v1.2.0-pre1",
		"v1.2.0-pre2"
	],
	"Time": "2018-10-18T21:48:48Z",
	"GoMod": "/opt/go/gopath/pkg/mod/cache/download/github.com/dependabot-fixtures/go-modules-lib/@v/v1.0.0.mod"
}
/opt/go/gopath/pkg/mod/cache/
/opt/go/gopath/pkg/mod/cache/download
/opt/go/gopath/pkg/mod/cache/download/golang.org
/opt/go/gopath/pkg/mod/cache/download/rsc.io
/opt/go/gopath/pkg/mod/cache/download/github.com
/opt/go/gopath/pkg/mod/cache/vcs
/opt/go/gopath/pkg/mod/cache/vcs/5673a25ea375597bb7799aed271d658fc11bcb2aced1a8a5011c001d4c2ead3a
/opt/go/gopath/pkg/mod/cache/vcs/35640be4a3d2ccf2debb48dd7a3972a84907e5170b0209b4f963f3c8d00f8cd6
/opt/go/gopath/pkg/mod/cache/vcs/e1b590dd5a36ddfffdb7af7f8fd6b632bb233a1ef9a2c80a52539fd7ebb2c225
/opt/go/gopath/pkg/mod/cache/vcs/38515699458adac9c8b61a0b44f9ad7a5f6edd7bcc2d7fae95930ec78f71e1b4
[dependabot-core-dev] /tmp/asdf $ du -csh /opt/go/gopath/pkg/mod/cache/
25M	/opt/go/gopath/pkg/mod/cache/
25M	total

Yikes!


Also worth a shout, GOPRIVATE=* is what makes this slow.
If dependabot could intelligently set that, or expose as a setting to customers, we can fetch versions much faster from the proxy, e.g. https://proxy.golang.org/github.com/dependabot-fixtures/go-modules-lib/@v/list

[dependabot-core-dev] /tmp/asdf $ rm -Rf /opt/go/gopath/pkg/mod/cache/; go list -m -versions --json github.com/dependabot-fixtures/go-modules-lib ; find /opt/go/gopath/pkg/mod/cache/ -maxdepth 2 -type d
{
	"Path": "github.com/dependabot-fixtures/go-modules-lib",
	"Version": "v1.0.0",
	"Versions": [
		"v1.0.0",
		"v1.0.1",
		"v1.1.0",
		"v1.2.0-pre1",
		"v1.2.0-pre2"
	],
	"Time": "2018-10-18T21:48:48Z",
	"GoMod": "/opt/go/gopath/pkg/mod/cache/download/github.com/dependabot-fixtures/go-modules-lib/@v/v1.0.0.mod"
}
/opt/go/gopath/pkg/mod/cache/
/opt/go/gopath/pkg/mod/cache/download
/opt/go/gopath/pkg/mod/cache/download/golang.org
/opt/go/gopath/pkg/mod/cache/download/rsc.io
/opt/go/gopath/pkg/mod/cache/download/github.com
[dependabot-core-dev] /tmp/asdf $ du -csh /opt/go/gopath/pkg/mod
100K	/opt/go/gopath/pkg/mod
100K	total

Now that's where it's at - the query time shouldn't scale with the target repository's size!


Aside: in the experiment where go list -m seemed like a good idea, I handle this by storing the module cache across update runs (bonus: it's the same cache used to make CI faster). Dependabot can't do that :/

@mctofu
Copy link
Contributor Author

mctofu commented May 5, 2021

@thepwagner Thanks for that insight! I think it's downloading all the transitive deps to create the go.sum file. If we instead used an empty go.mod then go list only downloads the target module into the cache. So that might be a path forward for this in the future but we'd need to look into support for exclude and maybe replace as well.

@jeffwidman
Copy link
Member

Thanks for the deep analysis and reporting the result publicly so that drive-by contributors like me don't try to re-invent the wheel.

Note that the transitive deps download may go away (or be greatly reduced in scope) when golang/go#36460 lands.

Given the really nice benefits listed above re: gaining retraction, major version support, etc this is probably worth re-investigating once go 1.17 lands (and probably worth waiting for a few bugfix releases as go.mod may have bugs right out of the gate given the heavy refactoring).

@mctofu
Copy link
Contributor Author

mctofu commented Aug 20, 2021

Now that go 1.17 is out we should give this another shot. Avoiding the need to maintain our own copy of the internal go modules libs would be a great benefit. See #4157.

@mctofu
Copy link
Contributor Author

mctofu commented Nov 20, 2021

go 1.17 didn't end up helping but I was able to make progress with an empty go.mod in #4434. Closing this in favor of that updated PR.

@mctofu mctofu closed this Nov 20, 2021
@jeffwidman jeffwidman deleted the go-modules-ignore-conditions branch August 8, 2022 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants