dependabot-terraform 0.147.0 → 0.148.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21f0249523d3bd37b7c26bfcc7e76b0246cb83ef62c8e7aabd76d3cf207476c9
4
- data.tar.gz: 791a20bc9878a4f7e5e1806824551bee1102dc9c9eb8b2dd58a0fbe1e880fd05
3
+ metadata.gz: 557281ba17818b79b7e7513d51032317191ea128bb2b70a9021967eb67e53394
4
+ data.tar.gz: 2f209922a5bdd9cfcd85f1746724eea11fa77709e7388aabd699d7dd192d5e83
5
5
  SHA512:
6
- metadata.gz: b464c8b17eebf7f615824a79b85d660446e13e38741bfe60d195bf78643c0a983536c267719f3f775e78be1308d93b8cf1ba0c58ee7bddefa49404b2f1652fe9
7
- data.tar.gz: 40b1ca7f782c0ed4d5309f293a72572d5f34be8963bbb4f7097d891a3545c7362d2331b029992776a4446ee5933bec22211d0848d9aff9df40d2b29839a50bf0
6
+ metadata.gz: f8dbf7cb6b636039b77c6b4834f0e24588c44836a73d28f6aab6ea1fe71dba615809185033cab51366629a3d69cb3c81e6ab4e1f782dc768620ddab0b91ec961
7
+ data.tar.gz: c5c1fbb0c5b9437a13023205dec1091a348d8ff99c8004f533352c97cdfd65b6070600ff40fcd7bd0fb5fb0bd1b202ce1c0d6b284fae97bd0aabf08a08810782
data/helpers/build CHANGED
@@ -14,13 +14,6 @@ fi
14
14
 
15
15
  os="$(uname -s | tr '[:upper:]' '[:lower:]')"
16
16
 
17
- json2hcl_checksum="d124ed13f3538c465fcab19e6015d311d3cd56f7dc2db7609b6e72fec666482d"
18
- json2hcl_url="https://github.com/kvz/json2hcl/releases/download/v0.0.6/json2hcl_v0.0.6_${os}_amd64"
19
- json2hcl_path="$install_dir/bin/json2hcl"
20
- wget -O "$json2hcl_path" "$json2hcl_url"
21
- echo "$json2hcl_checksum $json2hcl_path" | sha256sum -c
22
- chmod +x "$install_dir/bin/json2hcl"
23
-
24
17
  hcl2json_checksum="24068f1e25a34d8f8ca763f34fce11527472891bfa834d1504f665855021d5d4"
25
18
  hcl2json_url="https://github.com/tmccombs/hcl2json/releases/download/v0.3.3/hcl2json_${os}_amd64"
26
19
  hcl2json_path="$install_dir/bin/hcl2json"
@@ -10,7 +10,7 @@ module Dependabot
10
10
  include FileSelector
11
11
 
12
12
  def self.required_files_in?(filenames)
13
- filenames.any? { |f| f.end_with?(".tf", ".tfvars") }
13
+ filenames.any? { |f| f.end_with?(".tf", ".hcl") }
14
14
  end
15
15
 
16
16
  def self.required_files_message
@@ -27,15 +27,21 @@ module Dependabot
27
27
  terraform_files.each do |file|
28
28
  modules = parsed_file(file).fetch("module", {})
29
29
  modules.each do |name, details|
30
- dependency_set << build_terraform_dependency(file, name, details)
30
+ dependency_set << build_terraform_dependency(file, name, details, false)
31
+ end
32
+
33
+ parsed_file(file).fetch("terraform", []).each do |terraform|
34
+ required_providers = terraform.fetch("required_providers", {})
35
+ required_providers.each do |provider|
36
+ provider.each do |name, details|
37
+ dependency_set << build_terraform_dependency(file, name, details, true)
38
+ end
39
+ end
31
40
  end
32
41
  end
33
42
 
34
43
  terragrunt_files.each do |file|
35
- # legacy terragrunt (.tfvars) files have a top-level "terragrunt" key
36
- # that has since been removed.
37
- legacy_modules = (parsed_file(file).fetch("terragrunt", []).first || {}).fetch("terraform", [])
38
- modules = parsed_file(file).fetch("terraform", []) + legacy_modules
44
+ modules = parsed_file(file).fetch("terraform", [])
39
45
  modules.each do |details|
