yle_tf 0.1.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +26 -0
  5. data/.travis.yml +7 -0
  6. data/CHANGELOG.md +3 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +442 -0
  11. data/Rakefile +6 -0
  12. data/bin/tf +7 -0
  13. data/examples/envs/prod.tfvars +5 -0
  14. data/examples/envs/test.tfvars +2 -0
  15. data/examples/main.tf +10 -0
  16. data/examples/tf.yaml +4 -0
  17. data/examples/tf_hooks/pre/get_current_hash.sh +26 -0
  18. data/examples/variables.tf +22 -0
  19. data/lib/yle_tf.rb +70 -0
  20. data/lib/yle_tf/action.rb +29 -0
  21. data/lib/yle_tf/action/builder.rb +9 -0
  22. data/lib/yle_tf/action/command.rb +25 -0
  23. data/lib/yle_tf/action/copy_root_module.rb +22 -0
  24. data/lib/yle_tf/action/generate_vars_file.rb +27 -0
  25. data/lib/yle_tf/action/load_config.rb +17 -0
  26. data/lib/yle_tf/action/terraform_init.rb +63 -0
  27. data/lib/yle_tf/action/tf_hooks.rb +48 -0
  28. data/lib/yle_tf/action/tmpdir.rb +35 -0
  29. data/lib/yle_tf/action/verify_terraform_version.rb +41 -0
  30. data/lib/yle_tf/action/verify_tf_env.rb +24 -0
  31. data/lib/yle_tf/backend_config.rb +41 -0
  32. data/lib/yle_tf/cli.rb +88 -0
  33. data/lib/yle_tf/config.rb +45 -0
  34. data/lib/yle_tf/config/defaults.rb +35 -0
  35. data/lib/yle_tf/config/erb.rb +22 -0
  36. data/lib/yle_tf/config/file.rb +26 -0
  37. data/lib/yle_tf/config/loader.rb +108 -0
  38. data/lib/yle_tf/error.rb +4 -0
  39. data/lib/yle_tf/logger.rb +48 -0
  40. data/lib/yle_tf/plugin.rb +55 -0
  41. data/lib/yle_tf/plugin/action_hook.rb +23 -0
  42. data/lib/yle_tf/plugin/loader.rb +59 -0
  43. data/lib/yle_tf/plugin/manager.rb +49 -0
  44. data/lib/yle_tf/system.rb +22 -0
  45. data/lib/yle_tf/tf_hook.rb +90 -0
  46. data/lib/yle_tf/tf_hook/runner.rb +48 -0
  47. data/lib/yle_tf/vars_file.rb +42 -0
  48. data/lib/yle_tf/version.rb +3 -0
  49. data/lib/yle_tf/version_requirement.rb +25 -0
  50. data/lib/yle_tf_plugins/backends/file/command.rb +31 -0
  51. data/lib/yle_tf_plugins/backends/file/config.rb +17 -0
  52. data/lib/yle_tf_plugins/backends/file/plugin.rb +16 -0
  53. data/lib/yle_tf_plugins/backends/s3/command.rb +19 -0
  54. data/lib/yle_tf_plugins/backends/s3/plugin.rb +16 -0
  55. data/lib/yle_tf_plugins/commands/__default/command.rb +14 -0
  56. data/lib/yle_tf_plugins/commands/__default/plugin.rb +14 -0
  57. data/lib/yle_tf_plugins/commands/_config/command.rb +11 -0
  58. data/lib/yle_tf_plugins/commands/_config/plugin.rb +19 -0
  59. data/lib/yle_tf_plugins/commands/_shell/command.rb +15 -0
  60. data/lib/yle_tf_plugins/commands/_shell/plugin.rb +14 -0
  61. data/lib/yle_tf_plugins/commands/help/command.rb +55 -0
  62. data/lib/yle_tf_plugins/commands/help/plugin.rb +18 -0
  63. data/lib/yle_tf_plugins/commands/version/command.rb +20 -0
  64. data/lib/yle_tf_plugins/commands/version/plugin.rb +18 -0
  65. data/vendor/hash_deep_merge.rb +59 -0
  66. data/vendor/logger_level_patch.rb +29 -0
  67. data/vendor/middleware/LICENSE +23 -0
  68. data/vendor/middleware/builder.rb +149 -0
  69. data/vendor/middleware/runner.rb +69 -0
  70. data/yle_tf.gemspec +37 -0
  71. metadata +160 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/tf ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup' if File.exist? File.expand_path('../../Gemfile', __FILE__)
