#NEWLINE# def managed( name , entries , connect_spec = None ) : #NEWLINE# #INDENT# "Ensure the existance (or not) of LDAP entries and their attributes\n\n    Example:\n\n    .. code-block:: yaml\n\n        ldapi:///:\n          ldap.managed:\n            - connect_spec:\n                bind:\n                  method: sasl\n\n            - entries:\n\n              # make sure the entry doesn't exist\n              - cn=foo,ou=users,dc=example,dc=com:\n                - delete_others: True\n\n              # make sure the entry exists with only the specified\n              # attribute values\n              - cn=admin,dc=example,dc=com:\n                - delete_others: True\n                - replace:\n                    cn:\n                      - admin\n                    description:\n                      - LDAP administrator\n                    objectClass:\n                      - simpleSecurityObject\n                      - organizationalRole\n                    userPassword:\n                      - {{pillar.ldap_admin_password}}\n\n              # make sure the entry exists, its olcRootDN attribute\n              # has only the specified value, the olcRootDN attribute\n              # doesn't exist, and all other attributes are ignored\n              - 'olcDatabase={1}hdb,cn=config':\n                - replace:\n                    olcRootDN:\n                      - cn=admin,dc=example,dc=com\n                    # the admin entry has its own password attribute\n                    olcRootPW: []\n\n              # note the use of 'default'.  also note how you don't\n              # have to use list syntax if there is only one attribute\n              # value\n              - cn=foo,ou=users,dc=example,dc=com:\n                - delete_others: True\n                - default:\n                    userPassword: changeme\n                    shadowLastChange: 0\n                    # keep sshPublicKey if present, but don't create\n                    # the attribute if it is missing\n                    sshPublicKey: []\n                - replace:\n                    cn: foo\n                    uid: foo\n                    uidNumber: 1000\n                    gidNumber: 1000\n                    gecos: Foo Bar\n                    givenName: Foo\n                    sn: Bar\n                    homeDirectory: /home/foo\n                    loginShell: /bin/bash\n                    objectClass:\n                      - inetOrgPerson\n                      - posixAccount\n                      - top\n                      - ldapPublicKey\n                      - shadowAccount\n\n    :param name:\n        The URL of the LDAP server.  This is ignored if\n        ``connect_spec`` is either a connection object or a dict with\n        a ``'url'`` entry.\n\n    :param entries:\n        A description of the desired state of zero or more LDAP\n        entries.\n\n        ``entries`` is an iterable of dicts.  Each of these dict's\n        keys are the distinguished names (DNs) of LDAP entries to\n        manage.  Each of these dicts is processed in order.  A later\n        dict can reference an LDAP entry that was already mentioned in\n        an earlier dict, which makes it possible for later dicts to\n        enhance or alter the desired state of an LDAP entry.\n\n        The DNs are mapped to a description of the LDAP entry's\n        desired state.  These LDAP entry descriptions are themselves\n        iterables of dicts.  Each dict in the iterable is processed in\n        order.  They contain directives controlling the entry's state.\n        The key names the directive type and the value is state\n        information for the directive.  The specific structure of the\n        state information depends on the directive type.\n\n        The structure of ``entries`` looks like this::\n\n            [{dn1: [{directive1: directive1_state,\n                     directive2: directive2_state},\n                    {directive3: directive3_state}],\n              dn2: [{directive4: directive4_state,\n                     directive5: directive5_state}]},\n             {dn3: [{directive6: directive6_state}]}]\n\n        These are the directives:\n\n        * ``'delete_others'``\n            Boolean indicating whether to delete attributes not\n            mentioned in this dict or any of the other directive\n            dicts for this DN.  Defaults to ``False``.\n\n            If you don't want to delete an attribute if present, but\n            you also don't want to add it if it is missing or modify\n            it if it is present, you can use either the ``'default'``\n            directive or the ``'add'`` directive with an empty value\n            list.\n\n        * ``'default'``\n            A dict mapping an attribute name to an iterable of default\n            values for that attribute.  If the attribute already\n            exists, it is left alone.  If not, it is created using the\n            given list of values.\n\n            An empty value list is useful when you don't want to\n            create an attribute if it is missing but you do want to\n            preserve it if the ``'delete_others'`` key is ``True``.\n\n        * ``'add'``\n            Attribute values to add to the entry.  This is a dict\n            mapping an attribute name to an iterable of values to add.\n\n            An empty value list is useful when you don't want to\n            create an attribute if it is missing but you do want to\n            preserve it if the ``'delete_others'`` key is ``True``.\n\n        * ``'delete'``\n            Attribute values to remove from the entry.  This is a dict\n            mapping an attribute name to an iterable of values to\n            delete from the attribute.  If the iterable is empty, all\n            of the attribute's values are deleted.\n\n        * ``'replace'``\n            Attributes to replace.  This is a dict mapping an\n            attribute name to an iterable of values.  Any existing\n            values for the attribute are deleted, then the given\n            values are added.  The iterable may be empty.\n\n        In the above directives, the iterables of attribute values may\n        instead be ``None``, in which case an empty list is used, or a\n        scalar such as a string or number, in which case a new list\n        containing the scalar is used.\n\n        Note that if all attribute values are removed from an entry,\n        the entire entry is deleted.\n\n    :param connect_spec:\n        See the description of the ``connect_spec`` parameter of the\n        :py:func:`ldap3.connect <salt.modules.ldap3.connect>` function\n        in the :py:mod:`ldap3 <salt.modules.ldap3>` execution module.\n        If this is a dict and the ``'url'`` entry is not specified,\n        the ``'url'`` entry is set to the value of the ``name``\n        parameter.\n\n    :returns:\n        A dict with the following keys:\n\n        * ``'name'``\n            This is the same object passed to the ``name`` parameter.\n\n        * ``'changes'``\n            This is a dict describing the changes made (or, in test\n            mode, the changes that would have been attempted).  If no\n            changes were made (or no changes would have been\n            attempted), then this dict is empty.  Only successful\n            changes are included.\n\n            Each key is a DN of an entry that was changed (or would\n            have been changed).  Entries that were not changed (or\n            would not have been changed) are not included.  The value\n            is a dict with two keys:\n\n            * ``'old'``\n                The state of the entry before modification.  If the\n                entry did not previously exist, this key maps to\n                ``None``.  Otherwise, the value is a dict mapping each\n                of the old entry's attributes to a list of its values\n                before any modifications were made.  Unchanged\n                attributes are excluded from this dict.\n\n            * ``'new'``\n                The state of the entry after modification.  If the\n                entry was deleted, this key maps to ``None``.\n                Otherwise, the value is a dict mapping each of the\n                entry's attributes to a list of its values after the\n                modifications were made.  Unchanged attributes are\n                excluded from this dict.\n\n            Example ``'changes'`` dict where a new entry was created\n            with a single attribute containing two values::\n\n                {'dn1': {'old': None,\n                         'new': {'attr1': ['val1', 'val2']}}}\n\n            Example ``'changes'`` dict where a new attribute was added\n            to an existing entry::\n\n                {'dn1': {'old': {},\n                         'new': {'attr2': ['val3']}}}\n\n        * ``'result'``\n            One of the following values:\n\n            * ``True`` if no changes were necessary or if all changes\n              were applied successfully.\n            * ``False`` if at least one change was unable to be applied.\n            * ``None`` if changes would be applied but it is in test\n              mode.\n    " #NEWLINE# if ( connect_spec is None ) : #NEWLINE# #INDENT# connect_spec = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# #UNINDENT# try : #NEWLINE# #INDENT# connect_spec . setdefault ( 'url' , name ) #NEWLINE# #UNINDENT# except AttributeError : #NEWLINE# #INDENT# pass #NEWLINE# #UNINDENT# connect = __salt__ [ 'ldap3.connect' ] #NEWLINE# ldap3 = inspect . getmodule ( connect ) #NEWLINE# with connect ( connect_spec ) as l : #NEWLINE# #INDENT# ( old , new ) = _process_entries ( l , entries ) #NEWLINE# dn_set = OrderedDict ( ) #NEWLINE# dn_set . update ( old ) #NEWLINE# dn_set . update ( new ) #NEWLINE# dn_to_delete = set ( ) #NEWLINE# for dn in dn_set : #NEWLINE# #INDENT# o = old . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# n = new . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# for x in ( o , n ) : #NEWLINE# #INDENT# to_delete = set ( ) #NEWLINE# for ( attr , vals ) in six . iteritems ( x ) : #NEWLINE# #INDENT# if ( not len ( vals ) ) : #NEWLINE# #INDENT# to_delete . add ( attr ) #NEWLINE# #UNINDENT# #UNINDENT# for attr in to_delete : #NEWLINE# #INDENT# del x [ attr ] #NEWLINE# #UNINDENT# #UNINDENT# if ( o == n ) : #NEWLINE# #INDENT# dn_to_delete . add ( dn ) #NEWLINE# #UNINDENT# #UNINDENT# for dn in dn_to_delete : #NEWLINE# #INDENT# for x in ( old , new ) : #NEWLINE# #INDENT# x . pop ( dn , None ) #NEWLINE# #UNINDENT# del dn_set [ dn ] #NEWLINE# #UNINDENT# ret = { #NEWLINE# #INDENT# 'name' : name , #NEWLINE# 'changes' : { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } , #NEWLINE# 'result' : None , #NEWLINE# 'comment' : '' , #NEWLINE# #UNINDENT# } #NEWLINE# if ( old == new ) : #NEWLINE# #INDENT# ret [ 'comment' ] = 'LDAP entries already set' #NEWLINE# ret [ 'result' ] = True #NEWLINE# return ret #NEWLINE# #UNINDENT# if __opts__ [ 'test' ] : #NEWLINE# #INDENT# ret [ 'comment' ] = 'Would change LDAP entries' #NEWLINE# changed_old = old #NEWLINE# changed_new = new #NEWLINE# success_dn_set = dn_set #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# changed_old = OrderedDict ( ) #NEWLINE# changed_new = OrderedDict ( ) #NEWLINE# ret [ 'result' ] = True #NEWLINE# ret [ 'comment' ] = 'Successfully updated LDAP entries' #NEWLINE# errs = [ ] #NEWLINE# success_dn_set = OrderedDict ( ) #NEWLINE# for dn in dn_set : #NEWLINE# #INDENT# o = old . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# n = new . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# try : #NEWLINE# #INDENT# if len ( o ) : #NEWLINE# #INDENT# if len ( n ) : #NEWLINE# #INDENT# op = 'modify' #NEWLINE# assert ( o != n ) #NEWLINE# __salt__ [ 'ldap3.change' ] ( l , dn , o , n ) #NEWLINE# #UNINDENT# else : #NEWLINE# #INDENT# op = 'delete' #NEWLINE# __salt__ [ 'ldap3.delete' ] ( l , dn ) #NEWLINE# #UNINDENT# #UNINDENT# else : #NEWLINE# #INDENT# op = 'add' #NEWLINE# assert len ( n ) #NEWLINE# __salt__ [ 'ldap3.add' ] ( l , dn , n ) #NEWLINE# #UNINDENT# changed_old [ dn ] = o #NEWLINE# changed_new [ dn ] = n #NEWLINE# success_dn_set [ dn ] = True #NEWLINE# #UNINDENT# except ldap3 . LDAPError : #NEWLINE# #INDENT# log . exception ( 'failed to %s entry %s' , op , dn ) #NEWLINE# errs . append ( ( op , dn ) ) #NEWLINE# continue #NEWLINE# #UNINDENT# #UNINDENT# if len ( errs ) : #NEWLINE# #INDENT# ret [ 'result' ] = False #NEWLINE# ret [ 'comment' ] = ( 'failed to ' + ', ' . join ( ( ( ( op + ' entry ' ) + dn ) for ( op , dn ) in errs ) ) ) #NEWLINE# #UNINDENT# #UNINDENT# #UNINDENT# for dn in success_dn_set : #NEWLINE# #INDENT# o = changed_old . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# n = changed_new . get ( dn , { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } ) #NEWLINE# changes = { #NEWLINE# #INDENT# #NEWLINE# #UNINDENT# } #NEWLINE# ret [ 'changes' ] [ dn ] = changes #NEWLINE# for ( x , xn ) in ( ( o , 'old' ) , ( n , 'new' ) ) : #NEWLINE# #INDENT# if ( not len ( x ) ) : #NEWLINE# #INDENT# changes [ xn ] = None #NEWLINE# continue #NEWLINE# #UNINDENT# changes [ xn ] = dict ( ( ( attr , sorted ( vals ) ) for ( attr , vals ) in six . iteritems ( x ) if ( o . get ( attr , ( ) ) != n . get ( attr , ( ) ) ) ) ) #NEWLINE# #UNINDENT# #UNINDENT# return ret