44import re
55import sys
66import getopt
7- import collections
7+ import requests
88import comanage_utils as utils
99
1010
1111SCRIPT = os .path .basename (__file__ )
12- ENDPOINT = "https://registry.cilogon.org/registry/"
13- OSG_CO_ID = 7
12+ ENDPOINT = "https://registry-test.cilogon.org/registry/"
13+ TOPOLOGY_ENDPOINT = "https://topology.opensciencegrid.org/"
14+ LDAP_SERVER = "ldaps://ldap-test.cilogon.org"
15+ LDAP_USER = "uid=registry_user,ou=system,o=OSG,o=CO,dc=cilogon,dc=org"
16+ OSG_CO_ID = 8
17+ CACHE_FILENAME = "COmanage_Projects_cache.txt"
18+ CACHE_LIFETIME_HOURS = 0.5
1419
1520
1621_usage = f"""\
17- usage: [PASS=...] { SCRIPT } [OPTIONS]
22+ usage: { SCRIPT } [OPTIONS]
1823
1924OPTIONS:
2025 -u USER[:PASS] specify USER and optionally PASS on command line
2126 -c OSG_CO_ID specify OSG CO ID (default = { OSG_CO_ID } )
27+ -s LDAP_SERVER specify LDAP server to read data from
28+ -l LDAP_USER specify LDAP user for reading data from LDAP server
29+ -a ldap_authfile specify path to file to open and read LDAP authtok
2230 -d passfd specify open fd to read PASS
2331 -f passfile specify path to file to open and read PASS
2432 -e ENDPOINT specify REST endpoint
2533 (default = { ENDPOINT } )
2634 -o outfile specify output file (default: write to stdout)
2735 -g filter_group filter users by group name (eg, 'ap1-login')
28- -l localmaps specify a comma-delimited list of local HTCondor mapfiles to merge into outfile
36+ -m localmaps specify a comma-delimited list of local HTCondor mapfiles to merge into outfile
37+ -n min_users Specify minimum number of users required to update the output file (default: 100)
2938 -h display this help text
3039
3140PASS for USER is taken from the first of:
@@ -49,7 +58,11 @@ class Options:
4958 osg_co_id = OSG_CO_ID
5059 outfile = None
5160 authstr = None
61+ ldap_server = LDAP_SERVER
62+ ldap_user = LDAP_USER
63+ ldap_authtok = None
5264 filtergrp = None
65+ min_users = 100 # Bail out before updating the file if we have fewer than this many users
5366 localmaps = []
5467
5568
@@ -62,35 +75,12 @@ def get_osg_co_groups__map():
6275 #print("get_osg_co_groups__map()")
6376 resp_data = utils .get_osg_co_groups (options .osg_co_id , options .endpoint , options .authstr )
6477 data = utils .get_datalist (resp_data , "CoGroups" )
65- return { g ["Id" ]: g ["Name" ] for g in data }
66-
67-
68- def co_group_is_ospool (gid ):
69- #print(f"co_group_is_ospool({gid})")
70- resp_data = utils .get_co_group_identifiers (gid , options .endpoint , options .authstr )
71- data = utils .get_datalist (resp_data , "Identifiers" )
72- return any ( i ["Type" ] == "ospoolproject" for i in data )
73-
74-
75- def get_co_group_members__pids (gid ):
76- #print(f"get_co_group_members__pids({gid})")
77- resp_data = utils .get_co_group_members (gid , options .endpoint , options .authstr )
78- data = utils .get_datalist (resp_data , "CoGroupMembers" )
79- # For INF-1060: Temporary Fix until "The Great Project Provisioning" is finished
80- return [ m ["Person" ]["Id" ] for m in data if m ["Member" ] == True ]
81-
82-
83- def get_co_person_osguser (pid ):
84- #print(f"get_co_person_osguser({pid})")
85- resp_data = utils .get_co_person_identifiers (pid , options .endpoint , options .authstr )
86- data = utils .get_datalist (resp_data , "Identifiers" )
87- typemap = { i ["Type" ]: i ["Identifier" ] for i in data }
88- return typemap .get ("osguser" )
78+ return { g ["Name" ]: g ["Id" ] for g in data }
8979
9080
9181def parse_options (args ):
9282 try :
93- ops , args = getopt .getopt (args , 'u:c:d:f:g:e:o:l:h ' )
83+ ops , args = getopt .getopt (args , 'u:c:s:l:a: d:f:g:e:o:h:n:m ' )
9484 except getopt .GetoptError :
9585 usage ()
9686
@@ -99,21 +89,27 @@ def parse_options(args):
9989
10090 passfd = None
10191 passfile = None
92+ ldap_authfile = None
10293
10394 for op , arg in ops :
10495 if op == '-h' : usage ()
10596 if op == '-u' : options .user = arg
10697 if op == '-c' : options .osg_co_id = int (arg )
98+ if op == '-s' : options .ldap_server = arg
99+ if op == '-l' : options .ldap_user = arg
100+ if op == '-a' : ldap_authfile = arg
107101 if op == '-d' : passfd = int (arg )
108102 if op == '-f' : passfile = arg
109103 if op == '-e' : options .endpoint = arg
110104 if op == '-o' : options .outfile = arg
111105 if op == '-g' : options .filtergrp = arg
112- if op == '-l' : options .localmaps = arg .split ("," )
106+ if op == '-m' : options .localmaps = arg .split ("," )
107+ if op == '-n' : options .min_users = int (arg )
113108
114109 try :
115110 user , passwd = utils .getpw (options .user , passfd , passfile )
116111 options .authstr = utils .mkauthstr (user , passwd )
112+ options .ldap_authtok = utils .get_ldap_authtok (ldap_authfile )
117113 except PermissionError :
118114 usage ("PASS required" )
119115
@@ -123,36 +119,18 @@ def _deduplicate_list(items):
123119 """
124120 return list (dict .fromkeys (items ))
125121
126- def gid_pids_to_osguser_pid_gids (gid_pids , pid_osguser ):
127- pid_gids = collections .defaultdict (list )
128-
129- for gid in gid_pids :
130- for pid in gid_pids [gid ]:
131- if pid_osguser [pid ] is not None and gid not in pid_gids [pid ]:
132- pid_gids [pid ].append (gid )
133-
134- return pid_gids
135-
136-
137- def filter_by_group (pid_gids , groups , filter_group_name ):
138- groups_idx = { v : k for k ,v in groups .items () }
139- filter_gid = groups_idx [filter_group_name ] # raises KeyError if missing
140- filter_group_pids = set (get_co_group_members__pids (filter_gid ))
141- return { p : g for p ,g in pid_gids .items () if p in filter_group_pids }
142-
143-
144122def get_osguser_groups (filter_group_name = None ):
145- groups = get_osg_co_groups__map ( )
146- ospool_gids = filter ( co_group_is_ospool , groups )
147- gid_pids = { gid : get_co_group_members__pids ( gid ) for gid in ospool_gids }
148- all_pids = set ( pid for gid in gid_pids for pid in gid_pids [ gid ] )
149- pid_osguser = { pid : get_co_person_osguser ( pid ) for pid in all_pids }
150- pid_gids = gid_pids_to_osguser_pid_gids ( gid_pids , pid_osguser )
151- if filter_group_name is not None :
152- pid_gids = filter_by_group ( pid_gids , groups , filter_group_name )
153-
154- return { pid_osguser [ pid ]: map ( groups . get , gids )
155- for pid , gids in pid_gids . items () }
123+ ldap_users = utils . get_ldap_active_users_and_groups ( options . ldap_server , options . ldap_user , options . ldap_authtok , filter_group_name )
124+ topology_projects = requests . get ( f" { TOPOLOGY_ENDPOINT } /miscproject/json" ). json ( )
125+ project_names = topology_projects . keys ()
126+
127+ # Get COManage group IDs to preserve ordering from pre-LDAP migration script behavior
128+ groups_ids = get_osg_co_groups__map ( )
129+ return {
130+ user : sorted ([ g for g in groups if g in project_names ], key = lambda g : groups_ids . get ( g , 0 ))
131+ for user , groups in ldap_users . items ()
132+ if any ( g in project_names for g in groups )
133+ }
156134
157135
158136def parse_localmap (inputfile ):
@@ -204,6 +182,9 @@ def main(args):
204182 maps .append (parse_localmap (localmap ))
205183 osguser_groups_merged = merge_maps (maps )
206184
185+ # Sanity check, confirm we have generated a "sane" amount of user -> group mappings
186+ if len (osguser_groups_merged ) < options .min_users :
187+ raise RuntimeError (f"Refusing to update output file: only { len (osguser_groups_merged )} users found" )
207188 print_usermap (osguser_groups_merged )
208189
209190
0 commit comments