<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14892] CalDAVTester/trunk/odsetup.py</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.calendarserver.org//changeset/14892">14892</a></dd>
<dt>Author</dt> <dd>cdaboo@apple.com</dd>
<dt>Date</dt> <dd>2015-06-09 12:41:02 -0700 (Tue, 09 Jun 2015)</dd>
</dl>

<h3>Log Message</h3>
<pre>Now uses OD.framework. Tests for unwanted OD binds. Use ConfigRoot from actual config rather than hard-coded path.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#CalDAVTestertrunkodsetuppy">CalDAVTester/trunk/odsetup.py</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="CalDAVTestertrunkodsetuppy"></a>
<div class="modfile"><h4>Modified: CalDAVTester/trunk/odsetup.py (14891 => 14892)</h4>
<pre class="diff"><span>
<span class="info">--- CalDAVTester/trunk/odsetup.py        2015-06-09 00:42:41 UTC (rev 14891)
+++ CalDAVTester/trunk/odsetup.py        2015-06-09 19:41:02 UTC (rev 14892)
</span><span class="lines">@@ -31,9 +31,35 @@
</span><span class="cx"> import uuid
</span><span class="cx"> import xml.parsers.expat
</span><span class="cx"> 
</span><ins>+&quot;&quot;&quot;
+OpenDirectory.framework
+&quot;&quot;&quot;
+
+import objc as _objc
+
+__bundle__ = _objc.initFrameworkWrapper(
+    &quot;OpenDirectory&quot;,
+    frameworkIdentifier=&quot;com.apple.OpenDirectory&quot;,
+    frameworkPath=_objc.pathForFramework(
+        &quot;/System/Library/Frameworks/OpenDirectory.framework&quot;
+    ),
+    globals=globals()
+)
+
+# DS attributes we need
+kDSStdRecordTypeUsers = &quot;dsRecTypeStandard:Users&quot;
+kDSStdRecordTypeGroups = &quot;dsRecTypeStandard:Groups&quot;
+kDSStdRecordTypePlaces = &quot;dsRecTypeStandard:Places&quot;
+kDSStdRecordTypeResources = &quot;dsRecTypeStandard:Resources&quot;
+
+kDS1AttrGeneratedUID = &quot;dsAttrTypeStandard:GeneratedUID&quot;
+kDSNAttrRecordName = &quot;dsAttrTypeStandard:RecordName&quot;
+
+eDSExact = 0x2001
+
+
</ins><span class="cx"> sys_root = &quot;/Applications/Server.app/Contents/ServerRoot&quot;
</span><span class="cx"> os.environ[&quot;PATH&quot;] = &quot;%s/usr/bin:%s&quot; % (sys_root, os.environ[&quot;PATH&quot;])
</span><del>-conf_root = &quot;/Library/Server/Calendar and Contacts/Config&quot;
</del><span class="cx"> 
</span><span class="cx"> diradmin_user = &quot;admin&quot;
</span><span class="cx"> diradmin_pswd = &quot;&quot;
</span><span class="lines">@@ -120,9 +146,9 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> i18nattrs = {
</span><del>-    &quot;dsAttrTypeStandard:RealName&quot;: &quot;まだ&quot;,
-    &quot;dsAttrTypeStandard:FirstName&quot;: &quot;ま&quot;,
-    &quot;dsAttrTypeStandard:LastName&quot;: &quot;だ&quot;,
</del><ins>+    &quot;dsAttrTypeStandard:RealName&quot;: u&quot;まだ&quot;,
+    &quot;dsAttrTypeStandard:FirstName&quot;: u&quot;ま&quot;,
+    &quot;dsAttrTypeStandard:LastName&quot;: u&quot;だ&quot;,
</ins><span class="cx">     &quot;dsAttrTypeStandard:EMailAddress&quot;: &quot;i18nuser@example.com&quot;,
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -232,15 +258,15 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> records = (
</span><del>-    (&quot;/Users&quot;, &quot;testadmin&quot;, &quot;testadmin&quot;, adminattrs, 1),
-    (&quot;/Users&quot;, &quot;apprentice&quot;, &quot;apprentice&quot;, apprenticeattrs, 1),
-    (&quot;/Users&quot;, &quot;i18nuser&quot;, &quot;i18nuser&quot;, i18nattrs, 1),
-    (&quot;/Users&quot;, &quot;user%02d&quot;, &quot;user%02d&quot;, userattrs, None),
-    (&quot;/Users&quot;, &quot;public%02d&quot;, &quot;public%02d&quot;, publicattrs, number_of_publics),
-    (&quot;/Places&quot;, &quot;location%02d&quot;, &quot;location%02d&quot;, locationattrs, number_of_locations),
-    (&quot;/Places&quot;, &quot;delegatedroom&quot;, &quot;delegatedroom&quot;, delegatedroomattrs, 1),
-    (&quot;/Resources&quot;, &quot;resource%02d&quot;, &quot;resource%02d&quot;, resourceattrs, number_of_resources),
-    (&quot;/Groups&quot;, &quot;group%02d&quot;, &quot;group%02d&quot;, groupattrs, number_of_groups),
</del><ins>+    (kDSStdRecordTypeUsers, &quot;testadmin&quot;, &quot;testadmin&quot;, adminattrs, 1),
+    (kDSStdRecordTypeUsers, &quot;apprentice&quot;, &quot;apprentice&quot;, apprenticeattrs, 1),
+    (kDSStdRecordTypeUsers, &quot;i18nuser&quot;, &quot;i18nuser&quot;, i18nattrs, 1),
+    (kDSStdRecordTypeUsers, &quot;user%02d&quot;, &quot;user%02d&quot;, userattrs, None),
+    (kDSStdRecordTypeUsers, &quot;public%02d&quot;, &quot;public%02d&quot;, publicattrs, number_of_publics),
+    (kDSStdRecordTypePlaces, &quot;location%02d&quot;, &quot;location%02d&quot;, locationattrs, number_of_locations),
+    (kDSStdRecordTypePlaces, &quot;delegatedroom&quot;, &quot;delegatedroom&quot;, delegatedroomattrs, 1),
+    (kDSStdRecordTypeResources, &quot;resource%02d&quot;, &quot;resource%02d&quot;, resourceattrs, number_of_resources),
+    (kDSStdRecordTypeGroups, &quot;group%02d&quot;, &quot;group%02d&quot;, groupattrs, number_of_groups),
</ins><span class="cx"> )
</span><span class="cx"> 
</span><span class="cx"> def usage():
</span><span class="lines">@@ -251,6 +277,7 @@
</span><span class="cx">     -u uid    OpenDirectory Admin user id
</span><span class="cx">     -p pswd   OpenDirectory Admin user password
</span><span class="cx">     -c users  number of user accounts to create (default: 10)
</span><ins>+    -x        disable OD node checks
</ins><span class="cx">     -v        verbose logging
</span><span class="cx">     -V        very verbose logging
</span><span class="cx"> &quot;&quot;&quot;
</span><span class="lines">@@ -300,6 +327,83 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><ins>+class ODError(Exception):
+    pass
+
+
+
+class ODFamework(object):
+
+    def __init__(self, nodeName, user, pswd):
+        self.session = ODSession.defaultSession()
+        self.node, error = ODNode.nodeWithSession_name_error_(self.session, nodeName, None)
+        if error:
+            print(error)
+            raise ODError(error)
+
+        _ignore_result, error = self.node.setCredentialsWithRecordType_recordName_password_error_(
+            kDSStdRecordTypeUsers,
+            user,
+            pswd,
+            None
+        )
+        if error:
+            print(&quot;Unable to authenticate with directory %s: %s&quot; % (nodeName, error))
+            raise ODError(error)
+
+        print(&quot;Successfully authenticated with directory %s&quot; % (nodeName,))
+
+
+    def lookupRecordName(self, recordType, name):
+        query, error = ODQuery.queryWithNode_forRecordTypes_attribute_matchType_queryValues_returnAttributes_maximumResults_error_(
+            self.node,
+            recordType,
+            kDSNAttrRecordName,
+            eDSExact,
+            name,
+            [kDS1AttrGeneratedUID],
+            0,
+            None)
+        if error:
+            raise ODError(error)
+        records, error = query.resultsAllowingPartial_error_(False, None)
+        if error:
+            raise ODError(error)
+
+        if len(records) &lt; 1:
+            return None
+        if len(records) &gt; 1:
+            raise ODError(&quot;Multiple records for '%s' were found&quot; % (name,))
+
+        return records[0]
+
+
+    def createRecord(self, recordType, recordName, password, attrs):
+        record, error = self.node.createRecordWithRecordType_name_attributes_error_(
+            recordType,
+            recordName,
+            attrs,
+            None)
+        if error:
+            print(error)
+            raise ODError(error)
+        if recordType == kDSStdRecordTypeUsers:
+            _ignore_result, error = record.changePassword_toPassword_error_(None, password, None)
+            if error:
+                print(error)
+                raise ODError(error)
+        return record
+
+
+    def recordDetails(self, record):
+        details, error = record.recordDetailsForAttributes_error_(None, None)
+        if error:
+            print(error)
+            raise ODError(error)
+        return details
+
+
+
</ins><span class="cx"> def readConfig():
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Read useful information from calendarserver_config
</span><span class="lines">@@ -308,6 +412,7 @@
</span><span class="cx">     args = [
</span><span class="cx">         configutility,
</span><span class="cx">         &quot;ServerHostName&quot;,
</span><ins>+        &quot;ConfigRoot&quot;,
</ins><span class="cx">         &quot;DocumentRoot&quot;,
</span><span class="cx">         &quot;HTTPPort&quot;,
</span><span class="cx">         &quot;SSLPort&quot;,
</span><span class="lines">@@ -339,11 +444,12 @@
</span><span class="cx">         int(currentConfig[&quot;SSLPort&quot;]),
</span><span class="cx">         authtype,
</span><span class="cx">         currentConfig[&quot;DocumentRoot&quot;],
</span><ins>+        currentConfig[&quot;ConfigRoot&quot;],
</ins><span class="cx">     )
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def patchConfig(admin):
</del><ins>+def patchConfig(confroot, admin):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Patch the caldavd-user.plist file to make sure:
</span><span class="cx">        * the proper admin principal is configured
</span><span class="lines">@@ -388,7 +494,7 @@
</span><span class="cx">         &quot;lockRescheduleInterval&quot;: 1,
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    writePlist(plist, conf_root + &quot;/caldavd-user.plist&quot;)
</del><ins>+    writePlist(plist, confroot + &quot;/caldavd-user.plist&quot;)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="lines">@@ -447,9 +553,9 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def loadLists(path, records):
</span><del>-    if path == &quot;/Places&quot;:
</del><ins>+    if path == kDSStdRecordTypePlaces:
</ins><span class="cx">         result = cmd(cmdutility, locationlistcmd)
</span><del>-    elif path == &quot;/Resources&quot;:
</del><ins>+    elif path == kDSStdRecordTypeResources:
</ins><span class="cx">         result = cmd(cmdutility, resourcelistcmd)
</span><span class="cx">     else:
</span><span class="cx">         raise ValueError()
</span><span class="lines">@@ -465,10 +571,10 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def doToAccounts(protocol, f, users_only=False):
</del><ins>+def doToAccounts(odf, protocol, f, users_only=False):
</ins><span class="cx"> 
</span><span class="cx">     for record in records:
</span><del>-        if protocol == &quot;carddav&quot; and record[0] in (&quot;/Places&quot;, &quot;/Resources&quot;):
</del><ins>+        if protocol == &quot;carddav&quot; and record[0] in (kDSStdRecordTypePlaces, kDSStdRecordTypeResources):
</ins><span class="cx">             continue
</span><span class="cx">         if record[4] is None:
</span><span class="cx">             count = number_of_users
</span><span class="lines">@@ -484,13 +590,13 @@
</span><span class="cx">                         value = value % (ctr,)
</span><span class="cx">                     attrs[key] = value
</span><span class="cx">                 ruser = (record[1] % (ctr,), record[2] % (ctr,), attrs, 1)
</span><del>-                f(record[0], ruser)
</del><ins>+                f(odf, record[0], ruser)
</ins><span class="cx">         else:
</span><del>-            f(record[0], record[1:])
</del><ins>+            f(odf, record[0], record[1:])
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def doGroupMemberships():
</del><ins>+def doGroupMemberships(odf):
</ins><span class="cx"> 
</span><span class="cx">     memberships = (
</span><span class="cx">         (&quot;group01&quot;, (&quot;user01&quot;,), (),),
</span><span class="lines">@@ -503,60 +609,72 @@
</span><span class="cx">     )
</span><span class="cx"> 
</span><span class="cx">     for groupname, users, nestedgroups in memberships:
</span><ins>+        if verbose:
+            print &quot;Group membership: {}&quot;.format(groupname)
</ins><span class="cx"> 
</span><del>-        memberGUIDs = [guids[user] for user in users]
-        nestedGUIDs = [guids[group] for group in nestedgroups]
</del><ins>+        # Get group record
+        group = odf.lookupRecordName(kDSStdRecordTypeGroups, groupname)
+        if group is not None:
+            for user in users:
+                member = odf.lookupRecordName(kDSStdRecordTypeUsers, user)
+                if member is not None:
+                    _ignore_result, error = group.addMemberRecord_error_(member, None)
+                    if error:
+                        raise ODError(error)
+            for nested in nestedgroups:
+                member = odf.lookupRecordName(kDSStdRecordTypeGroups, nested)
+                if member is not None:
+                    _ignore_result, error = group.addMemberRecord_error_(member, None)
+                    if error:
+                        raise ODError(error)
</ins><span class="cx"> 
</span><del>-        cmd(&quot;dscl -u %s -P %s %s -append /Groups/%s \&quot;dsAttrTypeStandard:GroupMembers\&quot;%s&quot; % (diradmin_user, diradmin_pswd, directory_node, groupname, &quot;&quot;.join([&quot; \&quot;%s\&quot;&quot; % (guid,) for guid in memberGUIDs])), raiseOnFail=False)
-        cmd(&quot;dscl -u %s -P %s %s -append /Groups/%s \&quot;dsAttrTypeStandard:NestedGroups\&quot;%s&quot; % (diradmin_user, diradmin_pswd, directory_node, groupname, &quot;&quot;.join([&quot; \&quot;%s\&quot;&quot; % (guid,) for guid in nestedGUIDs])), raiseOnFail=False)
</del><span class="cx"> 
</span><span class="cx"> 
</span><ins>+def createUser(odf, path, user):
</ins><span class="cx"> 
</span><del>-def createUser(path, user):
</del><ins>+    if verbose:
+        print &quot;Create user: {}/{}&quot;.format(path, user[0])
</ins><span class="cx"> 
</span><del>-    if path in (&quot;/Users&quot;, &quot;/Groups&quot;,):
-        createUserViaDS(path, user)
</del><ins>+    if path in (kDSStdRecordTypeUsers, kDSStdRecordTypeGroups,):
+        createUserViaDS(odf, path, user)
</ins><span class="cx">     elif protocol == &quot;caldav&quot;:
</span><span class="cx">         createUserViaGateway(path, user)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def createUserViaDS(path, user):
</del><ins>+def createUserViaDS(odf, path, user):
</ins><span class="cx">     # Do dscl command line operations to create a calendar user
</span><span class="cx"> 
</span><span class="cx">     # Only create if it does not exist
</span><del>-    if cmd(&quot;dscl %s -list %s/%s&quot; % (directory_node, path, user[0]), raiseOnFail=False)[1] != 0:
</del><ins>+    record = odf.lookupRecordName(path, user[0])
+
+    if record is None:
</ins><span class="cx">         # Create the user
</span><del>-        cmd(&quot;dscl -u %s -P %s %s -create %s/%s&quot; % (diradmin_user, diradmin_pswd, directory_node, path, user[0]))
</del><ins>+        if kDS1AttrGeneratedUID in user[2]:
+            user[2][kDS1AttrGeneratedUID] = str(uuid.uuid4()).upper()
</ins><span class="cx"> 
</span><del>-        # Set the password (only for /Users)
-        if path == &quot;/Users&quot;:
-            cmd(&quot;dscl -u %s -P %s %s -passwd %s/%s %s&quot; % (diradmin_user, diradmin_pswd, directory_node, path, user[0], user[1]))
-
-        # Other attributes
-        for key, value in user[2].iteritems():
-            if key == &quot;dsAttrTypeStandard:GeneratedUID&quot;:
-                value = str(uuid.uuid4()).upper()
-            cmd(&quot;dscl -u %s -P %s %s -create %s/%s \&quot;%s\&quot; \&quot;%s\&quot;&quot; % (diradmin_user, diradmin_pswd, directory_node, path, user[0], key, value))
</del><ins>+        user = (user[0], user[1], dict([(k, [v],) for k, v in user[2].items()]),)
+        record = odf.createRecord(path, user[0], user[1], user[2])
</ins><span class="cx">     else:
</span><del>-        print &quot;%s/%s already exists&quot; % (path, user[0],)
</del><ins>+        if verbose:
+            print &quot;%s/%s already exists&quot; % (path, user[0],)
</ins><span class="cx"> 
</span><span class="cx">     # Now read the guid for this record
</span><span class="cx">     if user[0] in guids:
</span><del>-        result = cmd(&quot;dscl %s -read %s/%s GeneratedUID&quot; % (directory_node, path, user[0]))
-        guid = result[0].split()[1]
-        guids[user[0]] = guid
</del><ins>+        record = odf.lookupRecordName(path, user[0])
+        details = odf.recordDetails(record)
+        guids[user[0]] = details[kDS1AttrGeneratedUID][0]
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def createUserViaGateway(path, user):
</span><span class="cx"> 
</span><span class="cx">     # Check for existing
</span><del>-    if path == &quot;/Places&quot;:
</del><ins>+    if path == kDSStdRecordTypePlaces:
</ins><span class="cx">         if user[0] in locations:
</span><span class="cx">             guids[user[0]] = locations[user[0]]
</span><span class="cx">             return
</span><del>-    elif path == &quot;/Resources&quot;:
</del><ins>+    elif path == kDSStdRecordTypeResources:
</ins><span class="cx">         if user[0] in resources:
</span><span class="cx">             guids[user[0]] = resources[user[0]]
</span><span class="cx">             return
</span><span class="lines">@@ -564,7 +682,7 @@
</span><span class="cx">     guid = str(uuid.uuid4()).upper()
</span><span class="cx">     if user[0] in guids:
</span><span class="cx">         guids[user[0]] = guid
</span><del>-    if path == &quot;/Places&quot;:
</del><ins>+    if path == kDSStdRecordTypePlaces:
</ins><span class="cx">         cmd(
</span><span class="cx">             cmdutility,
</span><span class="cx">             locationcreatecmd % {
</span><span class="lines">@@ -573,7 +691,7 @@
</span><span class="cx">                 &quot;recordname&quot;: user[0]
</span><span class="cx">             }
</span><span class="cx">         )
</span><del>-    elif path == &quot;/Resources&quot;:
</del><ins>+    elif path == kDSStdRecordTypeResources:
</ins><span class="cx">         cmd(
</span><span class="cx">             cmdutility,
</span><span class="cx">             resourcecreatecmd % {
</span><span class="lines">@@ -587,26 +705,32 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def removeUser(path, user):
</del><ins>+def removeUser(odf, path, user):
</ins><span class="cx"> 
</span><del>-    if path in (&quot;/Users&quot;, &quot;/Groups&quot;,):
-        removeUserViaDS(path, user)
</del><ins>+    if verbose:
+        print &quot;Remove user: {}/{}&quot;.format(path, user[0])
+
+    if path in (kDSStdRecordTypeUsers, kDSStdRecordTypeGroups,):
+        removeUserViaDS(odf, path, user)
</ins><span class="cx">     else:
</span><span class="cx">         removeUserViaGateway(path, user)
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def removeUserViaDS(path, user):
</del><ins>+def removeUserViaDS(odf, path, user):
</ins><span class="cx">     # Do dscl command line operations to remove a calendar user
</span><span class="cx"> 
</span><del>-    # Create the user
-    cmd(&quot;dscl -u %s -P %s %s -delete %s/%s&quot; % (diradmin_user, diradmin_pswd, directory_node, path, user[0]), raiseOnFail=False)
</del><ins>+    record = odf.lookupRecordName(path, user[0])
+    if record is not None:
+        _ignore_result, error = record.deleteRecordAndReturnError_(None)
+        if error:
+            raise ODError(error)
</ins><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> def removeUserViaGateway(path, user):
</span><span class="cx"> 
</span><del>-    if path == &quot;/Places&quot;:
</del><ins>+    if path == kDSStdRecordTypePlaces:
</ins><span class="cx">         if user[0] not in locations:
</span><span class="cx">             return
</span><span class="cx">         guid = locations[user[0]]
</span><span class="lines">@@ -614,7 +738,7 @@
</span><span class="cx">             cmdutility,
</span><span class="cx">             locationremovecmd % {&quot;guid&quot;: guid, }
</span><span class="cx">         )
</span><del>-    elif path == &quot;/Resources&quot;:
</del><ins>+    elif path == kDSStdRecordTypeResources:
</ins><span class="cx">         if user[0] not in resources:
</span><span class="cx">             return
</span><span class="cx">         guid = resources[user[0]]
</span><span class="lines">@@ -627,14 +751,14 @@
</span><span class="cx"> 
</span><span class="cx"> 
</span><span class="cx"> 
</span><del>-def manageRecords(path, user):
</del><ins>+def manageRecords(odf, path, user):
</ins><span class="cx">     &quot;&quot;&quot;
</span><span class="cx">     Set proxies and auto-schedule for locations and resources
</span><span class="cx">     &quot;&quot;&quot;
</span><span class="cx"> 
</span><span class="cx">     # Do caldav_utility setup
</span><del>-    if path in (&quot;/Places&quot;, &quot;/Resources&quot;,):
-        if path in (&quot;/Places&quot;,):
</del><ins>+    if path in (kDSStdRecordTypePlaces, kDSStdRecordTypeResources,):
+        if path in (kDSStdRecordTypePlaces,):
</ins><span class="cx">             if user[0] == &quot;delegatedroom&quot;:
</span><span class="cx">                 cmd(&quot;%s --add-write-proxy groups:group05 --add-read-proxy groups:group07 --set-auto-schedule-mode=none locations:%s&quot; % (
</span><span class="cx">                     utility,
</span><span class="lines">@@ -686,8 +810,9 @@
</span><span class="cx"> 
</span><span class="cx">     protocol = &quot;caldav&quot;
</span><span class="cx">     serverinfo_default = details[protocol][&quot;serverinfo&quot;]
</span><ins>+    node_check = True
</ins><span class="cx">     try:
</span><del>-        options, args = getopt.getopt(sys.argv[1:], &quot;hn:p:u:f:c:vV&quot;)
</del><ins>+        options, args = getopt.getopt(sys.argv[1:], &quot;hn:p:u:f:c:vVx&quot;)
</ins><span class="cx"> 
</span><span class="cx">         for option, value in options:
</span><span class="cx">             if option == &quot;-h&quot;:
</span><span class="lines">@@ -706,6 +831,8 @@
</span><span class="cx">             elif option == &quot;-V&quot;:
</span><span class="cx">                 verbose = True
</span><span class="cx">                 veryverbose = True
</span><ins>+            elif option == &quot;-x&quot;:
+                node_check = False
</ins><span class="cx">             else:
</span><span class="cx">                 print &quot;Unrecognized option: %s&quot; % (option,)
</span><span class="cx">                 usage()
</span><span class="lines">@@ -728,23 +855,26 @@
</span><span class="cx">             usage()
</span><span class="cx">             raise ValueError
</span><span class="cx"> 
</span><del>-        checkDataSource(directory_node)
</del><ins>+        odf = ODFamework(directory_node, diradmin_user, diradmin_pswd)
</ins><span class="cx"> 
</span><ins>+        if node_check:
+            checkDataSource(directory_node)
+
</ins><span class="cx">         if args[0] == &quot;create&quot;:
</span><span class="cx">             # Read the caldavd.plist file and extract some information we will need.
</span><del>-            hostname, port, sslport, authtype, docroot = readConfig()
</del><ins>+            hostname, port, sslport, authtype, docroot, confroot = readConfig()
</ins><span class="cx"> 
</span><span class="cx">             # Now generate the OD accounts (caching guids as we go).
</span><span class="cx">             if protocol == &quot;caldav&quot;:
</span><del>-                loadLists(&quot;/Places&quot;, locations)
-                loadLists(&quot;/Resources&quot;, resources)
</del><ins>+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
</ins><span class="cx"> 
</span><del>-            doToAccounts(protocol, createUser)
-            doGroupMemberships()
-            doToAccounts(protocol, manageRecords)
</del><ins>+            doToAccounts(odf, protocol, createUser)
+            doGroupMemberships(odf)
+            doToAccounts(odf, protocol, manageRecords)
</ins><span class="cx"> 
</span><span class="cx">             # Patch the caldavd.plist file with the testadmin user's guid-based principal-URL
</span><del>-            patchConfig(&quot;/principals/__uids__/%s/&quot; % (guids[&quot;testadmin&quot;],))
</del><ins>+            patchConfig(confroot, &quot;/principals/__uids__/%s/&quot; % (guids[&quot;testadmin&quot;],))
</ins><span class="cx"> 
</span><span class="cx">             # Create an appropriate serverinfo.xml file from the template
</span><span class="cx">             buildServerinfo(serverinfo_default, hostname, port, sslport, authtype, docroot)
</span><span class="lines">@@ -752,23 +882,23 @@
</span><span class="cx"> 
</span><span class="cx">         elif args[0] == &quot;create-users&quot;:
</span><span class="cx">             # Read the caldavd.plist file and extract some information we will need.
</span><del>-            hostname, port, sslport, authtype, docroot = readConfig()
</del><ins>+            hostname, port, sslport, authtype, docroot, confroot = readConfig()
</ins><span class="cx"> 
</span><span class="cx">             # Now generate the OD accounts (caching guids as we go).
</span><span class="cx">             if protocol == &quot;caldav&quot;:
</span><del>-                loadLists(&quot;/Places&quot;, locations)
-                loadLists(&quot;/Resources&quot;, resources)
</del><ins>+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
</ins><span class="cx"> 
</span><del>-            doToAccounts(protocol, createUser, users_only=True)
</del><ins>+            doToAccounts(odf, protocol, createUser, users_only=True)
</ins><span class="cx"> 
</span><span class="cx">             # Create an appropriate serverinfo.xml file from the template
</span><span class="cx">             buildServerinfo(serverinfo_default, hostname, port, sslport, authtype, docroot)
</span><span class="cx"> 
</span><span class="cx">         elif args[0] == &quot;remove&quot;:
</span><span class="cx">             if protocol == &quot;caldav&quot;:
</span><del>-                loadLists(&quot;/Places&quot;, locations)
-                loadLists(&quot;/Resources&quot;, resources)
-            doToAccounts(protocol, removeUser)
</del><ins>+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
+            doToAccounts(odf, protocol, removeUser)
</ins><span class="cx"> 
</span><span class="cx">     except Exception, e:
</span><span class="cx">         traceback.print_exc()
</span></span></pre>
</div>
</div>

</body>
</html>