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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +164 -0
- data/README.md +489 -19
- data/TODO.md +14 -0
- data/lib/covalence.rb +41 -0
- data/lib/covalence/core/bootstrap.rb +8 -0
- data/lib/covalence/core/cli_wrappers/packer.yml +9 -0
- data/lib/covalence/core/cli_wrappers/packer_cli.rb +27 -0
- data/lib/covalence/core/cli_wrappers/popen_wrapper.rb +123 -0
- data/lib/covalence/core/cli_wrappers/terraform.yml +39 -0
- data/lib/covalence/core/cli_wrappers/terraform_cli.rb +119 -0
- data/lib/covalence/core/data_stores/hiera.rb +50 -0
- data/lib/covalence/core/entities/context.rb +38 -0
- data/lib/covalence/core/entities/environment.rb +24 -0
- data/lib/covalence/core/entities/input.rb +112 -0
- data/lib/covalence/core/entities/stack.rb +74 -0
- data/lib/covalence/core/entities/state_store.rb +65 -0
- data/lib/covalence/core/repositories/context_repository.rb +30 -0
- data/lib/covalence/core/repositories/environment_repository.rb +92 -0
- data/lib/covalence/core/repositories/input_repository.rb +56 -0
- data/lib/covalence/core/repositories/stack_repository.rb +89 -0
- data/lib/covalence/core/repositories/state_store_repository.rb +31 -0
- data/lib/covalence/core/services/hiera_syntax_service.rb +19 -0
- data/lib/covalence/core/services/packer_stack_tasks.rb +104 -0
- data/lib/covalence/core/services/terraform_stack_tasks.rb +212 -0
- data/lib/covalence/core/state_stores/atlas.rb +157 -0
- data/lib/covalence/core/state_stores/consul.rb +153 -0
- data/lib/covalence/core/state_stores/s3.rb +147 -0
- data/lib/covalence/environment_tasks.rb +328 -0
- data/lib/covalence/helpers/shell_interpolation.rb +28 -0
- data/lib/covalence/helpers/spec_dependencies.rb +21 -0
- data/lib/covalence/rake/rspec/envs_spec.rb +75 -0
- data/lib/covalence/rake/rspec/yaml_spec.rb +14 -0
- data/lib/covalence/spec_tasks.rb +59 -0
- data/lib/covalence/version.rb +3 -0
- metadata +344 -26
- data/.gitignore +0 -9
- data/Gemfile +0 -4
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/prometheus_unifio.rb +0 -5
- data/lib/prometheus_unifio/version.rb +0 -3
- 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
|