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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  # Base class for yle_tf errors
3
5
  Error = Class.new(StandardError)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../vendor/hash_deep_merge'
4
+
5
+ class YleTf
6
+ module Helpers
7
+ module Hash
8
+ module_function
9
+
10
+ # Returns deep merged new Hash
11
+ def deep_merge(target, source)
12
+ target.deep_merge(source)
13
+ end
14
+
15
+ # Returns deep copy of a Hash.
16
+ # `dup` and `clone` only return shallow copies.
17
+ def deep_copy(hash)
18
+ Marshal.load(Marshal.dump(hash))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
  require 'logger'
3
5
  require 'rubygems'
@@ -21,7 +23,6 @@ class YleTf
21
23
 
22
24
  def self.logger
23
25
  @logger ||= ::Logger.new(DEVICE).tap do |logger|
24
- patch_for_old_ruby(logger)
25
26
  logger.level = log_level
26
27
  logger.formatter = log_formatter
27
28
  end
@@ -63,14 +64,5 @@ class YleTf
63
64
  :brown
64
65
  end
65
66
  end
66
-
67
- # Patches the `::Logger` in older Ruby versions to
68
- # accept log level as a `String`
69
- def self.patch_for_old_ruby(logger)
70
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
71
- require_relative '../../vendor/logger_level_patch'
72
- logger.extend(LoggerLevelPatch)
73
- end
74
- end
75
67
  end
76
68
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  module Logger
3
5
  module Colorize
@@ -1,9 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  class Plugin
3
5
  autoload :ActionHook, 'yle_tf/plugin/action_hook'
4
6
  autoload :Loader, 'yle_tf/plugin/loader'
5
7
  autoload :Manager, 'yle_tf/plugin/manager'
6
8
 
9
+ DEFAULT_BACKEND = Object.new.freeze
7
10
  DEFAULT_COMMAND = Object.new.freeze
8
11
 
9
12
  def self.manager
@@ -30,7 +33,7 @@ class YleTf
30
33
  name = name.to_s if name.is_a?(Symbol)
31
34
  commands[name] = {
32
35
  synopsis: synopsis,
33
- proc: block
36
+ proc: block
34
37
  }
35
38
  end
36
39
 
@@ -49,7 +52,8 @@ class YleTf
49
52
  end
50
53
 
51
54
  def self.backend(type, &block)
52
- backends[type.to_sym] = block
55
+ type = type.to_s if type.is_a?(Symbol)
56
+ backends[type] = block
53
57
  end
54
58
  end
55
59
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  class Plugin
3
5
  class ActionHook
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
 
3
5
  class YleTf
@@ -13,7 +15,7 @@ class YleTf
13
15
 
14
16
  def self.load_core_plugins
15
17
  core_plugins.each do |plugin_file|
16
- Logger.debug("Loading core plugin: #{File.basename(plugin_file, '.rb')}")
18
+ Logger.debug { "Loading core plugin: #{core_plugin_name(plugin_file)}" }
17
19
  load(plugin_file)
18
20
  end
19
21
  end
@@ -36,6 +38,11 @@ class YleTf
36
38
  Dir.glob(File.expand_path('../../yle_tf_plugins/**/plugin.rb', __dir__))
37
39
  end
38
40
 
41
+ def self.core_plugin_name(path)
42
+ m = %r{.*/yle_tf_plugins/(?<name>.+?)/plugin\.rb$}.match(path)
43
+ m ? m[:name] : path
44
+ end
45
+
39
46
  def self.bundler_plugins
40
47
  plugins = Bundler.definition.current_dependencies.select do |dep|
41
48
  dep.groups.include?(BUNDLER_PLUGIN_GROUP)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
 
3
5
  class YleTf
@@ -42,6 +44,7 @@ class YleTf
42
44
  registered.each do |plugin|
43
45
  backends.merge!(plugin.backends)
44
46
  end
47
+ backends.default = backends.delete(DEFAULT_BACKEND)
45
48
  end
46
49
  end
47
50
  end
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'English'
2
4
  require 'open3'
3
5
  require 'shellwords'
6
+ require 'stringio'
4
7
  require 'thwait'
5
8
  require 'yle_tf/error'
6
9
  require 'yle_tf/logger'
@@ -14,7 +17,7 @@ class YleTf
14
17
  DEFAULT_ERROR_HANDLER = ->(_exit_code, error) { raise error }.freeze
15
18
 
16
19
  DEFAULT_IO_HANDLERS = {
17
- stdin: :dev_null,
20
+ stdin: :dev_null,
18
21
  stdout: :info,
19
22
  stderr: :error
20
23
  }.freeze
@@ -44,7 +47,7 @@ class YleTf
44
47
 
