Skip to content

Commit ce1b8a2

Browse files
committed
Merge branch 'master' into INF-1060-member-removals
2 parents 3d78c61 + 5236b3e commit ce1b8a2

7 files changed

Lines changed: 135 additions & 48 deletions

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ LABEL maintainer OSG Software <help@opensciencegrid.org>
44

55
COPY *.py /usr/local/bin/
66

7+
COPY requirements.txt /
8+
RUN pip3 install --upgrade pip && pip3 install --no-cache-dir -r requirements.txt
Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,50 @@
22

33
import os
44
import re
5-
import sys
65
import json
6+
import time
77
import urllib.error
88
import urllib.request
99
from ldap3 import Server, Connection, ALL, SAFE_SYNC
1010

11+
#PRODUCTION VALUES
1112

12-
MIN_TIMEOUT = 5
13-
MAX_TIMEOUT = 625
14-
TIMEOUTMULTIPLE = 5
13+
PRODUCTION_ENDPOINT = "https://registry.cilogon.org/registry/"
14+
PRODUCTION_LDAP_SERVER = "ldaps://ldap.cilogon.org"
15+
PRODUCTION_LDAP_USER = "uid=readonly_user,ou=system,o=OSG,o=CO,dc=cilogon,dc=org"
16+
PRODUCTION_OSG_CO_ID = 7
17+
PRODUCTION_UNIX_CLUSTER_ID = 1
18+
PRODUCTION_LDAP_TARGET_ID = 6
19+
20+
#TEST VALUES
21+
22+
TEST_ENDPOINT = "https://registry-test.cilogon.org/registry/"
23+
TEST_LDAP_SERVER = "ldaps://ldap-test.cilogon.org"
24+
TEST_LDAP_USER ="uid=registry_user,ou=system,o=OSG,o=CO,dc=cilogon,dc=org"
25+
TEST_OSG_CO_ID = 8
26+
TEST_UNIX_CLUSTER_ID = 10
27+
TEST_LDAP_TARGET_ID = 9
28+
29+
# Value for the base of the exponential backoff
30+
TIMEOUT_BASE = 5
31+
MAX_ATTEMPTS = 5
1532

1633

1734
GET = "GET"
1835
PUT = "PUT"
1936
POST = "POST"
2037
DELETE = "DELETE"
2138

39+
#Exceptions
40+
class Error(Exception):
41+
"""Base exception class for all exceptions defined"""
42+
pass
43+
44+
45+
class URLRequestError(Error):
46+
"""Class for exceptions due to not being able to fulfill a URLRequest"""
47+
pass
48+
2249

2350
def getpw(user, passfd, passfile):
2451
if ":" in user:
@@ -71,21 +98,28 @@ def call_api2(method, target, endpoint, authstr, **kw):
7198

