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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3c49e4fa77451a0f78931175dfeedfa2736d3a8
4
+ data.tar.gz: dc148ad917a0182a1a8e94db603a5d12f53c5a85
5
+ SHA512:
6
+ metadata.gz: f4a53fe0a6346889d69ccf9fa1933c4743ae4932afbde85799237ac837be6c10bb538cb58b14d202a2a53a12b9a4b2e1c79e2dcbe0a0b380312b93badc712eb3
7
+ data.tar.gz: 4890023cfae6d8c76e4fa97fa9cceb14f39930a37861b73f7a35eea57b0f96eaaf0d114a6ff684ff283b0083d9c5d11d9e572e933e8c3b12d31fd1c907c47f36
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in barking_iguana-compound.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # BarkingIguana::Compound
2
+
3
+ Compound testing for Ansible atoms.
4
+
5
+ When you need to test several roles and/or playbooks together over their lifetime e.g. testing database failover and recovery.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'barking_iguana-compound'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install barking_iguana-compound
22
+
23
+ ## Usage
24
+
25
+ Install it, as per above.
26
+
27
+ Normally you'll use Compond as part of a Rake task, so include it in your
28
+ `Rakefile`. Tell it to define Rake tasks for your compound tests - we assume
29
+ these live in `test/compound` - and where your playbooks live, your control
30
+ repository:
31
+
32
+ ```ruby
33
+ require 'barking_iguana/compound'
34
+ ansible_control_repo = File.dirname __FILE__
35
+ BarkingIguana::Compound.new('test/compound', ansible_control_repo).define_rake_tasks
36
+ ```
37
+
38
+ Now let's define a test managing hosts files. It's a trivial test, but it
39
+ suffices to demonstrate the capacilities of Compound.
40
+
41
+ ### Writing a compound test
42
+
43
+ Create a directory for the test:
44
+
45
+ $ mkdir -p test/compound/hosts_file_management
46
+
47
+ Each test will have several _stages_, which represent parts of the expected
48
+ lifecycle of your servers. For example, you set up your servers using Ansible,
49
+ to you probably have a setup stage. You may be setting up a HA cluster, so
50
+ perhaps you have a stage where the master in the cluster fails.
51
+
52
+ Each stage involves several actions. We'll cover them in detail below, but in
53
+ summary:
54
+
55
+ The `setup` action runs first. For each stage this action decides which virtual
56
+ machines should be powered on or off during the rest of the stage.
57
+
58
+ Next, the `converge` action runs the playbook for that stage, if one is provided
59
+ by you, to allow things to happen. The playbook may apply roles or kill
60
+ processes, whatever you need to simulate what's happening to your servers at
61
+ that part of the lifecycle.
62
+
63
+ Finally, the `verify` stage runs some serverspec tests which you will define
64
+ against the results of the `converge` action. This allows you to check what
65
+ happens after the tasks in the playbook have been run e.g. you may verify that a
66
+ standby server has become the master.
67
+
68
+ Each stage lives in a directory inside the test. They're executed in
69
+ alphabetical order, and you are encouraged to prefix each stage with numbers to
70
+ make it very clear which one should execute in which order.
71
+
72
+ We'll cover each of those actions in greater detail now, for the setup stage of
73
+ our hosts file example.
74
+
75
+ #### Test Stages
76
+
77
+ Start by creating the directory for that stage:
78
+
79
+ $ mkdir -p test/compound/hosts_file_management/000-setup
80
+
81
+ ##### The Setup Action
82
+
83
+ This action controls which virtual machines are available for you to work with,
84
+ by looking at an Ansible inventory file in the stage directory and starting all
85
+ the hosts it finds.
86
+
87
+ Let's create a simple inventory file with 2 hosts and 1 group.
88
+
89
+ The inventory file for each stage lives in the stage directory in a file called
90
+ `inventory`, so in our example so for that's `test/compound/hosts_file_management/000-setup/inventory`.
91
+
92
+ They're just normal Ansible inventory files, with only one restriction: the
93
+ `ansible_host` _must_ start with `10.8.`.
94
+
95
+ ```
96
+ [linux]
97
+ host001 ansible_host=10.8.100.11
98
+ host002 ansible_host=10.8.100.12
99
+ ```
100
+
101
+ When the setup action for a stage with this inventory is run, Compound will
102
+ launch two virtual machines for you to test with, `host001` and `host002` with
103
+ the respective IP addresses.
104
+
105
+ ##### The Converge Action
106
+
107
+ Now that virtual machines are available to use, the converge action can run
108
+ the playbook for your stage against them.
109
+
110
+ Playbooks live in the stage directory, in a file called `playbook.yml`. I'm
111
+ inventive like that. For our example stage that's `test/compound/hosts_file_management/000-setup/playbook.yml`.
112
+
113
+ For our setup action we'll apply the `hosts` role to all hosts.
114
+
115
+ ```
116
+ ---
117
+ - hosts: all
118
+ roles:
119
+ - hosts
120
+ ```
121
+
122
+ After a converge, Compound will attempt to verify whatever you ask it to.
123
+ Onwards, to the verify action.
124
+
125
+ ##### The Verify Action
126
+
127
+ Now we run automated tests to assert that our virtual machines behave like we
128
+ think they should, after the stage playbook has been applied in the converge
129
+ action.
130
+
131
+ The verify action is based around serverspec tests, arranged around the name of
132
+ the host which they test.
133
+
134
+ For example, to test `host001` we'd create tests under a `test/compound/hosts_file_management/host001/`
135
+ directory. Each test file must be suffixed with `_spec.rb`. A simple example
136
+ would be checking that the other host of the pair is resolvable now that the
137
+ hosts role has been applied to the hosts:
138
+
139
+ ```ruby
140
+ # test/compound/hosts_file_management/host001/resolving_spec.rb
141
+ describe host('host002') do
142
+ it "is resolvable" do
143
+ expect(subject).to be_resolvable
144
+ end
145
+ end
146
+ ```
147
+
148
+ ```ruby
149
+ # test/compound/hosts_file_management/host002/resolving_spec.rb
150
+ describe host('host001') do
151
+ it "is resolvable" do
152
+ expect(subject).to be_resolvable
153
+ end
154
+ end
155
+ ```
156
+
157
+ Compound will take the appropriate actions to make sure your tests are run on
158
+ the correct hosts, you don't need to worry about SSH keys, passwords or ports.
159
+
160
+ ### Running the tests
161
+
162
+ Since we've asked Compound to define rake tasks above, we can run those. The tasks generated are based on the directory names we use in the tests. The above test can be run like this:
163
+
164
+ $ bundle exec rake compound:hosts_file_management
165
+
166
+ You can see a list of all tests by asking Rake to list them:
167
+
168
+ $ bundle exec rake -T
169
+
170
+ ## Development
171
+
172
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
173
+
174
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
175
+
176
+ ## Contributing
177
+
178
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/barking_iguana-compound.
179
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'barking_iguana/compound/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "barking_iguana-compound"
8
+ spec.version = BarkingIguana::Compound::VERSION
9
+ spec.authors = ["Craig R Webster"]
10
+ spec.email = ["craig@barkingiguana.com"]
11
+
12
+ spec.summary = %q{Compound testing of Ansible playbooks}
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/barkingiguana/compound"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against " \
22
+ "public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.13"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
35
+
36
+ spec.add_dependency 'barking_iguana-logging'
37
+ spec.add_dependency 'barking_iguana-benchmark'
38
+ spec.add_dependency 'mixlib-shellout'
39
+ spec.add_dependency 'rspec-wait'
40
+ spec.add_dependency 'ansible_spec'
41
+ spec.add_dependency 'colorize'
42
+ spec.add_dependency 'hostlist_expression'
43
+ spec.add_dependency 'oj'
44
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "barking_iguana/compound"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ require 'barking_iguana/logging'
2
+ require 'barking_iguana/benchmark'
3
+
4
+ require 'erb'
5
+ require 'mixlib/shellout'
6
+ require 'hostlist_expression'
7
+ require 'oj'
8
+ require 'yaml'
9
+
10
+ require 'barking_iguana/compound/version'
11
+ require 'barking_iguana/compound/ansible'
12
+ require 'barking_iguana/compound/ansible/inventory'
13
+ require 'barking_iguana/compound/ansible/inventory_parser'
14
+ require 'barking_iguana/compound/ansible/playbook'
15
+ require 'barking_iguana/compound/test_stage'
16
+ require 'barking_iguana/compound/test'
17
+ require 'barking_iguana/compound/test_suite'
18
+ require 'barking_iguana/compound/host_manager'
19
+ require 'barking_iguana/compound/host'
20
+ require 'barking_iguana/compound/vagrant'
21
+
22
+ module BarkingIguana
23
+ module Compound
24
+ end
25
+ end
26
+
27
+ BarkingIguana::Logging.default_level = Logger.const_get(ENV['LOG_LEVEL'].upcase) if ENV['LOG_LEVEL']
@@ -0,0 +1,13 @@
1
+ module BarkingIguana
2
+ module Compound
3
+ module Ansible
4
+ def self.inventory *args
5
+ Inventory.new *args
6
+ end
7
+
8
+ def self.playbook *args
9
+ Playbook.new *args
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ module BarkingIguana
2
+ module Compound
3
+ module Ansible
4
+ class Inventory
5
+ attr_accessor :path
6
+ private :path=
7
+
8
+ def initialize path
9
+ self.path = path
10
+ end
11
+
12
+ def hostgroups
13
+ InventoryParser.load_targets path
14
+ end
15
+
16
+ def hosts
17
+ h = hostgroups
18
+ h.reject! { |hg| hg[0].empty? }
19
+ hosts = h.values.flatten.uniq { |h| h['uri'] }
20
+ hosts.map do |data|
21
+ name = data['name'].gsub(/ .*/, '')
22
+ Host.new name: name, uri: data['uri']
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,165 @@
1
+ module BarkingIguana
2
+ module Compound
3
+ module Ansible
4
+ class InventoryParser
5
+ def self.get_variables(host, group_idx, hosts=nil)
6
+ vars = {}
7
+ p = self.get_properties
8
+
9
+ # roles default
10
+ p[group_idx]['roles'].each do |role|
11
+ vars = load_vars_file(vars ,"roles/#{role}/defaults/main.yml")
12
+ end
13
+
14
+ # all group
15
+ vars = load_vars_file(vars ,'group_vars/all', true)
16
+
17
+ # each group vars
18
+ if p[group_idx].has_key?('group')
19
+ vars = load_vars_file(vars ,"group_vars/#{p[group_idx]['group']}", true)
20
+ end
21
+
22
+ # each host vars
23
+ vars = load_vars_file(vars ,"host_vars/#{host}", true)
24
+
25
+ # site vars
26
+ if p[group_idx].has_key?('vars')
27
+ vars = merge_variables(vars, p[group_idx]['vars'])
28
+ end
29
+
30
+ # roles vars
31
+ p[group_idx]['roles'].each do |role|
32
+ vars = load_vars_file(vars ,"roles/#{role}/vars/main.yml")
33
+ end
34
+
35
+ # multiple host and children dependencies group vars
36
+ unless hosts.nil? || p[group_idx]["hosts_childrens"].nil?
37
+ hosts_childrens = p[group_idx]["hosts_childrens"]
38
+ next_find_target = hosts
39
+ while(!next_find_target.nil? && hosts_childrens.size > 0)
40
+ vars = load_vars_file(vars ,"group_vars/#{next_find_target}", true)
41
+ group_vars_file = find_group_vars_file(hosts_childrens,next_find_target)
42
+ next_find_target = group_vars_file
43
+ hosts_childrens.delete(group_vars_file)
44
+ end
45
+ end
46
+
47
+ return vars
48
+
49
+ end
50
+
51
+ # param hash {"server"=>["192.168.0.103"], "databases"=>["192.168.0.104"], "pg:children"=>["server", "databases"]}
52
+ # param search ":children"
53
+ # param k "pg:children"
54
+ # return {"server"=>["192.168.0.103"], "databases"=>["192.168.0.104"], "pg"=>["192.168.0.103", "192.168.0.104"]}
55
+ def self.get_parent(hash,search,k)
56
+ k_parent = k.gsub(search,'')
57
+ arry = Array.new
58
+ hash["#{k}"].each{|group|
59
+ next if hash["#{group}"].nil?
60
+ arry = arry + hash["#{group}"]
61
+ }
62
+ h = Hash.new
63
+ h["#{k_parent}"] = arry
64
+ return h
65
+ end
66
+
67
+ def self.load_targets(file)
68
+ f = File.open(file).read
69
+ groups = Hash.new
70
+ group = ''
71
+ hosts = Hash.new
72
+ hosts.default = Hash.new
73
+ f.each_line{|line|
74
+ line = line.chomp
75
+ # skip
76
+ next if line.start_with?('#') #comment
77
+ next if line.empty? == true #null
78
+
79
+ # get group
80
+ if line.start_with?('[') && line.end_with?(']')
81
+ group = line.gsub('[','').gsub(']','')
82
+ groups["#{group}"] = Array.new
83
+ next
84
+ end
85
+
86
+ # get host
87
+ host_name = line.split[0]
88
+ if group.empty? == false
89
+ if groups.has_key?(line)
90
+ groups["#{group}"] << line
91
+ next
92
+ elsif host_name.include?("[") && host_name.include?("]")
93
+ # www[01:50].example.com
94
+ # db-[a:f].example.com
95
+ hostlist_expression(line,":").each{|h|
96
+ host = hosts[h.split[0]]
97
+ groups["#{group}"] << get_inventory_param(h).merge(host)
98
+ }
99
+ next
100
+ else
101
+ # 1つのみ、かつ:を含まない場合
102
+ # 192.168.0.1
103
+ # 192.168.0.1 ansible_ssh_host=127.0.0.1 ...
104
+ host = hosts[host_name]
105
+ groups["#{group}"] << get_inventory_param(line).merge(host)
106
+ next
107
+ end
108
+ else
109
+ if host_name.include?("[") && host_name.include?("]")
110
+ hostlist_expression(line, ":").each{|h|
111
+ hosts[h.split[0]] = get_inventory_param(h)
112
+ }
113
+ else
114
+ hosts[host_name] = get_inventory_param(line)
115
+ end
116
+ end
117
+ }
118
+
119
+ # parse children [group:children]
120
+ search = Regexp.new(":children".to_s)
121
+ groups.keys.each{|k|
122
+ unless (k =~ search).nil?
123
+ # get group parent & merge parent
124
+ groups.merge!(get_parent(groups,search,k))
125
+ # delete group children
126
+ if groups.has_key?("#{k}") && groups.has_key?("#{k.gsub(search,'')}")
127
+ groups.delete("#{k}")
128
+ end
129
+ end
130
+ }
131
+ return groups
132
+ end
133
+
134
+ # param ansible_ssh_port=22
135
+ # return: hash
136
+ def self.get_inventory_param(line)
137
+ host = Hash.new
138
+ # 初期値
139
+ host['name'] = line
140
+ host['port'] = 22
141
+ if line.include?(":") # 192.168.0.1:22
142
+ host['uri'] = line.split(":")[0]
143
+ host['port'] = line.split(":")[1].to_i
144
+ return host
145
+ end
146
+ # 192.168.0.1 ansible_ssh_port=22
147
+ line.split.each{|v|
148
+ unless v.include?("=")
149
+ host['uri'] = v
150
+ else
151
+ key,value = v.split("=")
152
+ host['port'] = value.to_i if key == "ansible_ssh_port" or key == "ansible_port"
153
+ host['private_key'] = value if key == "ansible_ssh_private_key_file"
154
+ host['user'] = value if key == "ansible_ssh_user" or key == "ansible_user"
155
+ host['uri'] = value if key == "ansible_ssh_host" or key == "ansible_host"
156
+ host['pass'] = value if key == "ansible_ssh_pass"
157
+ host['connection'] = value if key == "ansible_connection"
158
+ end
159
+ }
160
+ return host
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end