dependabot-helm 0.301.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 42ef13aa5b9541bdbae91b1d0b0905d6cdc2e22860cc20a8111aeb47aeba30a6
4
+ data.tar.gz: f95cabf75877547cd975a23db8e2513d8b09ddaa5d605e0b0518c30c6d3865e4
5
+ SHA512:
6
+ metadata.gz: b5ab882f6c82986780fba044851d7973c63e46e6101574adf3c6b03fb8bd9f4bb05e31294e2f908b18f88ef2ab4773094ff7fe70d31f1c2dffddd26f7501b7ce
7
+ data.tar.gz: b33df6337ad299e35b342c19559e43e601b4a07ae5901e024c9b3f0cd67e642aa9e7f44a4de68a7f1e90e9ebab7b9fef043a2b6dba564b059e2fa984876e7456
@@ -0,0 +1,67 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/shared/shared_file_fetcher"
5
+
6
+ module Dependabot
7
+ module Helm
8
+ class FileFetcher < Dependabot::Shared::SharedFileFetcher
9
+ FILENAME_REGEX = /.*\.ya?ml$/i
10
+
11
+ sig { override.returns(T::Array[DependencyFile]) }
12
+ def fetch_files
13
+ fetched_files = []
14
+ fetched_files += correctly_encoded_helm_files if allow_beta_ecosystems?
15
+
16
+ return fetched_files if fetched_files.any?
17
+
18
+ raise_appropriate_error
19
+ end
20
+
21
+ sig { override.returns(Regexp) }
22
+ def self.filename_regex
23
+ FILENAME_REGEX
24
+ end
25
+
26
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
27
+ def helm_files
28
+ @helm_files ||=
29
+ T.let(repo_contents(raise_errors: false)
30
+ .select { |f| f.type == "file" && f.name.match?(FILENAME_REGEX) }
31
+ .map { |f| fetch_file_from_host(f.name) }, T.nilable(T::Array[DependencyFile]))
32
+ end
33
+
34
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
35
+ def correctly_encoded_helm_files
36
+ helm_files.select { |f| T.must(f.content).valid_encoding? }
37
+ end
38
+
39
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
40
+ def incorrectly_encoded_helm_files_files
41
+ helm_files.reject { |f| T.must(f.content).valid_encoding? }
42
+ end
43
+
44
+ sig { override.returns(String) }
45
+ def self.required_files_message
46
+ "Repo must contain a Helm charts file."
47
+ end
48
+
49
+ private
50
+
51
+ sig { override.returns(String) }
52
+ def default_file_name
53
+ "Chart.yaml"
54
+ end
55
+
56
+ sig { override.returns(String) }
57
+ def file_type
58
+ "Helm Chart"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Dependabot::FileFetchers.register(
65
+ "helm",
66
+ Dependabot::Helm::FileFetcher
67
+ )
@@ -0,0 +1,192 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "yaml"
5
+ require "dependabot/shared/shared_file_parser"
6
+ require "dependabot/helm/package_manager"
7
+
8
+ module Dependabot
9
+ module Helm
10
+ class FileParser < Dependabot::Shared::SharedFileParser
11
+ extend T::Sig
12
+
13
+ CHART_YAML = /.*chart\.ya?ml$/i
14
+ VALUES_YAML = /.*values\.ya?ml$/i
15
+
16
+ sig { returns(Ecosystem) }
17
+ def ecosystem
18
+ @ecosystem ||= T.let(
19
+ Ecosystem.new(
20
+ name: ECOSYSTEM,
21
+ package_manager: HelmPackageManager.new
22
+ ),
23
+ T.nilable(Ecosystem)
24
+ )
25
+ end
26
+
27
+ sig { override.returns(T::Array[Dependabot::Dependency]) }
28
+ def parse
29
+ dependency_set = DependencySet.new
30
+ parse_chart_yaml_files(dependency_set)
31
+ parse_values_yaml_files(dependency_set)
32
+
33
+ dependency_set.dependencies
34
+ end
35
+
36
+ private
37
+
38
+ sig do
39
+ params(yaml: T::Hash[T.untyped, T.untyped], chart_file: Dependabot::DependencyFile,
40
+ dependency_set: DependencySet).void
41
+ end
42
+ def parse_dependencies(yaml, chart_file, dependency_set)
43
+ yaml["dependencies"].each do |dep|
44
+ next unless dep.is_a?(Hash) && dep["name"] && dep["version"] && dep["repository"]
45
+
46
+ parsed_line = {
47
+ "image" => dep["name"],
48
+ "tag" => dep["version"],
49
+ "registry" => dep["repository"],
50
+ "digest" => nil
51
+ }
52
+
53
+ dependency = build_dependency(chart_file, parsed_line, dep["version"])
54
+ dependency.requirements.map! do |req|
55
+ req[:metadata] = {} unless req[:metadata]
56
+ req[:metadata][:type] = :helm_chart
57
+ req
58
+ end
59
+
60
+ dependency_set << dependency
61
+ end
62
+ end
63
+
64
+ sig { params(dependency_set: DependencySet).void }
65
+ def parse_chart_yaml_files(dependency_set)
66
+ helm_chart_files.each do |chart_file|
67
+ yaml = YAML.safe_load(T.must(chart_file.content), aliases: true)
68
+ next unless yaml.is_a?(Hash)
69
+
70
+ parse_dependencies(yaml, chart_file, dependency_set) if yaml["dependencies"].is_a?(Array)
71
+ end
72
+ end
73
+
74
+ sig { params(dependency_set: DependencySet).void }
75
+ def parse_values_yaml_files(dependency_set)
76
+ helm_values_files.each do |values_file|
77
+ yaml = YAML.safe_load(T.must(values_file.content), aliases: true)
78
+ next unless yaml.is_a?(Hash)
79
+
80
+ find_images_in_hash(yaml).each do |image_details|
81
+ parsed_line = extract_image_details(image_details[:image])
82
+ next unless parsed_line
83
+
84
+ version = version_from(parsed_line)
85
+ next unless version
86
+
87
+ dependency = build_dependency(values_file, parsed_line, version)
88
+ T.must(dependency.requirements.first)[:source] =
89
+ T.must(dependency.requirements.first)[:source].merge(path: image_details[:path])
90
+
91
+ dependency_set << dependency
92
+ end
93
+ end
94
+ end
95
+
96
+ sig { params(image_string: String).returns(T.nilable(T::Hash[String, T.nilable(String)])) }
97
+ def extract_image_details(image_string)
98
+ return nil if image_string.match?(/\${[^}]+}/)
99
+
100
+ registry_match = image_string.match(%r{^(#{REGISTRY}/)?}o)
101
+ image_match = image_string.match(/#{IMAGE}/o)
102
+ tag_match = image_string.match(/#{TAG}/o)
103
+ digest_match = image_string.match(/#{DIGEST}/o)
104
+
105
+ return nil unless image_match
106
+
107
+ {
108
+ "registry" => registry_match && registry_match[:registry],
109
+ "image" => image_match[:image],
110
+ "tag" => tag_match && tag_match[:tag],
111
+ "digest" => digest_match && digest_match[:digest]
112
+ }
113
+ end
114
+
115
+ sig do
116
+ params(key: String, value: String, hash: T::Hash[T.untyped, T.untyped],
117
+ current_path: T::Array[String]).returns(T::Array[T::Hash[Symbol, String]])
118
+ end
119
+ def handle_string_value(key, value, hash, current_path)
120
+ images = []
121
+ if key == "repository" && hash["tag"].is_a?(String)
122
+ images << { path: current_path.join("."), image: "#{value}:#{hash['tag']}" }
123
+ elsif key == "image" && value.include?(":")
124
+ images << { path: current_path.join("."), image: value }
125
+ end
126
+ images
127
+ end
128
+
129
+ sig do
130
+ params(value: T::Array[T.untyped], current_path: T::Array[String]).returns(T::Array[T::Hash[Symbol, String]])
131
+ end
132
+ def handle_array_value(value, current_path)
133
+ images = []
134
+ value.each_with_index do |item, index|
135
+ images.concat(find_images_in_hash(item, current_path + [index.to_s])) if item.is_a?(Hash)
136
+ end
137
+ images
138
+ end
139
+
140
+ sig { params(hash: T.untyped, path: T.untyped).returns(T::Array[T.untyped]) }
141
+ def find_images_in_hash(hash, path = [])
142
+ images = []
143
+
144
+ hash.each do |key, value|
145
+ current_path = path + [key.to_s]
146
+
147
+ if value.is_a?(String) && (key.to_s == "image" || key.to_s == "repository")
148
+ images.concat(handle_string_value(key.to_s, value, hash, current_path))
149
+ elsif value.is_a?(Hash)
150
+ images.concat(find_images_in_hash(value, current_path))
151
+ elsif value.is_a?(Array)
152
+ images.concat(find_images_in_hash(value, current_path))
153
+ end
154
+ end
155
+
156
+ images
157
+ end
158
+
159
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
160
+ def helm_chart_files
161
+ dependency_files.select { |file| file.name.match(CHART_YAML) }
162
+ end
163
+
164
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
165
+ def helm_values_files
166
+ dependency_files.select { |file| file.name.match(VALUES_YAML) }
167
+ end
168
+
169
+ sig { override.returns(String) }
170
+ def package_manager
171
+ "helm"
172
+ end
173
+
174
+ sig { override.returns(String) }
175
+ def file_type
176
+ "helm chart"
177
+ end
178
+
179
+ sig { override.void }
180
+ def check_required_files
181
+ return if dependency_files.any?
182
+
183
+ raise "No #{file_type} files!"
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ Dependabot::FileParsers.register(
190
+ "helm",
191
+ Dependabot::Helm::FileParser
192
+ )
@@ -0,0 +1,210 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/shared/shared_file_updater"
5
+ require "yaml"
6
+
7
+ module Dependabot
8
+ module Helm
9
+ class FileUpdater < Dependabot::Shared::SharedFileUpdater
10
+ extend T::Sig
11
+ extend T::Helpers
12
+
13
+ CHART_YAML_REGEXP = /Chart\.ya?ml/i
14
+ VALUES_YAML_REGEXP = /values(?>\.[\w-]+)?\.ya?ml/i
15
+ YAML_REGEXP = /(Chart|values(?>\.[\w-]+)?)\.ya?ml/i
16
+ IMAGE_REGEX = /(?:image:|repository:\s*)/i
17
+
18
+ sig { override.returns(T::Array[Regexp]) }
19
+ def self.updated_files_regex
20
+ [CHART_YAML_REGEXP, VALUES_YAML_REGEXP]
21
+ end
22
+
23
+ sig { override.returns(String) }
24
+ def file_type
25
+ "Helm chart"
26
+ end
27
+
28
+ sig { override.returns(Regexp) }
29
+ def yaml_file_pattern
30
+ YAML_REGEXP
31
+ end
32
+
33
+ sig { override.returns(Regexp) }
34
+ def container_image_regex
35
+ IMAGE_REGEX
36
+ end
37
+
38
+ sig { override.params(escaped_declaration: String).returns(Regexp) }
39
+ def build_old_declaration_regex(escaped_declaration)
40
+ %r{#{IMAGE_REGEX}\s+["']?(docker\.io/)?#{escaped_declaration}["']?(?=\s|$)}
41
+ end
42
+
43
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
44
+ def updated_dependency_files
45
+ updated_files = []
46
+ dependency_files.each do |file|
47
+ next unless requirement_changed?(file, T.must(dependency))
48
+
49
+ if file.name.match?(CHART_YAML_REGEXP)
50
+ updated_files << updated_file(
51
+ file: file,
52
+ content: T.must(updated_chart_yaml_content(file))
53
+ )
54
+ elsif file.name.match?(VALUES_YAML_REGEXP)
55
+ updated_files << updated_file(
56
+ file: file,
57
+ content: T.must(updated_values_yaml_content(file))
58
+ )
59
+ end
60
+ end
61
+
62
+ updated_files.reject! { |f| dependency_files.include?(f) }
63
+ raise "No files changed!" if updated_files.none?
64
+
65
+ updated_files
66
+ end
67
+
68
+ private
69
+
70
+ sig do
71
+ params(content: String, yaml_obj: T::Hash[T.untyped, T.untyped],
72
+ file: Dependabot::DependencyFile).returns(String)
73
+ end
74
+ def update_chart_dependencies(content, yaml_obj, file)
75
+ if update_chart_dependency?(file)
76
+ yaml_obj["dependencies"].each do |dep|
77
+ next unless dep["name"] == T.must(dependency).name
78
+
79
+ old_version = dep["version"].to_s
80
+ new_version = T.must(dependency).version
81
+
82
+ pattern = /
83
+ (\s+-\sname:\s#{Regexp.escape(T.must(dependency).name)}.*?\n\s+version:\s)
84
+ ["']?#{Regexp.escape(old_version)}["']?
85
+ /mx
86
+ content = content.gsub(pattern) do |match|
87
+ match.gsub(/version: ["']?#{Regexp.escape(old_version)}["']?/, "version: #{new_version}")
88
+ end
89
+ end
90
+ end
91
+ content
92
+ end
93
+
94
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
95
+ def updated_chart_yaml_content(file)
96
+ content = file.content
97
+ yaml_obj = YAML.safe_load(T.must(content))
98
+
99
+ content = update_chart_dependencies(T.must(content), yaml_obj, file)
100
+
101
+ raise "Expected content to change!" if content == file.content
102
+
103
+ content
104
+ end
105
+
106
+ sig { params(content: String, path: String, old_tag: String, new_tag: String).returns(String) }
107
+ def update_tag(content, path, old_tag, new_tag)
108
+ indent_pattern = get_indent_pattern(content, path)
109
+ tag_pattern = /#{indent_pattern}tag:\s+["']?#{Regexp.escape(old_tag)}["']?/
110
+ content.gsub(tag_pattern, "#{indent_pattern}tag: #{new_tag}")
111
+ end
112
+
113
+ sig { params(content: String, path: String, old_image: String, new_image: String).returns(String) }
114
+ def update_image(content, path, old_image, new_image)
115
+ indent_pattern = get_indent_pattern(content, path)
116
+ image_pattern = /#{indent_pattern}image:\s+["']?#{Regexp.escape(old_image)}["']?/
117
+ content.gsub(image_pattern, "#{indent_pattern}image: #{new_image}")
118
+ end
119
+
120
+ sig { params(content: String, path_parts: T::Array[String]).returns(String) }
121
+ def update_tag_in_content(content, path_parts)
122
+ parent_path = T.must(path_parts[0...-1]).join(".")
123
+ old_tag = T.must(dependency).previous_version
124
+ new_tag = T.must(dependency).version
125
+ update_tag(content, parent_path, T.must(old_tag), T.must(new_tag))
126
+ end
127
+
128
+ sig { params(content: String, path: String, req: T::Hash[Symbol, T.untyped]).returns(String) }
129
+ def update_image_in_content(content, path, req)
130
+ old_image = build_old_image_string(req)
131
+ new_image = build_new_image_string(req)
132
+ update_image(content, path, old_image, new_image)
133
+ end
134
+
135
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
136
+ def updated_values_yaml_content(file)
137
+ content = file.content
138
+ req = T.must(dependency).requirements.find { |r| r[:file] == file.name }
139
+
140
+ if update_container_image?(file) && req&.dig(:source, :path)
141
+ path = req.dig(:source, :path).to_s
142
+ path_parts = path.split(".")
143
+
144
+ content = if path_parts.last == "tag"
145
+ update_tag_in_content(T.must(content), path_parts)
146
+ elsif path_parts.last == "image"
147
+ update_image_in_content(T.must(content), path, req)
148
+ else
149
+ content
150
+ end
151
+ end
152
+
153
+ raise "Expected content to change!" if content == file.content
154
+
155
+ content
156
+ end
157
+
158
+ sig { params(content: String, path: String).returns(String) }
159
+ def get_indent_pattern(content, path)
160
+ path_parts = path.split(".")
161
+ indent = T.let("", T.untyped)
162
+
163
+ path_parts.each do |part|
164
+ pattern = /^(#{indent}\s*)#{part}:/
165
+ if content.match(pattern)
166
+ indent = T.must(T.must(content.match(pattern))[1]) + " " # Add 2 spaces for next level
167
+ end
168
+ end
169
+
170
+ indent
171
+ end
172
+
173
+ sig { params(requirement: T::Hash[Symbol, T.untyped]).returns(String) }
174
+ def build_old_image_string(requirement)
175
+ old_source = requirement.fetch(:source)
176
+ prefix = old_source[:registry] ? "#{old_source[:registry]}/" : ""
177
+ name = T.must(dependency).name
178
+ tag = T.must(dependency).previous_version
179
+ digest = old_source[:digest] ? "@sha256:#{old_source[:digest]}" : ""
180
+
181
+ "#{prefix}#{name}:#{tag}#{digest}"
182
+ end
183
+
184
+ sig { params(requirement: T::Hash[Symbol, T.untyped]).returns(String) }
185
+ def build_new_image_string(requirement)
186
+ new_source = requirement.fetch(:source)
187
+ prefix = new_source[:registry] ? "#{new_source[:registry]}/" : ""
188
+ name = T.must(dependency).name
189
+ tag = T.must(dependency).version
190
+ digest = new_source[:digest] ? "@sha256:#{new_source[:digest]}" : ""
191
+
192
+ "#{prefix}#{name}:#{tag}#{digest}"
193
+ end
194
+
195
+ sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
196
+ def update_chart_dependency?(file)
197
+ reqs = T.must(dependency).requirements.select { |r| r[:file] == file.name }
198
+ reqs.any? { |r| r[:metadata]&.dig(:type) == :helm_chart }
199
+ end
200
+
201
+ sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
202
+ def update_container_image?(file)
203
+ reqs = T.must(dependency).requirements.select { |r| r[:file] == file.name }
204
+ reqs.any? { |r| r[:groups]&.include?("image") }
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ Dependabot::FileUpdaters.register("helm", Dependabot::Helm::FileUpdater)
@@ -0,0 +1,52 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/ecosystem"
6
+ require "dependabot/docker/version"
7
+
8
+ module Dependabot
9
+ module Helm
10
+ ECOSYSTEM = "helm"
11
+
12
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
13
+
14
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
15
+
16
+ class HelmPackageManager < Dependabot::Ecosystem::VersionManager
17
+ extend T::Sig
18
+
19
+ NAME = "helm"
20
+
21
+ # As helm updater is an in house custom utility, We use a placeholder
22
+ # version number for helm updater
23
+ VERSION = "1.0.0"
24
+
25
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
26
+
27
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
28
+
29
+ sig do
30
+ void
31
+ end
32
+ def initialize
33
+ super(
34
+ name: NAME,
35
+ version: Version.new(VERSION),
36
+ deprecated_versions: DEPRECATED_VERSIONS,
37
+ supported_versions: SUPPORTED_VERSIONS
38
+ )
39
+ end
40
+
41
+ sig { override.returns(T::Boolean) }
42
+ def deprecated?
43
+ false
44
+ end
45
+
46
+ sig { override.returns(T::Boolean) }
47
+ def unsupported?
48
+ false
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,199 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "dependabot/update_checkers"
6
+ require "dependabot/update_checkers/base"
7
+ require "dependabot/errors"
8
+ require "dependabot/docker/version"
9
+ require "dependabot/docker/requirement"
10
+ require "dependabot/shared/utils/credentials_finder"
11
+ require "excon"
12
+ require "yaml"
13
+
14
+ module Dependabot
15
+ module Helm
16
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
17
+ extend T::Sig
18
+
19
+ sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
20
+ def latest_version
21
+ @latest_version ||= T.let(fetch_latest_version, T.nilable(T.any(String, Gem::Version)))
22
+ end
23
+
24
+ sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
25
+ def latest_resolvable_version
26
+ latest_version
27
+ end
28
+
29
+ sig { override.returns(T.nilable(String)) }
30
+ def latest_resolvable_version_with_no_unlock
31
+ dependency.version
32
+ end
33
+
34
+ sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
35
+ def updated_requirements
36
+ return dependency.requirements unless latest_version
37
+
38
+ dependency.requirements.map do |req|
39
+ updated_metadata = req.fetch(:metadata).dup
40
+ updated_req = req.dup
41
+ if updated_metadata.key?(:type) && updated_metadata[:type] == :helm_chart
42
+ updated_req[:requirement] = latest_version.to_s
43
+ updated_req[:source][:tag] = latest_version.to_s
44
+ end
45
+
46
+ updated_req
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ sig { override.returns(T::Boolean) }
53
+ def latest_version_resolvable_with_full_unlock?
54
+ false
55
+ end
56
+
57
+ sig { params(requirements_to_unlock: T.nilable(Symbol)).returns(T::Boolean) }
58
+ def version_can_update?(requirements_to_unlock:) # rubocop:disable Lint/UnusedMethodArgument
59
+ return false unless latest_version
60
+
61
+ version_class.new(latest_version.to_s) > version_class.new(dependency.version)
62
+ end
63
+
64
+ sig { returns(T.nilable(T.any(String, Gem::Version))) }
65
+ def fetch_latest_version
66
+ case dependency_type
67
+ when :chart_dependency
68
+ fetch_latest_chart_version
69
+ when :image_reference
70
+ fetch_latest_image_version
71
+ else
72
+ Gem::Version.new(dependency.version)
73
+ end
74
+ end
75
+
76
+ sig { returns(Symbol) }
77
+ def dependency_type
78
+ req = dependency.requirements.first
79
+
80
+ return :image_reference if T.must(req)[:groups]&.include?("image")
81
+ return :chart_dependency if T.must(req).dig(:metadata, :type) == :helm_chart
82
+
83
+ :unknown
84
+ end
85
+
86
+ sig { params(index_url: String).returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
87
+ def fetch_helm_chart_index(index_url)
88
+ Dependabot.logger.info("Fetching Helm chart index from #{index_url}")
89
+
90
+ response = Excon.get(
91
+ index_url,
92
+ idempotent: true,
93
+ middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower]
94
+ )
95
+
96
+ Dependabot.logger.info("Received response from #{index_url} with status #{response.status}")
97
+
98
+ YAML.safe_load(response.body)
99
+ rescue Excon::Error => e
100
+ Dependabot.logger.error("Error fetching Helm index from #{index_url}: #{e.message}")
101
+ nil
102
+ rescue StandardError => e
103
+ Dependabot.logger.error("Error parsing Helm index: #{e.message}")
104
+ nil
105
+ end
106
+
107
+ sig { params(all_versions: T::Array[String]).returns(T::Array[String]) }
108
+ def filter_valid_versions(all_versions)
109
+ all_versions.reject do |version|
110
+ version_class.new(version) <= version_class.new(dependency.version) ||
111
+ ignore_requirements.any? { |r| r.satisfied_by?(version_class.new(version)) }
112
+ end
113
+ end
114
+
115
+ sig { returns(T.nilable(Gem::Version)) }
116
+ def fetch_latest_chart_version
117
+ source_url = dependency.requirements.first&.dig(:source, :registry)
118
+ return nil unless source_url
119
+
120
+ repo_url = source_url.to_s.strip.chomp("/")
121
+ chart_name = dependency.name
122
+ index_url = "#{repo_url}/index.yaml"
123
+
124
+ index = fetch_helm_chart_index(index_url)
125
+ return nil unless index && index["entries"] && index["entries"][chart_name]
126
+
127
+ all_versions = index["entries"][chart_name].map { |entry| entry["version"] }
128
+ Dependabot.logger.info("Found #{all_versions.length} versions for #{chart_name}")
129
+
130
+ valid_versions = filter_valid_versions(all_versions)
131
+ Dependabot.logger.info("After filtering, found #{valid_versions.length} valid versions for #{chart_name}")
132
+
133
+ return nil if valid_versions.empty?
134
+
135
+ highest_version = valid_versions.map { |v| version_class.new(v) }.max
136
+ Dependabot.logger.info("Highest valid version for #{chart_name} is #{highest_version}")
137
+
138
+ highest_version
139
+ end
140
+
141
+ sig { returns(T.nilable(String)) }
142
+ def fetch_latest_image_version
143
+ docker_dependency = build_docker_dependency
144
+
145
+ Dependabot.logger.info("Delegating to Docker UpdateChecker for image: #{docker_dependency.name}")
146
+
147
+ docker_checker = Dependabot::UpdateCheckers.for_package_manager("docker").new(
148
+ dependency: docker_dependency,
149
+ dependency_files: dependency_files,
150
+ credentials: credentials,
151
+ ignored_versions: ignored_versions,
152
+ security_advisories: security_advisories,
153
+ raise_on_ignored: raise_on_ignored
154
+ )
155
+
156
+ latest = docker_checker.latest_version
157
+ latest_version_str = latest&.to_s
158
+
159
+ Dependabot.logger.info("Docker UpdateChecker found latest version: #{latest_version_str || 'none'}")
160
+
161
+ latest_version_str
162
+ end
163
+
164
+ sig { returns(Dependabot::Dependency) }
165
+ def build_docker_dependency
166
+ source = T.must(dependency.requirements.first)[:source]
167
+ name = dependency.name
168
+ version = dependency.version
169
+
170
+ if source[:path]
171
+ parts = source[:path].split(".")
172
+ if parts.length > 1 && (parts.last == "tag" || parts.last == "image")
173
+ # The actual image name might be in image.repository
174
+ name = parts[0...-1].join(".")
175
+ end
176
+ end
177
+
178
+ registry = source[:registry] || "docker.io"
179
+
180
+ Dependency.new(
181
+ name: name,
182
+ version: version,
183
+ requirements: [{
184
+ requirement: nil,
185
+ groups: [],
186
+ file: T.must(dependency.requirements.first)[:file],
187
+ source: {
188
+ registry: registry,
189
+ tag: version
190
+ }
191
+ }],
192
+ package_manager: "helm"
193
+ )
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ Dependabot::UpdateCheckers.register("helm", Dependabot::Helm::UpdateChecker)
@@ -0,0 +1,23 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ # These all need to be required so the various classes can be registered in a
5
+ # lookup table of package manager names to concrete classes.
6
+
7
+ require "dependabot/docker"
8
+
9
+ require "dependabot/helm/file_fetcher"
10
+ require "dependabot/helm/file_parser"
11
+ require "dependabot/helm/file_updater"
12
+ require "dependabot/helm/update_checker"
13
+
14
+ Dependabot::Utils.register_version_class("helm", Dependabot::Docker::Version)
15
+ Dependabot::Utils.register_requirement_class("helm", Dependabot::Docker::Requirement)
16
+ Dependabot::MetadataFinders.register("helm", Dependabot::Docker::MetadataFinder)
17
+
18
+ require "dependabot/pull_request_creator/labeler"
19
+ Dependabot::PullRequestCreator::Labeler
20
+ .register_label_details("helm", name: "helm", colour: "E5F2FC")
21
+
22
+ require "dependabot/dependency"
23
+ Dependabot::Dependency.register_production_check("helm", ->(_) { true })
metadata ADDED
@@ -0,0 +1,290 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dependabot-helm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.301.1
5
+ platform: ruby
6
+ authors:
7
+ - Dependabot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dependabot-common
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.301.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.301.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: dependabot-docker
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.301.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.301.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: debug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.9.2
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.9.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: gpgme
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '13'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '13'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.12'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-its
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '1.3'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '1.3'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec-sorbet
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 1.9.2
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 1.9.2
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.67.0
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.67.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-performance
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 1.22.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 1.22.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: 2.29.1
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: 2.29.1
167
+ - !ruby/object:Gem::Dependency
168
+ name: rubocop-sorbet
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.8.5
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.8.5
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.22.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.22.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: turbo_tests
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 2.2.0
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 2.2.0
209
+ - !ruby/object:Gem::Dependency
210
+ name: vcr
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - "~>"
214
+ - !ruby/object:Gem::Version
215
+ version: '6.1'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '6.1'
223
+ - !ruby/object:Gem::Dependency
224
+ name: webmock
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '3.18'
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - "~>"
235
+ - !ruby/object:Gem::Version
236
+ version: '3.18'
237
+ - !ruby/object:Gem::Dependency
238
+ name: webrick
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '1.7'
244
+ type: :development
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '1.7'
251
+ description: Dependabot-Helm provides support for bumping Helm image tags via Dependabot.
252
+ If you want support for multiple package managers, you probably want the meta-gem
253
+ dependabot-omnibus.
254
+ email: opensource@github.com
255
+ executables: []
256
+ extensions: []
257
+ extra_rdoc_files: []
258
+ files:
259
+ - lib/dependabot/helm.rb
260
+ - lib/dependabot/helm/file_fetcher.rb
261
+ - lib/dependabot/helm/file_parser.rb
262
+ - lib/dependabot/helm/file_updater.rb
263
+ - lib/dependabot/helm/package_manager.rb
264
+ - lib/dependabot/helm/update_checker.rb
265
+ homepage: https://github.com/dependabot/dependabot-core
266
+ licenses:
267
+ - MIT
268
+ metadata:
269
+ bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
270
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.301.1
271
+ post_install_message:
272
+ rdoc_options: []
273
+ require_paths:
274
+ - lib
275
+ required_ruby_version: !ruby/object:Gem::Requirement
276
+ requirements:
277
+ - - ">="
278
+ - !ruby/object:Gem::Version
279
+ version: 3.1.0
280
+ required_rubygems_version: !ruby/object:Gem::Requirement
281
+ requirements:
282
+ - - ">="
283
+ - !ruby/object:Gem::Version
284
+ version: 3.1.0
285
+ requirements: []
286
+ rubygems_version: 3.5.22
287
+ signing_key:
288
+ specification_version: 4
289
+ summary: Provides Dependabot support for Helm
290
+ test_files: []