yle_tf 1.0.0 → 1.4.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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tf +1 -0
  3. data/lib/yle_tf.rb +4 -0
  4. data/lib/yle_tf/action.rb +4 -0
  5. data/lib/yle_tf/action/builder.rb +2 -0
  6. data/lib/yle_tf/action/command.rb +4 -2
  7. data/lib/yle_tf/action/copy_root_module.rb +2 -0
  8. data/lib/yle_tf/action/generate_vars_file.rb +2 -0
  9. data/lib/yle_tf/action/load_config.rb +9 -1
  10. data/lib/yle_tf/action/terraform_init.rb +63 -32
  11. data/lib/yle_tf/action/tf_hooks.rb +9 -5
  12. data/lib/yle_tf/action/tmpdir.rb +3 -1
  13. data/lib/yle_tf/action/verify_terraform_version.rb +19 -6
  14. data/lib/yle_tf/action/verify_tf_env.rb +2 -0
  15. data/lib/yle_tf/action/verify_yle_tf_version.rb +36 -0
  16. data/lib/yle_tf/action/write_terraformrc_defaults.rb +34 -17
  17. data/lib/yle_tf/backend.rb +47 -0
  18. data/lib/yle_tf/cli.rb +3 -1
  19. data/lib/yle_tf/config.rb +25 -9
  20. data/lib/yle_tf/config/defaults.rb +25 -11
  21. data/lib/yle_tf/config/erb.rb +2 -0
  22. data/lib/yle_tf/config/file.rb +2 -0
  23. data/lib/yle_tf/config/loader.rb +89 -59
  24. data/lib/yle_tf/config/migration.rb +117 -0
  25. data/lib/yle_tf/error.rb +2 -0
  26. data/lib/yle_tf/helpers/hash.rb +22 -0
  27. data/lib/yle_tf/logger.rb +2 -10
  28. data/lib/yle_tf/logger/colorize.rb +2 -0
  29. data/lib/yle_tf/plugin.rb +6 -2
  30. data/lib/yle_tf/plugin/action_hook.rb +2 -0
  31. data/lib/yle_tf/plugin/loader.rb +8 -1
  32. data/lib/yle_tf/plugin/manager.rb +3 -0
  33. data/lib/yle_tf/system.rb +5 -2
  34. data/lib/yle_tf/system/io_handlers.rb +5 -1
  35. data/lib/yle_tf/system/output_logger.rb +2 -0
  36. data/lib/yle_tf/system/tf_hook_output_logger.rb +2 -0
  37. data/lib/yle_tf/tf_hook.rb +11 -9
  38. data/lib/yle_tf/tf_hook/runner.rb +2 -0
  39. data/lib/yle_tf/vars_file.rb +16 -3
  40. data/lib/yle_tf/version.rb +3 -1
  41. data/lib/yle_tf/version_requirement.rb +2 -5
  42. data/lib/yle_tf_plugins/backends/{s3 → __default}/plugin.rb +6 -4
  43. data/lib/yle_tf_plugins/backends/file/backend.rb +89 -0
  44. data/lib/yle_tf_plugins/backends/file/plugin.rb +4 -2
  45. data/lib/yle_tf_plugins/commands/__default/command.rb +2 -0
  46. data/lib/yle_tf_plugins/commands/__default/plugin.rb +2 -0
  47. data/lib/yle_tf_plugins/commands/_config/command.rb +2 -0
  48. data/lib/yle_tf_plugins/commands/_config/plugin.rb +2 -0
  49. data/lib/yle_tf_plugins/commands/_shell/command.rb +2 -0
  50. data/lib/yle_tf_plugins/commands/_shell/plugin.rb +2 -0
  51. data/lib/yle_tf_plugins/commands/help/command.rb +9 -20
  52. data/lib/yle_tf_plugins/commands/help/plugin.rb +2 -0
  53. data/lib/yle_tf_plugins/commands/version/command.rb +2 -0
  54. data/lib/yle_tf_plugins/commands/version/plugin.rb +2 -0
  55. metadata +61 -22
  56. data/lib/yle_tf/backend_config.rb +0 -41
  57. data/lib/yle_tf_plugins/backends/file/command.rb +0 -31
  58. data/lib/yle_tf_plugins/backends/file/config.rb +0 -17
  59. data/lib/yle_tf_plugins/backends/s3/command.rb +0 -19
  60. data/lib/yle_tf_plugins/backends/swift/command.rb +0 -18
  61. data/lib/yle_tf_plugins/backends/swift/plugin.rb +0 -16
  62. data/vendor/logger_level_patch.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f901e67d48b00b9c2a6692677eee372f69db444952618773bdc6edac24d5fd0