40
46
  next unless details["source"]
41
47
 
@@ -48,12 +54,15 @@ module Dependabot
48
54
 
49
55
  private
50
56
 
51
- def build_terraform_dependency(file, name, details)
52
- details = details.first
57
+ def build_terraform_dependency(file, name, details, provider)
58
+ details = details.is_a?(Array) ? details.first : details
53
59
 
54
- source = source_from(details)
55
- dep_name =
56
- source[:type] == "registry" ? source[:module_identifier] : name
60
+ source = source_from(details, provider)
61
+ dep_name = case source[:type]
62
+ when "registry" then source[:module_identifier]
63
+ when "provider" then details["source"]
64
+ else name
65
+ end
57
66
  version_req = details["version"]&.strip
58
67
  version =
59
68
  if source[:type] == "git" then version_from_ref(source[:ref])
@@ -74,7 +83,7 @@ module Dependabot
74
83
  end
75
84
 
76
85
  def build_terragrunt_dependency(file, details)
77
- source = source_from(details)
86
+ source = source_from(details, false)
78
87
  dep_name =
79
88
  if Source.from_url(source[:url])
80
89
  Source.from_url(source[:url]).repo
@@ -98,7 +107,7 @@ module Dependabot
98
107
  end
99
108
 
100
109
  # Full docs at https://www.terraform.io/docs/modules/sources.html
101
- def source_from(details_hash)
110
+ def source_from(details_hash, provider)
102
111
  raw_source = details_hash.fetch("source")
103
112
  bare_source = get_proxied_source(raw_source)
104
113
 
@@ -109,17 +118,23 @@ module Dependabot
109
118
  when :github, :bitbucket, :git
110
119
  git_source_details_from(bare_source)
111
120
  when :registry
112
- registry_source_details_from(bare_source)
121
+ registry_source_details_from(bare_source, provider)
113
122
  end
114
123
 
115
124
  source_details[:proxy_url] = raw_source if raw_source != bare_source
116
125
  source_details
117
126
  end
118
127
 
119
- def registry_source_details_from(source_string)
128
+ def registry_source_details_from(source_string, provider)
120
129
  parts = source_string.split("//").first.split("/")
121
130
 
