dependabot-opentofu 0.373.0 → 0.374.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dependabot/opentofu/file_parser.rb +71 -7
- data/lib/dependabot/opentofu/file_updater.rb +25 -0
- data/lib/dependabot/opentofu/registry_client.rb +173 -1
- data/lib/dependabot/opentofu/requirements_updater.rb +15 -0
- data/lib/dependabot/opentofu/update_checker.rb +28 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa3a44a842816fbe0b288ec3a69f126b289901963dec69e46f2c81ee090aa22e
|
|
4
|
+
data.tar.gz: eb62f10d778dcc79fd4faa71618671c590c33623c712c06cb80dd48ecd94620d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1db6d58ed3b8210058685d3db3819bb50e161168a57b7febad44bfcc4a8bdf72bf71441b049cbd2b7d184cd01f9db054e4b39d349e6c762da013c268f19ed071
|
|
7
|
+
data.tar.gz: 449c4e645fe70e7dc48a27ba1e01ea4bc3369619d2e97c42d8b614fe92bcb72368cc102db2edbb20ffde07fb124dc3f455385f2755a8986d9be49d2821d910a1
|
|
@@ -30,6 +30,18 @@ module Dependabot
|
|
|
30
30
|
# https://opentofu.org/docs/language/providers/requirements/#source-addresses
|
|
31
31
|
PROVIDER_SOURCE_ADDRESS = %r{\A((?<hostname>.+)/)?(?<namespace>.+)/(?<name>.+)\z}
|
|
32
32
|
|
|
33
|
+
# Namespaces reserved for providers bundled with the OpenTofu/Terraform
|
|
34
|
+
# binary. Providers in these namespaces cannot be updated independently
|
|
35
|
+
# because their version tracks the binary itself.
|
|
36
|
+
# See: https://pkg.go.dev/github.com/opentofu/registry-address/v2#Provider.IsBuiltIn
|
|
37
|
+
BUILTIN_PROVIDER_NAMESPACES = T.let(
|
|
38
|
+
%w(
|
|
39
|
+
terraform.io/builtin
|
|
40
|
+
opentofu.org/builtin
|
|
41
|
+
).freeze,
|
|
42
|
+
T::Array[String]
|
|
43
|
+
)
|
|
44
|
+
|
|
33
45
|
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
|
34
46
|
def parse
|
|
35
47
|
dependency_set = DependencySet.new
|
|
@@ -56,6 +68,17 @@ module Dependabot
|
|
|
56
68
|
|
|
57
69
|
private
|
|
58
70
|
|
|
71
|
+
sig { params(details: T.any(String, T::Hash[String, T.untyped])).returns(T::Boolean) }
|
|
72
|
+
def builtin_provider?(details)
|
|
73
|
+
return false unless details.is_a?(Hash)
|
|
74
|
+
|
|
75
|
+
source_address = details["source"]
|
|
76
|
+
return false unless source_address.is_a?(String)
|
|
77
|
+
|
|
78
|
+
normalized = source_address.downcase
|
|
79
|
+
BUILTIN_PROVIDER_NAMESPACES.any? { |ns| normalized.start_with?("#{ns}/") }
|
|
80
|
+
end
|
|
81
|
+
|
|
59
82
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
60
83
|
sig { params(dependency_set: Dependabot::FileParsers::Base::DependencySet).void }
|
|
61
84
|
def parse_opentofu_files(dependency_set)
|
|
@@ -75,24 +98,29 @@ module Dependabot
|
|
|
75
98
|
details = details.first
|
|
76
99
|
|
|
77
100
|
source = source_from(details)
|
|
78
|
-
#
|
|
79
|
-
next if source
|
|
101
|
+
# nil sources are unpinned (e.g. OCI without a tag/digest); paths are local.
|
|
102
|
+
next if source.nil? || source[:type] == "path"
|
|
80
103
|
|
|
81
104
|
# Cannot update modules using early evaluation yet
|
|
82
|
-
if
|
|
105
|
+
if source[:type] == "interpolation"
|
|
83
106
|
Dependabot.logger.warn(
|
|
84
107
|
"Cannot parse module source name with early evaluation for #{name} in #{file.name}."
|
|
85
108
|
)
|
|
86
109
|
next
|
|
87
110
|
end
|
|
88
111
|
|
|
89
|
-
dependency_set << build_opentofu_dependency(file, name,
|
|
112
|
+
dependency_set << build_opentofu_dependency(file, name, source, details)
|
|
90
113
|
end
|
|
91
114
|
|
|
92
115
|
parsed_file(file).fetch("terraform", []).each do |opentofu|
|
|
93
116
|
required_providers = opentofu.fetch("required_providers", {})
|
|
94
117
|
required_providers.each do |provider|
|
|
95
118
|
provider.each do |name, details|
|
|
119
|
+
if builtin_provider?(details)
|
|
120
|
+
Dependabot.logger.info("Skipping built-in provider #{name} in #{file.name}")
|
|
121
|
+
next
|
|
122
|
+
end
|
|
123
|
+
|
|
96
124
|
dependency_set << build_provider_dependency(file, name, details)
|
|
97
125
|
end
|
|
98
126
|
end
|
|
@@ -137,6 +165,7 @@ module Dependabot
|
|
|
137
165
|
version_req = details["version"]&.strip
|
|
138
166
|
version =
|
|
139
167
|
if source[:type] == "git" then version_from_ref(source[:ref])
|
|
168
|
+
elsif source[:type] == "oci" then source[:version]
|
|
140
169
|
elsif version_req&.match?(/^\d/) then version_req
|
|
141
170
|
end
|
|
142
171
|
|
|
@@ -230,18 +259,21 @@ module Dependabot
|
|
|
230
259
|
|
|
231
260
|
source_details =
|
|
232
261
|
case source_type
|
|
233
|
-
# TODO: add support for OCI Registries https://opentofu.org/docs/cli/oci_registries/
|
|
234
262
|
when :http_archive, :path, :mercurial, :s3
|
|
235
263
|
{ type: source_type.to_s, url: bare_source }
|
|
236
264
|
when :github, :bitbucket, :git
|
|
237
265
|
git_source_details_from(bare_source)
|
|
266
|
+
when :oci
|
|
267
|
+
oci_source_details_from(bare_source)
|
|
238
268
|
when :registry
|
|
239
269
|
registry_source_details_from(bare_source)
|
|
240
270
|
when :interpolation
|
|
241
271
|
{ type: source_type.to_s, name: bare_source }
|
|
242
272
|
end
|
|
243
273
|
|
|
244
|
-
|
|
274
|
+
return nil if source_details.nil?
|
|
275
|
+
|
|
276
|
+
source_details[:proxy_url] = raw_source if raw_source != bare_source
|
|
245
277
|
source_details
|
|
246
278
|
end
|
|
247
279
|
|
|
@@ -260,6 +292,38 @@ module Dependabot
|
|
|
260
292
|
]
|
|
261
293
|
end
|
|
262
294
|
|
|
295
|
+
sig { params(source_string: T.untyped).returns(T.nilable(T::Hash[Symbol, T.nilable(String)])) }
|
|
296
|
+
def oci_source_details_from(source_string)
|
|
297
|
+
uri_part, query_part = source_string.split("oci://").last.split("?", 2)
|
|
298
|
+
|
|
299
|
+
# Sources with no query string are unpinned — nothing to update.
|
|
300
|
+
return nil if query_part.nil?
|
|
301
|
+
|
|
302
|
+
# `//` (not preceded by `:`) separates the bare repository from a
|
|
303
|
+
# sub-module path; only the bare repo is queryable for tags.
|
|
304
|
+
artifact_identifier, subdirectory = uri_part.split(%r{(?<!:)//}, 2)
|
|
305
|
+
|
|
306
|
+
qs = CGI.parse(query_part)
|
|
307
|
+
# Treat `?tag=` or `?digest=` (empty value) the same as the param
|
|
308
|
+
# being absent, so we don't propagate "" as a usable version.
|
|
309
|
+
tag = qs["tag"].first&.then { |v| v.empty? ? nil : v }
|
|
310
|
+
digest = qs["digest"].first&.then { |v| v.empty? ? nil : v }
|
|
311
|
+
|
|
312
|
+
if tag && digest
|
|
313
|
+
raise DependencyFileNotEvaluatable,
|
|
314
|
+
"Invalid OCI source '#{source_string}': only one of `tag` or `digest` may be specified"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
type: "oci",
|
|
319
|
+
artifact_identifier: artifact_identifier,
|
|
320
|
+
subdirectory: subdirectory,
|
|
321
|
+
tag: tag,
|
|
322
|
+
digest: digest,
|
|
323
|
+
version: tag || digest
|
|
324
|
+
}
|
|
325
|
+
end
|
|
326
|
+
|
|
263
327
|
sig { params(source_string: T.untyped).returns(T::Hash[Symbol, String]) }
|
|
264
328
|
def registry_source_details_from(source_string)
|
|
265
329
|
parts = source_string.split("//").first.split("/")
|
|
@@ -333,7 +397,7 @@ module Dependabot
|
|
|
333
397
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
334
398
|
sig { params(source_string: String).returns(Symbol) }
|
|
335
399
|
def source_type(source_string)
|
|
336
|
-
|
|
400
|
+
return :oci if source_string.include?("oci://")
|
|
337
401
|
return :interpolation if source_string.include?("${")
|
|
338
402
|
return :path if source_string.start_with?(".")
|
|
339
403
|
return :github if source_string.start_with?("github.com/")
|
|
@@ -92,6 +92,8 @@ module Dependabot
|
|
|
92
92
|
update_git_declaration(new_req, old_req, content, file.name)
|
|
93
93
|
when "registry", "provider"
|
|
94
94
|
update_registry_declaration(new_req, old_req, content)
|
|
95
|
+
when "oci"
|
|
96
|
+
update_oci_declaration(new_req, old_req, content)
|
|
95
97
|
else
|
|
96
98
|
raise "Don't know how to update a #{new_req[:source][:type]} " \
|
|
97
99
|
"declaration!"
|
|
@@ -124,6 +126,29 @@ module Dependabot
|
|
|
124
126
|
end
|
|
125
127
|
end
|
|
126
128
|
|
|
129
|
+
sig do
|
|
130
|
+
params(
|
|
131
|
+
new_req: T::Hash[Symbol, T.untyped],
|
|
132
|
+
old_req: T.nilable(T::Hash[Symbol, T.untyped]),
|
|
133
|
+
updated_content: String
|
|
134
|
+
)
|
|
135
|
+
.void
|
|
136
|
+
end
|
|
137
|
+
def update_oci_declaration(new_req, old_req, updated_content)
|
|
138
|
+
old_tag = old_req&.dig(:source, :tag)
|
|
139
|
+
new_tag = new_req[:source][:tag]
|
|
140
|
+
artifact = old_req&.dig(:source, :artifact_identifier)
|
|
141
|
+
return if old_tag.nil? || new_tag.nil? || artifact.nil? || old_tag == new_tag
|
|
142
|
+
|
|
143
|
+
# Scoped to this artifact's source string so unrelated modules with
|
|
144
|
+
# the same tag value aren't touched.
|
|
145
|
+
oci_source_re = %r{
|
|
146
|
+
(["']oci://#{Regexp.escape(artifact)}(?://[^"'?]*)?\?[^"']*\btag=)
|
|
147
|
+
#{Regexp.escape(old_tag)}
|
|
148
|
+
}x
|
|
149
|
+
updated_content.gsub!(oci_source_re) { T.must(Regexp.last_match(1)) + new_tag }
|
|
150
|
+
end
|
|
151
|
+
|
|
127
152
|
sig do
|
|
128
153
|
params(
|
|
129
154
|
new_req: T::Hash[Symbol, T.untyped],
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require "base64"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
4
7
|
require "dependabot/dependency"
|
|
5
8
|
require "dependabot/errors"
|
|
6
9
|
require "dependabot/registry_client"
|
|
@@ -29,7 +32,12 @@ module Dependabot
|
|
|
29
32
|
@api_base_url = T.let(API_BASE_URL, String)
|
|
30
33
|
@tokens = T.let(
|
|
31
34
|
credentials.each_with_object({}) do |item, memo|
|
|
32
|
-
|
|
35
|
+
# Only Bearer-token shaped creds belong here; OCI-only entries
|
|
36
|
+
# (username/password) would otherwise store a nil token and
|
|
37
|
+
# trigger a malformed `Authorization: Bearer ` header.
|
|
38
|
+
next unless item["type"] == "opentofu_registry" && item["token"]
|
|
39
|
+
|
|
40
|
+
memo[item["host"]] = item["token"]
|
|
33
41
|
end,
|
|
34
42
|
T::Hash[String, String]
|
|
35
43
|
)
|
|
@@ -69,6 +77,52 @@ module Dependabot
|
|
|
69
77
|
# rubocop:enable Metrics/AbcSize
|
|
70
78
|
# rubocop:enable Metrics/PerceivedComplexity
|
|
71
79
|
|
|
80
|
+
# Fetch all tags for an OCI module artifact via the Distribution v2
|
|
81
|
+
# `GET /v2/<repo>/tags/list` endpoint. Returns Version objects whose
|
|
82
|
+
# to_s preserves the original tag string (so `v1.0.0` round-trips).
|
|
83
|
+
#
|
|
84
|
+
# @param artifact_identifier [String] "<host[:port]>/<repo>"
|
|
85
|
+
# @param credentials [Array<Dependabot::Credential>]
|
|
86
|
+
# @return [Array<Dependabot::Opentofu::Version>]
|
|
87
|
+
sig do
|
|
88
|
+
params(
|
|
89
|
+
artifact_identifier: String,
|
|
90
|
+
credentials: T::Array[Dependabot::Credential]
|
|
91
|
+
).returns(T::Array[Dependabot::Opentofu::Version])
|
|
92
|
+
end
|
|
93
|
+
def self.all_oci_tags(artifact_identifier:, credentials: [])
|
|
94
|
+
host, _, repo = artifact_identifier.partition("/")
|
|
95
|
+
if host.empty? || repo.empty?
|
|
96
|
+
raise Dependabot::DependabotError, "Invalid OCI artifact: '#{artifact_identifier}'"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
scheme = oci_scheme_for(host)
|
|
100
|
+
next_url = T.let("#{scheme}://#{host}/v2/#{repo}/tags/list", T.nilable(String))
|
|
101
|
+
tags = T.let([], T::Array[String])
|
|
102
|
+
|
|
103
|
+
while next_url
|
|
104
|
+
response = oci_get(next_url, host: host, credentials: credentials)
|
|
105
|
+
case response.status
|
|
106
|
+
when 200
|
|
107
|
+
body = JSON.parse(response.body)
|
|
108
|
+
tags.concat(Array(body["tags"]))
|
|
109
|
+
when 401, 403
|
|
110
|
+
raise Dependabot::PrivateSourceAuthenticationFailure, host
|
|
111
|
+
when 404
|
|
112
|
+
raise Dependabot::DependabotError,
|
|
113
|
+
"OCI repository '#{repo}' not found on registry '#{host}'"
|
|
114
|
+
else
|
|
115
|
+
raise Dependabot::DependabotError,
|
|
116
|
+
"OCI registry '#{host}' returned HTTP #{response.status} listing tags for '#{repo}'"
|
|
117
|
+
end
|
|
118
|
+
next_url = oci_next_page_url(response.headers["Link"], base: "#{scheme}://#{host}")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
tags.filter_map { |t| Version.new(t) if Version.correct?(t) }
|
|
122
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
|
123
|
+
raise Dependabot::PrivateSourceBadResponse, host
|
|
124
|
+
end
|
|
125
|
+
|
|
72
126
|
# Fetch all the versions of a provider, and return a Version
|
|
73
127
|
# representation of them.
|
|
74
128
|
#
|
|
@@ -156,6 +210,124 @@ module Dependabot
|
|
|
156
210
|
"Available services: #{available}"
|
|
157
211
|
end
|
|
158
212
|
|
|
213
|
+
# localhost registries are commonly served over plain HTTP in dev.
|
|
214
|
+
sig { params(host: String).returns(String) }
|
|
215
|
+
def self.oci_scheme_for(host)
|
|
216
|
+
bare_host = host.split(":").first
|
|
217
|
+
%w(localhost 127.0.0.1).include?(bare_host) ? "http" : "https"
|
|
218
|
+
end
|
|
219
|
+
private_class_method :oci_scheme_for
|
|
220
|
+
|
|
221
|
+
sig do
|
|
222
|
+
params(
|
|
223
|
+
url: String,
|
|
224
|
+
host: String,
|
|
225
|
+
credentials: T::Array[Dependabot::Credential]
|
|
226
|
+
).returns(Excon::Response)
|
|
227
|
+
end
|
|
228
|
+
def self.oci_get(url, host:, credentials:)
|
|
229
|
+
cred = credentials.find do |c|
|
|
230
|
+
c["type"] == "opentofu_registry" && (c["host"] == host || c["registry"] == host)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
headers = {}
|
|
234
|
+
headers["Authorization"] = oci_auth_header(cred) if cred
|
|
235
|
+
|
|
236
|
+
response = Dependabot::RegistryClient.get(url: url, headers: headers)
|
|
237
|
+
|
|
238
|
+
# OCI Distribution Spec: on 401 without explicit credentials, attempt the
|
|
239
|
+
# WWW-Authenticate Bearer challenge to access public registries anonymously.
|
|
240
|
+
if response.status == 401 && cred.nil?
|
|
241
|
+
token = oci_anonymous_bearer_token(response.headers["WWW-Authenticate"])
|
|
242
|
+
if token
|
|
243
|
+
response = Dependabot::RegistryClient.get(
|
|
244
|
+
url: url,
|
|
245
|
+
headers: { "Authorization" => "Bearer #{token}" }
|
|
246
|
+
)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
response
|
|
251
|
+
end
|
|
252
|
+
private_class_method :oci_get
|
|
253
|
+
|
|
254
|
+
sig { params(www_authenticate: T.nilable(String)).returns(T.nilable(String)) }
|
|
255
|
+
def self.oci_anonymous_bearer_token(www_authenticate)
|
|
256
|
+
token_url = oci_bearer_token_url(www_authenticate)
|
|
257
|
+
return nil unless token_url
|
|
258
|
+
|
|
259
|
+
cached = oci_token_cache[token_url]
|
|
260
|
+
return cached[:token] if cached && T.cast(cached[:expires_at], Float) > Time.now.to_f
|
|
261
|
+
|
|
262
|
+
token_response = Dependabot::RegistryClient.get(url: token_url, headers: {})
|
|
263
|
+
return nil unless token_response.status == 200
|
|
264
|
+
|
|
265
|
+
body = JSON.parse(token_response.body)
|
|
266
|
+
token = body["token"]
|
|
267
|
+
expires_in = body.fetch("expires_in", 60).to_i
|
|
268
|
+
oci_token_cache[token_url] = { token: token, expires_at: Time.now.to_f + expires_in - 10 } if token
|
|
269
|
+
token
|
|
270
|
+
rescue StandardError
|
|
271
|
+
nil
|
|
272
|
+
end
|
|
273
|
+
private_class_method :oci_anonymous_bearer_token
|
|
274
|
+
|
|
275
|
+
# Parses a `WWW-Authenticate: Bearer realm="...",service="...",scope="..."`
|
|
276
|
+
# challenge and returns the token endpoint URL, or nil if not a Bearer challenge.
|
|
277
|
+
sig { params(www_authenticate: T.nilable(String)).returns(T.nilable(String)) }
|
|
278
|
+
def self.oci_bearer_token_url(www_authenticate)
|
|
279
|
+
return nil unless www_authenticate&.start_with?("Bearer ")
|
|
280
|
+
|
|
281
|
+
realm = www_authenticate[/realm="([^"]+)"/, 1]
|
|
282
|
+
service = www_authenticate[/service="([^"]+)"/, 1]
|
|
283
|
+
scope = www_authenticate[/scope="([^"]+)"/, 1]
|
|
284
|
+
return nil unless realm
|
|
285
|
+
|
|
286
|
+
params = {}
|
|
287
|
+
params["service"] = service if service
|
|
288
|
+
params["scope"] = scope if scope
|
|
289
|
+
"#{realm}?#{URI.encode_www_form(params)}"
|
|
290
|
+
end
|
|
291
|
+
private_class_method :oci_bearer_token_url
|
|
292
|
+
|
|
293
|
+
sig { returns(T::Hash[String, T::Hash[Symbol, T.untyped]]) }
|
|
294
|
+
def self.oci_token_cache
|
|
295
|
+
@oci_token_cache = T.let(
|
|
296
|
+
@oci_token_cache,
|
|
297
|
+
T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]])
|
|
298
|
+
)
|
|
299
|
+
@oci_token_cache ||= {}
|
|
300
|
+
end
|
|
301
|
+
private_class_method :oci_token_cache
|
|
302
|
+
|
|
303
|
+
# Basic for username/password pairs (OCI Distribution v2), Bearer for
|
|
304
|
+
# token-only creds (existing OpenTofu HTTP registry behaviour).
|
|
305
|
+
sig { params(cred: Dependabot::Credential).returns(String) }
|
|
306
|
+
def self.oci_auth_header(cred)
|
|
307
|
+
if cred["username"] && cred["password"]
|
|
308
|
+
"Basic " + Base64.strict_encode64("#{cred['username']}:#{cred['password']}")
|
|
309
|
+
else
|
|
310
|
+
"Bearer #{cred['token']}"
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
private_class_method :oci_auth_header
|
|
314
|
+
|
|
315
|
+
# Parses RFC 5988 `Link: <…>; rel="next"` from /tags/list responses.
|
|
316
|
+
sig { params(link_header: T.nilable(String), base: String).returns(T.nilable(String)) }
|
|
317
|
+
def self.oci_next_page_url(link_header, base:)
|
|
318
|
+
return nil if link_header.nil? || link_header.empty?
|
|
319
|
+
|
|
320
|
+
link_header.split(",").each do |part|
|
|
321
|
+
match = part.strip.match(/\A<([^>]+)>\s*;\s*rel="?next"?\z/)
|
|
322
|
+
next unless match
|
|
323
|
+
|
|
324
|
+
target = T.must(match[1])
|
|
325
|
+
return target.start_with?("http") ? target : "#{base}#{target}"
|
|
326
|
+
end
|
|
327
|
+
nil
|
|
328
|
+
end
|
|
329
|
+
private_class_method :oci_next_page_url
|
|
330
|
+
|
|
159
331
|
private
|
|
160
332
|
|
|
161
333
|
sig { returns(String) }
|
|
@@ -84,6 +84,7 @@ module Dependabot
|
|
|
84
84
|
case req.dig(:source, :type)
|
|
85
85
|
when "git" then update_git_requirement(req)
|
|
86
86
|
when "registry", "provider" then update_registry_requirement(req)
|
|
87
|
+
when "oci" then update_oci_requirement(req)
|
|
87
88
|
else req
|
|
88
89
|
end
|
|
89
90
|
end
|
|
@@ -108,6 +109,20 @@ module Dependabot
|
|
|
108
109
|
req.merge(source: req[:source].merge(ref: tag_for_latest_version))
|
|
109
110
|
end
|
|
110
111
|
|
|
112
|
+
sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
|
113
|
+
def update_oci_requirement(req)
|
|
114
|
+
return req unless defined?(@latest_version) && @latest_version
|
|
115
|
+
return req if req.dig(:source, :digest)
|
|
116
|
+
return req unless req.dig(:source, :tag)
|
|
117
|
+
|
|
118
|
+
new_tag = latest_version.to_s
|
|
119
|
+
return req if req.dig(:source, :tag) == new_tag
|
|
120
|
+
|
|
121
|
+
req.merge(
|
|
122
|
+
source: req[:source].merge(tag: new_tag, version: new_tag)
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
111
126
|
sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
|
|
112
127
|
def update_registry_requirement(req)
|
|
113
128
|
return req if req.fetch(:requirement).nil?
|
|
@@ -19,7 +19,7 @@ module Dependabot
|
|
|
19
19
|
require_relative "update_checker/latest_version_resolver"
|
|
20
20
|
|
|
21
21
|
ELIGIBLE_SOURCE_TYPES = T.let(
|
|
22
|
-
%w(git provider registry).freeze,
|
|
22
|
+
%w(git provider registry oci).freeze,
|
|
23
23
|
T::Array[String]
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -27,6 +27,7 @@ module Dependabot
|
|
|
27
27
|
def latest_version
|
|
28
28
|
return latest_version_for_git_dependency if git_dependency?
|
|
29
29
|
return latest_version_for_registry_dependency if registry_dependency?
|
|
30
|
+
return latest_version_for_oci_dependency if oci_dependency?
|
|
30
31
|
|
|
31
32
|
latest_version_for_provider_dependency if provider_dependency?
|
|
32
33
|
# Other sources (mercurial, path dependencies) just return `nil`
|
|
@@ -213,6 +214,32 @@ module Dependabot
|
|
|
213
214
|
dependency_source_details&.fetch(:type) == "provider"
|
|
214
215
|
end
|
|
215
216
|
|
|
217
|
+
sig { returns(T::Boolean) }
|
|
218
|
+
def oci_dependency?
|
|
219
|
+
return false if dependency_source_details.nil?
|
|
220
|
+
|
|
221
|
+
dependency_source_details&.fetch(:type) == "oci"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
sig { returns(T.nilable(Dependabot::Opentofu::Version)) }
|
|
225
|
+
def latest_version_for_oci_dependency
|
|
226
|
+
return unless oci_dependency?
|
|
227
|
+
# Digest pins are immutable; nothing to update to without a tag.
|
|
228
|
+
return if dependency_source_details&.fetch(:digest)
|
|
229
|
+
|
|
230
|
+
@latest_oci_version = T.let(@latest_oci_version, T.nilable(Dependabot::Opentofu::Version))
|
|
231
|
+
return @latest_oci_version if @latest_oci_version
|
|
232
|
+
|
|
233
|
+
identifier = T.must(dependency_source_details).fetch(:artifact_identifier)
|
|
234
|
+
versions = RegistryClient.all_oci_tags(
|
|
235
|
+
artifact_identifier: identifier,
|
|
236
|
+
credentials: credentials
|
|
237
|
+
)
|
|
238
|
+
versions.reject!(&:prerelease?) unless wants_prerelease?
|
|
239
|
+
versions.reject! { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
|
|
240
|
+
@latest_oci_version = versions.max
|
|
241
|
+
end
|
|
242
|
+
|
|
216
243
|
sig { returns(T.nilable(T::Hash[T.any(String, Symbol), T.untyped])) }
|
|
217
244
|
def dependency_source_details
|
|
218
245
|
dependency.source_details(allowed_types: ELIGIBLE_SOURCE_TYPES)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-opentofu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.374.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.374.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.374.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -262,7 +262,7 @@ licenses:
|
|
|
262
262
|
- MIT
|
|
263
263
|
metadata:
|
|
264
264
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
265
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
265
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.374.0
|
|
266
266
|
rdoc_options: []
|
|
267
267
|
require_paths:
|
|
268
268
|
- lib
|