covalence 0.0.1 → 0.7.9.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +164 -0
  3. data/README.md +489 -19
  4. data/TODO.md +14 -0
  5. data/lib/covalence.rb +41 -0
  6. data/lib/covalence/core/bootstrap.rb +8 -0
  7. data/lib/covalence/core/cli_wrappers/packer.yml +9 -0
  8. data/lib/covalence/core/cli_wrappers/packer_cli.rb +27 -0
  9. data/lib/covalence/core/cli_wrappers/popen_wrapper.rb +123 -0
  10. data/lib/covalence/core/cli_wrappers/terraform.yml +39 -0
  11. data/lib/covalence/core/cli_wrappers/terraform_cli.rb +119 -0
  12. data/lib/covalence/core/data_stores/hiera.rb +50 -0
  13. data/lib/covalence/core/entities/context.rb +38 -0
  14. data/lib/covalence/core/entities/environment.rb +24 -0
  15. data/lib/covalence/core/entities/input.rb +112 -0
  16. data/lib/covalence/core/entities/stack.rb +74 -0
  17. data/lib/covalence/core/entities/state_store.rb +65 -0
  18. data/lib/covalence/core/repositories/context_repository.rb +30 -0
  19. data/lib/covalence/core/repositories/environment_repository.rb +92 -0
  20. data/lib/covalence/core/repositories/input_repository.rb +56 -0
  21. data/lib/covalence/core/repositories/stack_repository.rb +89 -0
  22. data/lib/covalence/core/repositories/state_store_repository.rb +31 -0
  23. data/lib/covalence/core/services/hiera_syntax_service.rb +19 -0
  24. data/lib/covalence/core/services/packer_stack_tasks.rb +104 -0
  25. data/lib/covalence/core/services/terraform_stack_tasks.rb +212 -0
  26. data/lib/covalence/core/state_stores/atlas.rb +157 -0
  27. data/lib/covalence/core/state_stores/consul.rb +153 -0
  28. data/lib/covalence/core/state_stores/s3.rb +147 -0
  29. data/lib/covalence/environment_tasks.rb +328 -0
  30. data/lib/covalence/helpers/shell_interpolation.rb +28 -0
  31. data/lib/covalence/helpers/spec_dependencies.rb +21 -0
  32. data/lib/covalence/rake/rspec/envs_spec.rb +75 -0
  33. data/lib/covalence/rake/rspec/yaml_spec.rb +14 -0
  34. data/lib/covalence/spec_tasks.rb +59 -0
  35. data/lib/covalence/version.rb +3 -0
  36. metadata +344 -26
  37. data/.gitignore +0 -9
  38. data/Gemfile +0 -4
  39. data/LICENSE.txt +0 -21
  40. data/Rakefile +0 -2
  41. data/bin/console +0 -14
  42. data/bin/setup +0 -8
  43. data/lib/prometheus_unifio.rb +0 -5
  44. data/lib/prometheus_unifio/version.rb +0 -3
  45. data/prometheus_unifio.gemspec +0 -32
