blob: dcefbd9b878daa5a99ed277fbee6f8b490bd8e5a [file] [log] [blame]
Oleg Iurchenko5b1e5322017-10-20 00:29:20 +03001# -*- coding: utf-8 -*-
2'''
3Management of Keystone users
4============================
5
6:depends: - keystoneclient Python module
7:configuration: See :py:mod:`salt.modules.keystone` for setup instructions.
8
9.. code-block:: yaml
10
11 Keystone tenants:
12 keystoneng.tenant_present:
13 - names:
14 - admin
15 - demo
16 - service
17
18 Keystone roles:
19 keystoneng.role_present:
20 - names:
21 - admin
22 - Member
23
24 admin:
25 keystoneng.user_present:
26 - password: R00T_4CC3SS
27 - email: admin@domain.com
28 - roles:
29 admin: # tenants
30 - admin # roles
31 service:
32 - admin
33 - Member
34 - require:
35 - keystone: Keystone tenants
36 - keystone: Keystone roles
37
38 nova:
39 keystoneng.user_present:
40 - password: '$up3rn0v4'
41 - email: nova@domain.com
42 - tenant: service
43 - roles:
44 service:
45 - admin
46 - require:
47 - keystone: Keystone tenants
48 - keystone: Keystone roles
49
50 demo:
51 keystoneng.user_present:
52 - password: 'd3m0n$trati0n'
53 - email: demo@domain.com
54 - tenant: demo
55 - roles:
56 demo:
57 - Member
58 - require:
59 - keystone: Keystone tenants
60 - keystone: Keystone roles
61
62 nova service:
63 keystoneng.service_present:
64 - name: nova
65 - service_type: compute
66 - description: OpenStack Compute Service
67
68'''
69
Oleg Iurchenkofd0f5f32018-02-07 15:44:08 +020070# Import 3rd-party libs
71from salt.ext import six
Oleg Iurchenko5b1e5322017-10-20 00:29:20 +030072
73def __virtual__():
74 '''
75 Only load if the keystone module is in __salt__
76 '''
77 return 'keystoneng' if 'keystoneng.auth' in __salt__ else False
78
79
80_OS_IDENTITY_API_VERSION = 2
81_TENANT_ID = 'tenant_id'
82
83
84def _api_version(profile=None, **connection_args):
85 '''
86 Sets global variables _OS_IDENTITY_API_VERSION and _TENANT_ID
87 depending on API version.
88 '''
89 global _TENANT_ID
90 global _OS_IDENTITY_API_VERSION
91 try:
92 if float(__salt__['keystoneng.api_version'](profile=profile, **connection_args)) >= 3:
93 _TENANT_ID = 'project_id'
94 _OS_IDENTITY_API_VERSION = 3
95 except KeyError:
96 pass
97
98
99def user_present(name,
100 password,
101 email,
102 tenant=None,
103 enabled=True,
104 roles=None,
105 profile=None,
106 password_reset=True,
107 project=None,
108 **connection_args):
109 '''
110 Ensure that the keystone user is present with the specified properties.
111
112 name
113 The name of the user to manage
114
115 password
116 The password to use for this user.
117
118 .. note::
119
120 If the user already exists and a different password was set for
121 the user than the one specified here, the password for the user
122 will be updated. Please set the ``password_reset`` option to
123 ``False`` if this is not the desired behavior.
124
125 password_reset
126 Whether or not to reset password after initial set. Defaults to
127 ``True``.
128
129 email
130 The email address for this user
131
132 tenant
133 The tenant (name) for this user
134
135 project
136 The project (name) for this user (overrides tenant in api v3)
137
138 enabled
139 Availability state for this user
140
141 roles
142 The roles the user should have under given tenants.
143 Passed as a dictionary mapping tenant names to a list
144 of roles in this tenant, i.e.::
145
146 roles:
147 admin: # tenant
148 - admin # role
149 service:
150 - admin
151 - Member
152 '''
153 ret = {'name': name,
154 'changes': {},
155 'result': True,
156 'comment': 'User "{0}" will be updated'.format(name)}
157
158 _api_version(profile=profile, **connection_args)
159
160 if project and not tenant:
161 tenant = project
162
163 # Validate tenant if set
164 if tenant is not None:
165 tenantdata = __salt__['keystoneng.tenant_get'](name=tenant,
166 profile=profile,
167 **connection_args)
168 if 'Error' in tenantdata:
169 ret['result'] = False
170 ret['comment'] = 'Tenant / project "{0}" does not exist'.format(tenant)
171 return ret
172 tenant_id = tenantdata[tenant]['id']
173 else:
174 tenant_id = None
175
176 # Check if user is already present
177 user = __salt__['keystoneng.user_get'](name=name, profile=profile,
178 **connection_args)
179 if 'Error' not in user:
180
181 change_email = False
182 change_enabled = False
183 change_tenant = False
184 change_password = False
185
186 if user[name].get('email', None) != email:
187 change_email = True
188
189 if user[name].get('enabled', None) != enabled:
190 change_enabled = True
191
192 if tenant and (_TENANT_ID not in user[name] or
193 user[name].get(_TENANT_ID, None) != tenant_id):
194 change_tenant = True
195
196 if (password_reset is True and
197 not __salt__['keystoneng.user_verify_password'](name=name,
198 password=password,
199 profile=profile,
200 **connection_args)):
201 change_password = True
202
203 if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password):
204 ret['result'] = None
205 ret['comment'] = 'User "{0}" will be updated'.format(name)
206 if change_email is True:
207 ret['changes']['Email'] = 'Will be updated'
208 if change_enabled is True:
209 ret['changes']['Enabled'] = 'Will be True'
210 if change_tenant is True:
211 ret['changes']['Tenant'] = 'Will be added to "{0}" tenant'.format(tenant)
212 if change_password is True:
213 ret['changes']['Password'] = 'Will be updated'
214 return ret
215
216 ret['comment'] = 'User "{0}" is already present'.format(name)
217
218 if change_email:
219 __salt__['keystoneng.user_update'](name=name, email=email, profile=profile, **connection_args)
220 ret['comment'] = 'User "{0}" has been updated'.format(name)
221 ret['changes']['Email'] = 'Updated'
222
223 if change_enabled:
224 __salt__['keystoneng.user_update'](name=name, enabled=enabled, profile=profile, **connection_args)
225 ret['comment'] = 'User "{0}" has been updated'.format(name)
226 ret['changes']['Enabled'] = 'Now {0}'.format(enabled)
227
228 if change_tenant:
229 __salt__['keystoneng.user_update'](name=name, tenant=tenant, profile=profile, **connection_args)
230 ret['comment'] = 'User "{0}" has been updated'.format(name)
231 ret['changes']['Tenant'] = 'Added to "{0}" tenant'.format(tenant)
232
233 if change_password:
234 __salt__['keystoneng.user_password_update'](name=name, password=password, profile=profile,
235 **connection_args)
236 ret['comment'] = 'User "{0}" has been updated'.format(name)
237 ret['changes']['Password'] = 'Updated'
238
239 if roles:
240 for tenant in roles:
241 args = dict({'user_name': name, 'tenant_name':
242 tenant, 'profile': profile}, **connection_args)
243 tenant_roles = __salt__['keystoneng.user_role_list'](**args)
244 for role in roles[tenant]:
245 if role not in tenant_roles:
246 if __opts__.get('test'):
247 ret['result'] = None
248 ret['comment'] = 'User roles "{0}" will been updated'.format(name)
249 return ret
250 addargs = dict({'user': name, 'role': role,
251 'tenant': tenant,
252 'profile': profile},
253 **connection_args)
254 newrole = __salt__['keystoneng.user_role_add'](**addargs)
255 if 'roles' in ret['changes']:
256 ret['changes']['roles'].append(newrole)
257 else:
258 ret['changes']['roles'] = [newrole]
259 roles_to_remove = list(set(tenant_roles) - set(roles[tenant]))
260 for role in roles_to_remove:
261 if __opts__.get('test'):
262 ret['result'] = None
263 ret['comment'] = 'User roles "{0}" will been updated'.format(name)
264 return ret
265 addargs = dict({'user': name, 'role': role,
266 'tenant': tenant,
267 'profile': profile},
268 **connection_args)
269 oldrole = __salt__['keystoneng.user_role_remove'](**addargs)
270 if 'roles' in ret['changes']:
271 ret['changes']['roles'].append(oldrole)
272 else:
273 ret['changes']['roles'] = [oldrole]
274 else:
275 # Create that user!
276 if __opts__.get('test'):
277 ret['result'] = None
278 ret['comment'] = 'Keystone user "{0}" will be added'.format(name)
279 ret['changes']['User'] = 'Will be created'
280 return ret
281 __salt__['keystoneng.user_create'](name=name,
282 password=password,
283 email=email,
284 tenant_id=tenant_id,
285 enabled=enabled,
286 profile=profile,
287 **connection_args)
288 if roles:
289 for tenant in roles:
290 for role in roles[tenant]:
291 __salt__['keystoneng.user_role_add'](user=name,
292 role=role,
293 tenant=tenant,
294 profile=profile,
295 **connection_args)
296 ret['comment'] = 'Keystone user {0} has been added'.format(name)
297 ret['changes']['User'] = 'Created'
298
299 return ret
300
301
302def user_absent(name, profile=None, **connection_args):
303 '''
304 Ensure that the keystone user is absent.
305
306 name
307 The name of the user that should not exist
308 '''
309 ret = {'name': name,
310 'changes': {},
311 'result': True,
312 'comment': 'User "{0}" is already absent'.format(name)}
313
314 # Check if user is present
315 user = __salt__['keystoneng.user_get'](name=name, profile=profile,
316 **connection_args)
317 if 'Error' not in user:
318 if __opts__.get('test'):
319 ret['result'] = None
320 ret['comment'] = 'User "{0}" will be deleted'.format(name)
321 return ret
322 # Delete that user!
323 __salt__['keystoneng.user_delete'](name=name, profile=profile,
324 **connection_args)
325 ret['comment'] = 'User "{0}" has been deleted'.format(name)
326 ret['changes']['User'] = 'Deleted'
327
328 return ret
329
330
331def tenant_present(name, description=None, enabled=True, profile=None,
332 **connection_args):
333 '''
334 Ensures that the keystone tenant exists
335
336 name
337 The name of the tenant to manage
338
339 description
340 The description to use for this tenant
341
342 enabled
343 Availability state for this tenant
344 '''
345 ret = {'name': name,
346 'changes': {},
347 'result': True,
348 'comment': 'Tenant / project "{0}" already exists'.format(name)}
349
350 _api_version(profile=profile, **connection_args)
351
352 # Check if tenant is already present
353 tenant = __salt__['keystoneng.tenant_get'](name=name,
354 profile=profile,
355 **connection_args)
356
357 if 'Error' not in tenant:
358 if tenant[name].get('description', None) != description:
359 if __opts__.get('test'):
360 ret['result'] = None
361 ret['comment'] = 'Tenant / project "{0}" will be updated'.format(name)
362 ret['changes']['Description'] = 'Will be updated'
363 return ret
364 __salt__['keystoneng.tenant_update'](name=name,
365 description=description,
366 enabled=enabled,
367 profile=profile,
368 **connection_args)
369 ret['comment'] = 'Tenant / project "{0}" has been updated'.format(name)
370 ret['changes']['Description'] = 'Updated'
371 if tenant[name].get('enabled', None) != enabled:
372 if __opts__.get('test'):
373 ret['result'] = None
374 ret['comment'] = 'Tenant / project "{0}" will be updated'.format(name)
375 ret['changes']['Enabled'] = 'Will be {0}'.format(enabled)
376 return ret
377 __salt__['keystoneng.tenant_update'](name=name,
378 description=description,
379 enabled=enabled,
380 profile=profile,
381 **connection_args)
382 ret['comment'] = 'Tenant / project "{0}" has been updated'.format(name)
383 ret['changes']['Enabled'] = 'Now {0}'.format(enabled)
384 else:
385 if __opts__.get('test'):
386 ret['result'] = None
387 ret['comment'] = 'Tenant / project "{0}" will be added'.format(name)
388 ret['changes']['Tenant'] = 'Will be created'
389 return ret
390 # Create tenant
391 if _OS_IDENTITY_API_VERSION > 2:
392 created = __salt__['keystoneng.project_create'](name=name, domain='default', description=description,
393 enabled=enabled, profile=profile, **connection_args)
394 else:
395 created = __salt__['keystoneng.tenant_create'](name=name, description=description, enabled=enabled,
396 profile=profile, **connection_args)
Oleg Iurchenkofd0f5f32018-02-07 15:44:08 +0200397 # If tenant has been created succesfully 'created' is:
398 # {u'test_tenant3': {'enabled': True, 'NAME_ATTR': 'name', 'HUMAN_ID': False, 'name': u'test_tenant3', 'id': u'0a5f319f8a794bfc9045746069c76fd8'}}
399 # If tenant is not created:
400 # {'Error': 'Unable to resolve tenant id'}
401 if 'Error' in created and isinstance(created['Error'], six.string_types):
402 ret['result'] = False
403 ret['comment'] = 'Cannot create tenant / project "{0}"'.format(name)
404 else:
405 ret['changes']['Tenant'] = 'Created'
406 ret['result'] = True
407 ret['comment'] = 'Tenant / project "{0}" has been added'.format(name)
Oleg Iurchenko5b1e5322017-10-20 00:29:20 +0300408 return ret
409
410
411def tenant_absent(name, profile=None, **connection_args):
412 '''
413 Ensure that the keystone tenant is absent.
414
415 name
416 The name of the tenant that should not exist
417 '''
418 ret = {'name': name,
419 'changes': {},
420 'result': True,
421 'comment': 'Tenant / project "{0}" is already absent'.format(name)}
422
423 # Check if tenant is present
424 tenant = __salt__['keystoneng.tenant_get'](name=name,
425 profile=profile,
426 **connection_args)
427 if 'Error' not in tenant:
428 if __opts__.get('test'):
429 ret['result'] = None
430 ret['comment'] = 'Tenant / project "{0}" will be deleted'.format(name)
431 return ret
432 # Delete tenant
433 __salt__['keystoneng.tenant_delete'](name=name, profile=profile,
434 **connection_args)
435 ret['comment'] = 'Tenant / project "{0}" has been deleted'.format(name)
436 ret['changes']['Tenant/Project'] = 'Deleted'
437
438 return ret
439
440
441def project_present(name, description=None, enabled=True, profile=None,
442 **connection_args):
443 '''
444 Ensures that the keystone project exists
445 Alias for tenant_present from V2 API to fulfill
446 V3 API naming convention.
447
448 .. versionadded:: 2016.11.0
449
450 name
451 The name of the project to manage
452
453 description
454 The description to use for this project
455
456 enabled
457 Availability state for this project
458
459 .. code-block:: yaml
460
461 nova:
462 keystoneng.project_present:
463 - enabled: True
464 - description: 'Nova Compute Service'
465
466 '''
467
468 return tenant_present(name, description=description, enabled=enabled, profile=profile,
469 **connection_args)
470
471
472def project_absent(name, profile=None, **connection_args):
473 '''
474 Ensure that the keystone project is absent.
475 Alias for tenant_absent from V2 API to fulfill
476 V3 API naming convention.
477
478 .. versionadded:: 2016.11.0
479
480 name
481 The name of the project that should not exist
482
483 .. code-block:: yaml
484
485 delete_nova:
486 keystoneng.project_absent:
487 - name: nova
488 '''
489
490 return tenant_absent(name, profile=profile, **connection_args)
491
492
493def role_present(name, profile=None, **connection_args):
494 ''''
495 Ensures that the keystone role exists
496
497 name
498 The name of the role that should be present
499 '''
500 ret = {'name': name,
501 'changes': {},
502 'result': True,
503 'comment': 'Role "{0}" already exists'.format(name)}
504
505 # Check if role is already present
506 role = __salt__['keystoneng.role_get'](name=name, profile=profile,
507 **connection_args)
508
509 if 'Error' not in role:
510 return ret
511 else:
512 if __opts__.get('test'):
513 ret['result'] = None
514 ret['comment'] = 'Role "{0}" will be added'.format(name)
515 return ret
516 # Create role
517 __salt__['keystoneng.role_create'](name, profile=profile,
518 **connection_args)
519 ret['comment'] = 'Role "{0}" has been added'.format(name)
520 ret['changes']['Role'] = 'Created'
521 return ret
522
523
524def role_absent(name, profile=None, **connection_args):
525 '''
526 Ensure that the keystone role is absent.
527
528 name
529 The name of the role that should not exist
530 '''
531 ret = {'name': name,
532 'changes': {},
533 'result': True,
534 'comment': 'Role "{0}" is already absent'.format(name)}
535
536 # Check if role is present
537 role = __salt__['keystoneng.role_get'](name=name, profile=profile,
538 **connection_args)
539 if 'Error' not in role:
540 if __opts__.get('test'):
541 ret['result'] = None
542 ret['comment'] = 'Role "{0}" will be deleted'.format(name)
543 return ret
544 # Delete role
545 __salt__['keystoneng.role_delete'](name=name, profile=profile,
546 **connection_args)
547 ret['comment'] = 'Role "{0}" has been deleted'.format(name)
548 ret['changes']['Role'] = 'Deleted'
549
550 return ret
551
552
553def service_present(name, service_type, description=None,
554 profile=None, **connection_args):
555 '''
556 Ensure service present in Keystone catalog
557
558 name
559 The name of the service
560
561 service_type
562 The type of Openstack Service
563
564 description (optional)
565 Description of the service
566 '''
567 ret = {'name': name,
568 'changes': {},
569 'result': True,
570 'comment': 'Service "{0}" already exists'.format(name)}
571
572 # Check if service is already present
573 role = __salt__['keystoneng.service_get'](name=name,
574 profile=profile,
575 **connection_args)
576
577 if 'Error' not in role:
578 return ret
579 else:
580 if __opts__.get('test'):
581 ret['result'] = None
582 ret['comment'] = 'Service "{0}" will be added'.format(name)
583 return ret
584 # Create service
585 __salt__['keystoneng.service_create'](name, service_type,
586 description,
587 profile=profile,
588 **connection_args)
589 ret['comment'] = 'Service "{0}" has been added'.format(name)
590 ret['changes']['Service'] = 'Created'
591
592 return ret
593
594
595def service_absent(name, profile=None, **connection_args):
596 '''
597 Ensure that the service doesn't exist in Keystone catalog
598
599 name
600 The name of the service that should not exist
601 '''
602 ret = {'name': name,
603 'changes': {},
604 'result': True,
605 'comment': 'Service "{0}" is already absent'.format(name)}
606
607 # Check if service is present
608 role = __salt__['keystoneng.service_get'](name=name,
609 profile=profile,
610 **connection_args)
611 if 'Error' not in role:
612 if __opts__.get('test'):
613 ret['result'] = None
614 ret['comment'] = 'Service "{0}" will be deleted'.format(name)
615 return ret
616 # Delete service
617 __salt__['keystoneng.service_delete'](name=name,
618 profile=profile,
619 **connection_args)
620 ret['comment'] = 'Service "{0}" has been deleted'.format(name)
621 ret['changes']['Service'] = 'Deleted'
622
623 return ret
624
625
626def endpoint_present(name,
627 publicurl=None,
628 internalurl=None,
629 adminurl=None,
630 region=None,
631 profile=None,
632 url=None,
633 interface=None, **connection_args):
634 '''
635 Ensure the specified endpoints exists for service
636
637 name
638 The Service name
639
640 publicurl
641 The public url of service endpoint (for V2 API)
642
643 internalurl
644 The internal url of service endpoint (for V2 API)
645
646 adminurl
647 The admin url of the service endpoint (for V2 API)
648
649 region
650 The region of the endpoint
651
652 url
653 The endpoint URL (for V3 API)
654
655 interface
656 The interface type, which describes the visibility
657 of the endpoint. (for V3 API)
658
659 '''
660 ret = {'name': name,
661 'changes': {},
662 'result': True,
663 'comment': ''}
664 endpoint = __salt__['keystoneng.endpoint_get'](name, region,
665 profile=profile,
666 interface=interface,
667 **connection_args)
668
669 def _changes(desc):
670 return ret.get('comment', '') + desc + '\n'
671
672 def _create_endpoint():
673 if _OS_IDENTITY_API_VERSION > 2:
674 ret['changes'] = __salt__['keystoneng.endpoint_create'](
675 name,
676 region=region,
677 url=url,
678 interface=interface,
679 profile=profile,
680 **connection_args)
681 else:
682 ret['changes'] = __salt__['keystoneng.endpoint_create'](
683 name,
684 region=region,
685 publicurl=publicurl,
686 adminurl=adminurl,
687 internalurl=internalurl,
688 profile=profile,
689 **connection_args)
690
691 if endpoint and 'Error' not in endpoint and endpoint.get('region') == region:
692
693 if _OS_IDENTITY_API_VERSION > 2:
694
695 change_url = False
696 change_interface = False
697
698 if endpoint.get('url', None) != url:
699 ret['comment'] = _changes('URL changes from "{0}" to "{1}"'.format(endpoint.get('url', None), url))
700 change_url = True
701
702 if endpoint.get('interface', None) != interface:
703 ret['comment'] = _changes('Interface changes from "{0}" to "{1}"'.format(endpoint.get('interface', None), interface))
704 change_interface = True
705
706 if __opts__.get('test') and (change_url or change_interface):
707 ret['result'] = None
708 ret['changes']['Endpoint'] = 'Will be updated'
709 ret['comment'] += 'Endpoint for service "{0}" will be updated'.format(name)
710 return ret
711
712 if change_url:
713 ret['changes']['url'] = url
714
715 if change_interface:
716 ret['changes']['interface'] = interface
717
718 else:
719 change_publicurl = False
720 change_adminurl = False
721 change_internalurl = False
722
723 if endpoint.get('publicurl', None) != publicurl:
724 change_publicurl = True
725
726 ret['comment'] = _changes('Public URL changes from "{0}" to "{1}"'.format(
727 endpoint.get('publicurl', None), publicurl)
728 )
729
730 if endpoint.get('adminurl', None) != adminurl:
731 change_adminurl = True
732 ret['comment'] = _changes('Admin URL changes from "{0}" to "{1}"'.format(
733 endpoint.get('adminurl', None), adminurl)
734 )
735
736 if endpoint.get('internalurl', None) != internalurl:
737 change_internalurl = True
738 ret['comment'] = _changes(
739 'Internal URL changes from "{0}" to "{1}"'.format(
740 endpoint.get('internalurl', None),
741 internalurl
742 )
743 )
744
745 if __opts__.get('test') and (change_publicurl or change_adminurl or change_internalurl):
746 ret['result'] = None
747 ret['comment'] += 'Endpoint for service "{0}" will be updated'.format(name)
748 ret['changes']['Endpoint'] = 'Will be updated'
749 return ret
750
751 if change_publicurl:
752 ret['changes']['publicurl'] = publicurl
753
754 if change_adminurl:
755 ret['changes']['adminurl'] = adminurl
756
757 if change_internalurl:
758 ret['changes']['internalurl'] = internalurl
759
760 if ret['comment']: # changed
761 __salt__['keystoneng.endpoint_delete'](name, region, profile=profile, interface=interface, **connection_args)
762 _create_endpoint()
763 ret['comment'] += 'Endpoint for service "{0}" has been updated'.format(name)
764
765 else:
766 # Add new endpoint
767 if __opts__.get('test'):
768 ret['result'] = None
769 ret['changes']['Endpoint'] = 'Will be created'
770 ret['comment'] = 'Endpoint for service "{0}" will be added'.format(name)
771 return ret
772 _create_endpoint()
773 ret['comment'] = 'Endpoint for service "{0}" has been added'.format(name)
774
775 if ret['comment'] == '': # => no changes
776 ret['comment'] = 'Endpoint for service "{0}" already exists'.format(name)
777 return ret
778
779
780def endpoint_absent(name, region=None, profile=None, interface=None, **connection_args):
781 '''
782 Ensure that the endpoint for a service doesn't exist in Keystone catalog
783
784 name
785 The name of the service whose endpoints should not exist
786
787 region (optional)
788 The region of the endpoint. Defaults to ``RegionOne``.
789
790 interface
791 The interface type, which describes the visibility
792 of the endpoint. (for V3 API)
793 '''
794 ret = {'name': name,
795 'changes': {},
796 'result': True,
797 'comment': 'Endpoint for service "{0}"{1} is already absent'.format(name,
798 ', interface "{0}",'.format(interface) if interface is not None else '')}
799
800 # Check if service is present
801 endpoint = __salt__['keystoneng.endpoint_get'](name, region,
802 profile=profile,
803 interface=interface,
804 **connection_args)
805 if not endpoint:
806 return ret
807 else:
808 if __opts__.get('test'):
809 ret['result'] = None
810 ret['comment'] = 'Endpoint for service "{0}" will be deleted'.format(name)
811 return ret
812 # Delete service
813 __salt__['keystoneng.endpoint_delete'](name, region,
814 profile=profile,
815 interface=interface,
816 **connection_args)
817 ret['comment'] = 'Endpoint for service "{0}"{1} has been deleted'.format(name,
818 ', interface "{0}",'.format(interface) if interface is not None else '')
819 ret['changes']['endpoint'] = 'Deleted'
820 return ret