dependabot-terraform 0.367.0 → 0.368.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 389933322316b6c2adf5622aa7609f6a963328f40a0b917c20599b0a8d119b9f
4
- data.tar.gz: 28b9548394102c654c739c418f26687f546e527410cded37a6df51f628a930f9
3
+ metadata.gz: 70cf97fb42d98440944e843c09f2909b5206a0859715b170c17c94e355ade662
4
+ data.tar.gz: 6c8022c3e1a286992bbc83a1851c84ff293e29f6b5773d287e0498bc55b38150
5
5
  SHA512:
6
- metadata.gz: 41ea4cf23c32b98ec673ddbbaa85de54e151aca4a9021c11f6b782a5d4f45c14707ccb9e3c9f37c686415b1aa894a6691970473d8eb95feda318bb63a3be4aac
7
- data.tar.gz: 48067bf39c1f9e2d2e1aefd331f71894c226d89b3be6ecd7f13761b621e24cb488c0cb97ca77d6c82e5eccc6e1b9a33b1bc211cc32cf88353350e378fe575dd7
6
+ metadata.gz: 45336906f1e79f9fb1d27945e1a52e0e40e2a760128dfb5ca6fca7389dc05b87fe0121b005a598f391eeb2128b92fda4a3cd8c66ad1c15aead11a58fba8d3a65
7
+ data.tar.gz: 7c33db9982823d3ae2d92cf7b2f8ba03eca0e7505a7a5a02ef957b28b616bb2be7a73905a42879e0b0f3bea57a31c210d6520bb969bac1e615a675746a4d3feb
@@ -0,0 +1,178 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+ require "json"
6
+ require "open3"
7
+ require "securerandom"
8
+ require "pathname"
9
+ require "dependabot/shared_helpers"
10
+ require "dependabot/terraform/file_updater"
11
+
12
+ module Dependabot
13
+ module Terraform
14
+ class FileUpdater < Dependabot::FileUpdaters::Base
15
+ # Builds a Terraform CLI config that uses dev_overrides for non-target
16
+ # providers, preventing Terraform from resolving private/custom providers
17
+ # during lockfile updates.
18
+ class ProviderCliConfigBuilder
19
+ extend T::Sig
20
+
21
+ DEFAULT_REGISTRY = "registry.terraform.io"
22
+ DEFAULT_NAMESPACE = "hashicorp"
23
+
24
+ sig do
25
+ params(
26
+ dependency: Dependabot::Dependency,
27
+ terraform_files: T::Array[Dependabot::DependencyFile]
28
+ ).void
29
+ end
30
+ def initialize(dependency:, terraform_files:)
31
+ @dependency = dependency
32
+ @terraform_files = terraform_files
33
+ @terraform_cli_config_path = T.let(nil, T.nilable(String))
34
+ @dev_override_dir = T.let(nil, T.nilable(String))
35
+ end
36
+
37
+ sig { returns(T::Hash[String, String]) }
38
+ def env
39
+ sources = non_target_provider_sources
40
+ return {} if sources.empty?
41
+
42
+ config_path = generate_provider_dev_overrides_config(sources)
43
+ { "TF_CLI_CONFIG_FILE" => config_path }
44
+ end
45
+
46
+ sig { void }
47
+ def cleanup
48
+ if @terraform_cli_config_path
49
+ FileUtils.rm_f(@terraform_cli_config_path)
50
+ @terraform_cli_config_path = nil
51
+ end
52
+ return unless @dev_override_dir
53
+
54
+ FileUtils.rm_rf(@dev_override_dir)
55
+ @dev_override_dir = nil
56
+ end
57
+
58
+ private
59
+
60
+ sig { returns(Dependabot::Dependency) }
61
+ attr_reader :dependency
62
+
63
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
64
+ attr_reader :terraform_files
65
+
66
+ sig { returns(T::Array[String]) }
67
+ def non_target_provider_sources
68
+ target = target_provider_source
69
+ return [] unless target
70
+
71
+ collect_provider_sources_from_files.reject { |s| s == target }
72
+ end
73
+
74
+ sig { returns(T.nilable(String)) }
75
+ def target_provider_source
76
+ req = dependency.requirements.first
77
+ return unless req
78
+
79
+ source = req[:source]
80
+ return unless source && source[:type] == "provider"
81
+
82
+ hostname = source[:registry_hostname] || DEFAULT_REGISTRY
83
+ identifier = source[:module_identifier]
84
+ return unless identifier
85
+
86
+ "#{hostname}/#{identifier}".downcase
87
+ end
88
+
89
+ sig { returns(T::Array[String]) }
90
+ def collect_provider_sources_from_files
91
+ sources = T.let([], T::Array[String])
92
+ terraform_files.each do |file|
93
+ parsed = parse_hcl_file(file)
94
+ parsed.fetch("terraform", []).each do |terraform_block|
95
+ terraform_block.fetch("required_providers", {}).each do |providers|
96
+ providers.each do |name, details|
97
+ next unless details.is_a?(Hash)
98
+
99
+ source_address = details.fetch("source", nil)
100
+ sources << normalize_provider_source(source_address, name)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ sources.uniq
106
+ end
107
+
108
+ sig { params(source_address: T.nilable(String), name: String).returns(String) }
109
+ def normalize_provider_source(source_address, name)
110
+ if source_address
111
+ parts = source_address.split("/")
112
+ if parts.length == 2
113
+ "#{DEFAULT_REGISTRY}/#{source_address}".downcase
114
+ else
115
+ source_address.downcase
116
+ end
117
+ else
118
+ "#{DEFAULT_REGISTRY}/#{DEFAULT_NAMESPACE}/#{name}".downcase
119
+ end
120
+ end
121
+
122
+ sig { params(file: Dependabot::DependencyFile).returns(T::Hash[String, T.untyped]) }
123
+ def parse_hcl_file(file)
124
+ return {} unless file.content
125
+
126
+ SharedHelpers.in_a_temporary_directory do
127
+ File.write("tmp.tf", file.content)
128
+ command = "#{terraform_hcl2_parser_path} < tmp.tf"
129
+ stdout, _stderr, process = Open3.capture3(command)
130
+ return {} unless process.success?
131
+
132
+ JSON.parse(stdout)
133
+ end
134
+ rescue StandardError
135
+ {}
136
+ end
137
+
138
+ sig { params(provider_sources: T::Array[String]).returns(String) }
139
+ def generate_provider_dev_overrides_config(provider_sources)
140
+ dev_override_dir = File.join(Dir.tmpdir, "dependabot-tf-dev-#{SecureRandom.hex(4)}")
141
+ FileUtils.mkdir_p(dev_override_dir)
142
+ @dev_override_dir = dev_override_dir
143
+
144
+ overrides = provider_sources.map do |source|
145
+ " \"#{source}\" = \"#{dev_override_dir}\""
146
+ end.join("\n")
147
+
148
+ config_content = <<~CONFIG
149
+ provider_installation {
150
+ dev_overrides {
151
+ #{overrides}
152
+ }
153
+ direct {}
154
+ }
155
+ CONFIG
156
+
157
+ config_path = File.join(Dir.tmpdir, "dependabot-terraform-#{SecureRandom.hex(4)}.rc")
158
+ File.write(config_path, config_content)
159
+ @terraform_cli_config_path = config_path
160
+
161
+ config_path
162
+ end
163
+
164
+ sig { returns(String) }
165
+ def terraform_hcl2_parser_path
166
+ helper_bin_dir = File.join(native_helpers_root, "terraform/bin")
167
+ Pathname.new(File.join(helper_bin_dir, "hcl2json")).cleanpath.to_path
168
+ end
169
+
170
+ sig { returns(String) }
171
+ def native_helpers_root
172
+ default_path = File.join(__dir__, "../../../../helpers/install-dir")
173
+ ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -14,6 +14,8 @@ module Dependabot
14
14
  class FileUpdater < Dependabot::FileUpdaters::Base