@@ -0,0 +1,24 @@
1
+ require 'virtus'
2
+ require 'active_model'
3
+
4
+ require_relative 'stack'
5
+
6
+ module Covalence
7
+ class Environment
8
+ include Virtus.model
9
+ include ActiveModel::Validations
10
+
11
+ attribute :name, String
12
+ attribute :stacks, Array[Stack]
13
+
14
+ validates! :name, format: {
15
+ without: /\s+/,
16
+ message: "Environment %{attribute}: \"%{value}\" cannot contain spaces"
17
+ }
18
+
19
+ def initialize(attributes = {}, *args)
20
+ super
21
+ self.valid?
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,112 @@
1
+ require 'virtus'
2
+ require 'active_support/core_ext/string/inflections'
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_model'
5
+
6
+ require_relative '../../helpers/shell_interpolation'
7
+
8
+ module Covalence
9
+ class Input
10
+ include Virtus.model
11
+ include ActiveModel::Validations
12
+
13
+ attribute :name, String
14
+ # unprocessed value, could be remote
15
+ attribute :raw_value, Object
16
+
17
+ def initialize(attributes = {}, *args)
18
+ super
19
+ self.valid?
20
+ end
21
+
22
+ def value
23
+ return raw_value if !raw_value.is_a?(Hash)
24
+ get_value(raw_value)
25
+ end
26
+
27
+ def to_command_option
28
+ "#{name} = #{parse_input(value())}"
29
+ end
30
+
31
+ private
32
+
33
+ def get_value(input)
34
+ backend, type = parse_type(input)
35
+
36
+ if backend != "local"
37
+ remote_value = backend::lookup(type, input)
38
+ if remote_value.is_a?(Hash)
39
+ get_value(remote_value)
40
+ else
41
+ remote_value
42
+ end
43
+ elsif input.stringify_keys.has_key?('value')
44
+ input.stringify_keys.fetch('value')
45
+ else
46
+ input
47
+ end
48
+ end
49
+
50
+ def parse_array(input)
51
+ config = "[\n"
52
+ input.each do |v|
53
+ config += " #{parse_input(v)},\n"
54
+ end
55
+ config += "]"
56
+ end
57
+
58
+ def parse_hash(input)
59
+ config = "{\n"
60
+ input.each do |k,v|
61
+ config += " \"#{k}\" = #{parse_input(v)}\n"
62
+ end
63
+ config += "}"
64
+ end
65
+
66
+ def parse_input(input)
67
+ if input.nil?
68
+ "\"\""
69
+
70
+ elsif input.is_a?(Hash)
71
+ parse_hash(input)
72
+
73
+ elsif input.is_a?(Array)
74
+ parse_array(input)
75
+
76
+ elsif input.to_s.include?("$(")
77
+ "\"#{Covalence::Helpers::ShellInterpolation.parse_shell(input)}\""
78
+
79
+ else
80
+ "\"#{input}\""
81
+ end
82
+ end
83
+
84
+ # :reek:FeatureEnvy
85
+ def parse_type(input)
86
+ if input.stringify_keys.has_key?('type')
87
+ type = input.stringify_keys.fetch('type')
88
+
89
+ local_types = %w(
90
+ list
91
+ map
92
+ string
93
+ )
94
+
95
+ if local_types.any? {|local_type| type == local_type }
96
+ return [ "local", type ]
97
+ elsif type.include?('.')
98
+ pieces = type.split('.', 2)
99
+ return [ "Covalence::#{pieces.first.camelize}".constantize,
100
+ pieces[1] ]
101
+ else
102
+ errors.add(:base,
103
+ "invalid input type specified: #{type}",
104
+ strict: true)
105
+ end
106
+ else
107
+ return [ "local", "map" ]
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,74 @@
1
+ require 'virtus'
2
+ require 'active_model'
3
+ require 'semantic'
4
+
5
+ require_relative '../../../covalence'
6
+ require_relative 'state_store'
7
+ require_relative 'input'
8
+ require_relative 'context'
9
+
10
+ module Covalence
11
+ class Stack
12
+ include Virtus.model
13
+ include ActiveModel::Validations
14
+
15
+ attribute :type, String
16
+ attribute :name, String
17
+ attribute :environment_name, String
18
+ attribute :module_path, String
19
+ attribute :dependencies, Array[String]
20
+ attribute :packer_template, String
21
+ attribute :state_stores, Array[StateStore]
22
+ attribute :contexts, Array[Context]
23
+ attribute :inputs, Hash[String => Input]
24
+ attribute :args, String
25
+ attribute :workspace, String
26
+
27
+ validates! :type, inclusion: {
28
+ in: %w(terraform packer)
29
+ }
30
+ validates! :name, format: {
31
+ without: /\s+/,
32
+ message: "Stack %{attribute}: \"%{value}\" cannot contain spaces"
33
+ }
34
+
35
+ def initialize(attributes = {}, *arguments)
36
+ super
37
+ self.valid?
38
+ end
39
+
40
+ def full_name
41
+ "#{environment_name}-#{name}"
42
+ end
43
+
44
+ def materialize_cmd_inputs
45
+ if type == "terraform"
46
+ config = ""
47
+ inputs.values.map(&:to_command_option).each do |input|
48
+ config += input + "\n"
49
+ end
50
+ logger.info "\nStack inputs:\n\n#{config}"
51
+ File.open('covalence-inputs.tfvars','w') {|f| f.write(config)}
52
+ elsif type == "packer"
53
+ config = Hash.new
54
+ inputs.each do |name, input|
55
+ config[name] = input.value
56
+ end
57
+ config_json = JSON.generate(config)
58
+ logger.info "\nStack inputs:\n\n#{config_json}"
59
+ File.open('covalence-inputs.json','w') {|f| f.write(config_json)}
60
+ end
61
+ end
62
+
63
+ def materialize_state_inputs(store: state_stores.first)
64
+ config = store.get_config
65
+ logger.info "\nState store configuration:\n\n#{config}"
66
+ File.open('covalence-state.tf','w') {|f| f.write(config)}
67
+ end
68
+
69
+ def logger
70
+ Covalence::LOGGER
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,65 @@
1
+ require 'virtus'
2
+ require 'active_support/core_ext/string/inflections'
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_model'
5
+
6
+ # check to see how this works with plugins
7
+ Dir[File.expand_path('../state_stores/*.rb', File.dirname(__FILE__))].each do |file|
8
+ require file
9
+ end
10
+
11
+ module Covalence
12
+ class StateStore
13
+ include Virtus.model
14
+ include ActiveModel::Validations
15
+
16
+ attribute :params, Hash, :writer => :private
17
+ attribute :backend, Object, :writer => :private
18
+ attribute :workspace_enabled, Boolean
19
+
20
+ validate :validate_params_has_name,
21
+ :backend_has_state_store
22
+
23
+ def initialize(attributes = {}, *args)
24
+ super
25
+ self.valid?
26
+ end
27
+
28
+ def name
29
+ params.fetch('name')
30
+ end
31
+
32
+ # :reek:FeatureEnvy
33
+ def params=(params)
34
+ super(params.stringify_keys)
35
+ end
36
+
37
+ #TODO: prep different backend for plugins
38
+ # :reek:FeatureEnvy
39
+ def backend=(backend_name)
40
+ super("Covalence::#{backend_name.camelize}".constantize)
41
+ end
42
+
43
+ def get_config
44
+ backend::get_state_store(@params, @workspace_enabled)
45
+ end
46
+
47
+ private
48
+ def validate_params_has_name
49
+ if !params.has_key?('name')
50
+ errors.add(:base,
51
+ "Params #{params} missing 'name' parameter for the #{backend} state store",
52
+ strict: true)
53
+ end
54
+ end
55
+
56
+ def backend_has_state_store
57
+ backend_has_no_state_store = !backend.has_state_store? rescue true
58
+ if backend_has_no_state_store
59
+ errors.add(:base,
60
+ "#{backend} backend module does not support state storage",
61
+ strict: true)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,30 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require_relative '../entities/context'
3
+
4
+ module Covalence
5
+ class ContextRepository
6
+ class << self
7
+ def query_by_namespace(data_store, namespace, tool)
8
+ if tool == 'terraform'
9
+ query_tool_by_namespace(data_store, namespace)
10
+ else
11
+ Array.new(1, Context.new())
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def query_tool_by_namespace(data_store, namespace)
18
+ targets = data_store.hash_lookup("#{namespace}::targets", {})
19
+ contexts = targets.map do |name, values|
20
+ next if name.blank?
21
+ Context.new(name: name, values: values)
22
+ end
23
+ contexts.compact!
24
+ # always append blank context at the end.
25
+ contexts << Context.new()
26
+ contexts
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,92 @@
1
+ require_relative '../../../covalence'
2
+ require_relative '../data_stores/hiera'
3
+ require_relative '../entities/environment'
4
+ require_relative 'stack_repository'
5
+
6
+ module Covalence
7
+ class EnvironmentRepository
8
+ class << self
9
+ def find_all(data_store = HieraDB::Client.new(Covalence::CONFIG))
10
+ environments_hash = lookup_environments(data_store)
11
+
12
+ environments_hash.map do |environment_name, stack_names|
13
+ stacks = stack_names.map do |stack_name|
14
+ StackRepository.find(data_store, environment_name, stack_name)
15
+ end.compact
16
+
17
+ check_all_stacks_valid!(environment_name,
18
+ stack_names,
19
+ stacks)
20
+
21
+ Environment.new(name: environment_name,
22
+ stacks: stacks)
23
+ end
24
+ end
25
+
26
+ def find_filtered(task, data_store = HieraDB::Client.new(Covalence::CONFIG))
27
+ environments_hash = lookup_environments(data_store)
28
+ env_request = task['environment']
29
+ stk_request = task['stack']
30
+
31
+ if (env_request.nil? || !environments_hash.has_key?(env_request))
32
+ if RESERVED_NS.include?(env_request)
33
+ return Array.new(1, Environment.new(name: env_request,
34
+ stacks: stk_request))
35
+ else
36
+ raise "'#{env_request}' not found in environments"
37
+ end
38
+ end
39
+
40
+ stacks = nil
41
+ if (!stk_request.nil? && environments_hash[env_request].include?(stk_request))
42
+ stack_list = Array.new(1, stk_request)
43
+ stacks = Array.new(1, StackRepository.find(data_store, env_request, stk_request))
44
+ else
45
+ stack_list = environments_hash[env_request]
46
+ stacks = stack_list.map do |stack_name|
47
+ StackRepository.find(data_store, env_request, stack_name)
48
+ end.compact
49
+ end
50
+
51
+ check_all_stacks_valid!(env_request,
52
+ stack_list,
53
+ stacks)
54
+
55
+ Array.new(1, Environment.new(name: env_request,
56
+ stacks: stacks))
57
+ end
58
+
59
+ def populate_stack(stack, data_store = HieraDB::Client.new(Covalence::CONFIG))
60
+ StackRepository.populate(data_store, stack)
61
+ end
62
+
63
+ private
64
+
65
+ def lookup_environments(data_store)
66
+ environments_hash = data_store.hash_lookup('environments')
67
+ raise "Missing 'environments' configuration hash" if environments_hash.empty?
68
+ environments_hash
69
+ end
70
+
71
+ # Stacks are valid if they map to at least one tool (packer or terraform)
72
+ def check_all_stacks_valid!(environment_name, stack_list, stacks)
73
+ stack_names = stacks.map(&:name)
74
+ logger.debug("All stacks: #{stack_list}")
75
+ logger.debug("Targeted stacks: #{stack_names}")
76
+
77
+ invalid_stacks = stack_list - stack_names
78
+
79
+ if invalid_stacks.size > 0
80
+ error_string = <<-eos
81
+ Invalid stack(s) #{invalid_stacks} for environment #{environment_name}.
82
+ eos
83
+ raise error_string.strip
84
+ end
85
+ end
86
+
87
+ def logger
88
+ Covalence::LOGGER
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,56 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require_relative '../../../covalence'
4
+ require_relative '../entities/input'
5
+
6
+ module Covalence
7
+ class InputRepository
8
+ class << self
9
+ def query_by_namespace(data_store, namespace, tool)
10
+ results = Hash.new
11
+ if tool == 'terraform'
12
+ results = parse_var_file('terraform', data_store, namespace)
13
+ else
14
+ results = parse_var_file('packer', data_store, namespace)
15
+ end
16
+
17
+ results.merge(query_tool_by_namespace(data_store, namespace))
18
+ end
19
+
20
+ private
21
+
22
+ #TODO: refactor nested conditional
23
+ def parse_var_file(tool, data_store, namespace)
24
+ yaml_ext = %w(yaml yml)
25
+ json_ext = %w(json)
26
+
27
+ varfile = data_store.lookup("#{namespace}::vars-file", nil)
28
+ return {} unless varfile
29
+ tool_module_path ="Covalence::#{tool.upcase}".constantize
30
+ varfile = File.expand_path(File.join(tool_module_path, varfile.to_s))
31
+ if (File.file?(varfile) &&
32
+ (yaml_ext + json_ext).include?(File.extname(varfile)[1..-1]))
33
+ if json_ext.include?(File.extname(varfile)[1..-1])
34
+ tmp_hash = JSON.parse(File.read(varfile))
35
+ else
36
+ tmp_hash = YAML.load_file(varfile)
37
+ end
38
+
39
+ tmp_hash = tmp_hash.map do |name, raw_value|
40
+ [ name, Input.new(name: name, raw_value: raw_value) ]
41
+ end
42
+ Hash[*tmp_hash.flatten]
43
+ else
44
+ raise ArgumentError, "cannot parse non-yaml or non-json file: #{varfile}"
45
+ end
46
+ end
47
+
48
+ def query_tool_by_namespace(data_store, namespace)
49
+ tmp_hash = data_store.hash_lookup("#{namespace}::vars", {}).map do |name, raw_value|
50
+ [ name, Input.new(name: name, raw_value: raw_value) ]
51
+ end
52
+ Hash[*tmp_hash.flatten]
53
+ end
54
+ end
55
+ end
56
+ end