yle_tf 0.4.0 → 1.3.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/bin/tf +2 -1
  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 +31 -33
  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 +4 -2
  17. data/lib/yle_tf/backend.rb +42 -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 +22 -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 +9 -2
  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/{command.rb → backend.rb} +12 -13
  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 +2 -0
  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 -20
  56. data/lib/yle_tf/backend_config.rb +0 -41
  57. data/lib/yle_tf_plugins/backends/file/config.rb +0 -17
  58. data/lib/yle_tf_plugins/backends/s3/command.rb +0 -19
  59. data/vendor/logger_level_patch.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fef8984348903ca788037c6d3562009f2499d513375aca7b8a6e93d14e9cf7cb
4
- data.tar.gz: adf1e1f5a27f03c420cde1d49496956a224a88a5fa434cbd0eb4dcf90fa00810
3
+ metadata.gz: 7811f980206094727502839932ba9a800a4e37694ae26bc71d9c4935de38d3c3
4
+ data.tar.gz: 95e2c481a30b3caffb65c08407f9e222210fe01514f59ff88ca38a00915ea31b
5
5
  SHA512:
6
- metadata.gz: 834703a380e72cd8ccd7c8c3dd4f5013042f6fa39f230da61fa3859a860d02102415a48a1cd7065021439b5b506a13c6a184f39241aef7f4e719b2af9a42164e
7
- data.tar.gz: c432b0981046afd428eb2110dd45becfa810952c2d4e4d7263b117da82d5dd3da8c0a01713c8fff01ff5abe29c269dcfb99927d80a3941e8114dd6f573e9dc60
6
+ metadata.gz: 84010f77dfa1d2786f0c9b4a3783073ad15bbf6146bac16f4a49401b8b378e28f0747f82a6880eb01dda175fccbd1465f3e25391586e10c11d424e304fe8e74b
7
+ data.tar.gz: 95c19822da06edb58e250215fb0c5e2154da99e8881a2b8a8ce4fb9fe7c124772e81d7a2774595fdedb97cec3166e0b34e7c098ac20d8133ad97bfcb1196534e
data/bin/tf CHANGED
@@ -1,9 +1,10 @@
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 }
5
6
 
6
- require 'bundler/setup' if File.exist?(File.expand_path('../../Gemfile', __FILE__))
7
+ require 'bundler/setup' if File.exist?(File.expand_path('../Gemfile', __dir__))
7
8
  require 'yle_tf/cli'
8
9
 
9
10
  cli = YleTf::CLI.new(ARGV)
@@ -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,10 @@
1
- require 'yle_tf/error'
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+
2
5
  require 'yle_tf/logger'
3
6
  require 'yle_tf/plugin'
4
7
  require 'yle_tf/system'
5
- require 'yle_tf/version_requirement'
6
8
 
7
9
  class YleTf
8
10
  module Action
@@ -10,62 +12,58 @@ class YleTf
10
12
  TF_CMD_ARGS = %w[-input=false -no-color].freeze
11
13
 
12
14
  TF_CMD_OPTS = {
13
- env: { 'TF_IN_AUTOMATION' => 'true' }, # Reduces some output
14
- stdout: :debug # Hide the output to the debug level
15
+ env: { 'TF_IN_AUTOMATION' => 'true' }, # Reduces some output
16
+ stdout: :debug # Hide the output to the debug level
15
17
  }.freeze
16
18
 
19
+ attr_reader :config
20
+
17
21
  def initialize(app)
18
22
  @app = app
19
23
  end
20
24
 
21
25
  def call(env)
22
- config = env[:config]
23
- backend = backend_config(config)
26
+ @config = env[:config]
24
27
 
25
28
  Logger.info('Initializing Terraform')
26
29
  Logger.debug("Backend configuration: #{backend}")
27
30
 
28
- if VersionRequirement.pre_0_9?(env[:terraform_version])
29
- init_pre_0_9(backend)
30
- else
31
- init(backend)
32
- end
31
+ init(backend)
33
32
 
34
33
  @app.call(env)
35
34
  end
36
35
 
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
36
+ def init(backend)
37
+ Logger.debug('Configuring the backend')
38
+ backend.configure
39
+
40
+ Logger.debug('Symlinking errored.tfstate')
41
+ symlink_errored_tfstate
42
42
 
43
- Logger.debug('Fetching Terraform modules')
44
- YleTf::System.cmd('terraform', 'get', *TF_CMD_ARGS, TF_CMD_OPTS)
43
+ Logger.debug('Initializing Terraform')
44
+ YleTf::System.cmd('terraform', 'init', *TF_CMD_ARGS, **TF_CMD_OPTS)
45
45
  end
46
46
 
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
47
+ def backend
48
+ @backend ||= find_backend
53
49
  end
54
50
 
