barking_iguana-compound 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.md +179 -0
- data/Rakefile +6 -0
- data/barking_iguana-compound.gemspec +44 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/barking_iguana/compound.rb +27 -0
- data/lib/barking_iguana/compound/ansible.rb +13 -0
- data/lib/barking_iguana/compound/ansible/inventory.rb +28 -0
- data/lib/barking_iguana/compound/ansible/inventory_parser.rb +165 -0
- data/lib/barking_iguana/compound/ansible/playbook.rb +96 -0
- data/lib/barking_iguana/compound/host.rb +21 -0
- data/lib/barking_iguana/compound/host_manager.rb +63 -0
- data/lib/barking_iguana/compound/server_spec.rb +66 -0
- data/lib/barking_iguana/compound/spec_helper.rb +6 -0
- data/lib/barking_iguana/compound/test.rb +84 -0
- data/lib/barking_iguana/compound/test_stage.rb +102 -0
- data/lib/barking_iguana/compound/test_suite.rb +75 -0
- data/lib/barking_iguana/compound/vagrant.rb +79 -0
- data/lib/barking_iguana/compound/version.rb +5 -0
- data/resources/Vagrantfile.erb +18 -0
- metadata +221 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
module BarkingIguana
|
2
|
+
module Compound
|
3
|
+
module Ansible
|
4
|
+
class Playbook
|
5
|
+
attr_accessor :file, :inventory_path, :limit_pattern, :run_from, :user_name, :private_key_file, :io, :output_verbosity, :show_diff
|
6
|
+
|
7
|
+
def initialize file, run_from: nil
|
8
|
+
self.file = file
|
9
|
+
self.run_from = run_from
|
10
|
+
self.output_verbosity = 0
|
11
|
+
self.show_diff = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def verbosity n
|
15
|
+
self.output_verbosity = n.to_i
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def diff
|
20
|
+
self.show_diff = !self.show_diff
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def stream_to io
|
25
|
+
self.io = io
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def inventory name
|
30
|
+
self.inventory_path = name
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def user name
|
35
|
+
self.user_name = name
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def private_key key_file
|
40
|
+
self.private_key_file = key_file
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def limit pattern
|
45
|
+
self.limit_pattern = pattern
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def run
|
50
|
+
options = {}
|
51
|
+
options[:cwd] = run_from if run_from
|
52
|
+
options[:live_stream] = io if io
|
53
|
+
c = Mixlib::ShellOut.new(command, options)
|
54
|
+
c.run_command
|
55
|
+
c.error!
|
56
|
+
self
|
57
|
+
ensure
|
58
|
+
clean_up
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def clean_up
|
64
|
+
return unless run_from
|
65
|
+
wrapper_playbook.close
|
66
|
+
wrapper_playbook.delete
|
67
|
+
end
|
68
|
+
|
69
|
+
def playbook_path
|
70
|
+
return file unless run_from
|
71
|
+
wrapper_playbook.write wrapper_playbook_content
|
72
|
+
wrapper_playbook.rewind
|
73
|
+
wrapper_playbook.path
|
74
|
+
end
|
75
|
+
|
76
|
+
def wrapper_playbook_content
|
77
|
+
"---\n- include: #{file}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def wrapper_playbook
|
81
|
+
@wrapper_playbook ||= Tempfile.new(['wrapper-playbook-', '.yml'], run_from)
|
82
|
+
end
|
83
|
+
|
84
|
+
def command
|
85
|
+
c = "env ANSIBLE_RETRY_FILES_ENABLED=no ansible-playbook #{playbook_path} -i #{inventory_path}"
|
86
|
+
c << " -l #{limit_pattern}," unless limit_pattern.nil?
|
87
|
+
c << " -u #{user_name}" unless user_name.nil?
|
88
|
+
c << " -#{'v' * output_verbosity}" if output_verbosity > 0
|
89
|
+
c << " --diff" if show_diff
|
90
|
+
c << " --private-key=#{private_key_file}" unless private_key_file.nil?
|
91
|
+
c
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module BarkingIguana
|
2
|
+
module Compound
|
3
|
+
class Host
|
4
|
+
attr_accessor :name, :uri, :state
|
5
|
+
|
6
|
+
def initialize(name:, uri:)
|
7
|
+
self.name = name
|
8
|
+
self.uri = uri
|
9
|
+
self.state = 'unknown'
|
10
|
+
end
|
11
|
+
|
12
|
+
def ssh_key
|
13
|
+
"#{ENV['HOME']}/.vagrant.d/insecure_private_key"
|
14
|
+
end
|
15
|
+
|
16
|
+
def ssh_username
|
17
|
+
'vagrant'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module BarkingIguana
|
2
|
+
module Compound
|
3
|
+
class HostManager
|
4
|
+
attr_accessor :hosts
|
5
|
+
private :hosts=
|
6
|
+
|
7
|
+
attr_accessor :implementation
|
8
|
+
private :implementation=, :implementation
|
9
|
+
|
10
|
+
include BarkingIguana::Logging::Helper
|
11
|
+
include BarkingIguana::Benchmark
|
12
|
+
|
13
|
+
def initialize hosts = [], implementation_options = {}
|
14
|
+
self.hosts = hosts
|
15
|
+
if hosts.any? { |h| h.uri !~ /^10\.8\./ }
|
16
|
+
raise "Your hosts must be in the 10.8/16 CIDR to be managed by me"
|
17
|
+
end
|
18
|
+
self.implementation = Vagrant.new self, implementation_options
|
19
|
+
implementation.prepare
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy_all
|
23
|
+
destroy *hosts.map { |h| h.name }
|
24
|
+
end
|
25
|
+
|
26
|
+
def active
|
27
|
+
all.select { |h| h.state == 'running' }
|
28
|
+
end
|
29
|
+
|
30
|
+
def all
|
31
|
+
refresh_status
|
32
|
+
hosts
|
33
|
+
end
|
34
|
+
|
35
|
+
def refresh_status
|
36
|
+
benchmark "refreshing host status" do
|
37
|
+
implementation.refresh_status
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
{
|
42
|
+
launch: "up",
|
43
|
+
shutdown: "halt",
|
44
|
+
destroy: "destroy",
|
45
|
+
}.each do |interface, command|
|
46
|
+
define_method interface do |*host_names|
|
47
|
+
if host_names.empty?
|
48
|
+
logger.debug { "Not running anything because the hosts list is empty" }
|
49
|
+
else
|
50
|
+
host_names.sort!
|
51
|
+
benchmark "running #{interface} for #{host_names.join(', ')}" do
|
52
|
+
implementation.public_send command, *host_names
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_by_name name
|
59
|
+
all.detect { |h| h.name == name }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class ServerSpec
|
2
|
+
attr_accessor :stage
|
3
|
+
|
4
|
+
def initialize stage
|
5
|
+
self.stage = stage
|
6
|
+
end
|
7
|
+
|
8
|
+
include BarkingIguana::Logging::Helper
|
9
|
+
include BarkingIguana::Benchmark
|
10
|
+
|
11
|
+
def run
|
12
|
+
logger.debug { "Host tests: #{host_tests.inspect}" }
|
13
|
+
host_tests.each do |host_name, test_files|
|
14
|
+
command = "bundle exec ruby -S rspec -r #{control_repo_dir}/library/spec_helper.rb #{test_files.join(' ')}"
|
15
|
+
c = Mixlib::ShellOut.new command, live_stream: logger, cwd: control_repo_dir, env: env_for(host_name)
|
16
|
+
benchmark command do
|
17
|
+
c.run_command
|
18
|
+
end
|
19
|
+
c.error!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def control_repo_dir
|
24
|
+
stage.test.suite.control_directory
|
25
|
+
end
|
26
|
+
|
27
|
+
def host_for host_test
|
28
|
+
stage.test.host_manager.find_by_name host_test['name']
|
29
|
+
end
|
30
|
+
|
31
|
+
def env_for host_test
|
32
|
+
host = host_for host_test
|
33
|
+
raise "Could not find host #{host_test} in inventory #{hosts.inspect}" unless host
|
34
|
+
# TODO: Keep these in the host object
|
35
|
+
{
|
36
|
+
TARGET_HOST: host['uri'],
|
37
|
+
TARGET_SSH_USER: 'vagrant',
|
38
|
+
TARGET_SSH_KEY: "#{ENV['HOME']}/.vagrant.d/insecure_private_key"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def root_dir
|
43
|
+
stage.stage_file_root
|
44
|
+
end
|
45
|
+
|
46
|
+
def hosts
|
47
|
+
stage.test.host_manager.all
|
48
|
+
end
|
49
|
+
|
50
|
+
def host_tests
|
51
|
+
hosts.inject({}) do |a,e|
|
52
|
+
name = e.name
|
53
|
+
glob = "#{root_dir}/{#{name}/**/*_,}spec.rb"
|
54
|
+
logger.debug { "Host glob for #{name.inspect} = #{glob.inspect}" }
|
55
|
+
tests = Dir.glob glob
|
56
|
+
logger.debug { "Host tests for #{name.inspect} = #{tests.inspect}" }
|
57
|
+
if !tests.empty?
|
58
|
+
if e.state != 'running'
|
59
|
+
raise "There are tests for #{name} in #{stage.stage_directory}, but the host is #{e.state}"
|
60
|
+
end
|
61
|
+
a[e] = tests
|
62
|
+
end
|
63
|
+
a
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module BarkingIguana
|
2
|
+
module Compound
|
3
|
+
class Test
|
4
|
+
attr_accessor :suite
|
5
|
+
private :suite=
|
6
|
+
|
7
|
+
attr_accessor :directory
|
8
|
+
private :directory=
|
9
|
+
|
10
|
+
include BarkingIguana::Logging::Helper
|
11
|
+
include BarkingIguana::Benchmark
|
12
|
+
|
13
|
+
def initialize suite, directory
|
14
|
+
self.suite = suite
|
15
|
+
self.directory = directory
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
directory.sub(suite.directory + '/', '').tr('/', ':')
|
20
|
+
end
|
21
|
+
|
22
|
+
def stages
|
23
|
+
return [ TestStage.new(self, directory) ] if simple_test?
|
24
|
+
Dir[directory + '/*'].select { |d| File.directory? d }.map { |s| TestStage.new self, File.basename(s) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def simple_test?
|
28
|
+
File.exists? "#{directory}/playbook.yml"
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
benchmark "test #{name}" do
|
33
|
+
begin
|
34
|
+
stages.each &:run
|
35
|
+
ensure
|
36
|
+
teardown
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def teardown
|
42
|
+
benchmark "#{name}: destroying all hosts" do
|
43
|
+
host_manager.destroy_all
|
44
|
+
end
|
45
|
+
return unless custom_teardown?
|
46
|
+
command_line = "bash -ex #{custom_teardown_file}"
|
47
|
+
c = Mixlib::ShellOut.new command_line, cwd: directory, live_stream: logger
|
48
|
+
benchmark "#{name}: running custom teardown" do
|
49
|
+
c.run_command
|
50
|
+
end
|
51
|
+
c.error!
|
52
|
+
end
|
53
|
+
|
54
|
+
def custom_teardown?
|
55
|
+
File.exists? custom_teardown_file
|
56
|
+
end
|
57
|
+
|
58
|
+
def custom_teardown_file
|
59
|
+
File.expand_path "teardown.sh", directory
|
60
|
+
end
|
61
|
+
|
62
|
+
def host_manager
|
63
|
+
@host_manger ||= HostManager.new(hosts, driver_options)
|
64
|
+
end
|
65
|
+
|
66
|
+
def driver_options
|
67
|
+
options = {}
|
68
|
+
logger.debug { "Does #{vagrant_file_template_path} exist? -> #{File.exists? vagrant_file_template_path}" }
|
69
|
+
options[:vagrant_file_template_path] = vagrant_file_template_path if File.exists? vagrant_file_template_path
|
70
|
+
options[:root] = directory
|
71
|
+
options
|
72
|
+
end
|
73
|
+
|
74
|
+
def vagrant_file_template_path
|
75
|
+
File.join directory, 'Vagrantfile.erb'
|
76
|
+
end
|
77
|
+
|
78
|
+
def hosts
|
79
|
+
# FIXME: Implement uniqueness operators on Host
|
80
|
+
stages.map(&:hosts).flatten.uniq { |h| h.uri }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module BarkingIguana
|
2
|
+
module Compound
|
3
|
+
class TestStage
|
4
|
+
attr_accessor :test, :stage_directory
|
5
|
+
|
6
|
+
def initialize test, stage_directory
|
7
|
+
self.test = test
|
8
|
+
self.stage_directory = stage_directory
|
9
|
+
end
|
10
|
+
|
11
|
+
def actions
|
12
|
+
%i(setup converge verify).freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
include BarkingIguana::Logging::Helper
|
16
|
+
include BarkingIguana::Benchmark
|
17
|
+
|
18
|
+
def run
|
19
|
+
benchmark display_name do
|
20
|
+
actions.each do |action|
|
21
|
+
benchmark "#{display_name} action #{action}" do
|
22
|
+
public_send action
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def name
|
29
|
+
return if test.simple_test?
|
30
|
+
stage_directory
|
31
|
+
end
|
32
|
+
|
33
|
+
def display_name
|
34
|
+
return test.name if test.simple_test?
|
35
|
+
test.name + ' stage ' + stage_directory
|
36
|
+
end
|
37
|
+
|
38
|
+
def inventory_path
|
39
|
+
File.join stage_file_root, 'inventory'
|
40
|
+
end
|
41
|
+
|
42
|
+
def stage_file_root
|
43
|
+
return test.directory if test.simple_test?
|
44
|
+
File.expand_path stage_directory, test.directory
|
45
|
+
end
|
46
|
+
|
47
|
+
def playbook_path
|
48
|
+
"#{stage_file_root}/playbook.yml"
|
49
|
+
end
|
50
|
+
|
51
|
+
def inventory
|
52
|
+
Ansible.inventory inventory_path
|
53
|
+
end
|
54
|
+
|
55
|
+
def hosts
|
56
|
+
inventory.hosts
|
57
|
+
end
|
58
|
+
|
59
|
+
def control_directory
|
60
|
+
test.suite.control_directory
|
61
|
+
end
|
62
|
+
|
63
|
+
def playbook
|
64
|
+
Ansible.playbook(playbook_path, run_from: control_directory).inventory(inventory.path).private_key("#{ENV['HOME']}/.vagrant.d/insecure_private_key").user('vagrant').stream_to(logger).verbosity(2).diff
|
65
|
+
end
|
66
|
+
|
67
|
+
def suite
|
68
|
+
test.suite
|
69
|
+
end
|
70
|
+
|
71
|
+
def setup
|
72
|
+
desired_hosts = inventory.hosts.map { |h| h.name }.sort
|
73
|
+
logger.debug { "Desired hosts for #{display_name}: #{desired_hosts.join(', ')}" }
|
74
|
+
active_hosts = host_manager.active.map { |h| h.name }.sort
|
75
|
+
logger.debug { "Active hosts for #{display_name}: #{active_hosts.join(', ')}" }
|
76
|
+
hosts_to_launch = desired_hosts - active_hosts
|
77
|
+
logger.debug { "Launch hosts for #{display_name}: #{hosts_to_launch.join(', ')}" }
|
78
|
+
host_manager.launch *hosts_to_launch
|
79
|
+
hosts_to_stop = active_hosts - desired_hosts
|
80
|
+
logger.debug { "Stop hosts for #{display_name}: #{hosts_to_stop.join(', ')}" }
|
81
|
+
host_manager.shutdown *hosts_to_stop
|
82
|
+
end
|
83
|
+
|
84
|
+
def host_manager
|
85
|
+
test.host_manager
|
86
|
+
end
|
87
|
+
|
88
|
+
def converge
|
89
|
+
return unless File.exists? playbook_path
|
90
|
+
playbook.run
|
91
|
+
end
|
92
|
+
|
93
|
+
def verify
|
94
|
+
server_spec.run
|
95
|
+
end
|
96
|
+
|
97
|
+
def server_spec
|
98
|
+
@server_spec ||= ServerSpec.new(self)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|