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
@@ -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