15
15
  extend T::Sig
16
16
 
17
+ require_relative "file_updater/provider_cli_config_builder"
18
+
17
19
  include FileSelector
18
20
 
19
21
  PRIVATE_MODULE_ERROR = /Could not download module.*code from\n.*\"(?<repo>\S+)\":/
@@ -46,6 +48,8 @@ module Dependabot
46
48
  raise "No files changed!" if updated_files.none?
47
49
 
48
50
  updated_files
51
+ ensure
52
+ cleanup_terraform_cli_config
49
53
  end
50
54
 
51
55
  private
@@ -215,7 +219,8 @@ module Dependabot
215
219
 
216
220
  SharedHelpers.run_shell_command(
217
221
  "terraform providers lock -platform=#{arch} #{provider_source} -no-color",
218
- fingerprint: "terraform providers lock -platform=<arch> <provider_source> -no-color"
222
+ fingerprint: "terraform providers lock -platform=<arch> <provider_source> -no-color",
223
+ env: terraform_env
219
224
  )
220
225
 
221
226
  updated_lockfile = File.read(".terraform.lock.hcl")
@@ -282,7 +287,8 @@ module Dependabot
282
287
 
283
288
  SharedHelpers.run_shell_command(
284
289
  "terraform providers lock #{platforms} #{provider_source}",
285
- fingerprint: "terraform providers lock <platforms> <provider_source>"
290
+ fingerprint: "terraform providers lock <platforms> <provider_source>",
291
+ env: terraform_env
286
292
  )
