dependabot-helm 0.302.0 → 0.303.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/helm/file_fetcher.rb +13 -1
- data/lib/dependabot/helm/file_parser.rb +22 -9
- data/lib/dependabot/helm/file_updater/chart_updater.rb +76 -0
- data/lib/dependabot/helm/file_updater/image_updater.rb +116 -0
- data/lib/dependabot/helm/file_updater/lock_file_generator.rb +63 -0
- data/lib/dependabot/helm/file_updater.rb +44 -126
- data/lib/dependabot/helm/helpers.rb +56 -0
- data/lib/dependabot/helm/update_checker.rb +130 -40
- metadata +13 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53be03f1fcf2d32d85e040547378b00c860458ae23f782cba4fd437de7defa44
|
4
|
+
data.tar.gz: 3d55823e45e4fa2e31b80603e0613f1929b0f2512cd34bc39a49a88c8a8aa647
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd62305c3145cf79ee84cc653d50a8ad1cf0f77855c80c4dce6932e92c20835447d6b99c63453bc3fb1fa23ecdad0960ab363ac414cba6a4e2a159e5fc0c8888
|
7
|
+
data.tar.gz: 57c31fc74e516b7eeade03b18959bc902f546ccd39b22263a6dc0bf7b5c76e63a0d414be88bf41364d850f07ef5f76bea01a475561cd2dcd16584f4a2514edd8
|
@@ -7,11 +7,15 @@ module Dependabot
|
|
7
7
|
module Helm
|
8
8
|
class FileFetcher < Dependabot::Shared::SharedFileFetcher
|
9
9
|
FILENAME_REGEX = /.*\.ya?ml$/i
|
10
|
+
CHART_LOCK_REGEXP = /Chart\.lock/i
|
10
11
|
|
11
12
|
sig { override.returns(T::Array[DependencyFile]) }
|
12
13
|
def fetch_files
|
14
|
+
return [] unless allow_beta_ecosystems?
|
15
|
+
|
13
16
|
fetched_files = []
|
14
|
-
fetched_files += correctly_encoded_helm_files
|
17
|
+
fetched_files += correctly_encoded_helm_files
|
18
|
+
fetched_files += chart_locks
|
15
19
|
|
16
20
|
return fetched_files if fetched_files.any?
|
17
21
|
|
@@ -31,6 +35,14 @@ module Dependabot
|
|
31
35
|
.map { |f| fetch_file_from_host(f.name) }, T.nilable(T::Array[DependencyFile]))
|
32
36
|
end
|
33
37
|
|
38
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
39
|
+
def chart_locks
|
40
|
+
@chart_locks ||=
|
41
|
+
T.let(repo_contents(raise_errors: false)
|
42
|
+
.select { |f| f.type == "file" && f.name.match?(CHART_LOCK_REGEXP) }
|
43
|
+
.map { |f| fetch_file_from_host(f.name) }, T.nilable(T::Array[DependencyFile]))
|
44
|
+
end
|
45
|
+
|
34
46
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
35
47
|
def correctly_encoded_helm_files
|
36
48
|
helm_files.select { |f| T.must(f.content).valid_encoding? }
|
@@ -11,7 +11,9 @@ module Dependabot
|
|
11
11
|
extend T::Sig
|
12
12
|
|
13
13
|
CHART_YAML = /.*chart\.ya?ml$/i
|
14
|
+
CHART_LOCK = /.*chart\.lock$/i
|
14
15
|
VALUES_YAML = /.*values\.ya?ml$/i
|
16
|
+
DEFAULT_REPOSITORY = "https://charts.helm.sh/stable"
|
15
17
|
|
16
18
|
sig { returns(Ecosystem) }
|
17
19
|
def ecosystem
|
@@ -41,26 +43,38 @@ module Dependabot
|
|
41
43
|
end
|
42
44
|
def parse_dependencies(yaml, chart_file, dependency_set)
|
43
45
|
yaml["dependencies"].each do |dep|
|
44
|
-
next unless dep.is_a?(Hash) && dep["name"] && dep["version"]
|
46
|
+
next unless dep.is_a?(Hash) && dep["name"] && dep["version"]
|
45
47
|
|
46
48
|
parsed_line = {
|
47
49
|
"image" => dep["name"],
|
48
50
|
"tag" => dep["version"],
|
49
|
-
"registry" => dep["repository"],
|
51
|
+
"registry" => repository_from_registry(dep["repository"]),
|
50
52
|
"digest" => nil
|
51
53
|
}
|
52
54
|
|
53
55
|
dependency = build_dependency(chart_file, parsed_line, dep["version"])
|
54
|
-
dependency
|
55
|
-
req[:metadata] = {} unless req[:metadata]
|
56
|
-
req[:metadata][:type] = :helm_chart
|
57
|
-
req
|
58
|
-
end
|
56
|
+
add_dependency_type_to_dependency(dependency, :helm_chart)
|
59
57
|
|
60
58
|
dependency_set << dependency
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
62
|
+
sig { params(dependency: Dependabot::Dependency, type: Symbol).void }
|
63
|
+
def add_dependency_type_to_dependency(dependency, type)
|
64
|
+
dependency.requirements.map! do |req|
|
65
|
+
req[:metadata] = {} unless req[:metadata]
|
66
|
+
req[:metadata][:type] = type
|
67
|
+
req
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
sig { params(repository: T.nilable(String)).returns(String) }
|
72
|
+
def repository_from_registry(repository)
|
73
|
+
return DEFAULT_REPOSITORY if repository.nil?
|
74
|
+
|
75
|
+
repository
|
76
|
+
end
|
77
|
+
|
64
78
|
sig { params(dependency_set: DependencySet).void }
|
65
79
|
def parse_chart_yaml_files(dependency_set)
|
66
80
|
helm_chart_files.each do |chart_file|
|
@@ -85,8 +99,7 @@ module Dependabot
|
|
85
99
|
next unless version
|
86
100
|
|
87
101
|
dependency = build_dependency(values_file, parsed_line, version)
|
88
|
-
|
89
|
-
T.must(dependency.requirements.first)[:source].merge(path: image_details[:path])
|
102
|
+
add_dependency_type_to_dependency(dependency, :docker_image)
|
90
103
|
|
91
104
|
dependency_set << dependency
|
92
105
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
require "dependabot/dependency"
|
7
|
+
require "dependabot/shared/shared_file_updater"
|
8
|
+
require "fileutils"
|
9
|
+
require "tmpdir"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module Helm
|
13
|
+
class FileUpdater < Dependabot::Shared::SharedFileUpdater
|
14
|
+
class ChartUpdater
|
15
|
+
extend T::Sig
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
dependency: Dependabot::Dependency
|
20
|
+
).void
|
21
|
+
end
|
22
|
+
def initialize(dependency:)
|
23
|
+
@dependency = dependency
|
24
|
+
end
|
25
|
+
|
26
|
+
sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
|
27
|
+
def updated_chart_yaml_content(file)
|
28
|
+
content = file.content
|
29
|
+
yaml_obj = YAML.safe_load(T.must(content))
|
30
|
+
|
31
|
+
content = update_chart_dependencies(T.must(content), yaml_obj, file)
|
32
|
+
|
33
|
+
raise "Expected content to change!" if content == file.content
|
34
|
+
|
35
|
+
content
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
sig { returns(Dependabot::Dependency) }
|
41
|
+
attr_reader :dependency
|
42
|
+
|
43
|
+
sig do
|
44
|
+
params(content: String, yaml_obj: T::Hash[T.untyped, T.untyped],
|
45
|
+
file: Dependabot::DependencyFile).returns(String)
|
46
|
+
end
|
47
|
+
def update_chart_dependencies(content, yaml_obj, file)
|
48
|
+
if update_chart_dependency?(file) && yaml_obj["dependencies"]
|
49
|
+
yaml_obj["dependencies"].each do |dep|
|
50
|
+
next unless dep["name"] == dependency.name
|
51
|
+
|
52
|
+
old_version = dep["version"].to_s
|
53
|
+
new_version = dependency.version
|
54
|
+
|
55
|
+
pattern = /
|
56
|
+
(\s+-\s+name:\s+#{Regexp.escape(dependency.name)}.*?\n\s+)
|
57
|
+
(version:\s+)
|
58
|
+
["']?#{Regexp.escape(old_version)}["']?
|
59
|
+
/mx
|
60
|
+
content = content.gsub(pattern) do |match|
|
61
|
+
match.gsub(/version: ["']?#{Regexp.escape(old_version)}["']?/, "version: #{new_version}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
content
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
|
69
|
+
def update_chart_dependency?(file)
|
70
|
+
reqs = dependency.requirements.select { |r| r[:file] == file.name }
|
71
|
+
reqs.any? { |r| r[:metadata]&.dig(:type) == :helm_chart }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "dependabot/shared/shared_file_updater"
|
5
|
+
require "dependabot/dependency"
|
6
|
+
require "dependabot/dependency_file"
|
7
|
+
require "yaml"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Helm
|
11
|
+
class FileUpdater < Dependabot::Shared::SharedFileUpdater
|
12
|
+
class ImageUpdater
|
13
|
+
extend T::Sig
|
14
|
+
extend T::Helpers
|
15
|
+
|
16
|
+
sig { params(dependency: Dependency, dependency_files: T::Array[Dependabot::DependencyFile]).void }
|
17
|
+
def initialize(dependency:, dependency_files:)
|
18
|
+
@dependency_files = dependency_files
|
19
|
+
@dependency = dependency
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(file_name: String).returns(T.nilable(String)) }
|
23
|
+
def updated_values_yaml_content(file_name)
|
24
|
+
value_file = dependency_files.find { |f| f.name.match?(file_name) }
|
25
|
+
raise "Expected a values.yaml file to exist!" if value_file.nil?
|
26
|
+
|
27
|
+
content = value_file.content
|
28
|
+
yaml_stream = YAML.parse_stream(T.must(content))
|
29
|
+
|
30
|
+
update_image_tags_recursive(yaml_stream, T.must(content))
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
36
|
+
attr_reader :dependency_files
|
37
|
+
sig { returns(Dependabot::Dependency) }
|
38
|
+
attr_reader :dependency
|
39
|
+
|
40
|
+
sig { params(yaml_stream: Psych::Nodes::Stream, content: String).returns(String) }
|
41
|
+
def update_image_tags_recursive(yaml_stream, content)
|
42
|
+
updated_content = content.dup.split("\n")
|
43
|
+
|
44
|
+
yaml_stream.children.each do |document|
|
45
|
+
document.children.each do |root_node|
|
46
|
+
updated_content = find_and_update_images(root_node, updated_content)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
updated_content = updated_content.join("\n")
|
51
|
+
|
52
|
+
raise "Expected content to change!" if content == updated_content
|
53
|
+
|
54
|
+
updated_content
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { params(node: Psych::Nodes::Node, content: T::Array[String]).returns(T::Array[String]) }
|
58
|
+
def find_and_update_images(node, content)
|
59
|
+
if node.is_a?(Psych::Nodes::Mapping)
|
60
|
+
content = process_mapping_node(node, content)
|
61
|
+
elsif node.is_a?(Psych::Nodes::Sequence)
|
62
|
+
content = process_sequence_node(node, content)
|
63
|
+
end
|
64
|
+
|
65
|
+
content
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(node: Psych::Nodes::Node, content: T::Array[String]).returns(T::Array[String]) }
|
69
|
+
def process_mapping_node(node, content)
|
70
|
+
node.children.each_slice(2) do |key_node, value_node|
|
71
|
+
next unless key_node.is_a?(Psych::Nodes::Scalar)
|
72
|
+
|
73
|
+
key = key_node.value
|
74
|
+
content = process_image_key(key, value_node, content)
|
75
|
+
|
76
|
+
if value_node.is_a?(Psych::Nodes::Mapping) || value_node.is_a?(Psych::Nodes::Sequence)
|
77
|
+
content = find_and_update_images(value_node, content)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
content
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { params(node: Psych::Nodes::Node, content: T::Array[String]).returns(T::Array[String]) }
|
84
|
+
def process_sequence_node(node, content)
|
85
|
+
node.children.reduce(content) do |updated_content, child|
|
86
|
+
find_and_update_images(child, updated_content)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
sig { params(key: String, value_node: Psych::Nodes::Node, content: T::Array[String]).returns(T::Array[String]) }
|
91
|
+
def process_image_key(key, value_node, content)
|
92
|
+
return content unless key == "image" && value_node.is_a?(Psych::Nodes::Mapping)
|
93
|
+
|
94
|
+
dependency_name = dependency.name
|
95
|
+
dependency_version = T.must(dependency.version)
|
96
|
+
dependency_requirements = dependency.requirements
|
97
|
+
|
98
|
+
has_dependency = value_node.children.any? { |n| n.value == dependency_name }
|
99
|
+
return content unless has_dependency
|
100
|
+
|
101
|
+
dependency_requirements.each do |req|
|
102
|
+
next unless req[:metadata][:type] == :docker_image
|
103
|
+
|
104
|
+
version_scalar = value_node.children.find { |n| n.value == req[:source][:tag] }
|
105
|
+
next unless version_scalar
|
106
|
+
|
107
|
+
line = version_scalar.start_line
|
108
|
+
content[line] = T.must(content[line]).gsub(req[:source][:tag], dependency_version)
|
109
|
+
end
|
110
|
+
|
111
|
+
content
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "sorbet-runtime"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
require "dependabot/dependency"
|
7
|
+
require "dependabot/shared/shared_file_updater"
|
8
|
+
require "dependabot/helm/helpers"
|
9
|
+
require "fileutils"
|
10
|
+
require "tmpdir"
|
11
|
+
|
12
|
+
module Dependabot
|
13
|
+
module Helm
|
14
|
+
class FileUpdater < Dependabot::Shared::SharedFileUpdater
|
15
|
+
class LockFileGenerator
|
16
|
+
extend T::Sig
|
17
|
+
|
18
|
+
sig do
|
19
|
+
params(
|
20
|
+
dependencies: T::Array[Dependabot::Dependency],
|
21
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
22
|
+
repo_contents_path: String,
|
23
|
+
credentials: T::Array[Dependabot::Credential]
|
24
|
+
).void
|
25
|
+
end
|
26
|
+
def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
|
27
|
+
@dependencies = dependencies
|
28
|
+
@dependency_files = dependency_files
|
29
|
+
@repo_contents_path = repo_contents_path
|
30
|
+
@credentials = credentials
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { params(chart_lock: Dependabot::DependencyFile, updated_content: String).returns(String) }
|
34
|
+
def updated_chart_lock(chart_lock, updated_content)
|
35
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
36
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
37
|
+
File.write("Chart.yaml", updated_content)
|
38
|
+
Helpers.update_lock
|
39
|
+
|
40
|
+
File.read(chart_lock.name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
48
|
+
attr_reader :dependencies
|
49
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
50
|
+
attr_reader :dependency_files
|
51
|
+
sig { returns(String) }
|
52
|
+
attr_reader :repo_contents_path
|
53
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
54
|
+
attr_reader :credentials
|
55
|
+
|
56
|
+
sig { returns(String) }
|
57
|
+
def base_dir
|
58
|
+
T.must(dependency_files.first).directory
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "dependabot/shared/shared_file_updater"
|
5
|
+
require "dependabot/helm/file_updater/lock_file_generator"
|
6
|
+
require "dependabot/helm/file_updater/image_updater"
|
7
|
+
require "dependabot/helm/file_updater/chart_updater"
|
5
8
|
require "yaml"
|
6
9
|
|
7
10
|
module Dependabot
|
@@ -11,6 +14,7 @@ module Dependabot
|
|
11
14
|
extend T::Helpers
|
12
15
|
|
13
16
|
CHART_YAML_REGEXP = /Chart\.ya?ml/i
|
17
|
+
CHART_LOCK_REGEXP = /Chart\.lock/i
|
14
18
|
VALUES_YAML_REGEXP = /values(?>\.[\w-]+)?\.ya?ml/i
|
15
19
|
YAML_REGEXP = /(Chart|values(?>\.[\w-]+)?)\.ya?ml/i
|
16
20
|
IMAGE_REGEX = /(?:image:|repository:\s*)/i
|
@@ -47,14 +51,17 @@ module Dependabot
|
|
47
51
|
next unless requirement_changed?(file, T.must(dependency))
|
48
52
|
|
49
53
|
if file.name.match?(CHART_YAML_REGEXP)
|
54
|
+
updated_content = chart_updater.updated_chart_yaml_content(file)
|
50
55
|
updated_files << updated_file(
|
51
56
|
file: file,
|
52
|
-
content: T.must(
|
57
|
+
content: T.must(updated_content)
|
53
58
|
)
|
59
|
+
|
60
|
+
updated_files.concat(update_chart_locks(T.must(updated_content))) if chart_locks
|
54
61
|
elsif file.name.match?(VALUES_YAML_REGEXP)
|
55
62
|
updated_files << updated_file(
|
56
63
|
file: file,
|
57
|
-
content: T.must(updated_values_yaml_content(file))
|
64
|
+
content: T.must(image_updater.updated_values_yaml_content(file.name))
|
58
65
|
)
|
59
66
|
end
|
60
67
|
end
|
@@ -67,141 +74,52 @@ module Dependabot
|
|
67
74
|
|
68
75
|
private
|
69
76
|
|
70
|
-
sig
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
77
|
+
sig { params(updated_content: String).returns(T::Array[Dependabot::DependencyFile]) }
|
78
|
+
def update_chart_locks(updated_content)
|
79
|
+
chart_locks.map do |chart_lock|
|
80
|
+
updated_file(
|
81
|
+
file: chart_lock,
|
82
|
+
content: updated_chart_lock_content(chart_lock, updated_content)
|
83
|
+
)
|
90
84
|
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
85
|
end
|
112
86
|
|
113
|
-
sig {
|
114
|
-
def
|
115
|
-
|
116
|
-
|
117
|
-
|
87
|
+
sig { returns(LockFileGenerator) }
|
88
|
+
def lockfile_updater
|
89
|
+
@lockfile_updater ||= T.let(LockFileGenerator.new(
|
90
|
+
dependencies: dependencies,
|
91
|
+
dependency_files: dependency_files,
|
92
|
+
repo_contents_path: T.must(repo_contents_path),
|
93
|
+
credentials: credentials
|
94
|
+
), T.nilable(Dependabot::Helm::FileUpdater::LockFileGenerator))
|
118
95
|
end
|
119
96
|
|
120
|
-
sig {
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
new_tag = T.must(dependency).version
|
125
|
-
update_tag(content, parent_path, T.must(old_tag), T.must(new_tag))
|
97
|
+
sig { returns(ImageUpdater) }
|
98
|
+
def image_updater
|
99
|
+
@image_updater ||= T.let(ImageUpdater.new(dependency: T.must(dependency), dependency_files: dependency_files),
|
100
|
+
T.nilable(Dependabot::Helm::FileUpdater::ImageUpdater))
|
126
101
|
end
|
127
102
|
|
128
|
-
sig {
|
129
|
-
def
|
130
|
-
|
131
|
-
|
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}"
|
103
|
+
sig { returns(ChartUpdater) }
|
104
|
+
def chart_updater
|
105
|
+
@chart_updater ||= T.let(ChartUpdater.new(dependency: T.must(dependency)),
|
106
|
+
T.nilable(Dependabot::Helm::FileUpdater::ChartUpdater))
|
193
107
|
end
|
194
108
|
|
195
|
-
sig { params(
|
196
|
-
def
|
197
|
-
|
198
|
-
|
109
|
+
sig { params(chart_lock: Dependabot::DependencyFile, updated_content: String).returns(String) }
|
110
|
+
def updated_chart_lock_content(chart_lock, updated_content)
|
111
|
+
@updated_chart_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
112
|
+
@updated_chart_lock_content[chart_lock.name] ||=
|
113
|
+
lockfile_updater.updated_chart_lock(chart_lock, updated_content)
|
199
114
|
end
|
200
115
|
|
201
|
-
sig {
|
202
|
-
def
|
203
|
-
|
204
|
-
|
116
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
117
|
+
def chart_locks
|
118
|
+
@chart_locks ||= T.let(
|
119
|
+
dependency_files
|
120
|
+
.select { |f| f.name.match(CHART_LOCK_REGEXP) },
|
121
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
122
|
+
)
|
205
123
|
end
|
206
124
|
end
|
207
125
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "dependabot/dependency"
|
5
|
+
require "dependabot/file_parsers"
|
6
|
+
require "dependabot/file_parsers/base"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
require "sorbet-runtime"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Helm
|
12
|
+
module Helpers
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(name: String).returns(String) }
|
16
|
+
def self.search_releases(name)
|
17
|
+
Dependabot.logger.info("Searching Helm repository for: #{name}")
|
18
|
+
|
19
|
+
Dependabot::SharedHelpers.run_shell_command(
|
20
|
+
"helm search repo #{name} --versions --output=json",
|
21
|
+
fingerprint: "helm search repo <name> --versions --output=json"
|
22
|
+
).strip
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(repo_name: String, repo_url: String).returns(String) }
|
26
|
+
def self.add_repo(repo_name, repo_url)
|
27
|
+
Dependabot.logger.info("Adding Helm repository: #{repo_name} (#{repo_url})")
|
28
|
+
|
29
|
+
Dependabot::SharedHelpers.run_shell_command(
|
30
|
+
"helm repo add #{repo_name} #{repo_url}",
|
31
|
+
fingerprint: "helm repo add <repo_name> <repo_url>"
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { returns(String) }
|
36
|
+
def self.update_repo
|
37
|
+
Dependabot.logger.info("Updating Helm repositories")
|
38
|
+
|
39
|
+
Dependabot::SharedHelpers.run_shell_command(
|
40
|
+
"helm repo update",
|
41
|
+
fingerprint: "helm repo update"
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(String) }
|
46
|
+
def self.update_lock
|
47
|
+
Dependabot.logger.info("Updating Building Lock File")
|
48
|
+
|
49
|
+
Dependabot::SharedHelpers.run_shell_command(
|
50
|
+
"helm dependency update",
|
51
|
+
fingerprint: "helm dependency update"
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -8,8 +8,11 @@ require "dependabot/errors"
|
|
8
8
|
require "dependabot/docker/version"
|
9
9
|
require "dependabot/docker/requirement"
|
10
10
|
require "dependabot/shared/utils/credentials_finder"
|
11
|
+
require "dependabot/shared_helpers"
|
11
12
|
require "excon"
|
12
13
|
require "yaml"
|
14
|
+
require "json"
|
15
|
+
require "dependabot/helm/helpers"
|
13
16
|
|
14
17
|
module Dependabot
|
15
18
|
module Helm
|
@@ -38,10 +41,7 @@ module Dependabot
|
|
38
41
|
dependency.requirements.map do |req|
|
39
42
|
updated_metadata = req.fetch(:metadata).dup
|
40
43
|
updated_req = req.dup
|
41
|
-
if updated_metadata.key?(:type)
|
42
|
-
updated_req[:requirement] = latest_version.to_s
|
43
|
-
updated_req[:source][:tag] = latest_version.to_s
|
44
|
-
end
|
44
|
+
updated_req[:requirement] = latest_version.to_s if updated_metadata.key?(:type)
|
45
45
|
|
46
46
|
updated_req
|
47
47
|
end
|
@@ -49,6 +49,68 @@ module Dependabot
|
|
49
49
|
|
50
50
|
private
|
51
51
|
|
52
|
+
sig { override.returns(T::Array[Dependabot::Dependency]) }
|
53
|
+
def updated_dependencies_after_full_unlock
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
|
57
|
+
sig do
|
58
|
+
params(chart_name: String, repo_name: T.nilable(String),
|
59
|
+
repo_url: T.nilable(String)).returns(T.nilable(Gem::Version))
|
60
|
+
end
|
61
|
+
def fetch_releases_with_helm_cli(chart_name, repo_name, repo_url)
|
62
|
+
Dependabot.logger.info("Attempting to search for #{chart_name} using helm CLI")
|
63
|
+
releases = fetch_chart_releases(chart_name, repo_name, repo_url)
|
64
|
+
|
65
|
+
return nil unless releases && !releases.empty?
|
66
|
+
|
67
|
+
valid_releases = filter_valid_releases(releases)
|
68
|
+
return nil if valid_releases.empty?
|
69
|
+
|
70
|
+
highest_release = valid_releases.max_by { |release| version_class.new(release["version"]) }
|
71
|
+
Dependabot.logger.info(
|
72
|
+
"Found latest version #{T.must(highest_release)['version']} for #{chart_name} using helm search"
|
73
|
+
)
|
74
|
+
version_class.new(T.must(highest_release)["version"])
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(chart_name: String, repo_url: T.nilable(String)).returns(T.nilable(Gem::Version)) }
|
78
|
+
def fetch_releases_from_index(chart_name, repo_url)
|
79
|
+
Dependabot.logger.info("Falling back to index.yaml search for #{chart_name}")
|
80
|
+
return nil unless repo_url
|
81
|
+
|
82
|
+
index_url = build_index_url(repo_url)
|
83
|
+
index = fetch_helm_chart_index(index_url)
|
84
|
+
return nil unless index && index["entries"] && index["entries"][chart_name]
|
85
|
+
|
86
|
+
all_versions = index["entries"][chart_name].map { |entry| entry["version"] }
|
87
|
+
Dependabot.logger.info("Found #{all_versions.length} versions for #{chart_name} in index.yaml")
|
88
|
+
|
89
|
+
valid_versions = filter_valid_versions(all_versions)
|
90
|
+
Dependabot.logger.info("After filtering, found #{valid_versions.length} valid versions for #{chart_name}")
|
91
|
+
|
92
|
+
return nil if valid_versions.empty?
|
93
|
+
|
94
|
+
highest_version = valid_versions.map { |v| version_class.new(v) }.max
|
95
|
+
Dependabot.logger.info("Highest valid version for #{chart_name} is #{highest_version}")
|
96
|
+
|
97
|
+
highest_version
|
98
|
+
end
|
99
|
+
|
100
|
+
sig { params(releases: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.untyped]]) }
|
101
|
+
def filter_valid_releases(releases)
|
102
|
+
releases.reject do |release|
|
103
|
+
version_class.new(release["version"]) <= version_class.new(dependency.version) ||
|
104
|
+
ignore_requirements.any? { |r| r.satisfied_by?(version_class.new(release["version"])) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
sig { params(repo_url: String).returns(String) }
|
109
|
+
def build_index_url(repo_url)
|
110
|
+
repo_url_trimmed = repo_url.to_s.strip.chomp("/")
|
111
|
+
"#{repo_url_trimmed}/index.yaml"
|
112
|
+
end
|
113
|
+
|
52
114
|
sig { override.returns(T::Boolean) }
|
53
115
|
def latest_version_resolvable_with_full_unlock?
|
54
116
|
false
|
@@ -64,9 +126,9 @@ module Dependabot
|
|
64
126
|
sig { returns(T.nilable(T.any(String, Gem::Version))) }
|
65
127
|
def fetch_latest_version
|
66
128
|
case dependency_type
|
67
|
-
when :
|
129
|
+
when :helm_chart
|
68
130
|
fetch_latest_chart_version
|
69
|
-
when :
|
131
|
+
when :docker_image
|
70
132
|
fetch_latest_image_version
|
71
133
|
else
|
72
134
|
Gem::Version.new(dependency.version)
|
@@ -76,11 +138,66 @@ module Dependabot
|
|
76
138
|
sig { returns(Symbol) }
|
77
139
|
def dependency_type
|
78
140
|
req = dependency.requirements.first
|
141
|
+
type = T.must(req).dig(:metadata, :type)
|
79
142
|
|
80
|
-
|
81
|
-
|
143
|
+
type || :unknown
|
144
|
+
end
|
82
145
|
|
83
|
-
|
146
|
+
sig do
|
147
|
+
params(chart_name: String, repo_name: T.nilable(String),
|
148
|
+
repo_url: T.nilable(String)).returns(T.nilable(T::Array[T::Hash[String, T.untyped]]))
|
149
|
+
end
|
150
|
+
def fetch_chart_releases(chart_name, repo_name = nil, repo_url = nil)
|
151
|
+
Dependabot.logger.info("Fetching releases for Helm chart: #{chart_name}")
|
152
|
+
|
153
|
+
if repo_name && repo_url
|
154
|
+
begin
|
155
|
+
Helpers.add_repo(repo_name, repo_url)
|
156
|
+
Helpers.update_repo
|
157
|
+
rescue StandardError => e
|
158
|
+
Dependabot.logger.error("Error adding/updating Helm repository: #{e.message}")
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
begin
|
163
|
+
search_command = repo_name ? "#{repo_name}/#{chart_name}" : chart_name
|
164
|
+
Dependabot.logger.info("Searching for: #{search_command}")
|
165
|
+
|
166
|
+
json_output = Helpers.search_releases(search_command)
|
167
|
+
return nil if json_output.empty?
|
168
|
+
|
169
|
+
releases = JSON.parse(json_output)
|
170
|
+
Dependabot.logger.info("Found #{releases.length} releases for #{chart_name}")
|
171
|
+
releases
|
172
|
+
rescue StandardError => e
|
173
|
+
Dependabot.logger.error("Error fetching chart releases: #{e.message}")
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
sig { returns(T.nilable(Gem::Version)) }
|
179
|
+
def fetch_latest_chart_version
|
180
|
+
chart_name = dependency.name
|
181
|
+
source = dependency.requirements.first&.dig(:source)
|
182
|
+
repo_url = source&.dig(:registry)
|
183
|
+
repo_name = extract_repo_name(repo_url)
|
184
|
+
|
185
|
+
releases = fetch_releases_with_helm_cli(chart_name, repo_name, repo_url)
|
186
|
+
return releases if releases
|
187
|
+
|
188
|
+
fetch_releases_from_index(chart_name, repo_url)
|
189
|
+
end
|
190
|
+
|
191
|
+
sig { params(repo_url: T.nilable(String)).returns(T.nilable(String)) }
|
192
|
+
def extract_repo_name(repo_url)
|
193
|
+
return nil unless repo_url
|
194
|
+
|
195
|
+
name = repo_url.gsub(%r{^https?://}, "")
|
196
|
+
name = name.chomp("/")
|
197
|
+
name = name.gsub(/[^a-zA-Z0-9-]/, "-")
|
198
|
+
name = "repo-#{name}" unless name.match?(/^[a-zA-Z0-9]/)
|
199
|
+
|
200
|
+
name
|
84
201
|
end
|
85
202
|
|
86
203
|
sig { params(index_url: String).returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
|
@@ -113,32 +230,6 @@ module Dependabot
|
|
113
230
|
end
|
114
231
|
|
115
232
|
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
233
|
def fetch_latest_image_version
|
143
234
|
docker_dependency = build_docker_dependency
|
144
235
|
|
@@ -153,12 +244,11 @@ module Dependabot
|
|
153
244
|
raise_on_ignored: raise_on_ignored
|
154
245
|
)
|
155
246
|
|
156
|
-
|
157
|
-
latest_version_str = latest&.to_s
|
247
|
+
latest_version = docker_checker.latest_version
|
158
248
|
|
159
|
-
Dependabot.logger.info("Docker UpdateChecker found latest version: #{
|
249
|
+
Dependabot.logger.info("Docker UpdateChecker found latest version: #{latest_version || 'none'}")
|
160
250
|
|
161
|
-
|
251
|
+
version_class.new(latest_version)
|
162
252
|
end
|
163
253
|
|
164
254
|
sig { returns(Dependabot::Dependency) }
|
@@ -175,7 +265,7 @@ module Dependabot
|
|
175
265
|
end
|
176
266
|
end
|
177
267
|
|
178
|
-
registry = source[:registry] ||
|
268
|
+
registry = source[:registry] || nil
|
179
269
|
|
180
270
|
Dependency.new(
|
181
271
|
name: name,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependabot-helm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.303.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-03-
|
11
|
+
date: 2025-03-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dependabot-common
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.303.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.303.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: dependabot-docker
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - '='
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.303.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.303.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: debug
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -170,14 +170,14 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: 0.8.
|
173
|
+
version: 0.8.7
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: 0.8.
|
180
|
+
version: 0.8.7
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: simplecov
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -260,6 +260,10 @@ files:
|
|
260
260
|
- lib/dependabot/helm/file_fetcher.rb
|
261
261
|
- lib/dependabot/helm/file_parser.rb
|
262
262
|
- lib/dependabot/helm/file_updater.rb
|
263
|
+
- lib/dependabot/helm/file_updater/chart_updater.rb
|
264
|
+
- lib/dependabot/helm/file_updater/image_updater.rb
|
265
|
+
- lib/dependabot/helm/file_updater/lock_file_generator.rb
|
266
|
+
- lib/dependabot/helm/helpers.rb
|
263
267
|
- lib/dependabot/helm/package_manager.rb
|
264
268
|
- lib/dependabot/helm/update_checker.rb
|
265
269
|
homepage: https://github.com/dependabot/dependabot-core
|
@@ -267,7 +271,7 @@ licenses:
|
|
267
271
|
- MIT
|
268
272
|
metadata:
|
269
273
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
270
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
274
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.303.0
|
271
275
|
post_install_message:
|
272
276
|
rdoc_options: []
|
273
277
|
require_paths:
|