4
- data.tar.gz: 8f98e4fa5c53bdc58cc758aa46be51ff7867747b5fe187cb54b1a2b83e025f80
3
+ metadata.gz: e8df78c6d65e3180770988e0fdc1655bfa0af42d57d8f1b5dd19f26de1281a39
4
+ data.tar.gz: 10f59a1cdd3757d19dbeebfaaeb1328e41c367be464d87486b9bb8b93d6ef845
5
5
  SHA512:
6
- metadata.gz: 4aef3de1f089f711fffe0e538186516b3cd3a81c0b0f672e3302a66d2fb5c625dd22076a5f9ac7e6b228ad34ea90780f28f160ddc73709c3967717646bde56e6
7
- data.tar.gz: 79508c7c0031a6515820efa3c415a131ff3e6cf34e840cd0f479436bf41a86cde065671e292a3e88a26132a47297d5a78ed6f5ccbdde5cd6a89ec92508b66f0b
6
+ metadata.gz: dc31eb920ce29b2aeb8616739b89e370a0c36a8ff395e3a41b3a1176d4ac592a2da913b6ff11eb0a0c015c33f5819cf5e888b9056b324e7ef81c156f75fa4fc6
7
+ data.tar.gz: 2fbcf63e27d8a817b654087d676a9252db1acde67629ad66a07e4d12ac20e44396c707718c42c3ee594593bcad74fb5d1be108026b5a17c615dc4663eca42a97
data/bin/tf CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  # Catch Ctrl+C to avoid stack traces
4
5
  Signal.trap('INT') { abort }
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
  require 'yle_tf/version'
3
5
 
4
6
  class YleTf
7
+ TERRAFORM_VERSION_REQUIREMENT = '>= 0.9'
8
+
5
9
  autoload :Action, 'yle_tf/action'
6
10
  autoload :Error, 'yle_tf/error'
7
11
  autoload :Plugin, 'yle_tf/plugin'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  module Action
3
5
  autoload :Builder, 'yle_tf/action/builder'
@@ -10,11 +12,13 @@ class YleTf
10
12
  autoload :TmpDir, 'yle_tf/action/tmpdir'
11
13
  autoload :VerifyTerraformVersion, 'yle_tf/action/verify_terraform_version'
12
14
  autoload :VerifyTfEnv, 'yle_tf/action/verify_tf_env'
15
+ autoload :VerifyYleTfVersion, 'yle_tf/action/verify_yle_tf_version'
13
16
  autoload :WriteTerraformrcDefaults, 'yle_tf/action/write_terraformrc_defaults'
14
17
 
15
18
  def self.default_action_stack(command_class = nil)
16
19
  Builder.new do
17
20
  use LoadConfig
21
+ use VerifyYleTfVersion
18
22
  use VerifyTfEnv
19
23
  use TmpDir
20
24
  use WriteTerraformrcDefaults
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../../vendor/middleware/builder'
2
4
 
3
5
  class YleTf
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
 
3
5
  class YleTf
@@ -12,9 +14,9 @@ class YleTf
12
14
 
13
15
  def call(env)
14
16
  if env[:tf_options][:only_hooks]
15
- Logger.debug "Skipping command #{command.class} due to `--only-hooks`"
17
+ Logger.debug "Skipping command #{command} due to `--only-hooks`"
16
18
  else
17
- Logger.debug "Executing command #{command.class} with env: #{env.inspect}"
19
+ Logger.debug "Executing command #{command} with env: #{env.inspect}"
18
20
  command.new.execute(env)
19
21
  end
20
22
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
 
3
5
  require 'yle_tf/logger'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
  require 'yle_tf/vars_file'
3
5
 
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/config'
4
+ require 'yle_tf/logger'
2
5
 
3
6
  class YleTf
4
7
  module Action
@@ -8,10 +11,15 @@ class YleTf
8
11
  end
9
12
 
10
13
  def call(env)
11
- env[:config] ||= Config.new(env[:tf_env])
14
+ env[:config] ||= load_config(env[:tf_env])
12
15
 
13
16
  @app.call(env)
14
17
  end
18
+
19
+ def load_config(tf_env)
20
+ Logger.debug("Initializing configuration for the #{tf_env.inspect} environment")
21
+ Config.load(tf_env).tap { |config| Logger.debug(config.inspect) }
22
+ end
15
23
  end