122
- if parts.count == 3
131
+ if provider && parts.count == 2
132
+ {
133
+ "type": "provider",
134
+ "registry_hostname": "registry.terraform.io",
135
+ "module_identifier": source_string
136
+ }
137
+ elsif parts.count == 3
123
138
  {
124
139
  type: "registry",
125
140
  registry_hostname: "registry.terraform.io",
@@ -215,56 +230,6 @@ module Dependabot
215
230
  end
216
231
  # rubocop:enable Metrics/PerceivedComplexity
217
232
 
218
- def parsed_file_hcl2(file)
219
- SharedHelpers.in_a_temporary_directory do
220
- File.write("tmp.tf", file.content)
221
-
222
- command = "#{terraform_hcl2_parser_path} < tmp.tf"
223
- start = Time.now
224
- stdout, stderr, process = Open3.capture3(command)
225
- time_taken = Time.now - start
226
-
227
- unless process.success?
228
- raise SharedHelpers::HelperSubprocessFailed.new(
229
- message: stderr,
230
- error_context: {
231
- command: command,
232
- time_taken: time_taken,
233
- process_exit_value: process.to_s
234
- }
235
- )
236
- end
237
-
238
- JSON.parse(stdout)
239
- end
240
- end
241
-
242
- def parsed_file_hcl1(file)
243
- SharedHelpers.in_a_temporary_directory do
244
- File.write("tmp.tf", file.content)
245
-
246
- command = "#{terraform_parser_path} -reverse < tmp.tf"
247
- start = Time.now
248
- stdout, stderr, process = Open3.capture3(command)
249
- time_taken = Time.now - start
250
-
251
- unless process.success?
252
- raise SharedHelpers::HelperSubprocessFailed.new(
253
- message: stderr,
254
- error_context: {
255
- command: command,
256
- time_taken: time_taken,
257
- process_exit_value: process.to_s
258
- }
259
- )
260
- end
261
-
262
- json = JSON.parse(stdout)
263
- json["module"] = json.fetch("module", []).inject({}) { |memo, item| memo.merge(item) }
264
- json
265
- end
266
- end
267
-
268
233
  # == Returns:
269
234
  # A Hash representing each module found in the specified file
270
235
  #
@@ -289,12 +254,27 @@ module Dependabot
289
254
  # }
290
255
  def parsed_file(file)
291
256
  @parsed_buildfile ||= {}
292
- @parsed_buildfile[file.name] ||=
293
- if options[:legacy_terraform]
294
- parsed_file_hcl1(file)
295
- else
296
- parsed_file_hcl2(file)
257
+ @parsed_buildfile[file.name] ||= SharedHelpers.in_a_temporary_directory do
258
+ File.write("tmp.tf", file.content)
259
+
260
+ command = "#{terraform_hcl2_parser_path} < tmp.tf"
261
+ start = Time.now
262
+ stdout, stderr, process = Open3.capture3(command)
263
+ time_taken = Time.now - start
264
+
265
+ unless process.success?
266
+ raise SharedHelpers::HelperSubprocessFailed.new(
267
+ message: stderr,
268
+ error_context: {
269
+ command: command,
270
+ time_taken: time_taken,
271
+ process_exit_value: process.to_s
272
+ }
273
+ )
297
274
  end
275
+
276
+ JSON.parse(stdout)
277
+ end
298
278
  rescue SharedHelpers::HelperSubprocessFailed => e
299
279
  msg = e.message.strip
300
280
  raise Dependabot::DependencyFileNotParseable.new(file.path, msg)
@@ -12,7 +12,6 @@ module FileSelector
12
12
  end
13
13
 
14
14
  def terragrunt_file?(file_name)
15
- file_name != ".terraform.lock.hcl" &&
16
- (file_name.end_with?(".tfvars") || file_name.end_with?(".hcl"))
15
+ file_name != ".terraform.lock.hcl" && file_name.end_with?(".hcl")
17
16
  end
18
17
  end
@@ -11,7 +11,7 @@ module Dependabot
11
11
  include FileSelector
12
12
 
13
13
  def self.updated_files_regex
14
- [/\.tf$/, /\.tfvars$/, /\.hcl$/]
14
+ [/\.tf$/, /\.hcl$/]
15
15
  end
16
16
 
17
17
  def updated_dependency_files
@@ -47,7 +47,7 @@ module Dependabot
47
47
  case new_req[:source][:type]
48
48
  when "git"
49
49
  update_git_declaration(new_req, old_req, content, file.name)
50
- when "registry"
50
+ when "registry", "provider"
51
51
  update_registry_declaration(new_req, old_req, content)
52
52
  else
53
53
  raise "Don't know how to update a #{new_req[:source][:type]} "\
@@ -14,7 +14,7 @@ module Dependabot
14
14
  def look_up_source
15
15
  case new_source_type
16
16
  when "git" then find_source_from_git_url
17
- when "registry" then find_source_from_registry_details
17
+ when "registry", "provider" then find_source_from_registry_details
18
18
  else raise "Unexpected source type: #{new_source_type}"
19
19
  end
20
20
  end
@@ -36,29 +36,11 @@ module Dependabot
36
36
  Source.from_url(url)
37
37
  end
38
38
 
39
- # Registry API docs:
40
- # https://www.terraform.io/docs/registry/api.html
41
39
  def find_source_from_registry_details
42
40
  info = dependency.requirements.map { |r| r[:source] }.compact.first
43
-
44
41
  hostname = info[:registry_hostname] || info["registry_hostname"]
45
42
 
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
- raise "Response from registry was #{response.status}" unless response.status == 200
59
-
60
- source_url = JSON.parse(response.body).fetch("source")
61
- Source.from_url(source_url) if source_url
43
+ RegistryClient.new(hostname: hostname).source(dependency: dependency)
62
44
  end
63
45
  end
64
46
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/dependency"
4
+ require "dependabot/source"
5
+ require "dependabot/terraform/version"
6
+
7
+ module Dependabot
8
+ module Terraform
9
+ # Terraform::RegistryClient is a basic API client to interact with a
10
+ # terraform registry: https://www.terraform.io/docs/registry/api.html
11
+ class RegistryClient
12
+ PUBLIC_HOSTNAME = "registry.terraform.io"
13
+
14
+ def initialize(hostname:)
15
+ @hostname = hostname
16
+ end
17
+
18
+ # Fetch all the versions of a provider, and return a Version
19
+ # representation of them.
20
+ #
21
+ # @param identifier [String] the identifier for the dependency, i.e:
22
+ # "hashicorp/aws"
23
+ # @return [Array<Dependabot::Terraform::Version>]
24
+ # @raise [RuntimeError] when the versions cannot be retrieved
25
+ def all_provider_versions(identifier:)
26
+ # TODO: Implement service discovery for custom registries
27
+ return [] unless hostname == PUBLIC_HOSTNAME
28
+
29
+ response = get(endpoint: "providers/#{identifier}/versions")
30
+
31
+ JSON.parse(response).
32
+ fetch("versions").
33
+ map { |release| version_class.new(release.fetch("version")) }
34
+ end
35
+
36
+ # Fetch all the versions of a module, and return a Version
37
+ # representation of them.
38
+ #
39
+ # @param identifier [String] the identifier for the dependency, i.e:
40
+ # "hashicorp/consul/aws"
41
+ # @return [Array<Dependabot::Terraform::Version>]
42
+ # @raise [RuntimeError] when the versions cannot be retrieved
43
+ def all_module_versions(identifier:)
44
+ # TODO: Implement service discovery for custom registries
45
+ return [] unless hostname == PUBLIC_HOSTNAME
46
+
47
+ response = get(endpoint: "modules/#{identifier}/versions")
48
+
49
+ JSON.parse(response).
50
+ fetch("modules").first.fetch("versions").
51
+ map { |release| version_class.new(release.fetch("version")) }
52
+ end
53
+
54
+ # Fetch the "source" for a module or provider. We use the API to fetch
55
+ # the source for a dependency, this typically points to a source code
56
+ # repository, and then instantiate a Dependabot::Source object that we
57
+ # can use to fetch Metadata about a specific version of the dependency.
58
+ #
59
+ # @param dependency [Dependabot::Dependency] the dependency who's source
60
+ # we're attempting to find
61
+ # @return Dependabot::Source
62
+ # @raise [RuntimeError] when the source cannot be retrieved
63
+ def source(dependency:)
64
+ # TODO: Implement service discovery for custom registries
65
+ return unless hostname == PUBLIC_HOSTNAME
66
+
67
+ type = dependency.requirements.first[:source][:type]
68
+ endpoint = if type == "registry"
69
+ "modules/#{dependency.name}/#{dependency.version}"
70
+ elsif type == "provider"
71
+ "providers/#{dependency.name}/#{dependency.version}"
72
+ else
73
+ raise "Invalid source type"
74
+ end
75
+ response = get(endpoint: endpoint)
76
+
77
+ source_url = JSON.parse(response).fetch("source")
78
+ Source.from_url(source_url) if source_url
79
+ end
80
+
81
+ private
82
+
83
+ attr_reader :hostname
84
+
85
+ def get(endpoint:)
86
+ url = "https://#{hostname}/v1/#{endpoint}"
87
+
88
+ response = Excon.get(
89
+ url,
90
+ idempotent: true,
91
+ **SharedHelpers.excon_defaults
92
+ )
93
+
94
+ raise "Response from registry was #{response.status}" unless response.status == 200
95
+
96
+ response.body
97
+ end
98
+
99
+ def version_class
100
+ Version
101
+ end
102
+ end
103
+ end
104
+ end
@@ -72,7 +72,7 @@ module Dependabot
72
72
  requirements.map do |req|
73
73
  case req.dig(:source, :type)
74
74
  when "git" then update_git_requirement(req)
75
- when "registry" then update_registry_requirement(req)
75
+ when "registry", "provider" then update_registry_requirement(req)
76
76
  else req
77
77
  end
78
78
  end
@@ -6,6 +6,7 @@ require "dependabot/git_commit_checker"
6
6
  require "dependabot/terraform/requirements_updater"
7
7
  require "dependabot/terraform/requirement"
8
8
  require "dependabot/terraform/version"
9
+ require "dependabot/terraform/registry_client"
9
10
 
10
11
  module Dependabot
11
12
  module Terraform
@@ -13,6 +14,7 @@ module Dependabot
13
14
  def latest_version
14
15
  return latest_version_for_git_dependency if git_dependency?
15
16
  return latest_version_for_registry_dependency if registry_dependency?
17
+ return latest_version_for_provider_dependency if provider_dependency?
16
18
  # Other sources (mercurial, path dependencies) just return `nil`
17
19
  end
18
20
 
@@ -65,34 +67,40 @@ module Dependabot
65
67
 
66
68
  return @latest_version_for_registry_dependency if @latest_version_for_registry_dependency
67
69
 
68
- versions = all_registry_versions
70
+ versions = all_module_versions
69
71
  versions.reject!(&:prerelease?) unless wants_prerelease?
70
72
  versions.reject! { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
71
73
 
72
74
  @latest_version_for_registry_dependency = versions.max
73
75
  end
74
76
 
75
- def all_registry_versions
76
- hostname = dependency_source_details.fetch(:registry_hostname)
77
+ def all_module_versions
77
78
  identifier = dependency_source_details.fetch(:module_identifier)
79
+ registry_client.all_module_versions(identifier: identifier)
80
+ end
81
+
82
+ def all_provider_versions
83
+ identifier = dependency_source_details.fetch(:module_identifier)
84
+ registry_client.all_provider_versions(identifier: identifier)
85
+ end
78
86
 
79
- # TODO: Implement service discovery for custom registries
80
- return [] unless hostname == "registry.terraform.io"
87
+ def registry_client
88
+ @registry_client ||= begin
89
+ hostname = dependency_source_details.fetch(:registry_hostname)
90
+ RegistryClient.new(hostname: hostname)
91
+ end
92
+ end
81
93
 
82
- url = "https://registry.terraform.io/v1/modules/"\
83
- "#{identifier}/versions"
94
+ def latest_version_for_provider_dependency
95
+ return unless provider_dependency?
84
96
 
85
- response = Excon.get(
86
- url,
87
- idempotent: true,
88
- **SharedHelpers.excon_defaults
89
- )
97
+ return @latest_version_for_provider_dependency if @latest_version_for_provider_dependency
90
98
 
91
- raise "Response from registry was #{response.status}" unless response.status == 200
99
+ versions = all_provider_versions
100
+ versions.reject!(&:prerelease?) unless wants_prerelease?
101
+ versions.reject! { |v| ignore_requirements.any? { |r| r.satisfied_by?(v) } }
92
102
 
93
- JSON.parse(response.body).
94
- fetch("modules").first.fetch("versions").
95
- map { |release| version_class.new(release.fetch("version")) }
103
+ @latest_version_for_provider_dependency = versions.max
96
104
  end
97
105
 
98
106
  def wants_prerelease?
@@ -160,6 +168,12 @@ module Dependabot
160
168
  dependency_source_details.fetch(:type) == "registry"
161
169
  end
162
170
 
171
+ def provider_dependency?
172
+ return false if dependency_source_details.nil?
173
+
174
+ dependency_source_details.fetch(:type) == "provider"
175
+ end
176
+
163
177
  def dependency_source_details
164
178
  sources =
165
179
  dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-terraform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.147.0
4
+ version: 0.148.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-13 00:00:00.000000000 Z
11
+ date: 2021-05-19 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.147.0
19
+ version: 0.148.3
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.147.0
26
+ version: 0.148.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.14.0
103
+ version: 1.15.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.14.0
110
+ version: 1.15.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -192,6 +192,7 @@ files:
192
192
  - lib/dependabot/terraform/file_selector.rb
193
193
  - lib/dependabot/terraform/file_updater.rb
194
194
  - lib/dependabot/terraform/metadata_finder.rb
195
+ - lib/dependabot/terraform/registry_client.rb
195
196
  - lib/dependabot/terraform/requirement.rb
196
197
  - lib/dependabot/terraform/requirements_updater.rb
197
198
  - lib/dependabot/terraform/update_checker.rb