dh-proteus 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +4 -0
  6. data/Gemfile.lock +80 -0
  7. data/README.md +414 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/proteus +5 -0
  11. data/bin/proteus_testing +8 -0
  12. data/bin/setup +8 -0
  13. data/build.sh +28 -0
  14. data/dh-proteus.gemspec +50 -0
  15. data/lib/core_ext/hash.rb +14 -0
  16. data/lib/proteus.rb +9 -0
  17. data/lib/proteus/app.rb +86 -0
  18. data/lib/proteus/backend/backend.rb +41 -0
  19. data/lib/proteus/commands/apply.rb +53 -0
  20. data/lib/proteus/commands/clean.rb +22 -0
  21. data/lib/proteus/commands/destroy.rb +51 -0
  22. data/lib/proteus/commands/graph.rb +22 -0
  23. data/lib/proteus/commands/import.rb +75 -0
  24. data/lib/proteus/commands/move.rb +28 -0
  25. data/lib/proteus/commands/output.rb +29 -0
  26. data/lib/proteus/commands/plan.rb +55 -0
  27. data/lib/proteus/commands/remove.rb +71 -0
  28. data/lib/proteus/commands/render.rb +36 -0
  29. data/lib/proteus/commands/taint.rb +35 -0
  30. data/lib/proteus/common.rb +72 -0
  31. data/lib/proteus/config/config.rb +47 -0
  32. data/lib/proteus/context_management/context.rb +31 -0
  33. data/lib/proteus/context_management/helpers.rb +14 -0
  34. data/lib/proteus/generate.rb +18 -0
  35. data/lib/proteus/generators/context.rb +57 -0
  36. data/lib/proteus/generators/environment.rb +42 -0
  37. data/lib/proteus/generators/init.rb +40 -0
  38. data/lib/proteus/generators/module.rb +69 -0
  39. data/lib/proteus/generators/templates/config/config.yaml.erb +22 -0
  40. data/lib/proteus/generators/templates/context/main.tf.erb +7 -0
  41. data/lib/proteus/generators/templates/context/variables.tf.erb +3 -0
  42. data/lib/proteus/generators/templates/environment/terraform.tfvars.erb +6 -0
  43. data/lib/proteus/generators/templates/module/io.tf.erb +1 -0
  44. data/lib/proteus/generators/templates/module/module.tf.erb +1 -0
  45. data/lib/proteus/generators/templates/module/validator.rb.erb +9 -0
  46. data/lib/proteus/global_commands/validate.rb +45 -0
  47. data/lib/proteus/helpers.rb +91 -0
  48. data/lib/proteus/helpers/path_helpers.rb +90 -0
  49. data/lib/proteus/helpers/string_helpers.rb +13 -0
  50. data/lib/proteus/init.rb +16 -0
  51. data/lib/proteus/modules/manager.rb +53 -0
  52. data/lib/proteus/modules/terraform_module.rb +184 -0
  53. data/lib/proteus/templates/partial.rb +123 -0
  54. data/lib/proteus/templates/template_binding.rb +62 -0
  55. data/lib/proteus/validators/base_validator.rb +24 -0
  56. data/lib/proteus/validators/validation_dsl.rb +172 -0
  57. data/lib/proteus/validators/validation_error.rb +9 -0
  58. data/lib/proteus/validators/validation_helpers.rb +9 -0
  59. data/lib/proteus/version.rb +7 -0
  60. metadata +260 -0