16
24
  end
17
25
  end
@@ -1,8 +1,12 @@
1
- require 'yle_tf/error'
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+ require 'shellwords'
6
+
2
7
  require 'yle_tf/logger'
3
8
  require 'yle_tf/plugin'
4
9
  require 'yle_tf/system'
5
- require 'yle_tf/version_requirement'
6
10
 
7
11
  class YleTf
8
12
  module Action
@@ -10,62 +14,89 @@ class YleTf
10
14
  TF_CMD_ARGS = %w[-input=false -no-color].freeze
11
15
 
12
16
  TF_CMD_OPTS = {
13
- env: { 'TF_IN_AUTOMATION' => 'true' }, # Reduces some output
14
- stdout: :debug # Hide the output to the debug level
17
+ env: { 'TF_IN_AUTOMATION' => 'true' }, # Reduces some output
18
+ stdout: :debug # Hide the output to the debug level
15
19
  }.freeze
16
20
 
21
+ attr_reader :config
22
+
17
23
  def initialize(app)
18
24
  @app = app
19
25
  end
20
26
 
21
27
  def call(env)
22
- config = env[:config]
23
- backend = backend_config(config)
28
+ @config = env[:config]
24
29
 
25
30
  Logger.info('Initializing Terraform')
26
31
  Logger.debug("Backend configuration: #{backend}")
27
32
 
28
- if VersionRequirement.pre_0_9?(env[:terraform_version])
29
- init_pre_0_9(backend)
33
+ init_dir
34
+
35
+ if env[:tf_command] == 'init'
36
+ # Skip initializing Terraform here, as it will be done by the
37
+ # actuall command later in the middleware stack.
38
+ @app.call(env)
39
+ store_terraform_lock
30
40
  else
31
- init(backend)
41
+ init_terraform
42
+ store_terraform_lock
43
+ @app.call(env)
32
44
  end
33
45
 
34
- @app.call(env)
46
+ tear_down
35
47
  end
36
48
 
37
- def init_pre_0_9(backend)
38
- cli_args = backend.cli_args
39
- if cli_args
40
- YleTf::System.cmd('terraform', 'remote', 'config', *TF_CMD_ARGS, *cli_args, TF_CMD_OPTS)
41
- end
49
+ def init_dir
50
+ Logger.debug('Configuring the backend')
51
+ backend.configure
42
52
 
43
- Logger.debug('Fetching Terraform modules')
44
- YleTf::System.cmd('terraform', 'get', *TF_CMD_ARGS, TF_CMD_OPTS)
53
+ Logger.debug('Symlinking errored.tfstate')
54
+ symlink_to_module_dir('errored.tfstate')
45
55
  end
46
56
 
47
- def init(backend)
48
- Logger.debug('Generating the backend configuration')
49
- backend.generate_config do
50
- Logger.debug('Initializing Terraform')
51
- YleTf::System.cmd('terraform', 'init', *TF_CMD_ARGS, TF_CMD_OPTS)
52
- end
57
+ def tear_down
58
+ Logger.debug('Tearing down backend')
59
+ backend.tear_down
60
+ end
61
+
62
+ def init_terraform
63
+ Logger.debug('Initializing Terraform')
64
+ YleTf::System.cmd('terraform', 'init', *tf_init_args, **TF_CMD_OPTS)
65
+ end
66
+
67
+ def store_terraform_lock
68
+ Logger.debug('Storing .terraform.lock.hcl')
69
+ copy_to_module_dir('.terraform.lock.hcl')
53
70
  end
54
71
 
55
- def backend_config(config)
72
+ def backend
73
+ @backend ||= find_backend
74
+ end
75
+
76
+ def tf_init_args
77
+ TF_CMD_ARGS + Shellwords.split(ENV.fetch('TF_INIT_ARGS', ''))
78
+ end
79
+
80
+ def find_backend
56
81
  backend_type = config.fetch('backend', 'type').downcase
57
- backend_proc = backend_proc(backend_type)
82
+ backend_proc = Plugin.manager.backends[backend_type]
58
83
 
59
84
  klass = backend_proc.call
60
- klass.new.backend_config(config)
85
+ klass.new(config)
61
86
  end
62
87
 
