dependabot-docker 0.208.0 → 0.211.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dependabot/docker/file_fetcher.rb +51 -4
- data/lib/dependabot/docker/file_parser.rb +74 -3
- data/lib/dependabot/docker/file_updater.rb +66 -11
- metadata +20 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c194c165762186cd5b9ca1048491778c1a49b1ea11d19a23c44e7eb501e6231
|
4
|
+
data.tar.gz: '09532e3bed9e17abe29e4c1667d1918025f081973af0eef0178477f3cd7a6919'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7108f82ff87b6962c1f1ec837da3078cac2c0ee1d3dddcaf0956256c5ceaf26846be9e0e7674346cdcaf19f681fa0bfd04f15d26a803b2a58c54a826f4694f0
|
7
|
+
data.tar.gz: 639d3a059228017338711f8292a7d2a8154b9cf9b857d2177a12ceaf0fd00f57045d3a870652b8e638fbd8e73dd2436cb6c6e5cb77a0ecdbda99c761707785a7
|
@@ -6,27 +6,46 @@ require "dependabot/file_fetchers/base"
|
|
6
6
|
module Dependabot
|
7
7
|
module Docker
|
8
8
|
class FileFetcher < Dependabot::FileFetchers::Base
|
9
|
+
YAML_REGEXP = /^[^\.]+\.ya?ml$/i.freeze
|
10
|
+
DOCKER_REGEXP = /dockerfile/i.freeze
|
11
|
+
|
9
12
|
def self.required_files_in?(filenames)
|
10
|
-
filenames.any? { |f| f.match?(
|
13
|
+
filenames.any? { |f| f.match?(DOCKER_REGEXP) } or
|
14
|
+
filenames.any? { |f| f.match?(YAML_REGEXP) }
|
11
15
|
end
|
12
16
|
|
13
17
|
def self.required_files_message
|
14
|
-
"Repo must contain a Dockerfile."
|
18
|
+
"Repo must contain a Dockerfile or Kubernetes YAML files."
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
18
22
|
|
23
|
+
def kubernetes_enabled?
|
24
|
+
options.key?(:kubernetes_updates) && options[:kubernetes_updates]
|
25
|
+
end
|
26
|
+
|
19
27
|
def fetch_files
|
20
28
|
fetched_files = []
|
21
29
|
fetched_files += correctly_encoded_dockerfiles
|
30
|
+
fetched_files += correctly_encoded_yamlfiles if kubernetes_enabled?
|
22
31
|
|
23
32
|
return fetched_files if fetched_files.any?
|
24
33
|
|
25
|
-
if incorrectly_encoded_dockerfiles.none?
|
34
|
+
if !kubernetes_enabled? && incorrectly_encoded_dockerfiles.none?
|
26
35
|
raise(
|
27
36
|
Dependabot::DependencyFileNotFound,
|
28
37
|
File.join(directory, "Dockerfile")
|
29
38
|
)
|
39
|
+
elsif incorrectly_encoded_dockerfiles.none? && incorrectly_encoded_yamlfiles.none?
|
40
|
+
raise(
|
41
|
+
Dependabot::DependabotError,
|
42
|
+
"Found neither Kubernetes YAML nor Dockerfiles in #{directory}"
|
43
|
+
)
|
44
|
+
elsif incorrectly_encoded_dockerfiles.none?
|
45
|
+
raise(
|
46
|
+
Dependabot::DependencyFileNotParseable,
|
47
|
+
incorrectly_encoded_yamlfiles.first.path
|
48
|
+
)
|
30
49
|
else
|
31
50
|
raise(
|
32
51
|
Dependabot::DependencyFileNotParseable,
|
@@ -38,7 +57,7 @@ module Dependabot
|
|
38
57
|
def dockerfiles
|
39
58
|
@dockerfiles ||=
|
40
59
|
repo_contents(raise_errors: false).
|
41
|
-
select { |f| f.type == "file" && f.name.match?(
|
60
|
+
select { |f| f.type == "file" && f.name.match?(DOCKER_REGEXP) }.
|
42
61
|
map { |f| fetch_file_from_host(f.name) }
|
43
62
|
end
|
44
63
|
|
@@ -49,6 +68,34 @@ module Dependabot
|
|
49
68
|
def incorrectly_encoded_dockerfiles
|
50
69
|
dockerfiles.reject { |f| f.content.valid_encoding? }
|
51
70
|
end
|
71
|
+
|
72
|
+
def yamlfiles
|
73
|
+
@yamlfiles ||=
|
74
|
+
repo_contents(raise_errors: false).
|
75
|
+
select { |f| f.type == "file" && f.name.match?(YAML_REGEXP) }.
|
76
|
+
map { |f| fetch_file_from_host(f.name) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def likely_kubernetes_resource?(resource)
|
80
|
+
# Heuristic for being a Kubernetes resource. We could make this tighter but this probably works well.
|
81
|
+
resource.is_a?(::Hash) && resource.key?("apiVersion") && resource.key?("kind")
|
82
|
+
end
|
83
|
+
|
84
|
+
def correctly_encoded_yamlfiles
|
85
|
+
candidate_files = yamlfiles.select { |f| f.content.valid_encoding? }
|
86
|
+
candidate_files.select do |f|
|
87
|
+
# This doesn't handle multi-resource files, but it shouldn't matter, since the first resource
|
88
|
+
# in a multi-resource file had better be a valid k8s resource
|
89
|
+
content = ::YAML.safe_load(f.content, aliases: true)
|
90
|
+
likely_kubernetes_resource?(content)
|
91
|
+
rescue ::Psych::Exception
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def incorrectly_encoded_yamlfiles
|
97
|
+
yamlfiles.reject { |f| f.content.valid_encoding? }
|
98
|
+
end
|
52
99
|
end
|
53
100
|
end
|
54
101
|
end
|
@@ -34,6 +34,9 @@ module Dependabot
|
|
34
34
|
|
35
35
|
AWS_ECR_URL = /dkr\.ecr\.(?<region>[^.]+)\.amazonaws\.com/.freeze
|
36
36
|
|
37
|
+
IMAGE_SPEC =
|
38
|
+
%r{^(#{REGISTRY}/)?#{IMAGE}#{TAG}?#{DIGEST}?#{NAME}?}x.freeze
|
39
|
+
|
37
40
|
def parse
|
38
41
|
dependency_set = DependencySet.new
|
39
42
|
|
@@ -61,15 +64,18 @@ module Dependabot
|
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
67
|
+
manifest_files.each do |file|
|
68
|
+
dependency_set += workfile_file_dependencies(file)
|
69
|
+
end
|
70
|
+
|
64
71
|
dependency_set.dependencies
|
65
72
|
end
|
66
73
|
|
67
74
|
private
|
68
75
|
|
69
76
|
def dockerfiles
|
70
|
-
# The Docker file fetcher
|
71
|
-
|
72
|
-
dependency_files
|
77
|
+
# The Docker file fetcher fetches Dockerfiles and yaml files. Reject yaml files.
|
78
|
+
dependency_files.reject { |f| f.type == "file" && f.name.match?(/^[^\.]+\.ya?ml/i) }
|
73
79
|
end
|
74
80
|
|
75
81
|
def version_from(parsed_from_line)
|
@@ -154,6 +160,71 @@ module Dependabot
|
|
154
160
|
|
155
161
|
raise "No Dockerfile!"
|
156
162
|
end
|
163
|
+
|
164
|
+
def workfile_file_dependencies(file)
|
165
|
+
dependency_set = DependencySet.new
|
166
|
+
|
167
|
+
resources = file.content.split(/^---$/).map(&:strip).reject(&:empty?) # assuming a yaml file
|
168
|
+
resources.flat_map do |resource|
|
169
|
+
json = YAML.safe_load(resource, aliases: true)
|
170
|
+
images = deep_fetch_images(json).uniq
|
171
|
+
|
172
|
+
images.each do |string|
|
173
|
+
# TODO: Support Docker references and path references
|
174
|
+
details = string.match(IMAGE_SPEC).named_captures
|
175
|
+
details["registry"] = nil if details["registry"] == "docker.io"
|
176
|
+
|
177
|
+
version = version_from(details)
|
178
|
+
next unless version
|
179
|
+
|
180
|
+
dependency_set << build_image_dependency(file, details, version)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
dependency_set
|
185
|
+
rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias
|
186
|
+
raise Dependabot::DependencyFileNotParseable, file.path
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_image_dependency(file, details, version)
|
190
|
+
Dependency.new(
|
191
|
+
name: details.fetch("image"),
|
192
|
+
version: version,
|
193
|
+
package_manager: "docker",
|
194
|
+
requirements: [
|
195
|
+
requirement: nil,
|
196
|
+
groups: [],
|
197
|
+
file: file.name,
|
198
|
+
source: source_from(details)
|
199
|
+
]
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
def deep_fetch_images(json_obj)
|
204
|
+
case json_obj
|
205
|
+
when Hash then deep_fetch_images_from_hash(json_obj)
|
206
|
+
when Array then json_obj.flat_map { |o| deep_fetch_images(o) }
|
207
|
+
else []
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def deep_fetch_images_from_hash(json_object)
|
212
|
+
img = json_object.fetch("image", nil)
|
213
|
+
|
214
|
+
images =
|
215
|
+
if !img.nil? && img.is_a?(String) && !img.empty?
|
216
|
+
[img]
|
217
|
+
else
|
218
|
+
[]
|
219
|
+
end
|
220
|
+
|
221
|
+
images + json_object.values.flat_map { |obj| deep_fetch_images(obj) }
|
222
|
+
end
|
223
|
+
|
224
|
+
def manifest_files
|
225
|
+
# Dependencies include both Dockerfiles and yaml, select yaml.
|
226
|
+
dependency_files.select { |f| f.type == "file" && f.name.match?(/^[^\.]+\.ya?ml/i) }
|
227
|
+
end
|
157
228
|
end
|
158
229
|
end
|
159
230
|
end
|
@@ -10,7 +10,10 @@ module Dependabot
|
|
10
10
|
FROM_REGEX = /FROM(\s+--platform\=\S+)?/i.freeze
|
11
11
|
|
12
12
|
def self.updated_files_regex
|
13
|
-
[
|
13
|
+
[
|
14
|
+
/dockerfile/i,
|
15
|
+
/^[^\.]+\.ya?ml/i
|
16
|
+
]
|
14
17
|
end
|
15
18
|
|
16
19
|
def updated_dependency_files
|
@@ -19,11 +22,17 @@ module Dependabot
|
|
19
22
|
dependency_files.each do |file|
|
20
23
|
next unless requirement_changed?(file, dependency)
|
21
24
|
|
22
|
-
updated_files <<
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
updated_files << if file.name.match?(/^[^\.]+\.ya?ml/i)
|
26
|
+
updated_file(
|
27
|
+
file: file,
|
28
|
+
content: updated_yaml_content(file)
|
29
|
+
)
|
30
|
+
else
|
31
|
+
updated_file(
|
32
|
+
file: file,
|
33
|
+
content: updated_dockerfile_content(file)
|
34
|
+
)
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
updated_files.reject! { |f| dependency_files.include?(f) }
|
@@ -76,7 +85,11 @@ module Dependabot
|
|
76
85
|
|
77
86
|
modified_content = file.content
|
78
87
|
|
79
|
-
|
88
|
+
new_tags = new_tags(file)
|
89
|
+
|
90
|
+
old_tags.zip(new_tags).each do |old_tag, new_tag|
|
91
|
+
new_tag ||= new_tags.first
|
92
|
+
|
80
93
|
old_declaration =
|
81
94
|
if private_registry_url(file) then "#{private_registry_url(file)}/"
|
82
95
|
else
|
@@ -90,7 +103,7 @@ module Dependabot
|
|
90
103
|
|
91
104
|
modified_content = modified_content.
|
92
105
|
gsub(old_declaration_regex) do |old_dec|
|
93
|
-
old_dec.gsub(":#{old_tag}", ":#{new_tag
|
106
|
+
old_dec.gsub(":#{old_tag}", ":#{new_tag}")
|
94
107
|
end
|
95
108
|
end
|
96
109
|
|
@@ -120,10 +133,10 @@ module Dependabot
|
|
120
133
|
fetch(:source).fetch(:digest)
|
121
134
|
end
|
122
135
|
|
123
|
-
def
|
136
|
+
def new_tags(file)
|
124
137
|
dependency.requirements.
|
125
|
-
|
126
|
-
fetch(:source)[:tag]
|
138
|
+
select { |r| r[:file] == file.name }.
|
139
|
+
map { |r| r.fetch(:source)[:tag] }
|
127
140
|
end
|
128
141
|
|
129
142
|
def old_tags(file)
|
@@ -138,6 +151,48 @@ module Dependabot
|
|
138
151
|
find { |r| r[:file] == file.name }.
|
139
152
|
fetch(:source)[:registry]
|
140
153
|
end
|
154
|
+
|
155
|
+
def updated_yaml_content(file)
|
156
|
+
updated_content = update_image(file)
|
157
|
+
|
158
|
+
raise "Expected content to change!" if updated_content == file.content
|
159
|
+
|
160
|
+
updated_content
|
161
|
+
end
|
162
|
+
|
163
|
+
def update_image(file)
|
164
|
+
old_images = old_yaml_images(file)
|
165
|
+
return if old_images.empty?
|
166
|
+
|
167
|
+
modified_content = file.content
|
168
|
+
|
169
|
+
old_images.each do |old_image|
|
170
|
+
old_image_regex = /^\s+(?:-\s)?image:\s+#{old_image}(?=\s|$)/
|
171
|
+
modified_content = modified_content.gsub(old_image_regex) do |old_img|
|
172
|
+
old_img.gsub(old_image.to_s, new_yaml_image(file).to_s)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
modified_content
|
176
|
+
end
|
177
|
+
|
178
|
+
def new_yaml_image(file)
|
179
|
+
elt = dependency.requirements.find { |r| r[:file] == file.name }
|
180
|
+
prefix = elt.fetch(:source)[:registry] ? "#{elt.fetch(:source)[:registry]}/" : ""
|
181
|
+
digest = elt.fetch(:source)[:digest] ? "@#{elt.fetch(:source)[:digest]}" : ""
|
182
|
+
tag = elt.fetch(:source)[:tag] ? ":#{elt.fetch(:source)[:tag]}" : ""
|
183
|
+
"#{prefix}#{dependency.name}#{tag}#{digest}"
|
184
|
+
end
|
185
|
+
|
186
|
+
def old_yaml_images(file)
|
187
|
+
dependency.
|
188
|
+
previous_requirements.
|
189
|
+
select { |r| r[:file] == file.name }.map do |r|
|
190
|
+
prefix = r.fetch(:source)[:registry] ? "#{r.fetch(:source)[:registry]}/" : ""
|
191
|
+
digest = r.fetch(:source)[:digest] ? "@#{r.fetch(:source)[:digest]}" : ""
|
192
|
+
tag = r.fetch(:source)[:tag] ? ":#{r.fetch(:source)[:tag]}" : ""
|
193
|
+
"#{prefix}#{dependency.name}#{tag}#{digest}"
|
194
|
+
end
|
195
|
+
end
|
141
196
|
end
|
142
197
|
end
|
143
198
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependabot-docker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.211.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-08-
|
11
|
+
date: 2022-08-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dependabot-common
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.211.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.211.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: debase
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: parallel_tests
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.11.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.11.1
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: rake
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -128,14 +142,14 @@ dependencies:
|
|
128
142
|
requirements:
|
129
143
|
- - "~>"
|
130
144
|
- !ruby/object:Gem::Version
|
131
|
-
version: 1.
|
145
|
+
version: 1.35.1
|
132
146
|
type: :development
|
133
147
|
prerelease: false
|
134
148
|
version_requirements: !ruby/object:Gem::Requirement
|
135
149
|
requirements:
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
|
-
version: 1.
|
152
|
+
version: 1.35.1
|
139
153
|
- !ruby/object:Gem::Dependency
|
140
154
|
name: ruby-debug-ide
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|