barking_iguana-compound 0.1.1 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 424d8a8ee3ddc8e5aae5daf84f27788f6f2fd77e
4
- data.tar.gz: fb4124b4fa642abb33d64cb69532d2f991482ec6
3
+ metadata.gz: 660f437659c9d71510010c474a79652d23ae8f72
4
+ data.tar.gz: 6d9144927d86139196261607bffe1087ec1a60b9
5
5
  SHA512:
6
- metadata.gz: 39f6d055963b1e84e99362f32c9d0093cb92cd001867e26acb33b2734b97cf7935821ae4087d70441634a62298e49a144b4f2565dfb5ac48cdeae93f9d07e567
7
- data.tar.gz: e40b47bd3410c0b27a073fc7e42feb67e5d455b4171a28acb240fdcd5888eab8d02498282acaf4881130072a299e38b7c5501d25d637b752cd010d0e9ce76356
6
+ metadata.gz: 22aac3dbdb690d0b08230002e1a6391c8dbbc5b6c729064f1860027f552f81a7ad26b8906e4f8c223f2c6a3d0c54547d68cd2610f22d9073e4e02030dfcd5c5c
7
+ data.tar.gz: d2e833cc167db10884952f020accb87780b9341e0cac9264571d6548d1ae7c7d73bcd60bfdf78005fa78a76f03744619c278ffb24b1832cc54726000000f5c76
data/README.md CHANGED
@@ -1,179 +1,5 @@
1
- # BarkingIguana::Compound
1
+ # Compound
2
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.
3
+ For docs see [`docs/index.md`][0].
179
4
 
