Module: Dependabot::Maven::NativeHelpers

Extended by:
T::Sig
Defined in:
lib/dependabot/maven/native_helpers.rb

Constant Summary collapse

TRANSFER_FAILURE_REGEX =

Matches Maven's "Could not transfer artifact" failures, capturing the repository URL and HTTP status so we can classify auth vs. other errors.

%r{Could not transfer artifact (?<artifact>[^ ]+) from/to (?<repository_name>[^ ]+) \((?<repository_url>[^ ]+)\): status code: (?<status_code>[0-9]+)}
WRAPPER_PLUGIN_UNRESOLVED_REGEX =

Matches Maven's "Plugin ... could not be resolved" failures, used to detect when the wrapper plugin itself is unavailable behind the proxy.

/Plugin org\.apache\.maven\.plugins:maven-wrapper-plugin[^ ]* .*could not be resolved/
MAX_ERROR_SUMMARY_LENGTH =

Upper bound on the length of the Maven error summary included in raised errors, to avoid oversized error payloads while retaining the relevant failure detail.

2_000
ANSI_ESCAPE_REGEX =

Matches ANSI/VT100 control sequences. Maven can be configured to emit colored output (e.g. -Dstyle.color=always), wrapping markers like [ERROR] in escape codes; we strip these so classification and the surfaced summary see plain text.

%r{\e\[[0-9;?]*[ -/]*[@-~]}
DEPENDENCY_PLUGIN_VERSION =
T.let(version, T.nilable(String))

Class Method Summary collapse

Class Method Details

.handle_tool_error(output) ⇒ Object

Raises:

  • (DependabotError)


65
66
67
68
69
70
71
72
# File 'lib/dependabot/maven/native_helpers.rb', line 65

def self.handle_tool_error(output)
  if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
     (match[:status_code] == "403" || match[:status_code] == "401")
    raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
  end

  raise DependabotError, "mvn CLI failed with an unhandled error"
end

.handle_wrapper_error(error) ⇒ Object

Raises:

  • (Dependabot::MisconfiguredTooling)


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/dependabot/maven/native_helpers.rb', line 135

def self.handle_wrapper_error(error)
  # Strip ANSI color codes up front so classification and the surfaced summary see
  # plain text even when Maven is configured to emit colored output.
  output = error.message.gsub(ANSI_ESCAPE_REGEX, "")

  if (match = output.match(TRANSFER_FAILURE_REGEX)) &&
     (match[:status_code] == "403" || match[:status_code] == "401")
    raise Dependabot::PrivateSourceAuthenticationFailure, match[:repository_url]
  end

  if output.match?(WRAPPER_PLUGIN_UNRESOLVED_REGEX)
    raise Dependabot::DependencyFileNotResolvable, "Could not resolve the Maven Wrapper plugin."
  end

  # Only reclassify when Maven emitted its own `[ERROR]` diagnostics. Otherwise the
  # failure is not a Maven misconfiguration (e.g. timeout or missing executable), so
  # re-raise the original error; its full output is already in the job log.
  summary = mvn_error_summary(output)
  raise error unless summary

  raise Dependabot::MisconfiguredTooling.new("Maven Wrapper", summary)
end

.mvn_error_summary(output) ⇒ Object



164
165
166
167
168
169
170
# File 'lib/dependabot/maven/native_helpers.rb', line 164

def self.mvn_error_summary(output)
  error_lines = output.lines.map(&:chomp).select { |line| line.include?("[ERROR]") }
  return nil if error_lines.empty?

  summary = error_lines.join("\n")
  summary.length > MAX_ERROR_SUMMARY_LENGTH ? "#{summary[0, MAX_ERROR_SUMMARY_LENGTH]}..." : summary
end

.run_mvn_dependency_tree_plugin(file_name) ⇒ Object

Raises:

  • (DependabotError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/dependabot/maven/native_helpers.rb', line 48

def self.run_mvn_dependency_tree_plugin(file_name)
  raise DependabotError, "Could not resolve maven-dependency-plugin version" unless DEPENDENCY_PLUGIN_VERSION

  proxy_url = URI.parse(ENV.fetch("HTTPS_PROXY"))
  stdout, _, status = Open3.capture3(
    { "PROXY_HOST" => proxy_url.host },
    "mvn",
    "dependency:#{DEPENDENCY_PLUGIN_VERSION}:tree",
    "-DoutputFile=#{file_name}",
    "-DoutputType=json",
    "-e"
  )
  Dependabot.logger.info("mvn dependency:tree output: STDOUT:#{stdout}")
  handle_tool_error(stdout) unless status.success?
end

.run_mvnw_wrapper(version:, wrapper_plugin_version:, env:, distribution_type:, extra_args: [], cwd: nil) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/dependabot/maven/native_helpers.rb', line 93

def self.run_mvnw_wrapper(version:, wrapper_plugin_version:, env:, distribution_type:, extra_args: [], cwd: nil)
  # Use the fully-qualified plugin goal so the exact plugin version is
  # invoked regardless of the project's plugin group configuration.
  plugin_goal = "org.apache.maven.plugins:maven-wrapper-plugin:" \
                "#{wrapper_plugin_version}:wrapper"

  standard_args = [
    plugin_goal,
    "-Dmaven=#{version}",
    "-Dtype=#{distribution_type}",
    "--no-transfer-progress"
  ] + extra_args

  # Pass the argument vector directly instead of a pre-joined shell string.
  # `run_shell_command` shell-escapes string commands internally, so building
  # the command with `Shellwords.join` here would double-escape arguments
  # (e.g. `-Dmaven=3.6.3` becoming `-Dmaven\=3.6.3`), which Maven then fails
  # to parse. An argument vector is executed without an intermediate shell.
  cmd = ["mvn"] + standard_args
  run_cwd = cwd && cwd != "." ? cwd : nil

  output = SharedHelpers.run_shell_command(cmd, env: env, cwd: run_cwd)
  Dependabot.logger.info("mvn wrapper output: STDOUT:#{output}")
  output
rescue SharedHelpers::HelperSubprocessFailed => e
  # `run_shell_command` raises HelperSubprocessFailed on a non-zero exit, and the
  # updater sanitizes that into an opaque `SubprocessFailed` that only reports the
  # command and hides the real Maven output. Log the full output and re-raise a
  # classified Dependabot error so operators get an actionable message, mirroring
  # the `run_mvn_dependency_tree_plugin` path.
  Dependabot.logger.warn("mvn wrapper command failed:\n#{e.message}")
  handle_wrapper_error(e)
end