covalence 0.0.1 → 0.7.9.rc1
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.
- 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
|