5
+ [0]: https://barkingiguana.github.io/compound/
data/docs/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # CHANGELOG
2
+
3
+ Entries are in reverse chronological order.
4
+
5
+ ## *0.1.5* (Current Development)
6
+
7
+ Add release notes here, as things are added to the project.
8
+
9
+ ## *0.1.4* (2016-12-27)
10
+
11
+ * Bug fix: allow connecting to VMs when we generate their IP address on
12
+ the fly.
13
+
14
+ * Bug fix: correctly expand the staging directory when searching for
15
+ the stages `playbook.yml` or `inventory` file.
16
+
17
+ ## *0.1.3* (2016-12-27)
18
+
19
+ * Various bug fixes - mostly typos that would stop the full test run
20
+ happening.
21
+
22
+ * Add a command line tool, so we don't need the `Rakefile`. Currently this
23
+ only handles running the entire test suite.
24
+
25
+ * Stop requiring that the inventory files specify an IP address. If no
26
+ IP address is provided for a VM it will be assigned an unallocated one.
27
+
28
+ * Simplify asking Compound to define Rake tasks by allowing it to guess
29
+ the directories involved. These can still be overridden, but they
30
+ shouldn't normally need to be.
31
+
32
+ * Stop supporting the concept of 'simple' tests - tests which have only
33
+ one implicit stage. Compound feels most useful when focussing on
34
+ lifecycle style tests which have several stages.
35
+
36
+ ## *0.1.2* (2016-12-16)
37
+
38
+ Allow a default inventory and playbook for each test, living in the test
39
+ directory instead of in each stage. This is less likely to be useful for
40
+ the playbook, but a default inventory will reduce duplication.
41
+
42
+ ## *0.1.1* (2016-12-16)
43
+
44
+ Symlink wrapper playbooks instead of writing temporary playbooks with
45
+ includes.
46
+
47
+ ## *0.1.0* (2016-12-11)
48
+
49
+ Initial release.
data/docs/TODO.md ADDED
@@ -0,0 +1,12 @@
1
+ # TODO
2
+
3
+ * We can add machines to different networks because even though we require
4
+ `10.8.*`, the networks created are `/24`'s. These should either be `/16` to
5
+ avoid surprises, or we should make the CIDR configurable, or we should give an
6
+ example showing this behaviour.
7
+
8
+ * Tests. So ironic that a testing tool has no tests. For release 1.0.0 we need tests.
9
+
10
+ * A host doesn't have a `uri`, it's got an IP address.
11
+
12
+ * We should support the ansible remote user attributes in the inventory, for each host.
data/docs/_config.yml ADDED
@@ -0,0 +1 @@
1
+ theme: jekyll-theme-minimal
data/docs/index.md ADDED
@@ -0,0 +1,232 @@
1
+ # BarkingIguana::Compound
2
+
3
+ Compound testing for Ansible atoms.
4
+
5
+ Sometimes we need to test the interaction of several roles and/or playbooks,
6
+ especially over the expected lifetime of our tech stack.
7
+
8
+ Say we have a typical three tier web app; web, app and database tiers.
9
+
10
+ When an app server is put into maintenance mode, does the reverse proxy on the
11
+ web tier pick that up? Does it reconfigure the pool so that requests no longer
12
+ hit that app server? Does it do so in a reasonable time?
13
+
14
+ Previously we could test that the configuration of the reverse proxy was what we
15
+ expected to see on disk, but testing that it behaved in the way that we thought
16
+ it would was a case of deploying to a test servers and poking at the result
17
+ manually. Error prone, slow, not terribly maintainable, and it can in many cases
18
+ block uses of a server that it typically a shared resource. Very not fun.
19
+
20
+ With Compound, we can write Ansible inventories to represent real world
21
+ situations, run playbooks against them to install and configure the software we
22
+ want to test or simulate real world behaviour such as turning on maintenance
23
+ mode, and then assert the behaviour we expect using serverspec style tests.
24
+
25
+ ## Installation
26
+
27
+ I assume you have Ruby and `bundler` already installed, because I'm a Ruby
28
+ developer and this is a Ruby project. If you don't, you'll need to install
29
+ them now.
30
+
31
+ If you don't already have one, create a file called `Gemfile` in the root of
32
+ your Ansible control repository.
33
+
34
+ Add this line to your `Gemfile`:
35
+
36
+ ```ruby
37
+ gem 'barking_iguana-compound'
38
+ ```
39
+
40
+ And then execute:
41
+
42
+ $ bundle
43
+
44
+ Or install it yourself by running:
45
+
46
+ $ gem install barking_iguana-compound
47
+
48
+ You'll also need Vagrant, and VirtualBox, installed where you'd like to run
49
+ your tests.
50
+
51
+ ## Usage
52
+
53
+ Install it, as per above.
54
+
55
+ Normally you'll use Compond as part of a Rake task, so include it in a
56
+ `Rakefile` in the root of your project.
57
+
58
+ Tell Compound to define Rake tasks for your compound tests:
59
+
60
+ ```ruby
61
+ require 'barking_iguana/compound'
62
+ BarkingIguana::Compound::TestSuite.define_rake_tasks
63
+ ```
64
+
65
+ Now let's define a test managing hosts files. It's a trivial test, but it
66
+ suffices to demonstrate the capacilities of Compound.
67
+
68
+ ### Writing a compound test
69
+
70
+ Create a directory for the test:
71
+
72
+ $ mkdir -p test/compound/hosts_file_management
73
+
74
+ Each test will have several _stages_, which represent parts of the expected
75
+ lifecycle of your servers. For example, you set up your servers using Ansible,
76
+ to you probably have a setup stage. You may be setting up a HA cluster, so
77
+ perhaps you have a stage where the master in the cluster fails.
78
+
79
+ Each stage involves several actions. We'll cover them in detail below, but in
80
+ summary:
81
+
82
+ The `setup` action runs first. For each stage this action decides which virtual
83
+ machines should be powered on or off during the rest of the stage.
84
+
85
+ Next, the `converge` action runs the playbook for that stage, if one is provided
86
+ by you, to allow things to happen. The playbook may apply roles or kill
87
+ processes, whatever you need to simulate what's happening to your servers at
88
+ that part of the lifecycle.
89
+
90
+ Finally, the `verify` stage runs some serverspec tests which you will define
91
+ against the results of the `converge` action. This allows you to check what
92
+ happens after the tasks in the playbook have been run e.g. you may verify that a
93
+ standby server has become the master.
94
+
95
+ Each stage lives in a directory inside the test. They're executed in
96
+ alphabetical order, and you are encouraged to prefix each stage with numbers to
97
+ make it very clear which one should execute in which order.
98
+
99
+ We'll cover each of those actions in greater detail now, for the setup stage of
100
+ our hosts file example.
101
+
102
+ #### Test Stages
103
+
104
+ Start by creating the directory for that stage:
105
+
106
+ $ mkdir -p test/compound/hosts_file_management/000-setup
107
+
108
+ ##### The Setup Action
109
+
110
+ This action controls which virtual machines are available for you to work with,
111
+ by looking at an Ansible inventory file in the stage directory and starting all
112
+ the hosts it finds.
113
+
114
+ Let's create a simple inventory file with 2 hosts and 1 group.
115
+
116
+ The inventory file for each stage lives in the stage directory in a file called
117
+ `inventory`, so in our example so for that's `test/compound/hosts_file_management/000-setup/inventory`.
118
+
119
+ These are normal Ansible inventory files:
120
+
121
+ ```
122
+ [all:children]
123
+ web
124
+ application
125
+ database
126
+
127
+ [web]
128
+ host001
129
+
130
+ [application]
131
+ host002
132
+
133
+ [database]
134
+ host003
135
+ ```
136
+
137
+ When the setup action for a stage with this inventory is run, Compound will
138
+ launch three virtual machines for you to test with, `host001`, `host002`, and
139
+ `host003`. It will assign them IP addresses in the `10.8.42/24` range.
140
+
141
+ ##### The Converge Action
142
+
143
+ Now that virtual machines are available to use, the converge action can run
144
+ the playbook for your stage against them.
145
+
146
+ Playbooks live in the stage directory, in a file called `playbook.yml`. I'm
147
+ inventive like that. For our example stage that's `test/compound/hosts_file_management/000-setup/playbook.yml`.
148
+
149
+ For our setup action we'll apply the `hosts` role to all hosts.
150
+
151
+ ```
152
+ ---
153
+ - hosts: all
154
+ roles:
155
+ - hosts
156
+ ```
157
+
158
+ After a converge, Compound will attempt to verify whatever you ask it to.
159
+ Onwards, to the verify action.
160
+
161
+ ##### The Verify Action
162
+
163
+ Now we run automated tests to assert that our virtual machines behave like we
164
+ think they should, after the stage playbook has been applied in the converge
165
+ action.
166
+
167
+ The verify action is based around serverspec tests, arranged around the name of
168
+ the host which they test.
169
+
170
+ For example, to test `host001` we'd create tests under a `test/compound/hosts_file_management/host001/`
171
+ directory. Each test file must be suffixed with `_spec.rb`. A simple example
172
+ would be checking that the other host of the pair is resolvable now that the
173
+ hosts role has been applied to the hosts:
174
+
175
+ ```ruby
176
+ # test/compound/hosts_file_management/host001/resolving_spec.rb
177
+ describe host('host002') do
178
+ it "is resolvable" do
179
+ expect(subject).to be_resolvable
180
+ end
181
+ end
182
+ ```
183
+
184
+ ```ruby
185
+ # test/compound/hosts_file_management/host002/resolving_spec.rb
186
+ describe host('host001') do
187
+ it "is resolvable" do
188
+ expect(subject).to be_resolvable
189
+ end
190
+ end
191
+ ```
192
+
193
+ Compound will take the appropriate actions to make sure your tests are run on
194
+ the correct hosts, you don't need to worry about SSH keys, passwords or ports.
195
+
196
+ ### Running the tests
197
+
198
+ Since we've asked Compound to define rake tasks above, we can run those. The
199
+ tasks generated are based on the directory names we use in the tests. The above
200
+ test can be run like this:
201
+
202
+ $ bundle exec rake compound:hosts_file_management
203
+
204
+ You can see a list of all tests by asking Rake to list them:
205
+
206
+ $ bundle exec rake -T
207
+
208
+ ## Development
209
+
210
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
211
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
212
+ prompt that will allow you to experiment.
213
+
214
+ To install this gem onto your local machine, run `bundle exec rake install`. To
215
+ release a new version, update the version number in `version.rb`, and then run
216
+ `bundle exec rake release`, which will create a git tag for the version, push
217
+ git commits and tags, and push the `.gem` file to [rubygems.org][0].
218
+
219
+ ## Contributing
220
+
221
+ Bug reports and pull requests are welcome on GitHub at https://github.com/barkingiguana/compound.
222
+
223
+ If you'd like to contribute features, please do discuss them by opening an issue on GitHub.
224
+
225
+ Some things I'd like to tackle are listed in [docs/TODO.md][1].
226
+
227
+ Please keep [docs/CHANGELOG.md][2] updated as you add/remove/change things.
228
+
229
+
230
+ [0]: https://rubygems.org
231
+ [1]: http://barkingiguana.com/compound/TODO
232
+ [2]: http://barkingiguana.com/compound/CHANGELOG
data/exe/compound ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'barking_iguana/compound'
4
+
5
+ BarkingIguana::Compound::CommandLineClient.new(ARGV).run
@@ -19,7 +19,7 @@ module BarkingIguana
19
19
  hosts = h.values.flatten.uniq { |h| h['uri'] }