4
+ require 'yle_tf/cli'
5
+
6
+ cli = YleTf::CLI.new(ARGV)
7
+ cli.execute
@@ -0,0 +1,5 @@
1
+ aws_access_key = "PRODUCTION_ACCOUNT_ACCESS_KEY_HERE"
2
+ aws_secret_key = "PRODUCTION_ACCOUNT_SECRET_KEY_HERE"
3
+
4
+ # let's use a bigger instance type in prod
5
+ instance_type = "t2.medium"
@@ -0,0 +1,2 @@
1
+ aws_access_key = "TEST_ACCOUNT_ACCESS_KEY_HERE"
2
+ aws_secret_key = "TEST_ACCOUNT_SECRET_KEY_HERE"
data/examples/main.tf ADDED
@@ -0,0 +1,10 @@
1
+ provider "aws" {
2
+ access_key = "${var.aws_access_key}"
3
+ secret_key = "${var.aws_secret_key}"
4
+ region = "${var.region}"
5
+ }
6
+
7
+ resource "aws_instance" "example" {
8
+ ami = "ami-d7b9a2b1"
9
+ instance_type = "${var.instance_type}"
10
+ }
data/examples/tf.yaml ADDED
@@ -0,0 +1,4 @@
1
+ backend:
2
+ type: file
3
+ terraform:
4
+ version_requirement: "~> 0.9.11"
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+
3
+ set -eu
4
+
5
+ current_hash() {
6
+ CURRENT_GIT_HASH="$(git rev-parse HEAD)"
7
+
8
+ cat <<EOF > current_git_hash.tf.json
9
+ {
10
+ "variable": {
11
+ "current_git_hash": {
12
+ "value": "$CURRENT_GIT_HASH"
13
+ }
14
+ }
15
+ }
16
+ EOF
17
+ }
18
+
19
+ # Execute only for commands `plan` and `apply`
20
+ case "$TF_COMMAND" in
21
+ plan|apply)
22
+ current_hash
23
+ ;;
24
+ *)
25
+ ;;
26
+ esac
@@ -0,0 +1,22 @@
1
+ variable "region" {
2
+ description = "The AWS region for the resources"
3
+ default = "eu-west-1"
4
+ }
5
+
6
+ variable "env" {
7
+ # passed by `tf`
8
+ description = "The environment"
9
+ }
10
+
11
+ variable "aws_access_key" {
12
+ description = "Your AWS access key id"
13
+ }
14
+
15
+ variable "aws_secret_key" {
16
+ description = "Your AWS secret key"
17
+ }
18
+
19
+ variable "instance_type" {
20
+ description = "Instance type to use"
21
+ default = "t2.micro"
22
+ }
data/lib/yle_tf.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'yle_tf/logger'
2
+ require 'yle_tf/version'
3
+
4
+ class YleTf
5
+ autoload :Action, 'yle_tf/action'
6
+ autoload :Error, 'yle_tf/error'
7
+ autoload :Plugin, 'yle_tf/plugin'
8
+
9
+ attr_reader :tf_env, :tf_command, :tf_command_args, :tf_options
10
+ attr_writer :actions
11
+
12
+ def initialize(tf_options, tf_env, tf_command, tf_command_args = [])
13
+ Logger.debug("YleTf version: #{VERSION}")
14
+ Logger.debug("Ruby version: #{RUBY_VERSION}")
15
+ Logger.debug("tf_options: #{tf_options.inspect}")
16
+ Logger.debug("tf_env: #{tf_env.inspect}")
17
+ Logger.debug("tf_command: #{tf_command.inspect}")
18
+ Logger.debug("tf_command_args: #{tf_command_args.inspect}")
19
+
20
+ @tf_options = tf_options
21
+ @tf_env = tf_env
22
+ @tf_command = tf_command
23
+ @tf_command_args = tf_command_args
24
+
25
+ Plugin::Loader.load_plugins
26
+ end
27
+
28
+ def run(env = {})
29
+ Logger.debug('Building and running the stack')
30
+ apply_action_hooks
31
+ Logger.debug("actions: #{actions.inspect}")
32
+ env.merge!(action_env)
33
+ Logger.debug("env: #{env.inspect}")
34
+ actions.call(env)
35
+ end
36
+
37
+ def actions
38
+ @actions ||= build_action_stack
39
+ end
40
+
41
+ def build_action_stack
42
+ command_data = Plugin.manager.commands[tf_command]
43
+ command_proc = command_data[:proc]
44
+ command_proc.call
45
+ end
46
+
47
+ def apply_action_hooks
48
+ hooks = Plugin.manager.action_hooks
49
+ Logger.debug("Applying #{hooks.length} action hooks")
50
+ Plugin::ActionHook.new(actions).tap do |h|
51
+ hooks.each { |hook_proc| hook_proc.call(h) }
52
+ end
53
+ end
54
+
55
+ def action_env
56
+ {
57
+ tf_options: tf_options,
58
+ tf_env: tf_env,
59
+ tf_command: tf_command,
60
+ tf_command_args: tf_command_args,
61
+ tfvars: default_tfvars,
62
+ }
63
+ end
64
+
65
+ def default_tfvars
66
+ {
67
+ 'env' => tf_env,
68
+ }
69
+ end
70
+ end
@@ -0,0 +1,29 @@
1
+ class YleTf
2
+ module Action
3
+ autoload :Builder, 'yle_tf/action/builder'
4
+ autoload :Command, 'yle_tf/action/command'
5
+ autoload :CopyRootModule, 'yle_tf/action/copy_root_module'
6
+ autoload :GenerateVarsFile, 'yle_tf/action/generate_vars_file'
7
+ autoload :LoadConfig, 'yle_tf/action/load_config'
8
+ autoload :TerraformInit, 'yle_tf/action/terraform_init'
9
+ autoload :TfHooks, 'yle_tf/action/tf_hooks'
10
+ autoload :TmpDir, 'yle_tf/action/tmpdir'
11
+ autoload :VerifyTfEnv, 'yle_tf/action/verify_tf_env'
12
+ autoload :VerifyTerraformVersion, 'yle_tf/action/verify_terraform_version'
13
+
14
+ def self.default_action_stack(command_class = nil)
15
+ Builder.new do
16
+ use LoadConfig
17
+ use VerifyTfEnv
18
+ use TmpDir
19
+ use VerifyTerraformVersion
20
+ use CopyRootModule
21
+ use GenerateVarsFile
22
+ use TfHooks
23
+ use TerraformInit
24
+
25
+ use(Command, command_class) if command_class
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require_relative '../../../vendor/middleware/builder'
2
+
3
+ class YleTf
4
+ module Action
5
+ # Middleware builder for the actions
6
+ class Builder < Middleware::Builder
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'yle_tf/logger'
2
+
3
+ class YleTf
4
+ module Action
5
+ class Command
6
+ attr_reader :command
7
+
8
+ def initialize(app, command)
9
+ @app = app
10
+ @command = command
11
+ end
12
+
13
+ def call(env)
14
+ if env[:tf_options][:only_hooks]
15
+ Logger.debug "Skipping command #{command.class} due to `--only-hooks`"
16
+ else
17
+ Logger.debug "Executing command #{command.class} with env: #{env.inspect}"
18
+ command.new.execute(env)
19
+ end
20
+
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'fileutils'
2
+
3
+ require 'yle_tf/logger'
4
+
5
+ class YleTf
6
+ module Action
7
+ class CopyRootModule
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ config = env[:config]
14
+
15
+ Logger.debug("Copying the Terraform module from '#{config.module_dir}' to '#{Dir.pwd}'")
16
+ FileUtils.cp_r("#{config.module_dir}/.", '.')
17
+
18
+ @app.call(env)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require 'yle_tf/logger'
2
+ require 'yle_tf/vars_file'
3
+
4
+ class YleTf
5
+ module Action
6
+ class GenerateVarsFile
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ config = env[:config]
13
+
14
+ Logger.debug("Generating 'terraform.tfvars'")
15
+ vars_file = VarsFile.new('terraform.tfvars')
16
+ vars_file.append_vars(tfvars(env))
17
+ vars_file.append_file(VarsFile.find_env_vars_file(config))
18
+
19
+ @app.call(env)
20
+ end
21
+
22
+ def tfvars(env)
23
+ env[:tfvars].merge(env[:config].fetch('tfvars'))
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'yle_tf/config'
2
+
3
+ class YleTf
4
+ module Action
5
+ class LoadConfig
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ env[:config] ||= Config.new(env[:tf_env])
12
+
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ require 'yle_tf/error'
2
+ require 'yle_tf/logger'
3
+ require 'yle_tf/plugin'
4
+ require 'yle_tf/system'
5
+ require 'yle_tf/version_requirement'
6
+
7
+ class YleTf
8
+ module Action
9
+ class TerraformInit
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ config = env[:config]
16
+ backend = backend_config(config)
17
+
18
+ Logger.debug('Initializing Terraform with backend configuration:')
19
+ Logger.debug(backend.to_s)
20
+
21
+ if VersionRequirement.pre_0_9?(env[:terraform_version])
22
+ init_pre_0_9(backend)
23
+ else
24
+ init(backend)
25
+ end
26
+
27
+ @app.call(env)
28
+ end
29
+
30
+ def init_pre_0_9(backend)
31
+ cli_args = backend.cli_args
32
+ YleTf::System.cmd('terraform', 'remote', 'config', *cli_args) if cli_args
33
+
34
+ Logger.debug('Fetching Terraform modules')
35
+ YleTf::System.cmd('terraform', 'get')
36
+ end
37
+
38
+ def init(backend)
39
+ Logger.debug('Generating the backend configuration')
40
+ backend.generate_config do
41
+ Logger.debug('Initializing Terraform')
42
+ YleTf::System.cmd('terraform', 'init', '-no-color')
43
+ end
44
+ end
45
+
46
+ def backend_config(config)
47
+ backend_type = config.fetch('backend', 'type').downcase
48
+ backend_proc = backend_proc(backend_type)
49
+
50
+ klass = backend_proc.call
51
+ klass.new.backend_config(config)
52
+ end
53
+
54
+ def backend_proc(backend_type)
55
+ backends = Plugin.manager.backends
56
+ backends.fetch(backend_type.to_sym) do
57
+ raise Error, "Unknown backend type '#{backend_type}'. " \
58
+ "Supported backends: #{backends.keys.join(', ')}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,48 @@
1
+ require 'yle_tf/logger'
2
+ require 'yle_tf/tf_hook/runner'
3
+
4
+ require 'yle_tf/logger'
5
+ require 'yle_tf/tf_hook/runner'
6
+
7
+ class YleTf
8
+ module Action
9
+ class TfHooks
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ @env = env
16
+
17
+ hook_runner.run('pre')
18
+ @app.call(env)
19
+ hook_runner.run('post')
20
+ end
21
+
22
+ def hook_runner
23
+ if run_hooks?
24
+ TfHook::Runner.new(@env[:config], hook_env)
25
+ else
26
+ NoRunner
27
+ end
28
+ end
29
+
30
+ def hook_env
31
+ {
32
+ 'TF_COMMAND' => @env[:tf_command],
33
+ 'TF_ENV' => @env[:tf_env],
34
+ }
35
+ end
36
+
37
+ def run_hooks?
38
+ !@env[:tf_options][:no_hooks]
39
+ end
40
+
41
+ class NoRunner
42
+ def self.run(hook_type)
43
+ Logger.debug("Skipping #{hook_type} hooks due to `--no-hooks`")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+
4
+ require 'yle_tf/logger'
5
+
6
+ class YleTf
7
+ module Action
8
+ class TmpDir
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ def call(env)
14
+ config = env[:config]
15
+
16
+ tmpdir = Dir.mktmpdir(tmpdir_prefix(config))
17
+ Logger.debug("Temporary Terraform directory: #{tmpdir}")
18
+
19
+ Dir.chdir(tmpdir) do
20
+ @app.call(env)
21
+ end
22
+ ensure
23
+ FileUtils.rm_r(tmpdir) if tmpdir
24
+ end
25
+
26
+ def tmpdir_prefix(config)
27
+ if config
28
+ "tf_#{config.module_dir.basename}_#{config.tf_env}_"
29
+ else
30
+ 'tf_'
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end