63
- def backend_proc(backend_type)
64
- backends = Plugin.manager.backends
65
- backends.fetch(backend_type.to_sym) do
66
- raise Error, "Unknown backend type '#{backend_type}'. " \
67
- "Supported backends: #{backends.keys.join(', ')}"
68
- end
88
+ def symlink_to_module_dir(file)
89
+ local_path = Pathname.pwd.join(file)
90
+ remote_path = config.module_dir.join(file)
91
+
92
+ # Remove the possibly copied old file
93
+ local_path.unlink if local_path.exist?
94
+
95
+ local_path.make_symlink(remote_path)
96
+ end
97
+
98
+ def copy_to_module_dir(file)
99
+ FileUtils.cp(file, config.module_dir.to_s) if File.exist?(file)
69
100
  end
70
101
  end
71
102
  end
@@ -1,5 +1,4 @@
1
- require 'yle_tf/logger'
2
- require 'yle_tf/tf_hook/runner'
1
+ # frozen_string_literal: true
3
2
 
4
3
  require 'yle_tf/logger'
5
4
  require 'yle_tf/tf_hook/runner'
@@ -21,7 +20,7 @@ class YleTf
21
20
 
22
21
  def hook_runner
23
22
  if run_hooks?
24
- TfHook::Runner.new(@env[:config], hook_env)
23
+ TfHook::Runner.new(config, hook_env)
25
24
  else
26
25
  NoRunner
27
26
  end
@@ -29,11 +28,16 @@ class YleTf
29
28
 
30
29
  def hook_env
31
30
  {
32
- 'TF_COMMAND' => @env[:tf_command],
33
- 'TF_ENV' => @env[:tf_env],
31
+ 'TF_COMMAND' => @env[:tf_command],
32
+ 'TF_ENV' => @env[:tf_env],
33
+ 'TF_MODULE_DIR' => config.module_dir.to_s,
34
34
  }
35
35
  end
36
36
 
37
+ def config
38
+ @env[:config]
39
+ end
40
+
37
41
  def run_hooks?
38
42
  !@env[:tf_options][:no_hooks]
39
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'tmpdir'
3
5
 
@@ -20,7 +22,7 @@ class YleTf
20
22
  @app.call(env)
21
23
  end
22
24
  ensure
23
- FileUtils.rm_r(tmpdir) if tmpdir && Dir.exist?(tmpdir)
25
+ FileUtils.rm_rf(tmpdir, secure: true) if tmpdir && Dir.exist?(tmpdir)
24
26
  end
25
27
 
26
28
  def tmpdir_prefix(config)
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yle_tf'
1
4
  require 'yle_tf/error'
2
5
  require 'yle_tf/logger'
3
6
  require 'yle_tf/system'
@@ -17,22 +20,32 @@ class YleTf
17
20
  raise(Error, 'Terraform not found') if !version
18
21
 
19
22
  Logger.debug("Terraform version: #{version}")
20
- verify_version(env)
23
+
24
+ verify_version(version, requirement_by_yletf, required_by: 'YleTf')
25
+ verify_version(version, requirement_by_config(env), required_by: 'config')
21
26
 
22
27
  @app.call(env)
23
28
  end
24
29
 
25
30
  def terraform_version
26
31
  v = YleTf::System.read_cmd('terraform', 'version', error_handler: proc {})
27
- Regexp.last_match(1) if v =~ /^Terraform v([^\s]+)/
32
+ m = /^Terraform v(?<version>[^\s]+)/.match(v)
33
+ m && m[:version]
28
34
  end
29
35
 
30
- def verify_version(env)
31
- version = env[:terraform_version]
36
+ def requirement_by_config(env)
32
37
  requirement = env[:config].fetch('terraform', 'version_requirement') { nil }
38
+ VersionRequirement.new(requirement)
39
+ end
40
+
41
+ def requirement_by_yletf
42
+ VersionRequirement.new(YleTf::TERRAFORM_VERSION_REQUIREMENT)
43
+ end
33
44
 
34
- if !VersionRequirement.new(requirement).satisfied_by?(version)
35
- raise Error, "Terraform version '#{requirement}' required, '#{version}' found"
45
+ def verify_version(version, requirement, **opts)
46
+ if !requirement.satisfied_by?(version)
47
+ raise Error, "Terraform version '#{requirement}' required by #{opts[:required_by]}, " \
48
+ "'#{version}' found"
36
49
  end
37
50
  end
38
51
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/error'
2
4
  require 'yle_tf/vars_file'
