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.
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
data/TODO.md ADDED
@@ -0,0 +1,14 @@
1
+ - individual state-stores: would be nice if it had something to describe the backing input types
2
+ - consider httparty vs rest-client
3
+ - Use the invalid yaml syntax to figure out more things in rake tasks that needs to be lazy loaded
4
+ - Like the idea of maybe splitting out the syntax elements to a different gem. Gem installation & management would become easier
5
+ - missing spec around reports?
6
+ - Look into muting the reporter as dev default: https://github.com/ci-reporter/ci_reporter
7
+ - Look into getting HieraDB wrapper to read .yml files
8
+ - Terraform CLI in docker probably isn't going anywhere, might need to figure out a nicer way of handling this
9
+ - Use covalence to do verification on the open source stacks:
10
+ - terraform-aws-openvpn
11
+ - Plugin architecture
12
+ - rollup tasks in terraform need to check terraform only tasks
13
+ - stack_repository: backfill packer related tests
14
+ - packer: utils to gen uuids as inputs?
data/lib/covalence.rb ADDED
@@ -0,0 +1,41 @@
1
+ require "covalence/version"
2
+ require "logger"
3
+ require 'active_support/core_ext/object/blank'
4
+
5
+ if %w(development test).include?(ENV['RAKE_ENV'])
6
+ require 'byebug'
7
+ require 'awesome_print'
8
+ end
9
+
10
+ # :reek:TooManyConstants
11
+ module Covalence
12
+ # Configurable constants
13
+ #TODO: look into how WORKSPACE is being used, maybe this can just be an internal ROOT and make CONFIG not depend on WORKSPACE
14
+ WORKSPACE = File.absolute_path(ENV['COVALENCE_WORKSPACE'] || '.')
15
+ CONFIG = File.join(WORKSPACE, (ENV['COVALENCE_CONFIG'] || 'covalence.yaml'))
16
+ # TODO: could use better naming
17
+ PACKER = File.absolute_path(File.join(WORKSPACE, (ENV['COVALENCE_PACKER_DIR'] || '.')))
18
+ TERRAFORM = File.absolute_path(File.join(WORKSPACE, (ENV['COVALENCE_TERRAFORM_DIR'] || '.')))
19
+ TEST_ENVS = (ENV['COVALENCE_TEST_ENVS'] || "").split(',')
20
+ # Reserved namespace including default ci and spec
21
+ RESERVED_NS = [(ENV['COVALENCE_RESERVED_NAMESPACE'] || "").split(','), 'ci', 'spec']
22
+
23
+ TERRAFORM_CMD = ENV['TERRAFORM_CMD'] || "terraform"
24
+ TERRAFORM_VERSION = ENV['TERRAFORM_VERSION'] || `#{TERRAFORM_CMD} version`.split("\n", 2)[0].gsub('Terraform v','')
25
+ TERRAFORM_PLUGIN_CACHE = File.absolute_path("#{ENV['TF_PLUGIN_CACHE_DIR']}/linux_amd64" || "#{ENV['HOME']}/.terraform.d/plugin-cache/linus_amd64")
26
+
27
+ PACKER_CMD = ENV['PACKER_CMD'] || "packer"
28
+
29
+ # No-op shell command. Should not need to modify for most unix shells.
30
+ DRY_RUN_CMD = (ENV['COVALENCE_DRY_RUN_CMD'] || ":")
31
+ DEBUG_CLI = (ENV['COVALENCE_DEBUG'] || 'false') =~ (/(true|t|yes|y|1)$/i)
32
+
33
+ #DOCKER_ENV_FILE
34
+
35
+ # Internal constants
36
+ GEM_ROOT = File.expand_path('covalence', File.dirname(__FILE__)).freeze
37
+ # look into logger-colors
38
+ LOGGER = Logger.new(STDOUT)
39
+ LOG_LEVEL = String(ENV['COVALENCE_LOG'] || "warn").upcase
40
+ LOGGER.level = Logger.const_get(LOG_LEVEL)
41
+ end
@@ -0,0 +1,8 @@
1
+ require 'yaml'
2
+
3
+ require_relative '../../covalence'
4
+
5
+ Dir[File.expand_path("cli_wrappers/**/*.rb", File.dirname(__FILE__))].sort.each { |file| require file }
6
+ Dir[File.expand_path("entities/**/*.rb", File.dirname(__FILE__))].sort.each { |file| require file }
7
+ Dir[File.expand_path("repositories/**/*.rb", File.dirname(__FILE__))].sort.each { |file| require file }
8
+ Dir[File.expand_path("services/**/*.rb", File.dirname(__FILE__))].sort.each { |file| require file }
@@ -0,0 +1,9 @@
1
+ ---
2
+ version: Packer v1.0.0
3
+ commands:
4
+ build:
5
+ fix:
6
+ inspect:
7
+ push:
8
+ validate:
9
+ version:
@@ -0,0 +1,27 @@
1
+ module Covalence
2
+ class PackerCli
3
+ class << self
4
+ def require_init()
5
+ cmds_yml = File.expand_path("packer.yml", __dir__)
6
+ init_packer_cmds(cmds_yml)
7
+ end
8
+
9
+ private
10
+ def init_packer_cmds(file)
11
+ definition = YAML.load_file(file)
12
+
13
+ definition['commands'].each do |cmd, _|
14
+ packer_cmd = "packer_#{cmd}"
15
+
16
+ next if respond_to?(packer_cmd.to_sym)
17
+ define_singleton_method(packer_cmd) do |template, args: ''|
18
+ output = PopenWrapper.run([Covalence::PACKER_CMD, cmd], template, args)
19
+ (output == 0)
20
+ end #define_singleton_method
21
+ end # definition
22
+ end
23
+ end # class << self
24
+ end #PackerCli
25
+ end
26
+
27
+ Covalence::PackerCli.require_init
@@ -0,0 +1,123 @@
1
+ require 'open3'
2
+ require 'highline'
3
+
4
+ require_relative '../../../covalence'
5
+
6
+ module Covalence
7
+ class PopenWrapper
8
+ class << self
9
+
10
+ def run(cmds, path, args,
11
+ stdin_io: STDIN,
12
+ stdout_io: STDOUT,
13
+ stderr_io: STDERR,
14
+ debug: Covalence::DEBUG_CLI,
15
+ dry_run: false,
16
+ ignore_exitcode: false)
17
+
18
+ # TODO: implement path prefix for the docker runs, see @tf_cmd
19
+ cmd_string = [*cmds]
20
+ # TODO: cmd escape issues with -var.
21
+ cmd_string += [*args] unless args.blank?
22
+ cmd_string << path unless path.blank?
23
+
24
+ #TODO debug command string maybe
25
+ #TODO debug command args maybe
26
+
27
+ run_cmd = cmd_string.join(' ')
28
+ print_cmd_string(run_cmd)
29
+ if dry_run
30
+ run_cmd = Covalence::DRY_RUN_CMD
31
+ end
32
+
33
+ if debug
34
+ return 0 unless HighLine.new.agree('Execute? [y/n]')
35
+ end
36
+
37
+ spawn_subprocess(ENV, run_cmd, {
38
+ stdin_io: stdin_io,
39
+ stdout_io: stdout_io,
40
+ stderr_io: stderr_io,
41
+ ignore_exitcode: ignore_exitcode
42
+ })
43
+ end
44
+
45
+ private
46
+ def print_cmd_string(cmd_string)
47
+ Covalence::LOGGER.warn "---"
48
+ Covalence::LOGGER.warn cmd_string
49
+ end
50
+
51
+ def spawn_subprocess(env, run_cmd,
52
+ stdin_io: STDIN,
53
+ stdout_io: STDOUT,
54
+ stderr_io: STDERR,
55
+ ignore_exitcode: false)
56
+ Signal.trap("INT") {} #Disable parent process from exiting, orphaning the child fork below
57
+ wait_thread = nil
58
+
59
+ Open3.popen3(env, run_cmd) do |stdin, stdout, stderr, wait_thr|
60
+ mappings = { stdin_io => stdin, stdout => stdout_io, stderr => stderr_io }
61
+ wait_thread = wait_thr
62
+
63
+ Signal.trap("INT") {
64
+ Process.kill("INT", wait_thr.pid)
65
+ Process.wait(wait_thr.pid, Process::WNOHANG)
66
+
67
+ exit(wait_thr.value.exitstatus)
68
+ } # let SIGINT drop into the child process
69
+
70
+ handle_io_streams(mappings, stdin_io)
71
+ end
72
+
73
+ Signal.trap("INT") { exit } #Restore parent SIGINT
74
+
75
+ return 0 if ignore_exitcode
76
+ exit(wait_thread.value.exitstatus) unless wait_thread.value.success?
77
+ return wait_thread.value.exitstatus
78
+ end
79
+
80
+ def handle_io_streams(mappings, stdin_io)
81
+ inputs = mappings.keys
82
+ streams_ready_for_eof_check = []
83
+
84
+ until inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io) do
85
+
86
+ readable_inputs, _ = IO.select(inputs, [], [])
87
+ streams_ready_for_eof_check = readable_inputs
88
+
89
+ streams_ready_for_eof_check.select(&:eof).each do |src|
90
+ Covalence::LOGGER.debug "Stopping redirection from an IO in EOF: " + src.inspect
91
+ # `select`ing an IO which has reached EOF blocks forever.
92
+ # So you have to delete such IO from the array of IOs to `select`.
93
+ inputs.delete src
94
+
95
+ # You must close the child process' STDIN immeditely after the parent's STDIN reached EOF,
96
+ # or some kinds of child processes never exit.
97
+ # e.g.) echo foobar | joumae run -- cat
98
+ # After the `echo` finished outputting `foobar`, you have to tell `cat` about that or `cat` will wait for more inputs forever.
99
+ mappings[src].close if src == stdin_io
100
+ end
101
+
102
+ break if inputs.empty? || (inputs.size == 1 && inputs.first == stdin_io)
103
+
104
+ readable_inputs.each do |input|
105
+ begin
106
+ data = input.read_nonblock(1024)
107
+ output = mappings[input]
108
+ output.write(data)
109
+ output.flush
110
+ rescue EOFError => e
111
+ Covalence::LOGGER.debug "Reached EOF: #{e}"
112
+ inputs.delete input
113
+ rescue Errno::EPIPE => e
114
+ Covalence::LOGGER.debug "Handled error: #{e}: io: #{input.inspect}"
115
+ inputs.delete input
116
+ end
117
+ end #readable_inputs
118
+ end #until inputs.empty?
119
+ end #handle_io_streams
120
+
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,39 @@
1
+ ---
2
+ version: Terraform v0.10.6
3
+ commands:
4
+ apply:
5
+ #console:
6
+ destroy:
7
+ #env:
8
+ #list:
9
+ #select:
10
+ #new:
11
+ #delete:
12
+ fmt:
13
+ #force-unlock
14
+ get:
15
+ graph:
16
+ #import
17
+ #init:
18
+ #output:
19
+ plan:
20
+ providers:
21
+ push:
22
+ refresh:
23
+ show:
24
+ state:
25
+ #list:
26
+ #mv:
27
+ #pull:
28
+ #push:
29
+ #rm:
30
+ #show:
31
+ #taint:
32
+ validate:
33
+ #untaint:
34
+ workspace:
35
+ list:
36
+ select:
37
+ new:
38
+ delete:
39
+ version:
@@ -0,0 +1,119 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'active_support/core_ext/object/blank'
5
+ require 'open3'
6
+ require 'semantic'
7
+
8
+ require_relative '../../../covalence'
9
+ require_relative 'popen_wrapper'
10
+
11
+ module Covalence
12
+ class TerraformCli
13
+ def self.require_init()
14
+ if Semantic::Version.new(Covalence::TERRAFORM_VERSION) < Semantic::Version.new("0.9.0")
15
+ raise "Terraform v0.9.0 or newer required"
16
+ else
17
+ cmds_yml = File.expand_path("terraform.yml", __dir__)
18
+ end
19
+ init_terraform_cmds(cmds_yml)
20
+ end
21
+
22
+ # :reek:BooleanParameter
23
+ def self.terraform_clean(path, dry_run: false, verbose: true)
24
+ # standard run shouldn't need this since it does a chdir on a temp dir anyway
25
+ # something about cln_cmd when working with docker images
26
+ targets = [ File.join(File.expand_path(path), ".terraform") ] +
27
+ Dir.glob(File.join(File.expand_path(path), "*.tfstate*"))
28
+
29
+ FileUtils.rm_rf(targets, {
30
+ noop: dry_run,
31
+ verbose: verbose,
32
+ secure: true,
33
+ })
34
+ end
35
+
36
+ def self.terraform_check_style(path)
37
+ output, status = Open3.capture2e(ENV, Covalence::TERRAFORM_CMD, "fmt", "-write=false", path)
38
+ return false unless status.success?
39
+ output = output.split("\n")
40
+ (output.size == 0)
41
+ end
42
+
43
+ def self.terraform_init(path='', args: '', ignore_exitcode: false)
44
+ if ENV['TF_PLUGIN_LOCAL'] == 'true'
45
+ cmd = [Covalence::TERRAFORM_CMD, "init", "-get=false", "-input=false", "-plugin-dir=#{Covalence::TERRAFORM_PLUGIN_CACHE}"]
46
+ else
47
+ cmd = [Covalence::TERRAFORM_CMD, "init", "-get=false", "-input=false"]
48
+ end
49
+
50
+ output = PopenWrapper.run(
51
+ cmd,
52
+ path,
53
+ args,
54
+ ignore_exitcode: ignore_exitcode)
55
+ (output == 0)
56
+ end
57
+
58
+ def self.terraform_workspace(workspace, path='', args: '', ignore_exitcode: false)
59
+ cmd = [Covalence::TERRAFORM_CMD, "workspace", "new", workspace]
60
+
61
+ output = PopenWrapper.run(
62
+ cmd,
63
+ path,
64
+ args,
65
+ ignore_exitcode: ignore_exitcode)
66
+
67
+ (output == 0)
68
+ end
69
+
70
+ def self.terraform_output(output_var, args: '')
71
+ raise "TODO: implement me"
72
+ end
73
+
74
+ def self.terraform_taint(resource_name, args: '')
75
+ raise "TODO: implement me"
76
+ end
77
+
78
+ def self.terraform_untaint(resource_name, args: '')
79
+ raise "TODO: implement me"
80
+ end
81
+
82
+
83
+ class << self
84
+ private
85
+
86
+ # The only args that should be automated are ones that only expect some DIR/PATH as it's only
87
+ # required arg, most other things need a little bit more manual definition.
88
+ def init_terraform_cmds(file)
89
+ definition = YAML.load_file(file)
90
+
91
+ definition['commands'].each do |cmd, sub_hash|
92
+ if sub_hash.blank?
93
+ terraform_cmd = "terraform_#{cmd}"
94
+
95
+ next if respond_to?(terraform_cmd.to_sym)
96
+ define_singleton_method(terraform_cmd) do |path=Dir.pwd(), args: ''|
97
+ output = PopenWrapper.run([Covalence::TERRAFORM_CMD, cmd], path, args)
98
+ (output == 0)
99
+ end
100
+ elsif sub_hash.is_a?(Hash)
101
+ sub_hash.keys.each do |sub_command|
102
+ terraform_cmd = "terraform_#{cmd}_#{sub_command}"
103
+
104
+ next if respond_to?(terraform_cmd.to_sym)
105
+ define_singleton_method(terraform_cmd) do |path=Dir.pwd(), args: ''|
106
+ output = PopenWrapper.run([Covalence::TERRAFORM_CMD, cmd, sub_command], path, args)
107
+ (output == 0)
108
+ end
109
+ end
110
+ else
111
+ raise "Invalid yml context"
112
+ end
113
+ end
114
+ end #init_terraform_cmds
115
+ end #private
116
+ end #TerraformCli
117
+ end
118
+
119
+ Covalence::TerraformCli.require_init
@@ -0,0 +1,50 @@
1
+ require_relative '../../../covalence'
2
+ require 'yaml'
3
+ require 'hiera'
4
+
5
+ module Covalence
6
+ module HieraDB
7
+ # TODO: maybe HieraWrapper
8
+ # :reek:DataClump
9
+ class Client
10
+ attr_reader :scope
11
+
12
+ def initialize(config, scope = {})
13
+ @config = config
14
+ @scope = scope
15
+
16
+ begin
17
+ @client = Hiera.new(:config => config)
18
+ rescue RuntimeError => e
19
+ Covalence::LOGGER.error e.message
20
+ exit 1
21
+ end
22
+ end
23
+
24
+ def initialize_scope(scope)
25
+ self.class.new(@config, scope)
26
+ end
27
+
28
+ def set_scope(env, stack)
29
+ @scope = {
30
+ "environment" => env,
31
+ "stack" => stack
32
+ }
33
+ end
34
+
35
+ def lookup(key, default = nil, scope = @scope)
36
+ @client.lookup(key, default, scope)
37
+ end
38
+
39
+ def hash_lookup(key, default = nil, scope = @scope)
40
+ # https://github.com/puppetlabs/hiera/blob/d7ed74f4eec8f4fb1aa84cd0e158a595f86debd4/lib/hiera/backend.rb#L241
41
+ # def lookup(key, default, scope, order_override, resolution_type, context = {:recurse_guard => nil})
42
+ @client.lookup(key, default, scope, nil, :hash)
43
+ end
44
+
45
+ def array_lookup(key, default = nil, scope = @scope)
46
+ @client.lookup(key, default, scope, nil, :array)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_support/core_ext/object/blank'
2
+ require 'virtus'
3
+ require 'active_model'
4
+
5
+ module Covalence
6
+ # Maybe just call this targets
7
+ class Context
8
+ include Virtus.model
9
+ include ActiveModel::Validations
10
+
11
+ attribute :name, String, default: ''
12
+ attribute :values, Array, default: []
13
+
14
+ validates! :name, format: {
15
+ without: /(\s+|,)/,
16
+ message: "Context %{attribute}: \"%{value}\" cannot contain spaces or commas"
17
+ }
18
+
19
+ def initialize(attributes = {}, *args)
20
+ super
21
+ self.valid?
22
+ end
23
+
24
+ def namespace
25
+ return "" if name.blank?
26
+ "#{name}:"
27
+ end
28
+
29
+ def to_command_options
30
+ values.map { |value| "-target=\"#{value}\"" }
31
+ end
32
+
33
+ def to_packer_command_options
34
+ return "" if values.blank?
35
+ "-only=#{values.join(',')}"
36
+ end
37
+ end
38
+ end