20
20
  hosts.map do |data|
21
21
  name = data['name'].gsub(/ .*/, '')
22
- Host.new name: name, uri: data['uri']
22
+ Host.new name: name, ip_address: data['uri']
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,40 @@
1
+ module BarkingIguana
2
+ module Compound
3
+ module Ansible
4
+ class InventoryWriter
5
+ attr_accessor :path
6
+ private :path=
7
+
8
+ attr_accessor :hosts
9
+ private :hosts=, :hosts
10
+
11
+ def initialize path = nil
12
+ self.path = path || generate_random_filename
13
+ self.hosts = []
14
+ end
15
+
16
+ def add_host host
17
+ hosts << host
18
+ hosts.uniq! &:ip_address
19
+ end
20
+
21
+ def generate_random_filename
22
+ name = [
23
+ 'inventory',
24
+ Process.pid,
25
+ Time.now.to_i,
26
+ rand(9_999_999_999).to_s.rjust(10, '0')
27
+ ].join('-')
28
+ end
29
+
30
+ def write_file
31
+ File.open path, 'w' do |inventory|
32
+ hosts.sort_by(&:name).each do |h|
33
+ inventory.puts "#{h.inventory_name} ansible_host=#{h.ip_address} ansible_user=#{h.ssh_username} ansible_ssh_private_key_path=#{h.ssh_key} ansible_ssh_extra_args=\"#{h.ssh_extra_args}\""
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -2,13 +2,14 @@ module BarkingIguana
2
2
  module Compound
