dh-proteus 0.1.3

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