287
293
 
288
294
  updated_lockfile = File.read(".terraform.lock.hcl")
@@ -319,7 +325,10 @@ module Dependabot
319
325
  # -backend=false option used to ignore any backend configuration, as these won't be accessible
320
326
  # -input=false option used to immediately fail if it needs user input
321
327
  # -no-color option used to prevent any color characters being printed in the output
322
- SharedHelpers.run_shell_command("terraform init -backend=false -input=false -no-color")
328
+ SharedHelpers.run_shell_command(
329
+ "terraform init -backend=false -input=false -no-color",
330
+ env: terraform_env
331
+ )
323
332
  rescue SharedHelpers::HelperSubprocessFailed => e
324
333
  output = e.message
325
334
 
@@ -428,6 +437,30 @@ module Dependabot
428
437
  (?:(?!^\}).)*}
429
438
  /mix
430
439
  end
440
+
441
+ sig { returns(T::Hash[String, String]) }
442
+ def terraform_env
443
+ @terraform_env ||= T.let(
444
+ provider_cli_config_builder.env,
445
+ T.nilable(T::Hash[String, String])
446
+ )
447
+ end
448
+
449
+ sig { void }
450
+ def cleanup_terraform_cli_config
451
+ provider_cli_config_builder.cleanup
452
+ end
453
+
454
+ sig { returns(ProviderCliConfigBuilder) }
455
+ def provider_cli_config_builder
456
+ @provider_cli_config_builder ||= T.let(
457
+ ProviderCliConfigBuilder.new(
458
+ dependency: dependency,
459
+ terraform_files: terraform_files
460
+ ),
461
+ T.nilable(ProviderCliConfigBuilder)
462
+ )
463
+ end
431
464
  end
432
465
 
433
466
  class FileUpdaterErrorHandler
@@ -227,8 +227,11 @@ module Dependabot
227
227
  return uri.to_s if uri.scheme == "https"
228
228
  raise error("Unsupported scheme provided") if uri.host && uri.scheme
229
229
 
230
- uri.host = hostname
230
+ parsed_hostname = URI.parse("https://#{hostname}")
231
+ uri.host = parsed_hostname.host
231
232
  uri.scheme = "https"
233
+ # Only set port explicitly when it differs from the default HTTPS port
234
+ uri.port = parsed_hostname.port unless parsed_hostname.port == URI::HTTPS.default_port
232
235
  uri.to_s
233
236
  end
234
237
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-terraform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.367.0
4
+ version: 0.368.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.367.0
18
+ version: 0.368.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.367.0
25
+ version: 0.368.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: debug
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -248,6 +248,7 @@ files:
248
248
  - lib/dependabot/terraform/file_parser.rb
249
249
  - lib/dependabot/terraform/file_selector.rb
250
250
  - lib/dependabot/terraform/file_updater.rb
251
+ - lib/dependabot/terraform/file_updater/provider_cli_config_builder.rb
251
252
  - lib/dependabot/terraform/metadata_finder.rb
252
253
  - lib/dependabot/terraform/package/package_details_fetcher.rb
253
254
  - lib/dependabot/terraform/package_manager.rb
@@ -262,7 +263,7 @@ licenses:
262
263
  - MIT
263
264
  metadata:
264
265
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
265
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.367.0
266
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.368.0
266
267
  rdoc_options: []
267
268
  require_paths:
268
269
  - lib