dh-proteus 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +80 -0
- data/README.md +414 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/proteus +5 -0
- data/bin/proteus_testing +8 -0
- data/bin/setup +8 -0
- data/build.sh +28 -0
- data/dh-proteus.gemspec +50 -0
- data/lib/core_ext/hash.rb +14 -0
- data/lib/proteus.rb +9 -0
- data/lib/proteus/app.rb +86 -0
- data/lib/proteus/backend/backend.rb +41 -0
- data/lib/proteus/commands/apply.rb +53 -0
- data/lib/proteus/commands/clean.rb +22 -0
- data/lib/proteus/commands/destroy.rb +51 -0
- data/lib/proteus/commands/graph.rb +22 -0
- data/lib/proteus/commands/import.rb +75 -0
- data/lib/proteus/commands/move.rb +28 -0
- data/lib/proteus/commands/output.rb +29 -0
- data/lib/proteus/commands/plan.rb +55 -0
- data/lib/proteus/commands/remove.rb +71 -0
- data/lib/proteus/commands/render.rb +36 -0
- data/lib/proteus/commands/taint.rb +35 -0
- data/lib/proteus/common.rb +72 -0
- data/lib/proteus/config/config.rb +47 -0
- data/lib/proteus/context_management/context.rb +31 -0
- data/lib/proteus/context_management/helpers.rb +14 -0
- data/lib/proteus/generate.rb +18 -0
- data/lib/proteus/generators/context.rb +57 -0
- data/lib/proteus/generators/environment.rb +42 -0
- data/lib/proteus/generators/init.rb +40 -0
- data/lib/proteus/generators/module.rb +69 -0
- data/lib/proteus/generators/templates/config/config.yaml.erb +22 -0
- data/lib/proteus/generators/templates/context/main.tf.erb +7 -0
- data/lib/proteus/generators/templates/context/variables.tf.erb +3 -0
- data/lib/proteus/generators/templates/environment/terraform.tfvars.erb +6 -0
- data/lib/proteus/generators/templates/module/io.tf.erb +1 -0
- data/lib/proteus/generators/templates/module/module.tf.erb +1 -0
- data/lib/proteus/generators/templates/module/validator.rb.erb +9 -0
- data/lib/proteus/global_commands/validate.rb +45 -0
- data/lib/proteus/helpers.rb +91 -0
- data/lib/proteus/helpers/path_helpers.rb +90 -0
- data/lib/proteus/helpers/string_helpers.rb +13 -0
- data/lib/proteus/init.rb +16 -0
- data/lib/proteus/modules/manager.rb +53 -0
- data/lib/proteus/modules/terraform_module.rb +184 -0
- data/lib/proteus/templates/partial.rb +123 -0
- data/lib/proteus/templates/template_binding.rb +62 -0
- data/lib/proteus/validators/base_validator.rb +24 -0
- data/lib/proteus/validators/validation_dsl.rb +172 -0
- data/lib/proteus/validators/validation_error.rb +9 -0
- data/lib/proteus/validators/validation_helpers.rb +9 -0
- data/lib/proteus/version.rb +7 -0
- 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
|