This is an automated email from the ASF dual-hosted git repository.

gstein pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/steve.git

commit 7ab0692c38f1996d714252cc91e458b793a1cb6a
Author: Greg Stein <[email protected]>
AuthorDate: Fri Oct 3 20:52:55 2025 -0500

    Load ASF users into the "person" database.
    
    ASF-specific. Populate the "person" database.
    
    Useful as a template for mapping $your users into the persondb.
---
 v3/.gitignore                  |  1 +
 v3/pyproject.toml              |  3 ++
 v3/server/bin/asf-load-ldap.py | 77 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)

diff --git a/v3/.gitignore b/v3/.gitignore
index de50c4d..233cc0e 100644
--- a/v3/.gitignore
+++ b/v3/.gitignore
@@ -1,6 +1,7 @@
 poetry.lock
 
 server/bin/bs.zip
+server/bin/bind.txt
 server/config.yaml
 server/apptoken.txt
 
diff --git a/v3/pyproject.toml b/v3/pyproject.toml
index 14dcd58..59ea29b 100644
--- a/v3/pyproject.toml
+++ b/v3/pyproject.toml
@@ -27,6 +27,9 @@ types-aiofiles = "^24.1.0"
 
 [tool.poetry.group.server.dependencies]
 asfquart = ">=0.1.12"
+### not sure about this. not needed for server operation.
+### let the import fail, and the user can do a pip install.
+#python-ldap = "^3.0.0"
 
 [tool.poetry.extras]
 server = ["asfquart", ]  # Core dependencies for apache-steve[server]
diff --git a/v3/server/bin/asf-load-ldap.py b/v3/server/bin/asf-load-ldap.py
new file mode 100755
index 0000000..3c4d04c
--- /dev/null
+++ b/v3/server/bin/asf-load-ldap.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import pathlib
+import logging
+
+import ldap  # pip3 install python-ldap
+import asfpy.db
+import asfpy.stopwatch
+from easydict import EasyDict as edict
+
+_LOGGER = logging.getLogger(__name__)
+
+THIS_DIR = pathlib.Path(__file__).resolve().parent
+DB_FNAME = THIS_DIR.parent / 'steve.db'
+
+sys.path.insert(0, str(THIS_DIR.parent.parent))
+import steve.persondb
+
+# The ASF's LDAP server, available for read-only for finding potential voters.
+LDAP_URL = 'ldaps://ldap-us.apache.org/'
+LDAP_DN = 'ou=people,dc=apache,dc=org'
+LDAP_ATTR = 'memberUid'
+
+
[email protected]()
+def main():
+    pdb = steve.persondb.PersonDB(DB_FNAME)
+    # Reach into PDB for the CONN, and start a transaction for all
+    # of the inserts we will perform. (rather than default auto-commit)
+    pdb.db.conn.execute('BEGIN TRANSACTION')
+
+    client = ldap.initialize(LDAP_URL)
+    binddn, bindpw = [ s.strip()
+                       for s in open(THIS_DIR / 'bind.txt').readlines()[:2] ]
+    #print('BIND:', binddn, bindpw)
+    client.simple_bind_s(binddn, bindpw)
+
+    with asfpy.stopwatch.Stopwatch('run LDAP full scan'):
+        results = client.search_s(LDAP_DN, ldap.SCOPE_SUBTREE, 'uid=*', 
attrlist=None) #[LDAP_ATTR,])
+
+    count = 0
+    for r in results:
+        # r[0] is the CN(?) ... not needed
+        # r[1] is the {attr:[values..]} dict
+        entry = edict(r[1])
+
+        uid = entry.uid[0].decode('utf-8')
+        visname = entry.cn[0].decode('utf-8')
+        email = entry['asf-committer-email'][0].decode('utf-8')
+
+        pdb.add_person(uid, visname, email)
+        count += 1
+
+    # Reach into the CONN and do the commit.
+    pdb.db.conn.execute('COMMIT')
+
+    _LOGGER.info(f'Loaded {count} persons into {DB_FNAME}')
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    main()

Reply via email to