3 require 'puppet/provider/keystone_user/openstack'
4 require 'puppet/provider/openstack'
6 provider_class = Puppet::Type.type(:keystone_user).provider(:openstack)
9 Puppet::Type.type(:keystone_tenant).provider(:openstack)
12 describe provider_class do
14 shared_examples 'authenticated with environment variables' do
15 ENV['OS_USERNAME'] = 'test'
16 ENV['OS_PASSWORD'] = 'abc123'
17 ENV['OS_PROJECT_NAME'] = 'test'
18 ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
28 :email => 'foo@example.com',
29 :domain => 'foo_domain',
34 Puppet::Type::Keystone_user.new(user_attrs)
38 provider_class.new(resource)
41 def before_hook(delete, missing, noproject, user_cached)
42 provider.class.expects(:openstack).once
43 .with('domain', 'list', '--quiet', '--format', 'csv', [])
44 .returns('"ID","Name","Enabled","Description"
45 "foo_domain_id","foo_domain",True,"foo domain"
46 "bar_domain_id","bar_domain",True,"bar domain"
47 "another_domain_id","another_domain",True,"another domain"
48 "disabled_domain_id","disabled_domain",False,"disabled domain"
51 return # using cached user, so no user list
58 # delete will call the search again and should not return the deleted user
59 foo_returns = ['"ID","Name","Project Id","Domain","Description","Email","Enabled"
60 "1cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"foo_domain_id","foo description","foo@example.com",True
61 "2cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"bar_domain_id","foo description","foo@example.com",True
62 "3cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"another_domain_id","foo description","foo@example.com",True
72 provider.class.expects(:openstack).times(nn)
73 .with('user', 'list', '--quiet', '--format', 'csv', ['--long'])
74 .returns(*foo_returns)
77 before :each, :default => true do
78 before_hook(false, false, false, false)
80 before :each, :delete => true do
81 before_hook(true, false, false, false)
83 before :each, :missing => true do
84 before_hook(false, true, false, false)
86 before :each, :noproject => true do
87 before_hook(false, false, true, false)
89 before :each, :default_https => true do
90 before_hook(false, false, false, false)
92 before :each, :user_cached => true do
93 before_hook(false, false, false, true)
95 before :each, :nohooks => true do
99 describe 'when managing a user' do
100 it_behaves_like 'authenticated with environment variables' do
101 describe '#create' do
102 it 'creates a user' do
103 project_class.expects(:openstack).once
104 .with('domain', 'list', '--quiet', '--format', 'csv', [])
105 .returns('"ID","Name","Enabled","Description"
106 "foo_domain_id","foo_domain",True,"foo domain"
107 "bar_domain_id","bar_domain",True,"bar domain"
108 "another_domain_id","another_domain",True,"another domain"
109 "disabled_domain_id","disabled_domain",False,"disabled domain"
111 project_class.expects(:openstack)
112 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
113 .returns('"ID","Name","Domain ID","Description","Enabled"
114 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
115 "2cb05cfed7c24279be884ba4f6520262","foo","bar_domain_id","foo",True
117 provider.class.expects(:openstack)
118 .with('role', 'show', '--format', 'shell', '_member_')
122 provider.class.expects(:openstack)
123 .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '12b23f07d4a3448d8189521ab09610b0'])
124 provider.class.expects(:openstack)
125 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
126 .returns('email="foo@example.com"
128 id="12b23f07d4a3448d8189521ab09610b0"
133 expect(provider.exists?).to be_truthy
137 describe '#destroy' do
138 it 'destroys a user' do
139 provider.instance_variable_get('@property_hash')[:id] = 'my-user-id'
140 provider.class.expects(:openstack)
141 .with('user', 'delete', 'my-user-id')
143 expect(provider.exists?).to be_falsey
148 describe '#exists' do
149 context 'when user does not exist' do
150 subject(:response) do
151 response = provider.exists?
154 it { is_expected.to be_falsey }
158 describe '#instances', :default => true do
159 it 'finds every user' do
160 instances = provider.class.instances
161 expect(instances.count).to eq(3)
162 expect(instances[0].name).to eq('foo')
163 expect(instances[0].domain).to eq('another_domain')
164 expect(instances[1].name).to eq('foo::foo_domain')
165 expect(instances[2].name).to eq('foo::bar_domain')
169 describe '#tenant' do
170 it 'gets the tenant with default backend', :nohooks => true do
171 project_class.expects(:openstack)
172 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
173 .returns('"ID","Name","Domain ID","Description","Enabled"
174 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
175 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
177 provider.class.expects(:openstack)
178 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
179 .returns('"ID","Name","Domain ID","Description","Enabled"
180 "foo_project_id1","foo","foo_domain_id","",True
182 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
183 tenant = provider.tenant
184 expect(tenant).to eq('foo')
187 it 'gets the tenant with LDAP backend', :nohooks => true do
188 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
189 project_class.expects(:openstack)
190 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
191 .returns('"ID","Name","Domain ID","Description","Enabled"
192 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
193 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
195 provider.class.expects(:openstack)
196 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
197 .returns('"ID","Name","Domain ID","Description","Enabled"
198 "foo_project_id1","foo","foo_domain_id","",True
199 "bar_project_id2","bar","bar_domain_id","",True
200 "foo_project_id2","foo","another_domain_id","",True
202 tenant = provider.tenant
203 expect(tenant).to eq('foo')
206 describe '#tenant=' do
207 context 'when using default backend', :nohooks => true do
208 it 'sets the tenant' do
209 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
210 provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
211 project_class.expects(:openstack)
212 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
213 .returns('"ID","Name","Domain ID","Description","Enabled"
214 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
215 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
217 provider.class.expects(:openstack)
218 .with('role', 'show', '--format', 'shell', '_member_')
219 .returns('name="_member_"')
220 provider.class.expects(:openstack)
221 .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
222 provider.tenant=('bar')
225 context 'when using LDAP read-write backend', :nohooks => true do
226 it 'sets the tenant when _member_ role exists' do
227 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
228 provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
229 project_class.expects(:openstack)
230 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
231 .returns('"ID","Name","Domain ID","Description","Enabled"
232 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
233 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
235 provider.class.expects(:openstack)
236 .with('role', 'show', '--format', 'shell', '_member_')
237 .returns('name="_member_"')
238 provider.class.expects(:openstack)
239 .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
240 provider.tenant=('bar')
242 it 'sets the tenant when _member_ role does not exist' do
243 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
244 provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
245 project_class.expects(:openstack)
246 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
247 .returns('"ID","Name","Domain ID","Description","Enabled"
248 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
249 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
251 provider.class.expects(:openstack)
252 .with('role', 'show', '--format', 'shell', '_member_')
253 .raises(Puppet::ExecutionFailure, 'no such role _member_')
254 provider.class.expects(:openstack)
255 .with('role', 'create', '--format', 'shell', '_member_')
256 .returns('name="_member_"')
257 provider.class.expects(:openstack)
258 .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
259 provider.tenant=('bar')
262 context 'when using LDAP read-only backend', :nohooks => true do
263 it 'sets the tenant when _member_ role exists' do
264 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
265 provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
266 project_class.expects(:openstack)
267 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
268 .returns('"ID","Name","Domain ID","Description","Enabled"
269 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
270 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
272 provider.class.expects(:openstack)
273 .with('role', 'show', '--format', 'shell', '_member_')
274 .returns('name="_member_"')
275 provider.class.expects(:openstack)
276 .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
277 provider.tenant=('bar')
284 describe "#password", :nohooks => true do
288 :ensure => 'present',
292 :email => 'foo@example.com',
293 :domain => 'foo_domain',
298 Puppet::Type::Keystone_user.new(user_attrs)
302 provider_class.new(resource)
305 shared_examples 'with auth-url environment variable' do
306 ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
309 it_behaves_like 'with auth-url environment variable' do
310 it 'checks the password' do
311 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
313 Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mockcreds)
314 mockcreds.expects(:auth_url=).with('http://127.0.0.1:5000')
315 mockcreds.expects(:password=).with('foo')
316 mockcreds.expects(:username=).with('foo')
317 mockcreds.expects(:user_id=).with('1cb05cfed7c24279be884ba4f6520262')
318 mockcreds.expects(:project_id=).with('project-id-1')
319 mockcreds.expects(:to_env).returns(mockcreds)
320 Puppet::Provider::Openstack.expects(:openstack)
321 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
322 .returns('"ID","Name","Domain ID","Description","Enabled"
323 "project-id-1","foo","foo_domain_id","foo",True
325 Puppet::Provider::Openstack.expects(:openstack)
326 .with('token', 'issue', ['--format', 'value'])
327 .returns('2015-05-14T04:06:05Z
328 e664a386befa4a30878dcef20e79f167
329 8dce2ae9ecd34c199d2877bf319a3d06
330 ac43ec53d5a74a0b9f51523ae41a29f0
332 password = provider.password
333 expect(password).to eq('foo')
336 it 'fails the password check' do
337 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
338 Puppet::Provider::Openstack.expects(:openstack)
339 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
340 .returns('"ID","Name","Domain ID","Description","Enabled"
341 "project-id-1","foo","foo_domain_id","foo",True
343 Puppet::Provider::Openstack.expects(:openstack)
344 .with('token', 'issue', ['--format', 'value'])
345 .raises(Puppet::ExecutionFailure, 'HTTP 401 invalid authentication')
346 password = provider.password
347 expect(password).to eq(nil)
350 it 'checks the password with domain scoped token' do
351 provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
352 provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
354 Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mockcreds)
355 mockcreds.expects(:auth_url=).with('http://127.0.0.1:5000')
356 mockcreds.expects(:password=).with('foo')
357 mockcreds.expects(:username=).with('foo')
358 mockcreds.expects(:user_id=).with('1cb05cfed7c24279be884ba4f6520262')
359 mockcreds.expects(:domain_name=).with('foo_domain')
360 mockcreds.expects(:to_env).returns(mockcreds)
361 Puppet::Provider::Openstack.expects(:openstack)
362 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
363 .returns('"ID","Name","Domain ID","Description","Enabled"
365 Puppet::Provider::Openstack.expects(:openstack)
366 .with('token', 'issue', ['--format', 'value'])
367 .returns('2015-05-14T04:06:05Z
368 e664a386befa4a30878dcef20e79f167
369 8dce2ae9ecd34c199d2877bf319a3d06
370 ac43ec53d5a74a0b9f51523ae41a29f0
372 password = provider.password
373 expect(password).to eq('foo')
378 describe 'when updating a user with unmanaged password', :nohooks => true do
380 describe 'when updating a user with unmanaged password' do
385 :ensure => 'present',
388 :replace_password => 'False',
390 :email => 'foo@example.com',
391 :domain => 'foo_domain',
396 Puppet::Type::Keystone_user.new(user_attrs)
400 provider_class.new(resource)
403 it 'should not try to check password' do
404 expect(provider.password).to eq('foo')
409 it_behaves_like 'authenticated with environment variables' do
410 describe 'v3 domains with no domain in resource', :nohooks => true do
414 :ensure => 'present',
418 :email => 'foo@example.com',
422 it 'adds default domain to commands' do
423 provider_class.class_exec {
424 @default_domain_id = nil
427 'identity' => {'default_domain_id' => 'foo_domain_id'}
429 Puppet::Util::IniConfig::File.expects(:new).returns(mock)
430 File.expects(:exists?).with('/etc/keystone/keystone.conf').returns(true)
431 mock.expects(:read).with('/etc/keystone/keystone.conf')
432 provider.class.expects(:openstack)
433 .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
434 .returns('"ID","Name"
436 project_class.expects(:openstack)
437 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
438 .returns('"ID","Name","Domain ID","Description","Enabled"
439 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
440 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
442 provider.class.expects(:openstack)
443 .with('role', 'show', '--format', 'shell', '_member_')
447 provider.class.expects(:openstack)
448 .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
449 provider.class.expects(:openstack)
450 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
451 .returns('email="foo@example.com"
453 id="1cb05cfed7c24279be884ba4f6520262"
458 expect(provider.exists?).to be_truthy
459 expect(provider.id).to eq("1cb05cfed7c24279be884ba4f6520262")
463 describe 'v3 domains with domain in resource' do
467 :ensure => 'present',
471 :email => 'foo@example.com',
472 :domain => 'bar_domain',
476 it 'uses given domain in commands' do
477 project_class.expects(:openstack)
478 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
479 .returns('"ID","Name","Domain ID","Description","Enabled"
480 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
481 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
483 provider.class.expects(:openstack)
484 .with('role', 'show', '--format', 'shell', '_member_')
488 provider.class.expects(:openstack)
489 .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
490 provider.class.expects(:openstack)
491 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'bar_domain'])
492 .returns('email="foo@example.com"
494 id="2cb05cfed7c24279be884ba4f6520262"
499 expect(provider.exists?).to be_truthy
500 expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
504 describe 'v3 domains with domain in name/title' do
507 :name => 'foo::bar_domain',
508 :ensure => 'present',
512 :email => 'foo@example.com',
516 it 'uses given domain in commands' do
517 project_class.expects(:openstack)
518 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
519 .returns('"ID","Name","Domain ID","Description","Enabled"
520 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
521 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
523 provider.class.expects(:openstack)
524 .with('role', 'show', '--format', 'shell', '_member_')
528 provider.class.expects(:openstack)
529 .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
530 provider.class.expects(:openstack)
531 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'bar_domain'])
532 .returns('email="foo@example.com"
534 id="2cb05cfed7c24279be884ba4f6520262"
539 expect(provider.exists?).to be_truthy
540 expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
544 describe 'v3 domains with domain in name/title and in resource' do
547 :name => 'foo::bar_domain',
548 :ensure => 'present',
552 :email => 'foo@example.com',
553 :domain => 'foo_domain',
557 it 'uses the resource domain in commands' do
558 project_class.expects(:openstack)
559 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
560 .returns('"ID","Name","Domain ID","Description","Enabled"
561 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
562 "2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
564 provider.class.expects(:openstack)
565 .with('role', 'show', '--format', 'shell', '_member_')
569 provider.class.expects(:openstack)
570 .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
571 provider.class.expects(:openstack)
572 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
573 .returns('email="foo@example.com"
575 id="2cb05cfed7c24279be884ba4f6520262"
580 expect(provider.exists?).to be_truthy
581 expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
585 describe 'v3 domains with domain in name/title and in resource and in tenant' do
588 :name => 'foo::bar_domain',
589 :ensure => 'present',
592 :tenant => 'foo::foo_domain',
593 :email => 'foo@example.com',
594 :domain => 'foo_domain',
598 it 'uses the resource domain in commands' do
599 project_class.expects(:openstack)
600 .with('project', 'list', '--quiet', '--format', 'csv', '--long')
601 .returns('"ID","Name","Domain ID","Description","Enabled"
602 "1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
603 "2cb05cfed7c24279be884ba4f6520262","foo","bar_domain_id","foo",True
605 provider.class.expects(:openstack)
606 .with('role', 'show', '--format', 'shell', '_member_')
610 provider.class.expects(:openstack)
611 .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
612 provider.class.expects(:openstack)
613 .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
614 .returns('email="foo@example.com"
616 id="2cb05cfed7c24279be884ba4f6520262"
621 expect(provider.exists?).to be_truthy
622 expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")