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

Go modules ignore conditions #3631

Merged
merged 11 commits into from
May 5, 2021
1 change: 1 addition & 0 deletions bin/docker-dev-shell
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ docker run --rm -ti \
-v "$(pwd)/go_modules/.rubocop.yml:$CODE_DIR/go_modules/.rubocop.yml" \
-v "$(pwd)/go_modules/Gemfile:$CODE_DIR/go_modules/Gemfile" \
-v "$(pwd)/go_modules/dependabot-go_modules.gemspec:$CODE_DIR/go_modules/dependabot-go_modules.gemspec" \
-v "$(pwd)/go_modules/helpers:$CODE_DIR/go_modules/helpers" \
-v "$(pwd)/go_modules/lib:$CODE_DIR/go_modules/lib" \
-v "$(pwd)/go_modules/spec:$CODE_DIR/go_modules/spec" \
-v "$(pwd)/go_modules/script:$CODE_DIR/go_modules/script" \
Expand Down
4 changes: 2 additions & 2 deletions go_modules/helpers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ func main() {
funcErr error
)
switch helperParams.Function {
case "getUpdatedVersion":
case "getVersions":
var args updatechecker.Args
parseArgs(helperParams.Args, &args)
funcOut, funcErr = updatechecker.GetUpdatedVersion(&args)
funcOut, funcErr = updatechecker.GetVersions(&args)
case "updateDependencyFile":
var args updater.Args
parseArgs(helperParams.Args, &args)
Expand Down
41 changes: 10 additions & 31 deletions go_modules/helpers/updatechecker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,30 @@ package updatechecker
import (
"errors"
"io/ioutil"
"regexp"

"github.com/dependabot/gomodules-extracted/cmd/go/_internal_/modfetch"
"github.com/dependabot/gomodules-extracted/cmd/go/_internal_/modload"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
)

var (
pseudoVersionRegexp = regexp.MustCompile(`\b\d{14}-[0-9a-f]{12}$`)
)

type Dependency struct {
Name string `json:"name"`
Version string `json:"version"`
Indirect bool `json:"indirect"`
}

type IgnoreRange struct {
MinVersionInclusive string `json:"min_version_inclusive"`
MaxVersionExclusive string `json:"max_version_exclusive"`
Name string `json:"name"`
Version string `json:"version"`
}

type Args struct {
Dependency *Dependency `json:"dependency"`
IgnoreRanges []*IgnoreRange `json:"ignore_ranges"`
Dependency *Dependency `json:"dependency"`
}

func GetUpdatedVersion(args *Args) (interface{}, error) {
// GetVersions returns a list of versions for the given dependency that
// are within the same major version.
func GetVersions(args *Args) (interface{}, error) {
if args.Dependency == nil {
return nil, errors.New("Expected args.dependency to not be nil")
}

currentVersion := args.Dependency.Version
currentPrerelease := semver.Prerelease(currentVersion)
if pseudoVersionRegexp.MatchString(currentPrerelease) {
return currentVersion, nil
}

modload.InitMod()
thepwagner marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -60,32 +46,25 @@ func GetUpdatedVersion(args *Args) (interface{}, error) {
}

currentMajor := semver.Major(currentVersion)
latestVersion := args.Dependency.Version

var candidateVersions []string

Outer:
for _, v := range versions {
if semver.Major(v) != currentMajor {
continue
}

if semver.Compare(v, latestVersion) < 1 {
continue
}

if currentPrerelease == "" && semver.Prerelease(v) != "" {
continue
}

for _, exclude := range excludes {
if v == exclude {
continue Outer
}
}

latestVersion = v
candidateVersions = append(candidateVersions, v)
}

return latestVersion, nil
return candidateVersions, nil
}

func goModExcludes(dependency string) ([]string, error) {
Expand Down
67 changes: 8 additions & 59 deletions go_modules/lib/dependabot/go_modules/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,12 @@
require "dependabot/shared_helpers"
require "dependabot/errors"
require "dependabot/go_modules/native_helpers"
require "dependabot/go_modules/resolvability_errors"
require "dependabot/go_modules/version"

module Dependabot
module GoModules
class UpdateChecker < Dependabot::UpdateCheckers::Base
RESOLVABILITY_ERROR_REGEXES = [
# Package url/proxy doesn't include any redirect meta tags
/no go-import meta tags/,
# Package url 404s
/404 Not Found/,
/Repository not found/,
/unrecognized import path/
].freeze
require_relative "update_checker/latest_version_finder"

def latest_resolvable_version
# We don't yet support updating indirect dependencies for go_modules
Expand All @@ -33,7 +25,13 @@ def latest_resolvable_version
end

@latest_resolvable_version ||=
version_class.new(find_latest_resolvable_version.gsub(/^v/, ""))
LatestVersionFinder.new(
dependency: dependency,
dependency_files: dependency_files,
credentials: credentials,
ignored_versions: ignored_versions,
mctofu marked this conversation as resolved.
Show resolved Hide resolved
raise_on_ignored: raise_on_ignored,
).latest_version
end

# This is currently used to short-circuit latest_resolvable_version,
Expand All @@ -56,51 +54,6 @@ def updated_requirements

private

def find_latest_resolvable_version
SharedHelpers.in_a_temporary_directory do
SharedHelpers.with_git_configured(credentials: credentials) do
File.write("go.mod", go_mod.content)

# Turn off the module proxy for now, as it's causing issues with
# private git dependencies
env = { "GOPRIVATE" => "*" }

SharedHelpers.run_helper_subprocess(
command: NativeHelpers.helper_path,
env: env,
function: "getUpdatedVersion",
args: {
dependency: {
name: dependency.name,
version: "v" + dependency.version,
indirect: dependency.requirements.empty?
}
}
)
end
end
rescue SharedHelpers::HelperSubprocessFailed => e
retry_count ||= 0
retry_count += 1
retry if transitory_failure?(e) && retry_count < 2

handle_subprocess_error(e)
end

def handle_subprocess_error(error)
if RESOLVABILITY_ERROR_REGEXES.any? { |rgx| error.message =~ rgx }
ResolvabilityErrors.handle(error.message, credentials: credentials)
end

raise
end

def transitory_failure?(error)
return true if error.message.include?("EOF")

error.message.include?("Internal Server Error")
end

def latest_version_resolvable_with_full_unlock?
# Full unlock checks aren't implemented for Go (yet)
false
Expand Down Expand Up @@ -137,10 +90,6 @@ def default_source
{ type: "default", source: dependency.name }
end

def go_mod
@go_mod ||= dependency_files.find { |f| f.name == "go.mod" }
end

def git_commit_checker
@git_commit_checker ||=
GitCommitChecker.new(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# frozen_string_literal: true

require "excon"

require "dependabot/shared_helpers"
require "dependabot/errors"
require "dependabot/go_modules/requirement"
require "dependabot/go_modules/resolvability_errors"

module Dependabot
module GoModules
class UpdateChecker
class LatestVersionFinder
RESOLVABILITY_ERROR_REGEXES = [
# Package url/proxy doesn't include any redirect meta tags
/no go-import meta tags/,
# Package url 404s
/404 Not Found/,
/Repository not found/,
/unrecognized import path/
].freeze
PSEUDO_VERSION_REGEX = /\b\d{14}-[0-9a-f]{12}$/.freeze

def initialize(dependency:, dependency_files:, credentials:,
ignored_versions:, raise_on_ignored: false)
@dependency = dependency
@dependency_files = dependency_files
@credentials = credentials
@ignored_versions = ignored_versions
@raise_on_ignored = raise_on_ignored
end

def latest_version
@latest_version ||= fetch_latest_version
end

private

attr_reader :dependency, :dependency_files, :credentials, :ignored_versions

def fetch_latest_version
return dependency.version if dependency.version =~ PSEUDO_VERSION_REGEX

candidate_versions = available_versions
candidate_versions = filter_prerelease_versions(candidate_versions)
candidate_versions = filter_lower_versions(candidate_versions)
candidate_versions = filter_ignored_versions(candidate_versions)

candidate_versions.max
end

def available_versions
SharedHelpers.in_a_temporary_directory do
SharedHelpers.with_git_configured(credentials: credentials) do
File.write("go.mod", go_mod.content)
thepwagner marked this conversation as resolved.
Show resolved Hide resolved

# Turn off the module proxy for now, as it's causing issues with
# private git dependencies
env = { "GOPRIVATE" => "*" }

version_strings = SharedHelpers.run_helper_subprocess(
command: NativeHelpers.helper_path,
env: env,
function: "getVersions",
args: {
dependency: {
name: dependency.name,
version: "v" + dependency.version,
}
}
)

version_strings.select { |v| version_class.correct?(v) }
.map { |v| version_class.new(v) }
end
end
rescue SharedHelpers::HelperSubprocessFailed => e
retry_count ||= 0
retry_count += 1
retry if transitory_failure?(e) && retry_count < 2

handle_subprocess_error(e)
end

def handle_subprocess_error(error)
if RESOLVABILITY_ERROR_REGEXES.any? { |rgx| error.message =~ rgx }
ResolvabilityErrors.handle(error.message, credentials: credentials)
end

raise
end

def transitory_failure?(error)
return true if error.message.include?("EOF")

error.message.include?("Internal Server Error")
end

def go_mod
@go_mod ||= dependency_files.find { |f| f.name == "go.mod" }
end

def filter_prerelease_versions(versions_array)
return versions_array if wants_prerelease?

versions_array.reject(&:prerelease?)
end

def filter_lower_versions(versions_array)
versions_array.
select { |version| version > version_class.new(dependency.version) }
end

def filter_ignored_versions(versions_array)
filtered = versions_array.
reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
raise AllVersionsIgnored if @raise_on_ignored && filtered.empty? && versions_array.any?
Copy link
Contributor

Choose a reason for hiding this comment

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

Xref the conflict with #3644 , ezpz to resolve though!


filtered
end

def wants_prerelease?
@wants_prerelease ||=
begin
current_version = dependency.version
current_version && version_class.correct?(current_version) &&
version_class.new(current_version).prerelease?
end
end

def ignore_requirements
ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
end

def requirement_class
Utils.requirement_class_for_package_manager(
dependency.package_manager
)
end

def version_class
Utils.version_class_for_package_manager(dependency.package_manager)
end
end
end
end
end
19 changes: 17 additions & 2 deletions go_modules/spec/dependabot/go_modules/update_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,13 @@ module foobar
end
end

it "doesn't update to (Dependabot) ignored versions" do
# TODO: let(:ignored_versions) { ["..."] }
context "with Dependabot ignored versions" do
let(:ignored_versions) { ["> 1.0.1"] }

it "doesn't update to (Dependabot) ignored versions" do
expect(latest_resolvable_version).
to eq(Dependabot::GoModules::Version.new("1.0.1"))
end
end

context "when on a pre-release" do
Expand Down Expand Up @@ -179,6 +184,16 @@ module foobar
end
end

context "with a retracted update version" do
# latest release v1.0.1 is retracted
let(:dependency_name) { "github.com/dependabot-fixtures/go-modules-retracted" }

pending "doesn't update to the retracted version" do
Copy link
Contributor

Choose a reason for hiding this comment

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

Noice: this spec!

expect(latest_resolvable_version).
to eq(Dependabot::GoModules::Version.new("1.0.0"))
end
end

context "when the package url is internal/invalid" do
let(:dependency_files) { [go_mod] }
let(:project_name) { "unrecognized_import" }
Expand Down