yle_tf 0.4.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,33 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yle_tf/helpers/hash'
4
+
1
5
  class YleTf
2
6
  class Config
3
7
  module Defaults
4
8
  DEFAULT_CONFIG = {
5
- 'hooks' => {
6
- 'pre' => [],
9
+ 'hooks' => {
10
+ 'pre' => [],
7
11
  'post' => []
8
12
  },
9
- 'backend' => {
13
+ 'backend' => {
10
14
  'type' => 'file',
11
- 'bucket' => nil,
12
- 'file' => '<%= @module %>_<%= @env %>.tfstate',
13
- 'region' => nil,
14
- 'encrypt' => false,
15
+ 'file' => {
16
+ 'path' => '<%= @module %>_<%= @env %>.tfstate'
17
+ },
18
+ 's3' => {
19
+ 'key' => '<%= @module %>_<%= @env %>.tfstate'
20
+ }
15
21
  },
16
- 'tfvars' => {
22
+ 'tfvars' => {
17
23
  },
18
24
  'terraform' => {
19
25
  'version_requirement' => nil
26
+ },
27
+ 'yle_tf' => {
28
+ 'version_requirement' => nil
20
29
  }
21
30
  }.freeze
22
31
 
32
+ # Returns deep copy of the default config Hash.
23
33
  def default_config
24
- DEFAULT_CONFIG.dup
34
+ Helpers::Hash.deep_copy(DEFAULT_CONFIG)
25
35
  end
26
36
 
27
37
  def default_config_context
28
38
  {
29
- env: tf_env,
30
- module: module_dir.basename.to_s,
39
+ env: tf_env,
40
+ module: module_dir.basename.to_s,
41
+ module_dir: module_dir.to_s,
31
42
  }
32
43
  end
33
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  class YleTf
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  require 'yle_tf/logger'
@@ -1,11 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yle_tf/config/defaults'
2
4
  require 'yle_tf/config/erb'
3
5
  require 'yle_tf/config/file'
6
+ require 'yle_tf/config/migration'
7
+ require 'yle_tf/helpers/hash'
4
8
  require 'yle_tf/logger'
5
9
  require 'yle_tf/plugin'
6
10
 
7
- require_relative '../../../vendor/hash_deep_merge'
8
-
9
11
  class YleTf
10
12
  class Config
11
13
  class Loader
@@ -19,20 +21,88 @@ class YleTf
19
21
  end
20
22
 
21
23
  def load
22
- Logger.debug('Loading default config')
23
- config = default_config
24
- Logger.debug(config.inspect)
24
+ load_sequence = %i[
25
+ load_default_config
26
+ load_plugin_configurations
27
+ load_config_files
28
+ evaluate_configuration_strings
29
+ ]
30
+ load_sequence.inject({}) { |config, method| send(method, config) }
31
+ end
32
+
33
+ def load_default_config(_config)
34
+ task('Loading default config') { default_config }
35
+ end
36
+
37
+ def load_plugin_configurations(config)
38
+ Logger.debug('Loading configuration from plugins')
39
+
40
+ plugins.inject(config) do |prev_config, plugin|
41
+ migrate_and_merge_configuration(prev_config, plugin.default_config,
42
+ type: 'plugin', name: plugin.to_s)
43
+ end
44
+ end
45
+
46
+ def load_config_files(config)
47
+ Logger.debug('Loading configuration from files')
48
+
49
+ config_files.inject(config) do |prev_config, file|
50
+ migrate_and_merge_configuration(prev_config, file.read,
51
+ type: 'file', name: file.name)
52
+ end
53
+ end
54
+
55
+ def evaluate_configuration_strings(config)
56
+ task('Evaluating the configuration strings') { eval_config(config) }
57
+ end
58
+
59
+ def eval_config(config)
60
+ case config
61
+ when Hash
62
+ config.each_with_object({}) { |(key, value), h| h[key] = eval_config(value) }
63
+ when Array
64
+ config.map { |item| eval_config(item) }
65
+ when String
66
+ Config::ERB.evaluate(config, config_context)
67
+ else
68
+ config
69
+ end
70
+ end
25
71
 
26
- Logger.debug('Merging default configurations from plugins')
27
- merge_plugin_configurations(config)
28
- Logger.debug(config.inspect)
72
+ def plugins
73
+ Plugin.manager.registered
74
+ end
75
+
76
+ def config_files
77
+ module_dir.descend.lazy
78
+ .map { |dir| dir.join('tf.yaml') }
79
+ .select(&:exist?)
80
+ .map { |file| Config::File.new(file) }
81
+ end
29
82
 
30
- Logger.debug('Merging configurations from files')
31
- merge_config_files(config)
32
- Logger.debug(config.inspect)
83
+ def migrate_and_merge_configuration(prev_config, config, **opts)
84
+ task("- #{opts[:name]}") { config }
85
+ return prev_config if config.empty?
33
86
 
34
- Logger.debug('Evaluating the configuration strings')
35
- eval_config(config)
87
+ source = "#{opts[:type]}: '#{opts[:name]}'"
88
+ config = migrate_old_config(config, source: source)
89
+ deep_merge(prev_config, config, source: source)
90
+ end
91
+
92
+ def migrate_old_config(config, **opts)
93
+ task(' -> Migrating') do
94
+ Config::Migration.migrate_old_config(config, **opts)
95
+ end
96
+ end
97
+
98
+ def deep_merge(prev_config, config, **opts)
99
+ task(' -> Merging') do
100
+ Helpers::Hash.deep_merge(prev_config, config)
101
+ end
102
+ rescue StandardError => e
103
+ Logger.fatal("Failed to merge configuration from #{opts[:source]}:\n" \
104
+ "#{config.inspect}\ninto:\n#{prev_config.inspect}")
105
+ raise e
36
106
  end
37
107
 
38
108
  def config_context
@@ -54,53 +124,13 @@ class YleTf
54
124
  end
55
125
  end
56
126
 
57
- def merge_plugin_configurations(config)
58
- Plugin.manager.default_configs.each do |plugin_config|
59
- deep_merge(
60
- config, plugin_config,
61
- error_msg:
62
- "Failed to merge a plugin's default configuration:\n" \
63
- "#{plugin_config.inspect}\ninto:\n#{config.inspect}"
64
- )
65
- end
66
- end
67
-
68
- def merge_config_files(config)
69
- config_files do |file|
70
- Logger.debug(" - #{file}")
71
- deep_merge(
72
- config, file.read,
73
- error_msg:
74
- "Failed to merge configuration from '#{file}' into:\n" \
75
- "#{config.inspect}"
76
- )
77
- end
78
- end
127
+ # Helper to print debug information about the task and configuration
128
+ # after it
129
+ def task(message = nil)
130
+ Logger.debug(message) if message
79
131
 
80
- def deep_merge(config, new_config, opts = {})
81
- config.deep_merge!(new_config)
82
- rescue StandardError => e
83
- Logger.fatal(opts[:error_msg]) if opts[:error_msg]
84
- raise e
85
- end
86
-
87
- def config_files
88
- module_dir.descend do |dir|
89
- file = dir.join('tf.yaml')
90
- yield(Config::File.new(file)) if file.exist?
91
- end
92
- end
93
-
94
- def eval_config(config)
95
- case config
96
- when Hash
97
- config.each_with_object({}) { |(key, value), h| h[key] = eval_config(value) }
98
- when Array
99
- config.map { |item| eval_config(item) }
100
- when String
101
- Config::ERB.evaluate(config, config_context)
102
- else
103
- config
132
+ yield.tap do |config|
133
+ Logger.debug(" #{config.inspect}")
104
134
  end
105
135
  end
106
136
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'yle_tf/helpers/hash'
5
+ require 'yle_tf/logger'
6
+
7
+ class YleTf
8
+ class Config
9
+ class Migration
10
+ BACKEND_MIGRATIONS = {
11
+ 'file' => {
12
+ 'file' => 'path'
13
+ },
14
+ 's3' => {
15
+ 'region' => 'region',
16
+ 'bucket' => 'bucket',
17
+ 'file' => 'key',
18
+ 'encrypt' => 'encrypt'
19
+ },
20
+ 'swift' => {
21
+ 'region' => 'region_name',
22
+ 'container' => 'container',
23
+ 'archive_container' => 'archive_container'
24
+ }
25
+ }.freeze
26
+
27
+ include Helpers::Hash
28
+
29
+ def self.migrate_old_config(config, **opts)
30
+ new(config, **opts).migrated_config
31
+ end
32
+
33
+ attr_reader :config, :config_source
34
+
35
+ def initialize(config, **opts)
36
+ @config = config
37
+ @config_source = opts.fetch(:source)
38
+ end
39
+
40
+ def old_backend_config
41
+ config['backend']
42
+ end
43
+
44
+ def migrated_config
45
+ migrate_old_backend_config(&deprecation_warning)
46
+ end
47
+
48
+ # Returns a `Proc` to print deprecation warnings unless denied by an env var
49
+ def deprecation_warning
50
+ return nil if ENV['TF_OLD_CONFIG_WARNINGS'] == 'false'
51
+
52
+ lambda do |new_config|
53
+ Logger.warn("Old configuration found in #{config_source}")
54
+ Logger.warn("Please migrate to relevant parts of:\n" \
55
+ "#{sanitize_config(new_config)}")
56
+ Logger.warn(
57
+ 'See https://github.com/Yleisradio/yle_tf/wiki/Migrating-Configuration for more details'
58
+ )
59
+ end
60
+ end
61
+
62
+ # TODO: Remove support in v2.0
63
+ def migrate_old_backend_config
64
+ changed = false
65
+
66
+ new_config = BACKEND_MIGRATIONS.inject(config) do |prev_config, (type, keys)|
67
+ migrate_old_backend_config_keys(prev_config, type, keys) { changed = true }
68
+ end
69
+
70
+ yield(new_config) if changed && block_given?
71
+
72
+ new_config
73
+ end
74
+
75
+ def migrate_old_backend_config_keys(config, type, keys)
76
+ migrated_keys = find_old_backend_config_keys(keys)
77
+ return config if migrated_keys.empty?
78
+
79
+ defaults = {
80
+ 'backend' => {
81
+ type => {}
82
+ }
83
+ }
84
+ copy_with_defaults(config, defaults).tap do |new_config|
85
+ migrated_keys.each do |old_key, new_key|
86
+ new_config['backend'][type][new_key] = old_backend_config[old_key]
87
+ end
88
+
89
+ yield new_config
90
+ end
91
+ end
92
+
93
+ def find_old_backend_config_keys(keys)
94
+ return {} if !old_backend_config.is_a?(Hash)
95
+
96
+ keys.select do |old_key, _new_key|
97
+ old_backend_config.key?(old_key) &&
98
+ # Special case for 'file' as it is now used for option Hash for the
99
+ # 'file' backend
100
+ !(old_key == 'file' && old_backend_config['file'].is_a?(Hash))
101
+ end
102
+ end
103
+
104
+ def copy_with_defaults(config, defaults)
105
+ deep_merge(deep_copy(config), defaults)
106
+ end
107
+
108
+ def sanitize_config(config)
109
+ backend_config = config['backend'].select do |key, value|
110
+ key == 'type' || value.is_a?(Hash)
111
+ end
112
+
113
+ YAML.dump('backend' => backend_config)
114
+ end
115
+ end
116
+ end
117
+ end
@@ -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