dependabot-terraform 0.76.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/helpers/build +19 -0
- data/lib/dependabot/terraform.rb +11 -0
- data/lib/dependabot/terraform/file_fetcher.rb +50 -0
- data/lib/dependabot/terraform/file_parser.rb +267 -0
- data/lib/dependabot/terraform/file_updater.rb +130 -0
- data/lib/dependabot/terraform/metadata_finder.rb +70 -0
- data/lib/dependabot/terraform/requirement.rb +34 -0
- data/lib/dependabot/terraform/requirements_updater.rb +139 -0
- data/lib/dependabot/terraform/update_checker.rb +197 -0
- data/lib/dependabot/terraform/version.rb +25 -0
- metadata +180 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 781b56819ef8fc54cb1b635f913f81139d149c80e6461d86b9e61e36916aa9c8
|
4
|
+
data.tar.gz: ed1697ae2b43e42d4b76db373643d28c3dff4c0f67c07291f8180fc56da0c341
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b51f5746555c34bffed4736fa11217c8b24e8fb1d4f464846c750572e9e55f77680231b0d2603555f06ae14b35dc34bcc16808cd9740566c0d7ae24a2951900e
|
7
|
+
data.tar.gz: 01cbd90a18f476d9c7521c7facc5e225e1c2afd3b8954d4f0efb88daf69d370630d1a840b6eccbdd0add3c2fa277d46aa44935e6c6fd673d70dbe0b174f2cb54
|
data/helpers/build
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
|
5
|
+
install_dir=$1
|
6
|
+
if [ -z "$install_dir" ]; then
|
7
|
+
echo "usage: $0 INSTALL_DIR"
|
8
|
+
exit 1
|
9
|
+
fi
|
10
|
+
|
11
|
+
if [ ! -d "$install_dir/bin" ]; then
|
12
|
+
mkdir -p "$install_dir/bin"
|
13
|
+
fi
|
14
|
+
|
15
|
+
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
16
|
+
github_url="https://github.com/kvz/json2hcl"
|
17
|
+
url="${github_url}/releases/download/v0.0.6/json2hcl_v0.0.6_${os}_amd64"
|
18
|
+
wget -O "$install_dir/bin/json2hcl" "$url"
|
19
|
+
chmod +x "$install_dir/bin/json2hcl"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# These all need to be required so the various classes can be registered in a
|
4
|
+
# lookup table of package manager names to concrete classes.
|
5
|
+
require "dependabot/terraform/file_fetcher"
|
6
|
+
require "dependabot/terraform/file_parser"
|
7
|
+
require "dependabot/terraform/update_checker"
|
8
|
+
require "dependabot/terraform/file_updater"
|
9
|
+
require "dependabot/terraform/metadata_finder"
|
10
|
+
require "dependabot/terraform/requirement"
|
11
|
+
require "dependabot/terraform/version"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/file_fetchers"
|
4
|
+
require "dependabot/file_fetchers/base"
|
5
|
+
|
6
|
+
module Dependabot
|
7
|
+
module Terraform
|
8
|
+
class FileFetcher < Dependabot::FileFetchers::Base
|
9
|
+
def self.required_files_in?(filenames)
|
10
|
+
filenames.any? { |f| f.end_with?(".tf", ".tfvars") }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.required_files_message
|
14
|
+
"Repo must contain a Terraform configuration file."
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def fetch_files
|
20
|
+
fetched_files = []
|
21
|
+
fetched_files += terraform_files
|
22
|
+
fetched_files += terragrunt_files
|
23
|
+
|
24
|
+
return fetched_files if fetched_files.any?
|
25
|
+
|
26
|
+
raise(
|
27
|
+
Dependabot::DependencyFileNotFound,
|
28
|
+
File.join(directory, "<anything>.tf")
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def terraform_files
|
33
|
+
@terraform_files ||=
|
34
|
+
repo_contents(raise_errors: false).
|
35
|
+
select { |f| f.type == "file" && f.name.end_with?(".tf") }.
|
36
|
+
map { |f| fetch_file_from_host(f.name) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def terragrunt_files
|
40
|
+
@terragrunt_files ||=
|
41
|
+
repo_contents(raise_errors: false).
|
42
|
+
select { |f| f.type == "file" && f.name.end_with?(".tfvars") }.
|
43
|
+
map { |f| fetch_file_from_host(f.name) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Dependabot::FileFetchers.
|
50
|
+
register("terraform", Dependabot::Terraform::FileFetcher)
|
@@ -0,0 +1,267 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
require "excon"
|
5
|
+
require "nokogiri"
|
6
|
+
require "dependabot/dependency"
|
7
|
+
require "dependabot/file_parsers"
|
8
|
+
require "dependabot/file_parsers/base"
|
9
|
+
require "dependabot/git_commit_checker"
|
10
|
+
require "dependabot/shared_helpers"
|
11
|
+
require "dependabot/errors"
|
12
|
+
|
13
|
+
module Dependabot
|
14
|
+
module Terraform
|
15
|
+
class FileParser < Dependabot::FileParsers::Base
|
16
|
+
require "dependabot/file_parsers/base/dependency_set"
|
17
|
+
|
18
|
+
ARCHIVE_EXTENSIONS = %w(.zip .tbz2 .tgz .txz).freeze
|
19
|
+
|
20
|
+
def parse
|
21
|
+
dependency_set = DependencySet.new
|
22
|
+
|
23
|
+
terraform_files.each do |file|
|
24
|
+
modules = parsed_file(file).fetch("module", []).map(&:first)
|
25
|
+
modules.each do |name, details|
|
26
|
+
dependency_set << build_terraform_dependency(file, name, details)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
terragrunt_files.each do |file|
|
31
|
+
modules = parsed_file(file).fetch("terragrunt", []).first || {}
|
32
|
+
modules = modules.fetch("terraform", [])
|
33
|
+
modules.each do |details|
|
34
|
+
next unless details["source"]
|
35
|
+
|
36
|
+
dependency_set << build_terragrunt_dependency(file, details)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
dependency_set.dependencies
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def build_terraform_dependency(file, name, details)
|
46
|
+
details = details.first
|
47
|
+
|
48
|
+
source = source_from(details)
|
49
|
+
dep_name =
|
50
|
+
source[:type] == "registry" ? source[:module_identifier] : name
|
51
|
+
version_req = details["version"]&.strip
|
52
|
+
version =
|
53
|
+
if source[:type] == "git" then version_from_ref(source[:ref])
|
54
|
+
elsif version_req&.match?(/^\d/) then version_req
|
55
|
+
end
|
56
|
+
|
57
|
+
Dependency.new(
|
58
|
+
name: dep_name,
|
59
|
+
version: version,
|
60
|
+
package_manager: "terraform",
|
61
|
+
requirements: [
|
62
|
+
requirement: version_req,
|
63
|
+
groups: [],
|
64
|
+
file: file.name,
|
65
|
+
source: source
|
66
|
+
]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_terragrunt_dependency(file, details)
|
71
|
+
source = source_from(details)
|
72
|
+
dep_name =
|
73
|
+
if Source.from_url(source[:url])
|
74
|
+
Source.from_url(source[:url]).repo
|
75
|
+
else
|
76
|
+
source[:url]
|
77
|
+
end
|
78
|
+
|
79
|
+
version = version_from_ref(source[:ref])
|
80
|
+
|
81
|
+
Dependency.new(
|
82
|
+
name: dep_name,
|
83
|
+
version: version,
|
84
|
+
package_manager: "terraform",
|
85
|
+
requirements: [
|
86
|
+
requirement: nil,
|
87
|
+
groups: [],
|
88
|
+
file: file.name,
|
89
|
+
source: source
|
90
|
+
]
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Full docs at https://www.terraform.io/docs/modules/sources.html
|
95
|
+
def source_from(details_hash)
|
96
|
+
raw_source = details_hash.fetch("source")
|
97
|
+
bare_source = get_proxied_source(raw_source)
|
98
|
+
|
99
|
+
source_details =
|
100
|
+
case source_type(bare_source)
|
101
|
+
when :http_archive, :path, :mercurial, :s3
|
102
|
+
{ type: source_type(bare_source).to_s, url: bare_source }
|
103
|
+
when :github, :bitbucket, :git
|
104
|
+
git_source_details_from(bare_source)
|
105
|
+
when :registry
|
106
|
+
registry_source_details_from(bare_source)
|
107
|
+
end
|
108
|
+
|
109
|
+
source_details[:proxy_url] = raw_source if raw_source != bare_source
|
110
|
+
source_details
|
111
|
+
end
|
112
|
+
|
113
|
+
def registry_source_details_from(source_string)
|
114
|
+
parts = source_string.split("/")
|
115
|
+
|
116
|
+
if parts.count == 3
|
117
|
+
{
|
118
|
+
type: "registry",
|
119
|
+
registry_hostname: "registry.terraform.io",
|
120
|
+
module_identifier: source_string
|
121
|
+
}
|
122
|
+
elsif parts.count == 4
|
123
|
+
{
|
124
|
+
type: "registry",
|
125
|
+
registry_hostname: parts.first,
|
126
|
+
module_identifier: parts[1..3].join("/")
|
127
|
+
}
|
128
|
+
else
|
129
|
+
msg = "Invalid registry source specified: '#{source_string}'"
|
130
|
+
raise DependencyFileNotEvaluatable, msg
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def git_source_details_from(source_string)
|
135
|
+
git_url = source_string.strip.gsub(/^git::/, "")
|
136
|
+
unless git_url.start_with?("git@") || git_url.include?("://")
|
137
|
+
git_url = "https://" + git_url
|
138
|
+
end
|
139
|
+
|
140
|
+
bare_uri =
|
141
|
+
if git_url.include?("git@")
|
142
|
+
git_url.split("git@").last.sub(":", "/")
|
143
|
+
else
|
144
|
+
git_url.sub(%r{.*?://}, "")
|
145
|
+
end
|
146
|
+
|
147
|
+
querystr = URI.parse("https://" + bare_uri).query
|
148
|
+
git_url = git_url.split(%r{(?<!:)//}).first.gsub("?#{querystr}", "")
|
149
|
+
|
150
|
+
{
|
151
|
+
type: "git",
|
152
|
+
url: git_url,
|
153
|
+
branch: nil,
|
154
|
+
ref: CGI.parse(querystr.to_s)["ref"].first
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def version_from_ref(ref)
|
159
|
+
version_regex = GitCommitChecker::VERSION_REGEX
|
160
|
+
return unless ref&.match?(version_regex)
|
161
|
+
|
162
|
+
ref.match(version_regex).named_captures.fetch("version")
|
163
|
+
end
|
164
|
+
|
165
|
+
# See https://www.terraform.io/docs/modules/sources.html#http-urls for
|
166
|
+
# details of how Terraform handle HTTP(S) sources for modules
|
167
|
+
def get_proxied_source(raw_source)
|
168
|
+
return raw_source unless raw_source.start_with?("http")
|
169
|
+
|
170
|
+
uri = URI.parse(raw_source.split(%r{(?<!:)//}).first)
|
171
|
+
return raw_source if uri.path.end_with?(*ARCHIVE_EXTENSIONS)
|
172
|
+
return raw_source if URI.parse(raw_source).query.include?("archive=")
|
173
|
+
|
174
|
+
url = raw_source.split(%r{(?<!:)//}).first + "?terraform-get=1"
|
175
|
+
|
176
|
+
response = Excon.get(
|
177
|
+
url,
|
178
|
+
idempotent: true,
|
179
|
+
**SharedHelpers.excon_defaults
|
180
|
+
)
|
181
|
+
|
182
|
+
if response.headers["X-Terraform-Get"]
|
183
|
+
return response.headers["X-Terraform-Get"]
|
184
|
+
end
|
185
|
+
|
186
|
+
doc = Nokogiri::XML(response.body)
|
187
|
+
doc.css("meta").find do |tag|
|
188
|
+
tag.attributes&.fetch("name", nil)&.value == "terraform-get"
|
189
|
+
end&.attributes&.fetch("content", nil)&.value
|
190
|
+
end
|
191
|
+
|
192
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
193
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
194
|
+
def source_type(source_string)
|
195
|
+
return :path if source_string.start_with?(".")
|
196
|
+
return :github if source_string.start_with?("github.com/")
|
197
|
+
return :bitbucket if source_string.start_with?("bitbucket.org/")
|
198
|
+
return :git if source_string.start_with?("git::")
|
199
|
+
return :mercurial if source_string.start_with?("hg::")
|
200
|
+
return :s3 if source_string.start_with?("s3::")
|
201
|
+
|
202
|
+
if source_string.split("/").first.include?("::")
|
203
|
+
raise "Unknown src: #{source_string}"
|
204
|
+
end
|
205
|
+
|
206
|
+
return :registry unless source_string.start_with?("http")
|
207
|
+
|
208
|
+
path_uri = URI.parse(source_string.split(%r{(?<!:)//}).first)
|
209
|
+
query_uri = URI.parse(source_string)
|
210
|
+
return :http_archive if path_uri.path.end_with?(*ARCHIVE_EXTENSIONS)
|
211
|
+
return :http_archive if query_uri.query.include?("archive=")
|
212
|
+
|
213
|
+
raise "HTTP source, but not an archive!"
|
214
|
+
end
|
215
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
216
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
217
|
+
|
218
|
+
def parsed_file(file)
|
219
|
+
@parsed_buildfile ||= {}
|
220
|
+
@parsed_buildfile[file.name] ||=
|
221
|
+
SharedHelpers.in_a_temporary_directory do
|
222
|
+
File.write("tmp.tf", file.content)
|
223
|
+
|
224
|
+
command = "#{terraform_parser_path} -reverse < tmp.tf"
|
225
|
+
raw_response = nil
|
226
|
+
IO.popen(command) { |process| raw_response = process.read }
|
227
|
+
|
228
|
+
unless $CHILD_STATUS.success?
|
229
|
+
raise SharedHelpers::HelperSubprocessFailed.new(
|
230
|
+
raw_response,
|
231
|
+
command
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
JSON.parse(raw_response)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def terraform_parser_path
|
240
|
+
helper_bin_dir = File.join(native_helpers_root, "terraform/bin")
|
241
|
+
Pathname.new(File.join(helper_bin_dir, "json2hcl")).cleanpath.to_path
|
242
|
+
end
|
243
|
+
|
244
|
+
def native_helpers_root
|
245
|
+
default_path = File.join(__dir__, "../../../helpers/install-dir")
|
246
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
247
|
+
end
|
248
|
+
|
249
|
+
def terraform_files
|
250
|
+
dependency_files.select { |f| f.name.end_with?(".tf") }
|
251
|
+
end
|
252
|
+
|
253
|
+
def terragrunt_files
|
254
|
+
dependency_files.select { |f| f.name.end_with?(".tfvars") }
|
255
|
+
end
|
256
|
+
|
257
|
+
def check_required_files
|
258
|
+
return if [*terraform_files, *terragrunt_files].any?
|
259
|
+
|
260
|
+
raise "No Terraform configuration file!"
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
Dependabot::FileParsers.
|
267
|
+
register("terraform", Dependabot::Terraform::FileParser)
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/file_updaters"
|
4
|
+
require "dependabot/file_updaters/base"
|
5
|
+
require "dependabot/errors"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module Terraform
|
9
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
10
|
+
def self.updated_files_regex
|
11
|
+
[/\.tf$/, /\.tfvars$/]
|
12
|
+
end
|
13
|
+
|
14
|
+
def updated_dependency_files
|
15
|
+
updated_files = []
|
16
|
+
|
17
|
+
[*terraform_files, *terragrunt_files].each do |file|
|
18
|
+
next unless file_changed?(file)
|
19
|
+
|
20
|
+
updated_content = updated_terraform_file_content(file)
|
21
|
+
raise "Content didn't change!" if updated_content == file.content
|
22
|
+
|
23
|
+
updated_files << updated_file(file: file, content: updated_content)
|
24
|
+
end
|
25
|
+
|
26
|
+
raise "No files changed!" if updated_files.none?
|
27
|
+
|
28
|
+
updated_files
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def updated_terraform_file_content(file)
|
34
|
+
content = file.content.dup
|
35
|
+
|
36
|
+
reqs = dependency.requirements.zip(dependency.previous_requirements).
|
37
|
+
reject { |new_req, old_req| new_req == old_req }
|
38
|
+
|
39
|
+
# Loop through each changed requirement and update the files
|
40
|
+
reqs.each do |new_req, old_req|
|
41
|
+
raise "Bad req match" unless new_req[:file] == old_req[:file]
|
42
|
+
next unless new_req.fetch(:file) == file.name
|
43
|
+
|
44
|
+
case new_req[:source][:type]
|
45
|
+
when "git"
|
46
|
+
update_git_declaration(new_req, old_req, content, file.name)
|
47
|
+
when "registry"
|
48
|
+
update_registry_declaration(new_req, old_req, content)
|
49
|
+
else
|
50
|
+
raise "Don't know how to update a #{new_req[:source][:type]} "\
|
51
|
+
"declaration!"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
content
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_git_declaration(new_req, old_req, updated_content, filename)
|
59
|
+
url = old_req.fetch(:source)[:url].gsub(%r{^https://}, "")
|
60
|
+
tag = old_req.fetch(:source)[:ref]
|
61
|
+
url_regex = /#{Regexp.quote(url)}.*ref=#{Regexp.quote(tag)}/
|
62
|
+
|
63
|
+
declaration_regex = git_declaration_regex(filename)
|
64
|
+
|
65
|
+
updated_content.sub!(declaration_regex) do |regex_match|
|
66
|
+
regex_match.sub(url_regex) do |url_match|
|
67
|
+
url_match.sub(old_req[:source][:ref], new_req[:source][:ref])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_registry_declaration(new_req, old_req, updated_content)
|
73
|
+
updated_content.sub!(registry_declaration_regex) do |regex_match|
|
74
|
+
regex_match.sub(/version\s*=.*/) do |req_line_match|
|
75
|
+
req_line_match.sub(old_req[:requirement], new_req[:requirement])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def dependency
|
81
|
+
# Terraform updates will only ever be updating a single dependency
|
82
|
+
dependencies.first
|
83
|
+
end
|
84
|
+
|
85
|
+
def files_with_requirement
|
86
|
+
filenames = dependency.requirements.map { |r| r[:file] }
|
87
|
+
dependency_files.select { |file| filenames.include?(file.name) }
|
88
|
+
end
|
89
|
+
|
90
|
+
def terraform_files
|
91
|
+
dependency_files.select { |f| f.name.end_with?(".tf") }
|
92
|
+
end
|
93
|
+
|
94
|
+
def terragrunt_files
|
95
|
+
dependency_files.select { |f| f.name.end_with?(".tfvars") }
|
96
|
+
end
|
97
|
+
|
98
|
+
def check_required_files
|
99
|
+
return if [*terraform_files, *terragrunt_files].any?
|
100
|
+
|
101
|
+
raise "No Terraform configuration file!"
|
102
|
+
end
|
103
|
+
|
104
|
+
def registry_declaration_regex
|
105
|
+
/
|
106
|
+
(?<=\{)
|
107
|
+
(?:(?!^\}).)*
|
108
|
+
source\s*=\s*["']#{Regexp.escape(dependency.name)}["']
|
109
|
+
(?:(?!^\}).)*
|
110
|
+
/mx
|
111
|
+
end
|
112
|
+
|
113
|
+
def git_declaration_regex(filename)
|
114
|
+
# For terragrunt dependencies there's not a lot we can base the
|
115
|
+
# regex on. Just look for declarations within a `terraform` block
|
116
|
+
return /terraform\s*\{(?:(?!^\}).)*/m if filename.end_with?(".tfvars")
|
117
|
+
|
118
|
+
# For modules we can do better - filter for module blocks that use the
|
119
|
+
# name of the dependency
|
120
|
+
/
|
121
|
+
module\s+["']#{Regexp.escape(dependency.name)}["']\s*\{
|
122
|
+
(?:(?!^\}).)*
|
123
|
+
/mx
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
Dependabot::FileUpdaters.
|
130
|
+
register("terraform", Dependabot::Terraform::FileUpdater)
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "json"
|
5
|
+
require "dependabot/metadata_finders"
|
6
|
+
require "dependabot/metadata_finders/base"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Terraform
|
11
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
12
|
+
private
|
13
|
+
|
14
|
+
def look_up_source
|
15
|
+
case new_source_type
|
16
|
+
when "git" then find_source_from_git_url
|
17
|
+
when "registry" then find_source_from_registry_details
|
18
|
+
else raise "Unexpected source type: #{new_source_type}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_source_type
|
23
|
+
sources =
|
24
|
+
dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
|
25
|
+
|
26
|
+
return "default" if sources.empty?
|
27
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
28
|
+
|
29
|
+
sources.first[:type] || sources.first.fetch("type")
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_source_from_git_url
|
33
|
+
info = dependency.requirements.map { |r| r[:source] }.compact.first
|
34
|
+
|
35
|
+
url = info[:url] || info.fetch("url")
|
36
|
+
Source.from_url(url)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Registry API docs:
|
40
|
+
# https://www.terraform.io/docs/registry/api.html
|
41
|
+
def find_source_from_registry_details
|
42
|
+
info = dependency.requirements.map { |r| r[:source] }.compact.first
|
43
|
+
|
44
|
+
hostname = info[:registry_hostname] || info["registry_hostname"]
|
45
|
+
|
46
|
+
# TODO: Implement service discovery for custom registries
|
47
|
+
return unless hostname == "registry.terraform.io"
|
48
|
+
|
49
|
+
url = "https://registry.terraform.io/v1/modules/"\
|
50
|
+
"#{dependency.name}/#{dependency.version}"
|
51
|
+
|
52
|
+
response = Excon.get(
|
53
|
+
url,
|
54
|
+
idempotent: true,
|
55
|
+
**SharedHelpers.excon_defaults
|
56
|
+
)
|
57
|
+
|
58
|
+
unless response.status == 200
|
59
|
+
raise "Response from registry was #{response.status}"
|
60
|
+
end
|
61
|
+
|
62
|
+
source_url = JSON.parse(response.body).fetch("source")
|
63
|
+
Source.from_url(source_url) if source_url
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Dependabot::MetadataFinders.
|
70
|
+
register("terraform", Dependabot::Terraform::MetadataFinder)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils"
|
4
|
+
require "dependabot/terraform/version"
|
5
|
+
|
6
|
+
# Just ensures that Terraform requirements use Terraform versions
|
7
|
+
module Dependabot
|
8
|
+
module Terraform
|
9
|
+
class Requirement < Gem::Requirement
|
10
|
+
def self.parse(obj)
|
11
|
+
return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
12
|
+
|
13
|
+
unless (matches = PATTERN.match(obj.to_s))
|
14
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
15
|
+
raise BadRequirementError, msg
|
16
|
+
end
|
17
|
+
|
18
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
19
|
+
|
20
|
+
[matches[1] || "=", Terraform::Version.new(matches[2])]
|
21
|
+
end
|
22
|
+
|
23
|
+
# For consistency with other langauges, we define a requirements array.
|
24
|
+
# Terraform doesn't have an `OR` separator for requirements, so it
|
25
|
+
# always contains a single element.
|
26
|
+
def self.requirements_array(requirement_string)
|
27
|
+
[new(requirement_string)]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Dependabot::Utils.
|
34
|
+
register_requirement_class("terraform", Dependabot::Terraform::Requirement)
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
####################################################################
|
4
|
+
# For more details on Terraform version constraints, see: #
|
5
|
+
# https://www.terraform.io/docs/modules/usage.html#module-versions #
|
6
|
+
####################################################################
|
7
|
+
|
8
|
+
require "dependabot/terraform/version"
|
9
|
+
require "dependabot/terraform/requirement"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module Terraform
|
13
|
+
class RequirementsUpdater
|
14
|
+
def initialize(requirements:, latest_version:,
|
15
|
+
tag_for_latest_version:)
|
16
|
+
@requirements = requirements
|
17
|
+
@tag_for_latest_version = tag_for_latest_version
|
18
|
+
|
19
|
+
return unless latest_version
|
20
|
+
return unless version_class.correct?(latest_version)
|
21
|
+
|
22
|
+
@latest_version = version_class.new(latest_version)
|
23
|
+
end
|
24
|
+
|
25
|
+
def updated_requirements
|
26
|
+
return requirements unless latest_version
|
27
|
+
|
28
|
+
# Note: Order is important here. The FileUpdater needs the updated
|
29
|
+
# requirement at index `i` to correspond to the previous requirement
|
30
|
+
# at the same index.
|
31
|
+
requirements.map do |req|
|
32
|
+
case req.dig(:source, :type)
|
33
|
+
when "git" then update_git_requirement(req)
|
34
|
+
when "registry" then update_registry_requirement(req)
|
35
|
+
else req
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :requirements, :latest_version, :tag_for_latest_version
|
43
|
+
|
44
|
+
def update_git_requirement(req)
|
45
|
+
return req unless req.dig(:source, :ref)
|
46
|
+
return req unless tag_for_latest_version
|
47
|
+
|
48
|
+
req.merge(source: req[:source].merge(ref: tag_for_latest_version))
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_registry_requirement(req)
|
52
|
+
return req if req.fetch(:requirement).nil?
|
53
|
+
|
54
|
+
string_req = req.fetch(:requirement).strip
|
55
|
+
ruby_req = requirement_class.new(string_req)
|
56
|
+
return req if ruby_req.satisfied_by?(latest_version)
|
57
|
+
|
58
|
+
new_req =
|
59
|
+
if ruby_req.exact? then latest_version.to_s
|
60
|
+
elsif string_req.start_with?("~>")
|
61
|
+
update_twiddle_version(string_req).to_s
|
62
|
+
else
|
63
|
+
update_range(string_req).map(&:to_s).join(", ")
|
64
|
+
end
|
65
|
+
|
66
|
+
req.merge(requirement: new_req)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Updates the version in a "~>" constraint to allow the given version
|
70
|
+
def update_twiddle_version(req_string)
|
71
|
+
old_version = requirement_class.new(req_string).
|
72
|
+
requirements.first.last
|
73
|
+
updated_version = at_same_precision(latest_version, old_version)
|
74
|
+
req_string.sub(old_version.to_s, updated_version)
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_range(req_string)
|
78
|
+
requirement_class.new(req_string).requirements.flat_map do |r|
|
79
|
+
next r if r.satisfied_by?(latest_version)
|
80
|
+
|
81
|
+
case op = r.requirements.first.first
|
82
|
+
when "<", "<=" then [update_greatest_version(r, latest_version)]
|
83
|
+
when "!=" then []
|
84
|
+
else raise "Unexpected operation for unsatisfied req: #{op}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def at_same_precision(new_version, old_version)
|
90
|
+
release_precision =
|
91
|
+
old_version.to_s.split(".").select { |i| i.match?(/^\d+$/) }.count
|
92
|
+
prerelease_precision =
|
93
|
+
old_version.to_s.split(".").count - release_precision
|
94
|
+
|
95
|
+
new_release =
|
96
|
+
new_version.to_s.split(".").first(release_precision)
|
97
|
+
new_prerelease =
|
98
|
+
new_version.to_s.split(".").
|
99
|
+
drop_while { |i| i.match?(/^\d+$/) }.
|
100
|
+
first([prerelease_precision, 1].max)
|
101
|
+
|
102
|
+
[*new_release, *new_prerelease].join(".")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Updates the version in a "<" or "<=" constraint to allow the given
|
106
|
+
# version
|
107
|
+
def update_greatest_version(requirement, version_to_be_permitted)
|
108
|
+
if version_to_be_permitted.is_a?(String)
|
109
|
+
version_to_be_permitted =
|
110
|
+
version_class.new(version_to_be_permitted)
|
111
|
+
end
|
112
|
+
op, version = requirement.requirements.first
|
113
|
+
version = version.release if version.prerelease?
|
114
|
+
|
115
|
+
index_to_update =
|
116
|
+
version.segments.map.with_index { |seg, i| seg.zero? ? 0 : i }.max
|
117
|
+
|
118
|
+
new_segments = version.segments.map.with_index do |_, index|
|
119
|
+
if index < index_to_update
|
120
|
+
version_to_be_permitted.segments[index]
|
121
|
+
elsif index == index_to_update
|
122
|
+
version_to_be_permitted.segments[index] + 1
|
123
|
+
else 0
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
requirement_class.new("#{op} #{new_segments.join('.')}")
|
128
|
+
end
|
129
|
+
|
130
|
+
def version_class
|
131
|
+
Version
|
132
|
+
end
|
133
|
+
|
134
|
+
def requirement_class
|
135
|
+
Requirement
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/update_checkers"
|
4
|
+
require "dependabot/update_checkers/base"
|
5
|
+
require "dependabot/git_commit_checker"
|
6
|
+
require "dependabot/terraform/requirements_updater"
|
7
|
+
require "dependabot/terraform/requirement"
|
8
|
+
require "dependabot/terraform/version"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Terraform
|
12
|
+
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
13
|
+
def latest_version
|
14
|
+
return latest_version_for_git_dependency if git_dependency?
|
15
|
+
return latest_version_for_registry_dependency if registry_dependency?
|
16
|
+
# Other sources (mercurial, path dependencies) just return `nil`
|
17
|
+
end
|
18
|
+
|
19
|
+
def latest_resolvable_version
|
20
|
+
# No concept of resolvability for terraform modules (that we're aware
|
21
|
+
# of - there may be in future).
|
22
|
+
latest_version
|
23
|
+
end
|
24
|
+
|
25
|
+
def latest_resolvable_version_with_no_unlock
|
26
|
+
# Irrelevant, since Terraform doesn't have a lockfile
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def updated_requirements
|
31
|
+
RequirementsUpdater.new(
|
32
|
+
requirements: dependency.requirements,
|
33
|
+
latest_version: latest_version&.to_s,
|
34
|
+
tag_for_latest_version: tag_for_latest_version
|
35
|
+
).updated_requirements
|
36
|
+
end
|
37
|
+
|
38
|
+
def requirements_unlocked_or_can_be?
|
39
|
+
# If the requirement comes from a proxy URL then there's no way for
|
40
|
+
# us to update it
|
41
|
+
!proxy_requirement?
|
42
|
+
end
|
43
|
+
|
44
|
+
def requirement_class
|
45
|
+
Requirement
|
46
|
+
end
|
47
|
+
|
48
|
+
def version_class
|
49
|
+
Version
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def latest_version_resolvable_with_full_unlock?
|
55
|
+
# Full unlock checks aren't relevant for Terraform files
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
def updated_dependencies_after_full_unlock
|
60
|
+
raise NotImplementedError
|
61
|
+
end
|
62
|
+
|
63
|
+
def latest_version_for_registry_dependency
|
64
|
+
return unless registry_dependency?
|
65
|
+
|
66
|
+
if @latest_version_for_registry_dependency
|
67
|
+
return @latest_version_for_registry_dependency
|
68
|
+
end
|
69
|
+
|
70
|
+
versions = all_registry_versions
|
71
|
+
versions.reject!(&:prerelease?) unless wants_prerelease?
|
72
|
+
versions.reject! { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }
|
73
|
+
|
74
|
+
@latest_version_for_registry_dependency = versions.max
|
75
|
+
end
|
76
|
+
|
77
|
+
def all_registry_versions
|
78
|
+
hostname = dependency_source_details.fetch(:registry_hostname)
|
79
|
+
identifier = dependency_source_details.fetch(:module_identifier)
|
80
|
+
|
81
|
+
# TODO: Implement service discovery for custom registries
|
82
|
+
return unless hostname == "registry.terraform.io"
|
83
|
+
|
84
|
+
url = "https://registry.terraform.io/v1/modules/"\
|
85
|
+
"#{identifier}/versions"
|
86
|
+
|
87
|
+
response = Excon.get(
|
88
|
+
url,
|
89
|
+
idempotent: true,
|
90
|
+
**SharedHelpers.excon_defaults
|
91
|
+
)
|
92
|
+
|
93
|
+
unless response.status == 200
|
94
|
+
raise "Response from registry was #{response.status}"
|
95
|
+
end
|
96
|
+
|
97
|
+
JSON.parse(response.body).
|
98
|
+
fetch("modules").first.fetch("versions").
|
99
|
+
map { |release| version_class.new(release.fetch("version")) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def wants_prerelease?
|
103
|
+
current_version = dependency.version
|
104
|
+
if current_version &&
|
105
|
+
version_class.correct?(current_version) &&
|
106
|
+
version_class.new(current_version).prerelease?
|
107
|
+
return true
|
108
|
+
end
|
109
|
+
|
110
|
+
dependency.requirements.any? do |req|
|
111
|
+
req[:requirement]&.match?(/\d-[A-Za-z0-9]/)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def latest_version_for_git_dependency
|
116
|
+
# If the module isn't pinned then there's nothing for us to update
|
117
|
+
# (since there's no lockfile to update the version in). We still
|
118
|
+
# return the latest commit for the given branch, in order to keep
|
119
|
+
# this method consistent
|
120
|
+
unless git_commit_checker.pinned?
|
121
|
+
return git_commit_checker.head_commit_for_current_branch
|
122
|
+
end
|
123
|
+
|
124
|
+
# If the dependency is pinned to a tag that looks like a version then
|
125
|
+
# we want to update that tag. Because we don't have a lockfile, the
|
126
|
+
# latest version is the tag itself.
|
127
|
+
if git_commit_checker.pinned_ref_looks_like_version?
|
128
|
+
latest_tag = git_commit_checker.local_tag_for_latest_version&.
|
129
|
+
fetch(:tag)
|
130
|
+
version_rgx = GitCommitChecker::VERSION_REGEX
|
131
|
+
return unless latest_tag.match(version_rgx)
|
132
|
+
|
133
|
+
version = latest_tag.match(version_rgx).
|
134
|
+
named_captures.fetch("version")
|
135
|
+
return version_class.new(version)
|
136
|
+
end
|
137
|
+
|
138
|
+
# If the dependency is pinned to a tag that doesn't look like a
|
139
|
+
# version then there's nothing we can do.
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def tag_for_latest_version
|
144
|
+
return unless git_commit_checker.git_dependency?
|
145
|
+
return unless git_commit_checker.pinned?
|
146
|
+
return unless git_commit_checker.pinned_ref_looks_like_version?
|
147
|
+
|
148
|
+
latest_tag = git_commit_checker.local_tag_for_latest_version&.
|
149
|
+
fetch(:tag)
|
150
|
+
|
151
|
+
version_rgx = GitCommitChecker::VERSION_REGEX
|
152
|
+
return unless latest_tag.match(version_rgx)
|
153
|
+
|
154
|
+
latest_tag
|
155
|
+
end
|
156
|
+
|
157
|
+
def proxy_requirement?
|
158
|
+
dependency.requirements.any? do |req|
|
159
|
+
req.fetch(:source)&.fetch(:proxy_url, nil)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def registry_dependency?
|
164
|
+
return false if dependency_source_details.nil?
|
165
|
+
|
166
|
+
dependency_source_details.fetch(:type) == "registry"
|
167
|
+
end
|
168
|
+
|
169
|
+
def dependency_source_details
|
170
|
+
sources =
|
171
|
+
dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
|
172
|
+
|
173
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
174
|
+
|
175
|
+
sources.first
|
176
|
+
end
|
177
|
+
|
178
|
+
def git_dependency?
|
179
|
+
git_commit_checker.git_dependency?
|
180
|
+
end
|
181
|
+
|
182
|
+
def git_commit_checker
|
183
|
+
@git_commit_checker ||=
|
184
|
+
GitCommitChecker.new(
|
185
|
+
dependency: dependency,
|
186
|
+
credentials: credentials,
|
187
|
+
ignored_versions: ignored_versions,
|
188
|
+
requirement_class: Requirement,
|
189
|
+
version_class: Version
|
190
|
+
)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
Dependabot::UpdateCheckers.
|
197
|
+
register("terraform", Dependabot::Terraform::UpdateChecker)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Terraform pre-release versions use 1.0.1-rc1 syntax, which Gem::Version
|
4
|
+
# converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that
|
5
|
+
# alteration.
|
6
|
+
#
|
7
|
+
# See, for example, https://releases.hashicorp.com/terraform/
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module Terraform
|
11
|
+
class Version < Gem::Version
|
12
|
+
def initialize(version)
|
13
|
+
@version_string = version.to_s
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@version_string
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Dependabot::Utils.
|
25
|
+
register_version_class("terraform", Dependabot::Terraform::Version)
|
metadata
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dependabot-terraform
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.76.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dependabot
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dependabot-core
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.76.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.76.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '12'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.8'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-its
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec_junit_formatter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.4'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.4'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.61'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.61'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: vcr
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.4'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.4'
|
139
|
+
description: Automated dependency management for Ruby, JavaScript, Python, PHP, Elixir,
|
140
|
+
Rust, Java, .NET, Elm and Go
|
141
|
+
email: support@dependabot.com
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- helpers/build
|
147
|
+
- lib/dependabot/terraform.rb
|
148
|
+
- lib/dependabot/terraform/file_fetcher.rb
|
149
|
+
- lib/dependabot/terraform/file_parser.rb
|
150
|
+
- lib/dependabot/terraform/file_updater.rb
|
151
|
+
- lib/dependabot/terraform/metadata_finder.rb
|
152
|
+
- lib/dependabot/terraform/requirement.rb
|
153
|
+
- lib/dependabot/terraform/requirements_updater.rb
|
154
|
+
- lib/dependabot/terraform/update_checker.rb
|
155
|
+
- lib/dependabot/terraform/version.rb
|
156
|
+
homepage: https://github.com/dependabot/dependabot-core
|
157
|
+
licenses:
|
158
|
+
- Nonstandard
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 2.5.0
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 2.5.0
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.7.6
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: Terraform support for dependabot-core
|
180
|
+
test_files: []
|