3
3
  module Ansible
4
4
  class Playbook
5
- attr_accessor :file, :inventory_path, :limit_pattern, :run_from, :user_name, :private_key_file, :io, :output_verbosity, :show_diff
5
+ attr_accessor :file, :inventory_paths, :limit_pattern, :run_from, :user_name, :private_key_file, :io, :output_verbosity, :show_diff
6
6
 
7
7
  def initialize file, run_from: nil
8
8
  self.file = file
9
9
  self.run_from = run_from
10
10
  self.output_verbosity = 0
11
11
  self.show_diff = false
12
+ self.inventory_paths = []
12
13
  end
13
14
 
14
15
  def verbosity n
@@ -27,7 +28,7 @@ module BarkingIguana
27
28
  end
28
29
 
29
30
  def inventory name
30
- self.inventory_path = name
31
+ self.inventory_paths << name
31
32
  self
32
33
  end
33
34
 
@@ -67,17 +68,13 @@ module BarkingIguana
67
68
 
68
69
  # TODO: Symlink the playbook to wrapper_playbook.path
69
70
  # so we can use the group_vars, host_vars, etc.
70
- def playbook_path
71
+ def playbook_paths
71
72
  return file unless run_from
72
73
  tempfile = wrapper_playbook
73
74
  FileUtils.symlink file, tempfile
74
75
  tempfile
75
76
  end
76
77
 
77
- def wrapper_playbook_content
78
- "---\n- include: #{file}"
79
- end
80
-
81
78
  def wrapper_playbook
82
79
  @wrapper_playbook ||= begin
