vagrant-ansible_auto 0.1.5 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +172 -0
- data/README.md +53 -12
- data/Rakefile +9 -7
- data/TODO.md +14 -0
- data/Vagrantfile +37 -15
- data/lib/vagrant/ansible_auto/cap/guest/posix/check_open_port.rb +22 -3
- data/lib/vagrant/ansible_auto/cap/guest/posix/executable_installed.rb +10 -2
- data/lib/vagrant/ansible_auto/cap/guest/posix/gateway_addresses.rb +8 -23
- data/lib/vagrant/ansible_auto/cap/guest/posix/private_key.rb +16 -1
- data/lib/vagrant/ansible_auto/cap/guest/posix/public_key.rb +18 -3
- data/lib/vagrant/ansible_auto/cap/guest/posix/ssh_server_address.rb +22 -12
- data/lib/vagrant/ansible_auto/cap/guest/posix.rb +16 -0
- data/lib/vagrant/ansible_auto/command/inventory.rb +37 -11
- data/lib/vagrant/ansible_auto/command/root.rb +34 -31
- data/lib/vagrant/ansible_auto/config.rb +74 -33
- data/lib/vagrant/ansible_auto/errors.rb +30 -1
- data/lib/vagrant/ansible_auto/host.rb +123 -34
- data/lib/vagrant/ansible_auto/inventory.rb +196 -34
- data/lib/vagrant/ansible_auto/plugin.rb +23 -8
- data/lib/vagrant/ansible_auto/provisioner.rb +121 -79
- data/lib/vagrant/ansible_auto/util/config.rb +61 -0
- data/lib/vagrant/ansible_auto/util/hash_with_indifferent_access.rb +58 -0
- data/lib/vagrant/ansible_auto/util/keys.rb +49 -0
- data/lib/vagrant/ansible_auto/util/shell_quote.rb +24 -0
- data/lib/vagrant/ansible_auto/version.rb +2 -1
- data/lib/vagrant/ansible_auto.rb +15 -0
- data/locales/en.yml +34 -0
- data/spec/spec_helper.rb +5 -85
- data/spec/support/context.rb +111 -0
- data/spec/support/matchers.rb +45 -0
- data/spec/unit/vagrant/ansible_auto/config_spec.rb +72 -0
- data/spec/unit/vagrant/ansible_auto/host_spec.rb +131 -0
- data/spec/unit/vagrant/ansible_auto/inventory_spec.rb +349 -0
- data/spec/unit/vagrant/ansible_auto/provisioner_spec.rb +248 -0
- data/spec/unit/vagrant/ansible_auto/util/config_spec.rb +63 -0
- data/spec/unit/vagrant/ansible_auto/util/keys_spec.rb +66 -0
- data/vagrant-ansible_auto.gemspec +6 -4
- data/vagrant-spec.config.rb +3 -0
- data/yard/extensions.rb +45 -0
- metadata +36 -11
- data/Vagrantfile2 +0 -4
- data/Vagrantfile3 +0 -8
- data/Vagrantfile4 +0 -31
- data/lib/vagrant/ansible_auto/cap/guest/posix/bash_installed.rb +0 -30
- data/lib/vagrant/ansible_auto/util.rb +0 -24
- data/spec/vagrant/ansible_auto/host_spec.rb +0 -43
- data/spec/vagrant/ansible_auto/inventory_spec.rb +0 -79
@@ -0,0 +1,349 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'vagrant/ansible_auto/errors'
|
6
|
+
require 'vagrant/ansible_auto/inventory'
|
7
|
+
|
8
|
+
describe VagrantPlugins::AnsibleAuto::Inventory do
|
9
|
+
include_context 'inventory'
|
10
|
+
|
11
|
+
let(:inventory_hosts) { %w[db bastion firewall] }
|
12
|
+
|
13
|
+
let(:complex_group) do
|
14
|
+
{
|
15
|
+
hosts: inventory_hosts,
|
16
|
+
vars: {
|
17
|
+
this: 'n',
|
18
|
+
uh: 'that',
|
19
|
+
n: 'uh'
|
20
|
+
},
|
21
|
+
children: %w[staging qa]
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#add_group' do
|
26
|
+
it 'adds a list of hosts to the group cache' do
|
27
|
+
inventory.add_group(:mygroup, *inventory_hosts)
|
28
|
+
inventory.groups.tap do |groups|
|
29
|
+
expect(groups).to have_key('mygroup')
|
30
|
+
expect(groups['mygroup']).to include(*inventory_hosts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'appends new hosts to existing groups' do
|
35
|
+
inventory.add_group(:mygroup, *inventory_hosts)
|
36
|
+
inventory.add_group(:mygroup, 'repo')
|
37
|
+
inventory.groups.tap do |groups|
|
38
|
+
expect(groups).to have_key('mygroup')
|
39
|
+
expect(groups['mygroup']).to include('repo', *inventory_hosts)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'recognizes a hash in the style returned by executable inventories' do
|
44
|
+
inventory.add_group(:mygroup, complex_group)
|
45
|
+
|
46
|
+
inventory.groups.tap do |groups|
|
47
|
+
expect(groups).to have_key('mygroup')
|
48
|
+
expect(groups['mygroup']).to include(*inventory_hosts)
|
49
|
+
end
|
50
|
+
|
51
|
+
inventory.vars.tap do |vars|
|
52
|
+
expect(vars).to have_key('mygroup')
|
53
|
+
expect(vars['mygroup']).to include(this: 'n', uh: 'that', n: 'uh')
|
54
|
+
end
|
55
|
+
|
56
|
+
inventory.children.tap do |children|
|
57
|
+
expect(children).to have_key('mygroup')
|
58
|
+
expect(children['mygroup']).to include('staging', 'qa')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'given an invalid group name' do
|
63
|
+
it 'raises an error' do
|
64
|
+
expect { inventory.add_group('_') }.to raise_error do |error|
|
65
|
+
expect(error).to be_a(VagrantPlugins::AnsibleAuto::InvalidGroupNameError)
|
66
|
+
expect(error.message).to match(/_ is not a valid group name/)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#add_host' do
|
73
|
+
include_context 'host'
|
74
|
+
|
75
|
+
it 'adds a host to the host cache' do
|
76
|
+
inventory.add_host(host)
|
77
|
+
expect(inventory.hosts).to include(host)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'does not allow duplicates' do
|
81
|
+
2.times { inventory.add_host(host) }
|
82
|
+
expect(inventory.hosts.size).to eq 1
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'coerces instances of Vagrant::Machine to instances of Host' do
|
86
|
+
inventory.add_host(machine)
|
87
|
+
expect(inventory.hosts).to all(be_a(VagrantPlugins::AnsibleAuto::Host))
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'coerces names + hostvars to instances of Host' do
|
91
|
+
inventory.add_host('mymachine', ssh_host: 'foo.bar.net')
|
92
|
+
mymachine = VagrantPlugins::AnsibleAuto::Host.new('mymachine', ssh_host: 'foo.bar.net')
|
93
|
+
expect(inventory.hosts).to all(be_a(VagrantPlugins::AnsibleAuto::Host))
|
94
|
+
expect(inventory.hosts).to include(mymachine)
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'given an object that cannot be converted to a Host or HostMachine' do
|
98
|
+
it 'raises an error' do
|
99
|
+
expect { inventory.add_host(6) }.to raise_error(
|
100
|
+
VagrantPlugins::AnsibleAuto::Errors::InvalidHostTypeError,
|
101
|
+
/cannot add object of type \w+ as inventory host/
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#groups=' do
|
108
|
+
let(:new_groups) do
|
109
|
+
{
|
110
|
+
'foo' => %w[bar baz quux],
|
111
|
+
'pink' => %w[elephants on parade]
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
before do
|
116
|
+
inventory.groups = new_groups
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'assigns the groups to the inventory' do
|
120
|
+
expect(inventory.groups['foo']).to include('bar', 'baz', 'quux')
|
121
|
+
expect(inventory.groups['pink']).to include('elephants', 'on', 'parade')
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'given a :vars group' do
|
125
|
+
let(:group_vars) do
|
126
|
+
{
|
127
|
+
'this' => 'that',
|
128
|
+
'some' => 'thing'
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
let(:new_groups) do
|
133
|
+
{
|
134
|
+
'foo' => %w[mee maw],
|
135
|
+
'foo:vars' => group_vars
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'assigns the vars to the given group' do
|
140
|
+
expect(inventory.vars['foo']).to include(group_vars)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'given a :children group' do
|
145
|
+
let(:group_children) do
|
146
|
+
%w[baz quux]
|
147
|
+
end
|
148
|
+
|
149
|
+
let(:new_groups) do
|
150
|
+
{
|
151
|
+
'foo' => %w[d chain],
|
152
|
+
'foo:children' => group_children
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'assigns the provided groups as children of the parent group' do
|
157
|
+
expect(inventory.children['foo']).to include(*group_children)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'given a colon-separated group name without :vars or :children' do
|
162
|
+
let(:new_groups) do
|
163
|
+
{
|
164
|
+
'bleep' => %w[ing computer],
|
165
|
+
'bleep:bloop' => %w[er reel]
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'treats the group heading as a simple group name' do
|
170
|
+
expect(inventory.groups).to include('bleep:bloop')
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
context 'given a colon-separated group with backslash-escaped :vars or :children' do
|
175
|
+
let(:new_groups) do
|
176
|
+
{
|
177
|
+
'bleep' => %w[ing computer],
|
178
|
+
'bleep\:vars' => %w[ity lacrosse],
|
179
|
+
'bleep\:children' => %w[of the corn]
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'treats the group heading as a simple group name' do
|
184
|
+
expect(inventory.groups).to include('bleep\:vars')
|
185
|
+
expect(inventory.groups).to include('bleep\:children')
|
186
|
+
expect(inventory.vars[:bleep]).to be_empty
|
187
|
+
expect(inventory.children[:bleep]).to be_empty
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
describe '#groups' do
|
193
|
+
it 'returns a hash of sets' do
|
194
|
+
expect(inventory.groups).to be_a(Hash)
|
195
|
+
expect(inventory.groups[:foo]).to be_a(Set)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe '#hosts=' do
|
200
|
+
it 'sets the hosts for the inventory' do
|
201
|
+
inventory.hosts = %w[huey dewey louie]
|
202
|
+
expect(inventory.hosts.map(&:name)).to eq(%w[huey dewey louie])
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'wipes out any existing hosts' do
|
206
|
+
inventory.hosts = %w[huey dewey louie]
|
207
|
+
expect(inventory.hosts.map(&:name)).to eq(%w[huey dewey louie])
|
208
|
+
inventory.hosts = %w[heckle jeckle]
|
209
|
+
expect(inventory.hosts.map(&:name)).to eq(%w[heckle jeckle])
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe '#hosts' do
|
214
|
+
it 'returns a set' do
|
215
|
+
expect(inventory.hosts).to be_a(Set)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe '#vars=' do
|
220
|
+
it 'sets the vars for the inventory' do
|
221
|
+
inventory.vars = { bah: { hum: 'bug' } }
|
222
|
+
expect(inventory.vars).to eq('bah' => { 'hum' => 'bug' })
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'wipes out existing vars' do
|
226
|
+
inventory.vars = { bah: { hum: 'bug' } }
|
227
|
+
expect(inventory.vars).to eq('bah' => { 'hum' => 'bug' })
|
228
|
+
inventory.vars = { bah: { ram: 'ewe' } }
|
229
|
+
expect(inventory.vars).to eq('bah' => { 'ram' => 'ewe' })
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe '#vars' do
|
234
|
+
it 'returns a hash of hashes' do
|
235
|
+
expect(inventory.vars).to be_a(Hash)
|
236
|
+
expect(inventory.vars[:meh]).to be_a(Hash)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#children=' do
|
241
|
+
it 'sets the group children for the inventory' do
|
242
|
+
inventory.children = { cronus: ['zeus'] }
|
243
|
+
expect(inventory.children.keys).to include('cronus')
|
244
|
+
expect(inventory.children[:cronus]).to include('zeus')
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'wipes out any existing children' do
|
248
|
+
inventory.children = { cronus: ['zeus'] }
|
249
|
+
expect(inventory.children.keys).to include('cronus')
|
250
|
+
expect(inventory.children[:cronus]).to include('zeus')
|
251
|
+
inventory.children = { saturn: ['jupiter'] }
|
252
|
+
expect(inventory.children.keys).to include('saturn')
|
253
|
+
expect(inventory.children.keys).not_to include('cronus')
|
254
|
+
expect(inventory.children[:saturn]).to include('jupiter')
|
255
|
+
expect(inventory.children[:cronus]).not_to include('zeus')
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe '#children' do
|
260
|
+
it 'returns a hash of sets' do
|
261
|
+
expect(inventory.children).to be_a(Hash)
|
262
|
+
expect(inventory.children[:quux]).to be_a(Set)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
describe 'merge' do
|
267
|
+
let(:inventory2) { inventory.clone }
|
268
|
+
|
269
|
+
it 'returns a new inventory with hosts, groups, vars, and children merged' do
|
270
|
+
inventory.hosts = %w[huey dewey louie]
|
271
|
+
inventory.groups = { ducks: %w[huey dewey louie] }
|
272
|
+
inventory.children = { birds: %w[ducks] }
|
273
|
+
inventory.vars = { birds: { of: 'a feather' }, ducks: { out: 'quick' } }
|
274
|
+
|
275
|
+
inventory2.hosts = %w[heckle jeckle launchpad]
|
276
|
+
inventory2.groups = { crows: %w[heckle jeckle], ducks: %w[launchpad] }
|
277
|
+
inventory2.children = { birds: %w[crows] }
|
278
|
+
inventory2.vars = { birds: { on: 'the wing' }, crows: { with: 'delight' } }
|
279
|
+
|
280
|
+
inventory3 = inventory.merge(inventory2)
|
281
|
+
expect(inventory3.hosts.map(&:name)).to include('huey', 'dewey', 'louie', 'launchpad', 'heckle', 'jeckle')
|
282
|
+
expect(inventory3.vars).to eq('birds' => { 'on' => 'the wing', 'of' => 'a feather' }, 'ducks' => { 'out' => 'quick' }, 'crows' => { 'with' => 'delight' })
|
283
|
+
expect(inventory3.groups.keys).to include('ducks', 'crows')
|
284
|
+
expect(inventory3.children['birds']).to include('ducks', 'crows')
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
describe '#hostvars' do
|
289
|
+
it "returns a hash each host's hostvars mapped to its inventory hostname"
|
290
|
+
end
|
291
|
+
|
292
|
+
describe '#validate!' do
|
293
|
+
context 'when nonextant children are defined for a group' do
|
294
|
+
it 'raises an error' do
|
295
|
+
inventory.groups = ['parent']
|
296
|
+
inventory.children_of('parent', 'child')
|
297
|
+
expect { inventory.validate! }.to raise_error(
|
298
|
+
VagrantPlugins::AnsibleAuto::Errors::GroupMissingChildError,
|
299
|
+
/group parent defines nonextant child group child/
|
300
|
+
)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
describe '#to_ini' do
|
306
|
+
include_context 'host'
|
307
|
+
|
308
|
+
let(:host1) do
|
309
|
+
VagrantPlugins::AnsibleAuto::Host.new('blurgh').tap do |h|
|
310
|
+
h.inventory_hostname = 'tweedledee'
|
311
|
+
h.ansible_ssh_user = 'me'
|
312
|
+
h.ansible_ssh_host = '10.10.0.20'
|
313
|
+
h.ansible_ssh_port = 2200
|
314
|
+
h.ansible_ssh_private_key_file = 'me_id_rsa'
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
let(:host2) do
|
319
|
+
VagrantPlugins::AnsibleAuto::Host.new('bleh').tap do |h|
|
320
|
+
h.inventory_hostname = 'tweedledum'
|
321
|
+
h.ansible_ssh_user = 'you'
|
322
|
+
h.ansible_ssh_host = '192.168.1.88'
|
323
|
+
h.ansible_ssh_port = 2201
|
324
|
+
h.ansible_ssh_private_key_file = 'you_id_rsa'
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'returns the inventory as an INI-style document' do
|
329
|
+
inventory.add_group(:mygroup, complex_group)
|
330
|
+
inventory.add_host(host1)
|
331
|
+
inventory.add_host(host2)
|
332
|
+
expect(inventory.to_ini).to eq(unindent(<<-INVENTORY).chomp)
|
333
|
+
tweedledee ansible_ssh_host=10.10.0.20 ansible_ssh_port=2200 ansible_ssh_private_key_file=me_id_rsa ansible_ssh_user=me
|
334
|
+
tweedledum ansible_ssh_host=192.168.1.88 ansible_ssh_port=2201 ansible_ssh_private_key_file=you_id_rsa ansible_ssh_user=you
|
335
|
+
[mygroup]
|
336
|
+
bastion
|
337
|
+
db
|
338
|
+
firewall
|
339
|
+
[mygroup:children]
|
340
|
+
qa
|
341
|
+
staging
|
342
|
+
[mygroup:vars]
|
343
|
+
n = uh
|
344
|
+
this = n
|
345
|
+
uh = that
|
346
|
+
INVENTORY
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'log4r'
|
6
|
+
|
7
|
+
require 'vagrant/config/v2/dummy_config'
|
8
|
+
|
9
|
+
require 'vagrant/ansible_auto/provisioner'
|
10
|
+
|
11
|
+
describe VagrantPlugins::AnsibleAuto::Provisioner do
|
12
|
+
include_context 'inventory'
|
13
|
+
include_context 'config'
|
14
|
+
|
15
|
+
def mock_capabilities(machine)
|
16
|
+
capabilities = {
|
17
|
+
ssh_server_address: ->(other) { [other.ssh_info[:host], other.ssh_info[:port]] },
|
18
|
+
authorized_key?: ->(content, *args) { [true] },
|
19
|
+
fetch_public_key: ->(path) {},
|
20
|
+
insert_public_key: ->(key) { [] },
|
21
|
+
ansible_installed: ->(*) { true }
|
22
|
+
}
|
23
|
+
|
24
|
+
allow(machine.guest).to receive(:capability?) { |cap| capabilities.key? cap }
|
25
|
+
|
26
|
+
allow(machine.guest).to receive(:capability) do |cap, *args, &block|
|
27
|
+
capabilities[cap].call(*args, &block) if capabilities.key? cap
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:provisioner) { described_class.new(machine, config) }
|
32
|
+
let(:root_config) { Vagrant::Config::V2::DummyConfig.new }
|
33
|
+
let(:existing_file) { File.expand_path(__FILE__) }
|
34
|
+
let(:logger) { Log4r::Logger.new('vagrant::provisioners::ansible_auto::spec') }
|
35
|
+
|
36
|
+
before do |example|
|
37
|
+
unless example.metadata.fetch(:skip_before) { false }
|
38
|
+
# Allows us to set expectations on the provisioner's @logger instance
|
39
|
+
# variable
|
40
|
+
log4r_logger_new_original = Log4r::Logger.method(:new)
|
41
|
+
allow(Log4r::Logger).to receive(:new) do |namespace, *args|
|
42
|
+
if namespace == 'vagrant::provisioners::ansible_auto'
|
43
|
+
logger
|
44
|
+
else
|
45
|
+
log4r_logger_new_original.call(namespace, *args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
allow(root_config).to receive(:ansible).and_return(VagrantPlugins::AnsibleAuto::Config.new)
|
50
|
+
|
51
|
+
config.playbook = existing_file
|
52
|
+
config.finalize!
|
53
|
+
root_config.finalize!
|
54
|
+
machines.each do |m|
|
55
|
+
m.config.ansible.finalize!
|
56
|
+
mock_capabilities(m)
|
57
|
+
end
|
58
|
+
|
59
|
+
provisioner.configure(root_config)
|
60
|
+
|
61
|
+
allow(machine.communicate).to receive(:execute) do |*args|
|
62
|
+
0
|
63
|
+
end
|
64
|
+
|
65
|
+
allow(machine.communicate).to receive(:upload) do |*args|
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
allow(machine.communicate).to receive(:test) do |*args|
|
70
|
+
true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
after do |example|
|
76
|
+
provisioner.provision unless example.metadata.fetch(:skip_after) { false }
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#provision' do
|
80
|
+
it 'creates an ansible inventory', skip_after: true do
|
81
|
+
expect(machine.communicate).to receive(:upload) do |from, to|
|
82
|
+
contents = File.read(from)
|
83
|
+
expect(contents).to match(/ansible_connection=local/)
|
84
|
+
end
|
85
|
+
expect { provisioner.provision }.not_to raise_error
|
86
|
+
expect(provisioner.config.inventory).to be_a VagrantPlugins::AnsibleAuto::Inventory
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when strict host key checking' do
|
90
|
+
let(:strict_host_key_checking_options) do
|
91
|
+
"ansible_ssh_extra_args='-o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes -o StrictHostKeyChecking=no'"
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'is enabled' do
|
95
|
+
before do
|
96
|
+
config.strict_host_key_checking = true
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'omits StrictHostKeyChecking=yes and other SSH options from the inventory' do
|
100
|
+
expect(machine.communicate).to receive(:upload) do |from, to|
|
101
|
+
contents = File.read(from)
|
102
|
+
expect(contents).not_to match(/#{strict_host_key_checking_options}/)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'is disabled' do
|
108
|
+
before do
|
109
|
+
config.strict_host_key_checking = false
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'includes StrictHostKeyChecking=yes and other SSH options in the inventory' do
|
113
|
+
expect(machine.communicate).to receive(:upload) do |from, to|
|
114
|
+
contents = File.read(from)
|
115
|
+
expect(contents).to match(/#{strict_host_key_checking_options}/)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'when control machine public key insertion is enabled' do
|
122
|
+
let(:public_key_content) { 'iamapublickey' }
|
123
|
+
let(:private_key_content) { 'iamaprivatekey' }
|
124
|
+
let(:openssh_content) { 'ssh iamsomebinarystuff vagrant' }
|
125
|
+
|
126
|
+
before do
|
127
|
+
config.insert_control_machine_public_key = true
|
128
|
+
config.upload_inventory_host_private_keys = false
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'and the control machine does not yet have a keypair configured' do
|
132
|
+
before do
|
133
|
+
allow(machine.communicate).to receive(:test).with(/^test -r/).and_return(false)
|
134
|
+
allow(Vagrant::Util::Keypair).to receive(:create).and_return([nil, private_key_content, openssh_content])
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'creates and uploads a new keypair' do
|
138
|
+
expect(provisioner).to receive(:write_and_chown_and_chmod_remote_file).with(private_key_content, anything).and_call_original
|
139
|
+
expect(provisioner).to receive(:write_and_chown_and_chmod_remote_file).with(openssh_content, anything).and_call_original
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'and the keypair is configured' do
|
144
|
+
before do
|
145
|
+
allow(provisioner).to receive(:control_machine_public_key).and_return(public_key_content)
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'and the public key' do
|
149
|
+
context 'is already authorized' do
|
150
|
+
before do
|
151
|
+
machines[1..-1].each do |m|
|
152
|
+
allow(m.guest).to receive(:capability).with(:authorized_key?, public_key_content).and_return(true)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'does not attempt to insert the public key' do
|
157
|
+
machines[1..-1].each do |m|
|
158
|
+
expect(m.guest).not_to receive(:capability).with(:insert_public_key, public_key_content)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'is not authorized' do
|
164
|
+
before do
|
165
|
+
machines[1..-1].each do |m|
|
166
|
+
allow(m.guest).to receive(:capability).with(:authorized_key?, public_key_content).and_return(false)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'and the other host permits public key insertion' do
|
171
|
+
it 'attempts to insert the public key' do
|
172
|
+
machines[1..-1].each do |m|
|
173
|
+
expect(m.guest).to receive(:capability).with(:insert_public_key, public_key_content)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'and the other host does not permit public key insertion' do
|
179
|
+
it 'issues a warning' do
|
180
|
+
machines[1..-1].each do |m|
|
181
|
+
allow(m.guest).to receive(:capability?).with(:insert_public_key).and_return(false)
|
182
|
+
expect(logger).to receive(:warn).with(/\ACannot insert control machine public key on/)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'when inventory host private key upload is enabled' do
|
192
|
+
let(:private_key_content) { 'iamaprivatekey' }
|
193
|
+
|
194
|
+
before do
|
195
|
+
config.insert_control_machine_public_key = false
|
196
|
+
config.upload_inventory_host_private_keys = true
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'and the host has a private key configured' do
|
200
|
+
it 'uploads the private key to the control machine' do
|
201
|
+
machines[1..-1].each do |m|
|
202
|
+
expect(provisioner).to receive(:fetch_private_key).with(m).and_return(private_key_content)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'and the host does not have a private key configured' do
|
208
|
+
it 'issues a warning' do
|
209
|
+
machines[1..-1].each do |m|
|
210
|
+
allow(provisioner).to receive(:fetch_private_key).with(m).and_return(nil)
|
211
|
+
expect(logger).to receive(:warn).with(/\APrivate key for .*? not available for upload; provisioner will likely fail/)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'when both public key insertion and private key upload are disabled' do
|
218
|
+
before do
|
219
|
+
config.insert_control_machine_public_key = false
|
220
|
+
config.upload_inventory_host_private_keys = false
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'issues a warning message' do
|
224
|
+
expect(machine.ui).to receive(:warn).with(/unable to insert public key or upload existing private key/).at_least(:once)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'given a machine that is not ready for SSH' do
|
229
|
+
before do
|
230
|
+
config.host_connect_tries = 1
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'errors out if a machine is not ready for SSH', skip_after: true do
|
234
|
+
allow(machines[1].communicate).to receive(:ready?).and_return(false)
|
235
|
+
expect { provisioner.provision }.to raise_error(Vagrant::Errors::SSHNotReady)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context 'given a machine that is not configured for the current environment' do
|
240
|
+
it 'carps and ignores the machine', skip_after: true do
|
241
|
+
expect(logger).to receive(:warn).with(/\AMachine .*? not configured for this environment/).at_least(:once)
|
242
|
+
expect(logger).to receive(:warn).with(/\AUnable to find machine/).at_least(:once)
|
243
|
+
allow(machine.env).to receive(:machine).and_raise(Vagrant::Errors::MachineNotFound, name: 'dummy')
|
244
|
+
provisioner.provision
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
require 'vagrant/ansible_auto/util/config'
|
6
|
+
|
7
|
+
describe VagrantPlugins::AnsibleAuto::Util::Config do
|
8
|
+
let(:including_klass) do
|
9
|
+
Class.new do
|
10
|
+
include VagrantPlugins::AnsibleAuto::Util::Config
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_const("#{including_klass.name}::UNSET_VALUE", Object.new)
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:including_instance) { including_klass.new }
|
19
|
+
let(:unset_value) { including_klass.const_get :UNSET_VALUE }
|
20
|
+
let(:unset_values) { [nil, @meh, unset_value] }
|
21
|
+
|
22
|
+
describe '#unset?' do
|
23
|
+
context 'given a nil value, undefined value, or the special UNSET_VALUE object' do
|
24
|
+
it 'returns true' do
|
25
|
+
expect(unset_values.map { |o| including_instance.unset? o }).to all(be true)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'given anything else' do
|
30
|
+
it 'returns false' do
|
31
|
+
expect(['hey', 10, Object.new].map { |o| including_instance.unset? o }).to all(be false)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#conditional_merge' do
|
37
|
+
context 'given unset values in the first argument' do
|
38
|
+
let(:righthand) { Object.new }
|
39
|
+
|
40
|
+
it 'returns the second argument' do
|
41
|
+
expect(unset_values.map { |o| including_instance.conditional_merge(o, righthand) }).to all(eq(righthand))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'given unset values in the second argument' do
|
46
|
+
let(:lefthand) { Object.new }
|
47
|
+
|
48
|
+
it 'returns the second argument' do
|
49
|
+
expect(unset_values.map { |o| including_instance.conditional_merge(lefthand, o) }).to all(eq(lefthand))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'given two hashes' do
|
54
|
+
let(:lefthand) { { a: 'hash', b: { real: 'dude' } } }
|
55
|
+
let(:righthand) { { b: { real: 'calm', and: %w[do not move] }, c: 'you later' } }
|
56
|
+
let(:merged) { { a: 'hash', b: { real: 'calm', and: %w[do not move] }, c: 'you later' } }
|
57
|
+
|
58
|
+
it 'merges the second argument into the first' do
|
59
|
+
expect(including_instance.conditional_merge(lefthand, righthand)).to eq(merged)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|