55
- def backend_config(config)
51
+ def find_backend
56
52
  backend_type = config.fetch('backend', 'type').downcase
57
- backend_proc = backend_proc(backend_type)
53
+ backend_proc = Plugin.manager.backends[backend_type]
58
54
 
59
55
  klass = backend_proc.call
60
- klass.new.backend_config(config)
56
+ klass.new(config)
61
57
  end
62
58
 
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
59
+ def symlink_errored_tfstate
60
+ local_path = Pathname.pwd.join('errored.tfstate')
61
+ remote_path = config.module_dir.join('errored.tfstate')
62
+
63
+ # Remove the possibly copied old file
64
+ local_path.unlink if local_path.exist?
65
+
66
+ local_path.make_symlink(remote_path)
69
67
  end
70
68
  end
71
69
  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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'yle_tf/logger'
3
5
 
@@ -5,10 +7,10 @@ class YleTf
5
7
  module Action
6
8
  class WriteTerraformrcDefaults
7
9
  # Path of the Terraform CLI configuration file
8
- RC_PATH = '~/.terraformrc'.freeze
10
+ RC_PATH = '~/.terraformrc'
9
11
 
10
12
  # Path of the plugin cache directory
11
- DEFAULT_PLUGIN_CACHE_PATH = '~/.terraform.d/plugin-cache'.freeze
13
+ DEFAULT_PLUGIN_CACHE_PATH = '$HOME/.terraform.d/plugin-cache'
12
14
 
13
15
  def initialize(app)
14
16
  @app = app
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ class YleTf
6
+ class Backend
7
+ BACKEND_CONFIG_FILE = '_backend.tf.json'
8
+
9
+ attr_reader :config
10
+
11
+ def initialize(config)
12
+ @config = config
13
+ end
14
+
15
+ def type
16
+ @type ||= config.fetch('backend', 'type')
17
+ end
18
+
19
+ def backend_specific_config
20
+ config.fetch('backend', type)
21
+ end
22
+
23
+ # Generate backend configuration file for Terraform
24
+ def configure
25
+ data = {
26
+ terraform: [{
27
+ backend: [to_h]
28
+ }]
29
+ }
30
+ File.write(BACKEND_CONFIG_FILE, JSON.pretty_generate(data))
31
+ end
32
+
33
+ # Returns the backend configuration as a `Hash` for Terraform
34
+ def to_h
35
+ { type => backend_specific_config }
36
+ end
37
+
38
+ def to_s
39
+ to_h.to_s
40
+ end
41
+ end
42
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf'
2
4
 
3
5
  class YleTf
@@ -48,7 +50,7 @@ class YleTf
48
50
  if TF_OPTIONS.include?(arg)
49
51
  @tf_options[key(arg)] = true
50
52
  else
51
- STDERR.puts "Unknown option '#{arg}'"
53
+ warn "Unknown option '#{arg}'"
52
54
  @tf_command = 'help'
53
55
  @tf_env = 'error'
54
56
  break
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'yaml'
3
5
 
@@ -10,16 +12,23 @@ class YleTf
10
12
  class Config
11
13
  NotFoundError = Class.new(Error)
12
14
 
13
- attr_reader :config, :tf_env, :module_dir
15
+ # Loads the configuration based on the environment
16
+ def self.load(tf_env)
17
+ opts = {
18
+ tf_env: tf_env,
19
+ module_dir: Pathname.pwd
20
+ }
14
21
 
15
- def initialize(tf_env)
16
- Logger.debug("Initializing configuration for the #{tf_env.inspect} environment")
22
+ config = Loader.new(opts).load
23
+ new(config, **opts)
24
+ end
17
25
 
18
- @tf_env = tf_env
19
- @module_dir = Pathname.pwd
20
- @config = Loader.new(tf_env: tf_env, module_dir: module_dir).load
26
+ attr_reader :config, :tf_env, :module_dir
21
27
 
22
- Logger.debug(inspect)
28
+ def initialize(config, **opts)
29
+ @config = config
30
+ @tf_env = opts[:tf_env]
31
+ @module_dir = opts[:module_dir]
23
32
  end
24
33
 
25
34
  def to_s
@@ -33,8 +42,15 @@ class YleTf
33
42
  block ||= DEFAULT_NOT_FOUND_BLOCK
34
43
 
35
44
  keys.inject(config) do |conf, key|
36
- break block.call(keys) if !conf || !conf.key?(key)
37
- conf[key]
45
+ next conf[key] if conf.is_a?(Hash) && conf.key?(key)
46
+
47
+ if !conf.nil? && !conf.is_a?(Hash)
48
+ Logger.warn(
49
+ "Configuration [#{keys.join(' > ')}] includes non-hash element #{conf.inspect}"
50
+ )
51
+ end
52
+
53
+ break block.call(keys)
38
54
  end
39
55
  end
40
56