3
5
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yle_tf/error'
4
+ require 'yle_tf/logger'
5
+ require 'yle_tf/version'
6
+ require 'yle_tf/version_requirement'
7
+
8
+ class YleTf
9
+ module Action
10
+ class VerifyYleTfVersion
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ Logger.debug('Verifying YleTf version')
17
+
18
+ requirement = requirement(env[:config])
19
+ verify_version(requirement)
20
+
21
+ @app.call(env)
22
+ end
23
+
24
+ def requirement(config)
25
+ requirement = config.fetch('yle_tf', 'version_requirement') { nil }
26
+ VersionRequirement.new(requirement)
27
+ end
28
+
29
+ def verify_version(requirement)
30
+ return if requirement.satisfied_by?(YleTf::VERSION)
31
+
32
+ raise Error, "YleTf version '#{YleTf::VERSION}', '#{requirement}' required by config"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,52 +1,69 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
4
+ require 'pathname'
5
+
2
6
  require 'yle_tf/logger'
3
7
 
4
8
  class YleTf
5
9
  module Action
6
10
  class WriteTerraformrcDefaults
7
11
  # Path of the Terraform CLI configuration file
8
- RC_PATH = '~/.terraformrc'.freeze
12
+ RC_PATH = '~/.terraformrc'
9
13
 
10
14
  # Path of the plugin cache directory
11
- DEFAULT_PLUGIN_CACHE_PATH = '~/.terraform.d/plugin-cache'.freeze
15
+ DEFAULT_PLUGIN_CACHE_PATH = '~/.terraform.d/plugin-cache'
12
16
 
13
17
  def initialize(app)
14
18
  @app = app
15
19
  end
16
20
 
17
21
  def call(env)
18
- Logger.debug("Writing default configuration to '#{RC_PATH}'")
19
- open_rc_file do |rc_file|
20
- keys = existing_keys(rc_file)
21
-
22
- configure_checkpoint(rc_file) if !keys.include?('disable_checkpoint')
23
- configure_plugin_cache_dir(rc_file) if !keys.include?('plugin_cache_dir')
22
+ if rc_file.exist?
23
+ Logger.debug("Terraform configuration file '#{RC_PATH}' already exists")
24
+ if !existing_keys.include?('plugin_cache_dir')
25
+ Logger.warn("'plugin_cache_dir' not configured in '#{RC_PATH}'")
26
+ end
27
+ else
28
+ Logger.debug("Writing default configuration to '#{RC_PATH}'")
29
+ write_default_config
24
30
  end
25
31
 
26
32
  @app.call(env)
27
33
  end
28
34
 
29
- def open_rc_file(&block)
30
- File.open(File.expand_path(RC_PATH), 'a+', &block)
35
+ def rc_file
36
+ @rc_file ||= Pathname.new(RC_PATH).expand_path
31
37
  end
32
38
 
33
- def existing_keys(rc_file)
34
- [].tap do |keys|
35
- rc_file.each_line do |line|
39
+ def existing_keys
40
+ @existing_keys ||= [].tap do |keys|
41
+ rc_file.readlines.each do |line|
42
+ # The matcher is a bit naive, but enough for out use
36
43
  keys << Regexp.last_match(1) if line =~ /^(.+?)[ \t]*=/
37
44
  end
38
45
  end
39
46
  end
40
47
 
41
- def configure_checkpoint(rc_file)
48
+ def write_default_config
49
+ rc_file.open('w') do |rc_file|
50
+ configure_checkpoint(rc_file)
51
+ configure_plugin_cache_dir(rc_file)
52
+ end
53
+ end
54
+
55
+ def configure_checkpoint(file)
42
56
  Logger.info("Disabling Terraform upgrade and security bulletin checks by '#{RC_PATH}'")
43
57
 
44
- rc_file.puts('disable_checkpoint = true')
58
+ file.puts('disable_checkpoint = true')
45
59
  end
46
60
 
47
- def configure_plugin_cache_dir(rc_file)
61
+ def configure_plugin_cache_dir(file)
48
62
  Logger.info("Configuring global Terraform plugin cache by '#{RC_PATH}'")
49
- rc_file.puts("plugin_cache_dir = \"#{DEFAULT_PLUGIN_CACHE_PATH}\"")
63
+ # Replace `~` with `$HOME` as it is not expanded correctly in all architectures.
64
+ # Can't use `$HOME` in the constant though, as it won't be expanded by
65
+ # `expand_path` below. Can't win this game.
66
+ file.puts("plugin_cache_dir = \"#{DEFAULT_PLUGIN_CACHE_PATH.sub(/^~/, '$HOME')}\"")
50
67
 
51
68
  dir = File.expand_path(DEFAULT_PLUGIN_CACHE_PATH)
52
69
  return if File.directory?(dir)