Skip to content

Commit 69e14e0

Browse files
Initial Commit for project osggid assignment script
1 parent 46c2108 commit 69e14e0

2 files changed

Lines changed: 272 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.vscode/launch.json

group_identifier_assigner.py

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import re
5+
import sys
6+
import json
7+
import getopt
8+
import urllib.error
9+
import urllib.request
10+
11+
SCRIPT = os.path.basename(__file__)
12+
ENDPOINT = "https://registry-test.cilogon.org/registry/"
13+
OSG_CO_ID = 8
14+
MINTIMEOUT = 5
15+
MAXTIMEOUT = 625
16+
TIMEOUTMULTIPLE = 5
17+
18+
GET = "GET"
19+
PUT = "PUT"
20+
POST = "POST"
21+
DELETE = "DELETE"
22+
23+
_usage = f"""\
24+
usage: [PASS=...] {SCRIPT} [OPTIONS]
25+
26+
OPTIONS:
27+
-u USER[:PASS] specify USER and optionally PASS on command line
28+
-c OSG_CO_ID specify OSG CO ID (default = {OSG_CO_ID})
29+
-d passfd specify open fd to read PASS
30+
-f passfile specify path to file to open and read PASS
31+
-e ENDPOINT specify REST endpoint
32+
(default = {ENDPOINT})
33+
-o outfile specify output file (default: write to stdout)
34+
-t minTimeout set minimum timeout, in seconds, for API call (default to {MINTIMEOUT})
35+
-T maxTimeout set maximum timeout, in seconds, for API call (default to {MAXTIMEOUT})
36+
-h display this help text
37+
38+
PASS for USER is taken from the first of:
39+
1. -u USER:PASS
40+
2. -d passfd (read from fd)
41+
3. -f passfile (read from file)
42+
4. read from $PASS env var
43+
"""
44+
45+
46+
def usage(msg=None):
47+
if msg:
48+
print(msg + "\n", file=sys.stderr)
49+
50+
print(_usage, file=sys.stderr)
51+
sys.exit()
52+
53+
54+
class Options:
55+
endpoint = ENDPOINT
56+
user = "co_8.william_test"
57+
osg_co_id = OSG_CO_ID
58+
outfile = None
59+
authstr = None
60+
min_timeout = MINTIMEOUT
61+
max_timeout = MAXTIMEOUT
62+
63+
64+
options = Options()
65+
66+
67+
def getpw(user, passfd, passfile):
68+
if ":" in user:
69+
user, pw = user.split(":", 1)
70+
elif passfd is not None:
71+
pw = os.fdopen(passfd).readline().rstrip("\n")
72+
elif passfile is not None:
73+
pw = open(passfile).readline().rstrip("\n")
74+
elif "PASS" in os.environ:
75+
pw = os.environ["PASS"]
76+
else:
77+
usage("PASS required")
78+
return user, pw
79+
80+
81+
def mkauthstr(user, passwd):
82+
from base64 import encodebytes
83+
84+
raw_authstr = "%s:%s" % (user, passwd)
85+
return encodebytes(raw_authstr.encode()).decode().replace("\n", "")
86+
87+
88+
def mkrequest(method, target, data, **kw):
89+
url = os.path.join(options.endpoint, target)
90+
if kw:
91+
url += "?" + "&".join("{}={}".format(k, v) for k, v in kw.items())
92+
req = urllib.request.Request(url, json.dumps(data).encode("utf-8"))
93+
req.add_header("Authorization", "Basic %s" % options.authstr)
94+
req.add_header("Content-Type", "application/json")
95+
req.get_method = lambda: method
96+
return req
97+
98+
99+
def call_api(target, **kw):
100+
return call_api2(GET, target, **kw)
101+
102+
103+
def call_api2(method, target, **kw):
104+
return call_api3(method, target, data=None, **kw)
105+
106+
107+
def call_api3(method, target, data, **kw):
108+
req = mkrequest(method, target, data, **kw)
109+
trying = True
110+
currentTimeout = options.min_timeout
111+
while trying:
112+
try:
113+
resp = urllib.request.urlopen(req, timeout=currentTimeout)
114+
payload = resp.read()
115+
trying = False
116+
except urllib.error.URLError as exception:
117+
if currentTimeout < options.max_timeout:
118+
currentTimeout *= TIMEOUTMULTIPLE
119+
else:
120+
sys.exit(
121+
f"Exception raised after maximum timeout {options.max_timeout} seconds reached. "
122+
+ f"Exception reason: {exception.reason}.\n Request: {req.full_url}"
123+
)
124+
125+
return json.loads(payload) if payload else None
126+
127+
128+
def get_osg_co_groups():
129+
return call_api("co_groups.json", coid=options.osg_co_id)
130+
131+
132+
# primary api calls
133+
134+
135+
def get_co_group_identifiers(gid):
136+
return call_api("identifiers.json", cogroupid=gid)
137+
138+
139+
def get_co_group_members(gid):
140+
return call_api("co_group_members.json", cogroupid=gid)
141+
142+
143+
def get_co_person_identifiers(pid):
144+
return call_api("identifiers.json", copersonid=pid)
145+
146+
147+
def get_datalist(data, listname):
148+
return data[listname] if data else []
149+
150+
151+
def identifier_index(id_list, id_type):
152+
found_list = list((id["Type"] == id_type for id in id_list))
153+
if any(found_list):
154+
return found_list.index(True)
155+
return -1
156+
157+
158+
def identifier_matches(id_list, id_type, regex_string):
159+
pattern = re.compile(regex_string)
160+
index = identifier_index(id_list, id_type)
161+
return (index != -1) & (pattern.match(id_list[index]["Identifier"]) is not None)
162+
163+
164+
def add_identifier_to_group(gid, type, identifier_name):
165+
new_identifier_info = {
166+
"Version": "1.0",
167+
"Type": type,
168+
"Identifier": identifier_name,
169+
"Login": False,
170+
"Person": {"Type": "Group", "Id": str(gid)},
171+
"Status": "Active",
172+
}
173+
data = {
174+
"RequestType": "Identifiers",
175+
"Version": "1.0",
176+
"Identifiers": [new_identifier_info],
177+
}
178+
return call_api3(POST, "identifiers.json", data)
179+
180+
181+
def parse_options(args):
182+
try:
183+
ops, args = getopt.getopt(args, "u:c:d:f:e:o:t:T:h")
184+
except getopt.GetoptError:
185+
usage()
186+
187+
if args:
188+
usage("Extra arguments: %s" % repr(args))
189+
190+
passfd = None
191+
passfile = None
192+
193+
for op, arg in ops:
194+
if op == "-h":
195+
usage()
196+
if op == "-u":
197+
options.user = arg
198+
if op == "-c":
199+
options.osg_co_id = int(arg)
200+
if op == "-d":
201+
passfd = int(arg)
202+
if op == "-f":
203+
passfile = arg
204+
if op == "-e":
205+
options.endpoint = arg
206+
if op == "-o":
207+
options.outfile = arg
208+
if op == "-t":
209+
options.min_timeout = float(arg)
210+
if op == "-T":
211+
options.max_timeout = float(arg)
212+
213+
user, passwd = getpw(options.user, passfd, passfile)
214+
options.authstr = mkauthstr(user, passwd)
215+
216+
217+
def main(args):
218+
parse_options(args)
219+
220+
# get groups with 'OSPool project name' matching "Yes-*" that don't have a 'OSG GID'
221+
222+
co_groups = get_osg_co_groups()["CoGroups"]
223+
ospool_pattern_str = "Yes-"
224+
highest_osggid = 0
225+
226+
for group in co_groups:
227+
gid = group["Id"]
228+
identifier_data = get_co_group_identifiers(gid)
229+
if identifier_data:
230+
identifier_list = identifier_data["Identifiers"]
231+
232+
project_id_index = identifier_index(
233+
id_list=identifier_list, id_type="ospoolproject"
234+
)
235+
if project_id_index != -1:
236+
project_id = identifier_list[project_id_index]["Identifier"]
237+
is_project = (
238+
re.compile(ospool_pattern_str + "*").match(project_id) is not None
239+
)
240+
else:
241+
is_project = False
242+
243+
osggid_index = identifier_index(identifier_list, "osggid")
244+
if osggid_index != -1:
245+
highest_osggid = max(
246+
highest_osggid, int(identifier_list[osggid_index]["Identifier"])
247+
)
248+
elif is_project is True:
249+
# for each, set a 'OSG GID' starting from 200000 and a 'OSG Group Name' that is the group name
250+
251+
osggid_to_assign = (
252+
200000 if highest_osggid + 1 < 200000 else highest_osggid + 1
253+
)
254+
highest_osggid = osggid_to_assign
255+
add_identifier_to_group(
256+
gid, type="osggid", identifier_name=osggid_to_assign
257+
)
258+
259+
project_name = project_id.removeprefix(ospool_pattern_str).lower()
260+
add_identifier_to_group(gid, type="osggroup", identifier_name=project_name)
261+
print(
262+
f"project {project_id}: added osggid {osggid_to_assign} and osg project name {project_name}"
263+
)
264+
265+
266+
if __name__ == "__main__":
267+
try:
268+
main(sys.argv[1:])
269+
except urllib.error.HTTPError as e:
270+
print(e, file=sys.stderr)
271+
sys.exit(1)

0 commit comments

Comments
 (0)