45
48
  def self.read_cmd(*args, **opts)
46
49
  buffer = StringIO.new
47
- cmd(*args, opts.merge(stdout: buffer))
50
+ cmd(*args, **opts.merge(stdout: buffer))
48
51
  buffer.string
49
52
  end
50
53
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/error'
2
4
  require 'yle_tf/logger'
3
5
  require 'yle_tf/system/output_logger'
@@ -22,6 +24,7 @@ class YleTf
22
24
  if !handler.respond_to?(:call)
23
25
  raise YleTf::Error, "Unknown input handler #{handler.inspect}"
24
26
  end
27
+
25
28
  handler
26
29
  end
27
30
  end
@@ -42,6 +45,7 @@ class YleTf
42
45
  if !handler.respond_to?(:call)
43
46
  raise YleTf::Error, "Unknown output handler #{handler.inspect}"
44
47
  end
48
+
45
49
  handler
46
50
  end
47
51
  end
@@ -104,7 +108,7 @@ class YleTf
104
108
  while (data = source.readpartial(BLOCK_SIZE))
105
109
  target.write(data)
106
110
  end
107
- rescue EOFError # rubocop:disable Lint/HandleExceptions
111
+ rescue EOFError # rubocop:disable Lint/SuppressedException
108
112
  # All read
109
113
  rescue IOError => e
110
114
  YleTf::Logger.debug e.full_message
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
 
3
5
  class YleTf
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
  require 'yle_tf/system/output_logger'
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fileutils'
2
4
  require 'tmpdir'
3
5
 
@@ -14,8 +16,8 @@ class YleTf
14
16
  def self.from_config(config, tf_env)
15
17
  TfHook.new(
16
18
  description: config['description'],
17
- source: config['source'],
18
- vars: merge_vars(config['vars'], tf_env)
19
+ source: config['source'],
20
+ vars: merge_vars(config['vars'], tf_env)
19
21
  )
20
22
  end
21
23
 
@@ -23,7 +25,7 @@ class YleTf
23
25
  def self.from_file(path)
24
26
  TfHook.new(
25
27
  description: File.basename(path),
26
- path: path
28
+ path: path
27
29
  )
28
30
  end
29
31
 
@@ -43,10 +45,10 @@ class YleTf
43
45
  Logger.info("Running hook '#{description}'")
44
46
  YleTf::System.cmd(
45
47
  path,
46
- env: vars.merge(tf_vars),
48
+ env: vars.merge(tf_vars),
47
49
  progname: File.basename(path),
48
- stdout: System::TfHookOutputLogger.new(:info),
49
- stderr: System::TfHookOutputLogger.new(:error)
50
+ stdout: System::TfHookOutputLogger.new(:info),
51
+ stderr: System::TfHookOutputLogger.new(:error)
50
52
  )
51
53
  ensure
52
54
  delete_tmpdir
@@ -57,9 +59,9 @@ class YleTf
57
59
  raise Error, "Invalid or missing `source` for hook '#{description}'" if !m
58
60
 
59
61
  {
60
- uri: m[:uri],
62
+ uri: m[:uri],
61
63
  path: m[:path],
62
- ref: m[:ref] || 'master'
64
+ ref: m[:ref] || 'master'
63
65
  }
64
66
  end
65
67
 
@@ -83,7 +85,7 @@ class YleTf
83
85
  end
84
86
 
85
87
  def delete_tmpdir
86
- FileUtils.rm_r(@tmpdir) if @tmpdir && Dir.exist?(@tmpdir)
88
+ FileUtils.rm_rf(@tmpdir, secure: true) if @tmpdir && Dir.exist?(@tmpdir)
87
89
  @tmpdir = nil
88
90
  end
89
91
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/logger'
2
4
  require 'yle_tf/tf_hook'
3
5
 
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
4
  class VarsFile
3
- ENV_DIR = 'envs'.freeze
5
+ ENV_DIR = 'envs'
4
6
 
5
7
  # Returns the env specific tfvars file path if it exists
6
8
  def self.find_env_vars_file(config)
@@ -10,7 +12,7 @@ class YleTf
10
12
 
11
13
  # Returns all envs that have tfvars files
12
14
  def self.list_all_envs(config)
13
- Dir.glob("#{config.module_dir}/#{ENV_DIR}/*.tfvars").map do |path|
15
+ Dir.glob("#{config.module_dir}/#{ENV_DIR}/*.tfvars").sort.map do |path|
14
16
  File.basename(path, '.tfvars')
15
17
  end
16
18
  end
@@ -36,9 +38,20 @@ class YleTf
36
38
  File.open(path, 'a') do |file|
37
39
  file.puts # ensure we don't append to an existing line
38
40
  vars.each do |key, value|