83
80
  name = [
@@ -91,13 +88,16 @@ module BarkingIguana
91
88
  end
92
89
 
93
90
  def command
94
- c = "env ANSIBLE_RETRY_FILES_ENABLED=no ansible-playbook #{playbook_path} -i #{inventory_path}"
95
- c << " -l #{limit_pattern}," unless limit_pattern.nil?
96
- c << " -u #{user_name}" unless user_name.nil?
97
- c << " -#{'v' * output_verbosity}" if output_verbosity > 0
98
- c << " --diff" if show_diff
99
- c << " --private-key=#{private_key_file}" unless private_key_file.nil?
100
- c
91
+ c = ["env ANSIBLE_RETRY_FILES_ENABLED=no ansible-playbook #{playbook_paths}"]
92
+ inventory_paths.each do |i|
93
+ c << "-i #{i}"
94
+ end
95
+ c << "-l #{limit_pattern}," unless limit_pattern.nil?
96
+ c << "-u #{user_name}" unless user_name.nil?
97
+ c << "-#{'v' * output_verbosity}" if output_verbosity > 0
98
+ c << "--diff" if show_diff
99
+ c << "--private-key=#{private_key_file}" unless private_key_file.nil?
100
+ c.join ' '
101
101
  end
102
102
  end
103
103
  end
@@ -0,0 +1,25 @@
1
+ module BarkingIguana
2
+ module Compound
3
+ class CommandLineClient
4
+ attr_accessor :argv
5
+ private :argv=, :argv
6
+
7
+ def initialize argv
8
+ self.argv = argv
9
+ end
10
+
11
+ def run
12
+ # TODO: Make it possible to select the test to run if desired
13
+ test_suite.run
14
+ end
15
+
16
+ def test_suite
17
+ @test_suite ||= TestSuite.new('test/compound', control_directory: working_directory)
18
+ end
19
+
20
+ def working_directory
21
+ Dir.pwd
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,14 +1,27 @@
1
1
  module BarkingIguana
2
2
  module Compound
3
3
  class Host
4
- attr_accessor :name, :uri, :state
4
+ attr_accessor :inventory_name, :ip_address, :state
5
5
 
6
- def initialize(name:, uri:)
7
- self.name = name
8
- self.uri = uri
6
+ def initialize(name:, ip_address:)
7
+ self.inventory_name = name
8
+ self.ip_address = ip_address
9
9
  self.state = 'unknown'
10
10
  end
11
11
 
12
+ def name
13
+ @name ||= inventory_name.gsub(/[^a-z0-9\-]/, '-')
14
+ end
15
+
16
+ def <=> other
17
+ name <=> other.name
18
+ end
19
+ include Comparable
20
+
21
+ def assign_ip_address new_ip_address
22
+ self.ip_address = new_ip_address
23
+ end
24
+
12
25
  def ssh_key
13
26
  "#{ENV['HOME']}/.vagrant.d/insecure_private_key"
14
27
  end
@@ -16,6 +29,10 @@ module BarkingIguana
16
29
  def ssh_username
17
30
  'vagrant'
18
31
  end
32
+
33
+ def ssh_extra_args
34
+ '-o StrictHostKeyChecking=no'
35
+ end
19
36
  end
20
37
  end
21
38
  end
@@ -12,9 +12,6 @@ module BarkingIguana
12
12
 
13
13
  def initialize hosts = [], implementation_options = {}
14
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
15
  self.implementation = Vagrant.new self, implementation_options
19
16
  implementation.prepare
20
17
  end
@@ -40,7 +40,7 @@ class ServerSpec
40
40
  end
41
41
 
42
42
  def root_dir
43
- stage.stage_file_root
43
+ stage.directory
44
44
  end
45
45
 
46
46
  def hosts
@@ -20,17 +20,13 @@ module BarkingIguana
20
20
  end
21
21
 
22
22
  def stages
23
- return [ TestStage.new(self, directory) ] if simple_test?
24
23
  Dir[directory + '/*'].select { |d| File.directory? d }.map { |s| TestStage.new self, File.basename(s) }
25
24
  end
26
25
 
27
- def simple_test?
28
- File.exists? "#{directory}/playbook.yml"
29
- end
30
-
31
26
  def run
32
27
  benchmark "test #{name}" do
33
28
  begin
29
+ logger.debug { "#{name}: found #{stages.size} stages: #{stages.map(&:name).map(&:inspect).join(', ')}" }
34
30
  stages.each &:run
35
31
  ensure
36
32
  teardown
@@ -60,7 +56,11 @@ module BarkingIguana
60
56
  end
61
57
 
62
58
  def host_manager
63
- @host_manger ||= HostManager.new(hosts, driver_options)
59
+ @host_manger ||= begin
60
+ # FIXME: Implement uniqueness operators on Host
61
+ hosts = stages.map(&:hosts).flatten.uniq(&:ip_address).sort
62
+ HostManager.new(hosts, driver_options)
63
+ end
64
64
  end
65
65
 
66
66
  def driver_options
@@ -76,8 +76,7 @@ module BarkingIguana
76
76
  end
77
77
 
78
78
  def hosts
79
- # FIXME: Implement uniqueness operators on Host
80
- stages.map(&:hosts).flatten.uniq { |h| h.uri }
79
+ host_manager.all
81
80
  end
82
81
  end
83
82
  end
@@ -1,11 +1,11 @@
1
1
  module BarkingIguana
2
2
  module Compound
3
3
  class TestStage
4
- attr_accessor :test, :stage_directory
4
+ attr_accessor :test, :directory
5
5
 
6
- def initialize test, stage_directory
6
+ def initialize test, directory
7
7
  self.test = test
8
- self.stage_directory = stage_directory
8
+ self.directory = directory
9
9
  end
10
10
 
11
11
  def actions
@@ -26,34 +26,68 @@ module BarkingIguana
26
26
  end
27
27
 
28
28
  def name
29
- return if test.simple_test?
30
- stage_directory
29
+ directory
31
30
  end
32
31
 
33
32
  def display_name
34
- return test.name if test.simple_test?
35
- test.name + ' stage ' + stage_directory
33
+ test.name + ' stage ' + directory
36
34
  end
37
35
 
38
36
  def inventory_path
39
- File.join stage_file_root, 'inventory'
37
+ @inventory_path ||= stage_file_with_fallback('inventory')
40
38
  end
41
39
 
42
- def stage_file_root
43
- return test.directory if test.simple_test?
44
- File.expand_path stage_directory, test.directory
40
+ def stage_directory
41
+ File.expand_path directory, test.directory
42
+ end
43
+
44
+ def stage_file_with_fallback file_name
45
+ logger.debug { "Searching for #{file_name.inspect}" }
46
+ stage_file = File.expand_path file_name, stage_directory
47
+ logger.debug { "Checking #{stage_file.inspect}" }
48
+ if File.exists? stage_file
49
+ logger.debug { "Found #{file_name.inspect} at #{stage_file.inspect}" }
50
+ return stage_file
51
+ end
52
+ test_file = File.expand_path file_name, test.directory
53
+ logger.debug { "Assuming it'll be at #{test_file.inspect}" }
54
+ test_file
45
55
  end
46
56
 
47
57
  def playbook_path
48
- "#{stage_file_root}/playbook.yml"
58
+ @playbook_path ||= stage_file_with_fallback('playbook.yml')
49
59
  end
50
60
 
51
- def inventory
61
+ def original_inventory
52
62
  Ansible.inventory inventory_path
53
63
  end
54
64
 
65
+ def generated_inventory
66
+ @generated_inventory ||= generate_inventory
67
+ end
68
+
69
+ def generate_inventory
70
+ benchmark "#{name}: generating inventory for test stage" do
71
+ Dir.mktmpdir('inventory').tap do |d|
72
+ logger.debug { "#{name}: inventory directory is #{d.inspect}" }
73
+ connection_file = File.expand_path 'connection', d
74
+ Ansible::InventoryWriter.new(connection_file).tap do |i|
75
+ benchmark "#{name}: generating connection inventory at #{connection_file}" do
76
+ test.hosts.each do |h|
77
+ i.add_host h
78
+ end
79
+ i.write_file
80
+ end
81
+ end
82
+ original_file = File.expand_path 'original', d
83
+ logger.debug { "#{name}: copying original inventory to #{original_file} from #{inventory_path}" }
84
+ FileUtils.copy inventory_path, original_file
85
+ end
86
+ end
87
+ end
88
+
55
89
  def hosts
56
- inventory.hosts
90
+ original_inventory.hosts
57
91
  end
58
92
 
59
93
  def control_directory
@@ -61,7 +95,7 @@ module BarkingIguana
61
95
  end
62
96
 
63
97
  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
98
+ Ansible.playbook(playbook_path, run_from: control_directory).inventory(generated_inventory).stream_to(logger).verbosity(2).diff
65
99
  end
66
100
 
67
101
  def suite
@@ -69,9 +103,9 @@ module BarkingIguana
69
103
  end
70
104
 
71
105
  def setup
72
- desired_hosts = inventory.hosts.map { |h| h.name }.sort
106
+ desired_hosts = original_inventory.hosts.sort.map(&:name)
73
107
  logger.debug { "Desired hosts for #{display_name}: #{desired_hosts.join(', ')}" }
74
- active_hosts = host_manager.active.map { |h| h.name }.sort
108
+ active_hosts = host_manager.active.sort.map(&:name)
75
109
  logger.debug { "Active hosts for #{display_name}: #{active_hosts.join(', ')}" }
76
110
  hosts_to_launch = desired_hosts - active_hosts
77
111
  logger.debug { "Launch hosts for #{display_name}: #{hosts_to_launch.join(', ')}" }
@@ -86,8 +120,14 @@ module BarkingIguana
86
120
  end
87
121
 
88
122
  def converge
89
- return unless File.exists? playbook_path
123
+ unless File.exists? playbook_path
124
+ logger.debug { "Not running anything because #{playbook_path.inspect} does not exist" }
125
+ return
126
+ end
90
127
  playbook.run
128
+ ensure
129
+ logger.debug { "Removing generated inventory from #{generated_inventory}" }
130
+ # FileUtils.rm_r generated_inventory
91
131
  end
92
132
 
93
133
  def verify
@@ -1,6 +1,10 @@
1
1
  module BarkingIguana
2
2
  module Compound
3
3
  class TestSuite
4
+ def self.define_rake_tasks
5
+ new.define_rake_tasks
6
+ end
7
+
4
8
  def define_rake_tasks
5
9
  Rake::Task.define_task name do
6
10
  run
@@ -11,29 +15,20 @@ module BarkingIguana
11
15
  test.run
12
16
  end.add_description "Run #{test.name} test from #{name} suite"
13
17
 
14
- Rake::Task.define_task "#{name}:#{test.name}:destroy" do
15
- test.teardown
16
- end.add_description "Tear down #{test.name} test from #{name} suite"
17
-
18
- test.stages.each do |stage|
19
- stage.actions.each do |action|
20
- Rake::Task.define_task "#{name}:#{test.name}:#{action}" do
21
- stage.public_send action
22
- end.add_description "Run action #{action} of the #{test.name} test from #{name} suite"
23
- end
24
- end if test.simple_test?
25
-
26
18
  test.stages.each do |stage|
27
19
  Rake::Task.define_task "#{name}:#{test.name}:#{stage.name}" do
28
20
  stage.run
29
- end.add_description "Run stage #{stage.name} of the #{test.name} test from #{name} suite"
30
-
21
+ end.add_description "Run #{stage.name} stage of the #{test.name} test from #{name} suite"
31
22
  stage.actions.each do |action|
32
23
  Rake::Task.define_task "#{name}:#{test.name}:#{stage.name}:#{action}" do
33
24
  stage.public_send action
34
- end.add_description "Run action #{action} of stage #{stage.name} of the #{test.name} test from #{name} suite"
25
+ end.add_description "Run action #{action} for #{stage.name} stage of the #{test.name} test from #{name} suite"
35
26
  end
36
- end unless test.simple_test?
27
+ end
28
+
29
+ Rake::Task.define_task "#{name}:#{test.name}:destroy" do
30
+ test.teardown
31
+ end.add_description "Tear down #{test.name} test from #{name} suite"
37
32
  end
38
33
  end
39
34
 
@@ -43,9 +38,17 @@ module BarkingIguana
43
38
  attr_accessor :directory
44
39
  private :directory=
45
40
 
46
- def initialize directory, control_directory
47
- self.directory = directory
48
- self.control_directory = control_directory
41
+ def initialize(directory = nil, control_directory: nil)
42
+ self.control_directory = control_directory || guess_directory
43
+ self.directory = directory || File.expand_path("test/compound", control_directory)
44
+ end
45
+
46
+ def guess_directory
47
+ caller.detect do |trace|
48
+ path = trace.split(/:/, 2)[0]
49
+ file = File.basename path
50
+ break File.dirname path if file == 'Rakefile'
51
+ end
49
52
  end
50
53
 
51
54
  def tests
@@ -42,13 +42,26 @@ class Vagrant
42
42
  end
43
43
  end
44
44
 
45
- def write_file
45
+ def write_vagrant_file
46
46
  logger.debug { "Writing Vagrantfile to #{vagrant_file_path}" }
47
47
  File.open vagrant_file_path, 'w' do |f|
48
48
  f.puts vagrant_file_content
49
49
  end
50
50
  end
51
- alias_method :prepare, :write_file
51
+
52
+ def assign_ip_addresses
53
+ hosts.each do |h|
54
+ next if valid_ip_address? h.ip_address
55
+ ip_address = next_available_ip_address
56
+ logger.debug { "Assigning #{h.name} an IP address: #{ip_address}" }
57
+ h.assign_ip_address ip_address
58
+ end
59
+ end
60
+
61
+ def prepare
62
+ assign_ip_addresses
63
+ write_vagrant_file
64
+ end
52
65
 
53
66
  def vagrant_file_content
54
67
  ERB.new(vagrant_file_template).result binding
@@ -73,7 +86,25 @@ class Vagrant
73
86
  end
74
87
  end
75
88
 
89
+ private
90
+
76
91
  def hosts
77
92
  manager.hosts
78
93
  end
94
+
95
+ def next_available_ip_address
96
+ unassigned_ip_addresses.shift
97
+ end
98
+
99
+ def valid_ip_address? ip_address
100
+ ip_address =~ /^10\.8\./
101
+ end
102
+
103
+ def unassigned_ip_addresses
104
+ @unassigned_ip_addresses ||= begin
105
+ assignable_ip_addresses = (10..200).to_a.map { |dd| "10.8.42.#{dd}" }
106
+ assigned_ip_addresses = hosts.map(&:ip_address)
107
+ assignable_ip_addresses - assigned_ip_addresses
108
+ end
109
+ end
79
110
  end
@@ -1,5 +1,5 @@
1
1
  module BarkingIguana
2
2
  module Compound
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.4"
4
4
  end
5
5
  end
@@ -11,12 +11,15 @@ require 'barking_iguana/compound/version'
11
11
  require 'barking_iguana/compound/ansible'
12
12
  require 'barking_iguana/compound/ansible/inventory'
13
13
  require 'barking_iguana/compound/ansible/inventory_parser'
14
+ require 'barking_iguana/compound/ansible/inventory_writer'
14
15
  require 'barking_iguana/compound/ansible/playbook'
16
+ require 'barking_iguana/compound/command_line_client'
17
+ require 'barking_iguana/compound/host_manager'
18
+ require 'barking_iguana/compound/host'
19
+ require 'barking_iguana/compound/server_spec'
15
20
  require 'barking_iguana/compound/test_stage'
16
21
  require 'barking_iguana/compound/test'
17
22
  require 'barking_iguana/compound/test_suite'
18
- require 'barking_iguana/compound/host_manager'
19
- require 'barking_iguana/compound/host'
20
23
  require 'barking_iguana/compound/vagrant'
21
24
 
22
25
  module BarkingIguana
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barking_iguana-compound
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig R Webster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-16 00:00:00.000000000 Z
11
+ date: 2016-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -167,7 +167,8 @@ dependencies:
167
167
  description: Compound testing of Ansible playbooks
168
168
  email:
169
169
  - craig@barkingiguana.com
170
- executables: []
170
+ executables:
171
+ - compound
171
172
  extensions: []
172
173
  extra_rdoc_files: []
173
174
  files:
@@ -179,11 +180,18 @@ files:
179
180
  - barking_iguana-compound.gemspec
180
181
  - bin/console
181
182
  - bin/setup
183
+ - docs/CHANGELOG.md
184
+ - docs/TODO.md
185
+ - docs/_config.yml
186
+ - docs/index.md
187
+ - exe/compound
182
188
  - lib/barking_iguana/compound.rb
183
189
  - lib/barking_iguana/compound/ansible.rb
184
190
  - lib/barking_iguana/compound/ansible/inventory.rb
185
191
  - lib/barking_iguana/compound/ansible/inventory_parser.rb
192
+ - lib/barking_iguana/compound/ansible/inventory_writer.rb
186
193
  - lib/barking_iguana/compound/ansible/playbook.rb
194
+ - lib/barking_iguana/compound/command_line_client.rb
187
195
  - lib/barking_iguana/compound/host.rb
188
196
  - lib/barking_iguana/compound/host_manager.rb
189
197
  - lib/barking_iguana/compound/server_spec.rb