@@ -0,0 +1,28 @@
1
+ module Proteus
2
+ module Commands
3
+ module Move
4
+ def self.included(thor_class)
5
+ thor_class.class_eval do
6
+
7
+ desc "move FROM TO", "Moves an existing resource within the Terraform state"
8
+ def move(from, to)
9
+ init(verbose: parent_options[:verbose])
10
+ confirm question: "Do you really want to move #{from} to #{to} in context '(#{context}, #{environment})'?", color: :on_red, exit_code: 0 do
11
+
12
+ state_move_command = <<~STATE_MOVE_COMMAND
13
+ cd #{context_path(context)} && \
14
+ terraform state mv \
15
+ -var-file=#{var_file(context, environment)} \
16
+ #{aws_profile} \
17
+ #{from} \
18
+ #{to}
19
+ STATE_MOVE_COMMAND
20
+ syscall state_move_command.squeeze(' '), dryrun: dryrun
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,29 @@
1
+ module Proteus
2
+ module Commands
3
+ module Output
4
+ def self.included(thor_class)
5
+ thor_class.class_eval do
6
+
7
+ desc "output", "Query Terraform outputs"
8
+ long_desc <<-LONGDESC
9
+ Query Terraform outputs.
10
+ LONGDESC
11
+ option :name, type: :string, default: nil, required: false
12
+ def output
13
+
14
+ init(verbose: parent_options[:verbose])
15
+
16
+ terraform_command = <<~TERRAFORM_COMMAND
17
+ cd #{context_path(context)} && \
18
+ terraform output \
19
+ -state=#{state_file(context, environment)} \
20
+ #{options[:name] ? options[:name] : ''}
21
+ TERRAFORM_COMMAND
22
+
23
+ syscall(terraform_command.squeeze(' '))
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,55 @@
1
+ module Proteus
2
+ module Commands
3
+ module Plan
4
+ def self.included(thor_class)
5
+ thor_class.class_eval do
6
+
7
+ desc "plan", "Runs terraform plan"
8
+ long_desc <<-LONGDESC
9
+ Runs terraform plan.
10
+
11
+ With --limit option, the plan run will be limited to the specified targets
12
+ LONGDESC
13
+ option :limit, type: :array, aliases: "-l", default: []
14
+ option :destroy, type: :boolean, default: false
15
+ def plan
16
+ module_manager = Proteus::Modules::Manager.new(context: context, environment: environment)
17
+ module_manager.clean
18
+ module_manager.render_modules
19
+
20
+ fmt_cmd = <<~FMT_CMD
21
+ cd #{context_path(context)} && \
22
+ terraform fmt -list=true .
23
+ FMT_CMD
24
+
25
+ fmt_output = syscall(
26
+ fmt_cmd.squeeze(' '),
27
+ capture: true,
28
+ suppress: true
29
+ )
30
+
31
+ say "Formatted files:", :green
32
+ say fmt_output, :green
33
+
34
+ init(verbose: parent_options[:verbose]) if not dryrun
35
+
36
+ terraform_command = <<~TERRAFORM_COMMAND
37
+ cd #{context_path(context)} && \
38
+ terraform plan \
39
+ #{"-destroy" if options[:destroy]} \
40
+ -input=false \
41
+ -refresh=true \
42
+ -module-depth=-1 \
43
+ -var-file=#{var_file(context, environment)} \
44
+ -out=#{plan_file(context, environment)} \
45
+ #{aws_profile} #{limit(options[:limit])} \
46
+ #{context_path(context)}
47
+ TERRAFORM_COMMAND
48
+
49
+ syscall terraform_command.squeeze(' '), dryrun: dryrun
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,71 @@
1
+ module Proteus
2
+ module Commands
3
+ module Remove
4
+ def self.included(thor_class)
5
+ thor_class.class_eval do
6
+
7
+ desc "remove", "Remove a resource from the terraform state"
8
+ long_desc <<-LONGDESC
9
+ Remove a resource from the terraform state
10
+ --bulk Enables bulk import mode
11
+
12
+ --resource_address Terraform address of resource to remove from the terraform state
13
+
14
+ --resources_file File containing resource addresses and identifiers
15
+ LONGDESC
16
+ option :bulk, type: :boolean, aliases: "-b", required: false, default: false
17
+ option :resource_address, type: :string, aliases: "-a", required: false, default: nil
18
+ option :resources_file, type: :string, aliases: "-f", required: false, default: nil
19
+ def remove
20
+ if options[:bulk]
21
+ if !options[:resources_file]
22
+ say "Supply a file containing resource identifiers and Terraform addresses for bulk operations", :red
23
+ exit 1
24
+ end
25
+ else
26
+ if !options[:resource_address]
27
+ say "You need to supply a resource address.", :red
28
+ exit 1
29
+ end
30
+ end
31
+
32
+ init(verbose: parent_options[:verbose])
33
+
34
+ confirm question: "Do you really want to run 'terraform state rm' in context '(#{context}, #{environment})'?", color: :on_red, exit_code: 0 do
35
+ state_remove_command = <<~STATE_REMOVE_COMMAND
36
+ cd #{context_path(context)} && \
37
+ terraform state rm \
38
+ -var-file=#{var_file(context, environment)} \
39
+ #{aws_profile} \
40
+ %{resource_addresses}
41
+ STATE_REMOVE_COMMAND
42
+
43
+ if options[:bulk]
44
+ if File.file?(options[:resources_file])
45
+ File.open(options[:resources_file], "r") do |file|
46
+ resource_addresses = []
47
+ file.each_line do |line|
48
+ resource = line.chomp.split(" = ")
49
+ resource_addresses << resource[0]
50
+ end
51
+
52
+ resource_addresses.each_slice(500) do |slice|
53
+ syscall (state_remove_command % { resource_addresses: slice.join(' ') }).squeeze(' ')
54
+ end
55
+
56
+ end
57
+ else
58
+ say "File #{options[:resources_file]} does not exist.", :red
59
+ exit 1
60
+ end
61
+ else
62
+ syscall (state_remove_command % { resource_addresses: options[:resource_address] })
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,36 @@
1
+ require 'proteus/modules/manager'
2
+
3
+ module Proteus
4
+ module Commands
5
+ module Render
6
+ def self.included(thor_class)
7
+ thor_class.class_eval do
8
+
9
+ desc "render", "Renders the module templates"
10
+ long_desc <<-LONGDESC
11
+ Renders the module templates without running Terraform
12
+ LONGDESC
13
+ def render
14
+ render_backend
15
+ module_manager = Proteus::Modules::Manager.new(context: context, environment: environment)
16
+ module_manager.render_modules
17
+
18
+ fmt_cmd = <<~FMT_CMD
19
+ cd #{context_path(context)} && \
20
+ terraform fmt -list=true .
21
+ FMT_CMD
22
+
23
+ fmt_output = syscall(
24
+ fmt_cmd.squeeze(' '),
25
+ capture: true,
26
+ suppress: true
27
+ )
28
+
29
+ say "Formatted files:", :green
30
+ say fmt_output, :green
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module Proteus
2
+ module Commands
3
+ module Taint
4
+ def self.included(thor_class)
5
+ thor_class.class_eval do
6
+
7
+ desc "taint", "Taints an existing resource"
8
+ long_desc <<-LONGDESC
9
+ Taints an existing resource
10
+
11
+ --resource The resource to taint
12
+ LONGDESC
13
+ option :resource, type: :string, aliases: "-r", required: true
14
+ option :module, type: :string, aliases: "-m", required: false, default: nil
15
+ def taint
16
+ init(verbose: parent_options[:verbose])
17
+ confirm question: "Do you really want to run 'terraform taint' on environment '#{environment}' in context '#{context}'?", color: :on_red, exit_code: 0 do
18
+
19
+ taint_command = <<~TAINT_COMMAND
20
+ cd #{context_path(context)} && \
21
+ terraform taint \
22
+ -var-file=#{var_file(context, environment)} \
23
+ #{aws_profile} \
24
+ #{options[:module] ? "-module=#{options[:module]}" : ""} \
25
+ #{options[:resource]}
26
+ TAINT_COMMAND
27
+ syscall taint_command.squeeze(' ')
28
+ end
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,72 @@
1
+ require 'proteus/commands/apply'
2
+ require 'proteus/commands/clean'
3
+ require 'proteus/commands/destroy'
4
+ require 'proteus/commands/graph'
5
+ require 'proteus/commands/import'
6
+ require 'proteus/commands/move'
7
+ require 'proteus/commands/output'
8
+ require 'proteus/commands/plan'
9
+ require 'proteus/commands/remove'
10
+ require 'proteus/commands/render'
11
+ require 'proteus/commands/taint'
12
+
13
+ module Proteus
14
+ class Common < Thor
15
+ include Helpers
16
+ include Proteus::Helpers::PathHelpers
17
+
18
+ Proteus::Commands.constants.each do |command|
19
+ include const_get("Proteus::Commands::#{command}")
20
+ end
21
+
22
+ private
23
+
24
+ def context
25
+ self.class.context
26
+ end
27
+
28
+ def environment
29
+ self.class.environment
30
+ end
31
+
32
+ def render_backend
33
+ Proteus::Backend::Backend.new(config: config, context: context, environment: environment).render
34
+ end
35
+
36
+ def init(verbose: false)
37
+ say "initializing", :green
38
+ say "environment: #{environment}", :green
39
+
40
+ render_backend
41
+
42
+ `rm -rf #{context_path(context)}/.terraform/*.tf*`
43
+ `rm -rf #{context_path(context)}/.terraform/modules`
44
+ `rm -rf #{context_path(context)}/terraform.tfstate*`
45
+
46
+ terraform_command = <<~TERRAFORM_COMMAND
47
+ cd #{context_path(context)} && \
48
+ terraform init \
49
+ -backend-config='key=#{config[:backend][:key_prefix]}#{context}-#{environment}.tfstate' \
50
+ #{aws_profile} \
51
+ #{context_path(context)}
52
+ TERRAFORM_COMMAND
53
+
54
+ output = syscall terraform_command.squeeze(' '), suppress: true, capture: true
55
+ say(output, :green) if verbose
56
+ end
57
+
58
+ def aws_profile
59
+ config[:providers].select {|p| p[:name] == 'aws' }.first[:environments].each do |provider|
60
+ return "-var 'aws_profile=#{provider[:profile]}'" if environment =~ (/#{provider[:match]}/)
61
+ end
62
+
63
+ say "No provider found matching environment #{environment}.", :red
64
+ exit 1
65
+ end
66
+
67
+ def dryrun
68
+ parent_options[:dryrun]
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,47 @@
1
+ module Proteus
2
+ module Config
3
+ def config
4
+ unless @config
5
+ @config = YAML.load(File.read(File.expand_path(config_path))).with_indifferent_access
6
+ begin
7
+ validator = ConfigValidator.new(@config)
8
+ rescue Proteus::Validators::ValidationError => validation_error
9
+ say "ConfigValidator: #{validation_error.message} [#{config_path}] #{"\u2718".encode('utf-8')}", :red
10
+ exit 1
11
+ end
12
+ end
13
+
14
+ @config
15
+ end
16
+
17
+ class ConfigValidator < Proteus::Validators::BaseValidator
18
+ def validate
19
+ within :providers do
20
+ ensure_data_type Array
21
+
22
+ each do
23
+ within :environments do
24
+ ensure_uniqueness_across :match
25
+ end
26
+ end
27
+ end
28
+
29
+ within :slack_webhooks, optional: true do
30
+ ensure_data_type Array
31
+
32
+ each do
33
+ ensure_keys :match, :url
34
+ end
35
+ end
36
+
37
+ within :backend do
38
+ ensure_presence :key_prefix
39
+
40
+ within :bucket do
41
+ ensure_keys :name, :region, :profile
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,31 @@
1
+ require 'proteus/helpers/path_helpers'
2
+
3
+ module Proteus
4
+ module ContextManagement
5
+ class Context
6
+ include Proteus::Helpers::PathHelpers
7
+
8
+ attr_accessor :name
9
+
10
+ def initialize(name:)
11
+ @name = name
12
+
13
+ ensure_temp_directory
14
+ end
15
+
16
+ def environments
17
+ Dir.glob("#{File.expand_path(File.join(environments_path(@name)))}/*.tfvars").map do |vars_file|
18
+ File.basename(vars_file).split('.')[1]
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def ensure_temp_directory
25
+ unless File.directory?(context_temp_directory(@name))
26
+ Dir.mkdir(context_temp_directory(@name))
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,14 @@
1
+ module Proteus
2
+ module ContextManagement
3
+ module Helpers
4
+ include Proteus::Helpers::PathHelpers
5
+ def contexts
6
+ say "loading contexts", :green
7
+
8
+ Dir.glob(File.join(contexts_path, "*")).collect do |context_path|
9
+ Context.new(name: File.basename(context_path))
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ require 'proteus/generators/context'
2
+ require 'proteus/generators/environment'
3
+ require 'proteus/generators/module'
4
+
5
+ module Proteus
6
+ class Generate < Thor
7
+ include Thor::Actions
8
+ include Helpers
9
+
10
+ def self.source_root
11
+ File.expand_path(File.join(File.dirname(__FILE__), 'generators', 'templates'))
12
+ end
13
+
14
+ include Generators::Context
15
+ include Generators::Environment
16
+ include Generators::Module
17
+ end
18
+ end