39
- file.puts %(#{key} = "#{value}")
41
+ file.puts "#{key} = #{eval_value(value)}"
40
42
  end
41
43
  end
42
44
  end
45
+
46
+ def eval_value(value)
47
+ case value
48
+ when Hash
49
+ %({ #{value.map { |k, v| "#{k} = #{eval_value(v)}" }.join(', ')} })
50
+ when Array
51
+ %([ #{value.map { |item| eval_value(item) }.join(', ')} ])
52
+ else
53
+ %("#{value}")
54
+ end
55
+ end
43
56
  end
44
57
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class YleTf
2
- VERSION = '1.0.0'.freeze
4
+ VERSION = '1.4.0'
3
5
  end
@@ -1,13 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rubygems'
2
4
 
3
5
  class YleTf
4
6
  # Helper class for comparing versions
5
7
  class VersionRequirement
6
- # Checks if the specified Terrform version is older than 0.9
7
- def self.pre_0_9?(terraform_version)
8
- new('< 0.9.0-beta').satisfied_by?(terraform_version)
9
- end
10
-
11
8
  attr_reader :requirement
12
9
 
13
10
  def initialize(requirement)
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf'
4
+ require 'yle_tf/backend'
2
5
 
3
6
  module YleTfPlugins
4
7
  module Backends
5
- module S3
8
+ module Default
6
9
  class Plugin < YleTf::Plugin
7
10
  register
8
11
 
9
- backend('s3') do
10
- require_relative 'command'
11
- Command
12
+ backend(DEFAULT_BACKEND) do
13
+ YleTf::Backend
12
14
  end
13
15
  end
14
16
  end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+ require 'shellwords'
6
+
7
+ require 'yle_tf/backend'
8
+ require 'yle_tf/logger'
9
+ require 'yle_tf/system'
10
+
11
+ module YleTfPlugins
12
+ module Backends
13
+ module File
14
+ class Backend < YleTf::Backend
15
+ def configure
16
+ if !encrypt?
17
+ create_tfstate(tfstate_path)
18
+ symlink_tfstate
19
+ elsif tfstate_path.exist?
20
+ decrypt_tfstate
21
+ end
22
+ end
23
+
24
+ def tear_down
25
+ encrypt_tfstate if encrypt? && local_tfstate_path.exist?
26
+ end
27
+
28
+ def create_tfstate(path)
29
+ return if path.exist?
30
+
31
+ YleTf::Logger.debug('Creating state file')
32
+ path.write(tfstate_template, perm: 0o644)
33
+ end
34
+
35
+ def symlink_tfstate
36
+ YleTf::Logger.info("Symlinking state to '#{tfstate_path}'")
37
+ local_tfstate_path.make_symlink(tfstate_path)
38
+ end
39
+
40
+ def encrypt?
41
+ config.fetch('backend', type, 'encrypt')
42
+ end
43
+
44
+ def decrypt_tfstate
45
+ YleTf::Logger.info("Decrypting state from '#{tfstate_path}'")
46
+
47
+ cmd = config.fetch('backend', type, 'decrypt_command')
48
+ cmd.gsub!('{{FROM}}', tfstate_path.to_s)
49
+ cmd.gsub!('{{TO}}', local_tfstate_path.to_s)
50
+
51
+ # Split the command to have nicer logs
52
+ YleTf::System.cmd(*Shellwords.split(cmd))
53
+ end
54
+
55
+ def encrypt_tfstate
56
+ YleTf::Logger.info("Encrypting state to '#{tfstate_path}'")
57
+
58
+ cmd = config.fetch('backend', type, 'encrypt_command')
59
+ cmd.gsub!('{{FROM}}', local_tfstate_path.to_s)
60
+ cmd.gsub!('{{TO}}', tfstate_path.to_s)
61
+
62
+ YleTf::System.cmd(*Shellwords.split(cmd),
63
+ error_handler: method(:on_encrypt_error))
64
+ end
65
+
66
+ def on_encrypt_error(_exit_code, error)
67
+ plain_tfstate_path = "#{tfstate_path}.plaintext"
68
+
69
+ YleTf::Logger.warn("Copying unencrypted state to '#{plain_tfstate_path}'")
70
+ FileUtils.cp(local_tfstate_path.to_s, plain_tfstate_path)
71
+
72
+ raise error
73
+ end
74
+
75
+ def tfstate_path
76
+ @tfstate_path ||= config.module_dir.join(config.fetch('backend', type, 'path'))
77
+ end
78
+
79
+ def local_tfstate_path
80
+ @local_tfstate_path ||= Pathname.pwd.join('terraform.tfstate')
81
+ end
82
+
83
+ def tfstate_template
84
+ '{"version": 1}'
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end