7299
def call_api3(method, target, data, endpoint, authstr, **kw):
73100
req = mkrequest(method, target, data, endpoint, authstr, **kw)
74-
trying = True
75-
currentTimeout = MIN_TIMEOUT
76-
while trying:
101+
req_attempts = 0
102+
current_timeout = TIMEOUT_BASE
103+
total_timeout = 0
104+
payload = None
105+
while req_attempts < MAX_ATTEMPTS:
77106
try:
78-
resp = urllib.request.urlopen(req, timeout=currentTimeout)
79-
payload = resp.read()
80-
trying = False
107+
resp = urllib.request.urlopen(req, timeout=current_timeout)
108+
# exception catching, mainly for request timeouts, "Service Temporarily Unavailable" (Rate limiting), and DNS failures.
81109
except urllib.error.URLError as exception:
82-
if currentTimeout < MAX_TIMEOUT:
83-
currentTimeout *= TIMEOUTMULTIPLE
84-
else:
85-
sys.exit(
86-
f"Exception raised after maximum number of retries and/or timeout {MAX_TIMEOUT} seconds reached. "
87-
+ f"Exception reason: {exception.reason}.\n Request: {req.full_url}"
110+
req_attempts += 1
111+
if req_attempts >= MAX_ATTEMPTS:
112+
raise URLRequestError(
113+
"Exception raised after maximum number of retries reached after total backoff of " +
114+
f"{total_timeout} seconds. Retries: {req_attempts}. "
115+
+ f"Exception reason: {exception}.\n Request: {req.full_url}"
88116
)
117+
time.sleep(current_timeout)
118+
total_timeout += current_timeout
119+
current_timeout *= TIMEOUT_BASE
120+
else:
121+
payload = resp.read()
122+
break
89123

90124
return json.loads(payload) if payload else None
91125

@@ -184,7 +218,7 @@ def identifier_from_list(id_list, id_type):
184218
def identifier_matches(id_list, id_type, regex_string):
185219
pattern = re.compile(regex_string)
186220
value = identifier_from_list(id_list, id_type)
187-
return (value is not None) & (pattern.match(value) is not None)
221+
return (value is not None) and (pattern.match(value) is not None)
188222

189223

190224
def rename_co_group(gid, group, newname, endpoint, authstr):

create_project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import getopt
88
import urllib.error
99
import urllib.request
10-
import comanage_scripts_utils as utils
10+
import comanage_utils as utils
1111

1212

1313
SCRIPT = os.path.basename(__file__)

group_fixup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import collections
88
import urllib.error
99
import urllib.request
10-
import comanage_scripts_utils as utils
10+
import comanage_utils as utils
1111

1212

1313
SCRIPT = os.path.basename(__file__)

osg-comanage-project-usermap.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#!/usr/bin/env python3
22

33
import os
4+
import re
45
import sys
56
import time
67
import getopt
78
import urllib.error
89
import urllib.request
9-
import comanage_scripts_utils as utils
10+
import comanage_utils as utils
1011

1112

1213
SCRIPT = os.path.basename(__file__)
@@ -33,6 +34,7 @@
3334
(default = {ENDPOINT})
3435
-o outfile specify output file (default: write to stdout)
3536
-g filter_group filter users by group name (eg, 'ap1-login')
37+
-l localmaps specify a comma-delimited list of local HTCondor mapfiles to merge into outfile
3638
-h display this help text
3739
3840
PASS for USER is taken from the first of:
@@ -52,14 +54,15 @@ def usage(msg=None):
5254

5355
class Options:
5456
endpoint = ENDPOINT
55-
user = "co_8.project_script"
57+
user = "co_7.project_script"
5658
osg_co_id = OSG_CO_ID
5759
outfile = None
5860
authstr = None
5961
ldap_server = LDAP_SERVER
6062
ldap_user = LDAP_USER
6163
ldap_authtok = None
6264
filtergrp = None
65+
localmaps = []
6366

6467

6568
options = Options()
@@ -91,7 +94,8 @@ def get_co_group_members__pids(gid):
9194
#print(f"get_co_group_members__pids({gid})")
9295
resp_data = utils.get_co_group_members(gid, options.endpoint, options.authstr)
9396
data = utils.get_datalist(resp_data, "CoGroupMembers")
94-
return [ m["Person"]["Id"] for m in data ]
97+
# For INF-1060: Temporary Fix until "The Great Project Provisioning" is finished
98+
return [ m["Person"]["Id"] for m in data if m["Member"] == True]
9599

96100

97101
def get_co_person_osguser(pid):
@@ -127,6 +131,7 @@ def parse_options(args):
127131
if op == '-e': options.endpoint = arg
128132
if op == '-o': options.outfile = arg
129133
if op == '-g': options.filtergrp = arg
134+
if op == '-l': options.localmaps = arg.split(",")
130135

131136
try:
132137
user, passwd = utils.getpw(options.user, passfd, passfile)
@@ -135,6 +140,11 @@ def parse_options(args):
135140
except PermissionError:
136141
usage("PASS required")
137142

143+
def _deduplicate_list(items):
144+
""" Deduplicate a list while maintaining order by converting it to a dictionary and then back to a list.
145+
Used to ensure a consistent ordering for output group lists, since sets are unordered.
146+
"""
147+
return list(dict.fromkeys(items))
138148

139149
def get_ldap_group_members_dict():
140150
group_data_dict = dict()
@@ -215,6 +225,32 @@ def get_osguser_groups(filter_group_name=None):
215225
return usernames_to_project_map
216226

217227

228+
def parse_localmap(inputfile):
229+
user_groupmap = dict()
230+
with open(inputfile, 'r', encoding='utf-8') as file:
231+
for line in file:
232+
# Split up 3 semantic columns
233+
split_line = line.strip().split(maxsplit=2)
234+
if split_line[0] == "*" and len(split_line) == 3:
235+
line_groups = re.split(r'[ ,]+', split_line[2])
236+
if split_line[1] in user_groupmap:
237+
user_groupmap[split_line[1]] = _deduplicate_list(user_groupmap[split_line[1]] + line_groups)
238+
else:
239+
user_groupmap[split_line[1]] = line_groups
240+
return user_groupmap
241+
242+
243+
def merge_maps(maps):
244+
merged_map = dict()
245+
for projectmap in maps:
246+
for key in projectmap.keys():
247+
if key in merged_map:
248+
merged_map[key] = _deduplicate_list(merged_map[key] + projectmap[key])
249+
else:
250+
merged_map[key] = projectmap[key]
251+
return merged_map
252+
253+
218254
def print_usermap_to_file(osguser_groups, file):
219255
for osguser, groups in sorted(osguser_groups.items()):
220256
print("* {} {}".format(osguser, ",".join(group.strip() for group in groups)), file=file)
@@ -232,12 +268,17 @@ def main(args):
232268
parse_options(args)
233269

234270
osguser_groups = get_osguser_groups(options.filtergrp)
235-
print_usermap(osguser_groups)
271+
272+
maps = [osguser_groups]
273+
for localmap in options.localmaps:
274+
maps.append(parse_localmap(localmap))
275+
osguser_groups_merged = merge_maps(maps)
276+
277+
print_usermap(osguser_groups_merged)
236278

237279

238280
if __name__ == "__main__":
239281
try:
240282
main(sys.argv[1:])
241-
except urllib.error.HTTPError as e:
242-
print(e, file=sys.stderr)
243-
sys.exit(1)
283+
except Exception as e:
284+
sys.exit(e)

0 commit comments

Comments
 (0)