yle_tf 1.0.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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)