18903: handle deleted users properly.
[arvados.git] / tools / user-activity / arvados_user_activity / main.py
1 #!/usr/bin/env python3
2 # Copyright (C) The Arvados Authors. All rights reserved.
3 #
4 # SPDX-License-Identifier: AGPL-3.0
5
6 import argparse
7 import sys
8
9 import arvados
10 import arvados.util
11 import datetime
12 import ciso8601
13
14 def parse_arguments(arguments):
15     arg_parser = argparse.ArgumentParser()
16     arg_parser.add_argument('--days', type=int, required=True)
17     args = arg_parser.parse_args(arguments)
18     return args
19
20 def getowner(arv, uuid, owners):
21     if uuid is None:
22         return None
23     if uuid[6:11] == "tpzed":
24         return uuid
25
26     if uuid not in owners:
27         try:
28             gp = arv.groups().get(uuid=uuid).execute()
29             owners[uuid] = gp["owner_uuid"]
30         except:
31             owners[uuid] = None
32
33     return getowner(arv, owners[uuid], owners)
34
35 def getuserinfo(arv, uuid):
36     try:
37         u = arv.users().get(uuid=uuid).execute()
38     except:
39         return "deleted user (%susers/%s)" % (arv.config()["Services"]["Workbench1"]["ExternalURL"],
40                                                        uuid)
41     prof = "\n".join("  %s: \"%s\"" % (k, v) for k, v in u["prefs"].get("profile", {}).items() if v)
42     if prof:
43         prof = "\n"+prof+"\n"
44     return "%s %s <%s> (%susers/%s)%s" % (u["first_name"], u["last_name"], u["email"],
45                                                        arv.config()["Services"]["Workbench1"]["ExternalURL"],
46                                                        uuid, prof)
47
48 collectionNameCache = {}
49 def getCollectionName(arv, pdh):
50     if pdh not in collectionNameCache:
51         u = arv.collections().list(filters=[["portable_data_hash","=",pdh]]).execute().get("items")
52         if len(u) < 1:
53             return "(deleted)"
54         collectionNameCache[pdh] = u[0]["name"]
55     return collectionNameCache[pdh]
56
57 def getname(u):
58     return "\"%s\" (%s)" % (u["name"], u["uuid"])
59
60 def main(arguments=None):
61     if arguments is None:
62         arguments = sys.argv[1:]
63
64     args = parse_arguments(arguments)
65
66     arv = arvados.api()
67
68     since = datetime.datetime.utcnow() - datetime.timedelta(days=args.days)
69
70     print("User activity on %s between %s and %s\n" % (arv.config()["ClusterID"],
71                                                        (datetime.datetime.now() - datetime.timedelta(days=args.days)).isoformat(sep=" ", timespec="minutes"),
72                                                        datetime.datetime.now().isoformat(sep=" ", timespec="minutes")))
73
74     events = arvados.util.keyset_list_all(arv.logs().list, filters=[["created_at", ">=", since.isoformat()]])
75
76     users = {}
77     owners = {}
78
79     for e in events:
80         owner = getowner(arv, e["object_owner_uuid"], owners)
81         users.setdefault(owner, [])
82         event_at = ciso8601.parse_datetime(e["event_at"]).astimezone().isoformat(sep=" ", timespec="minutes")
83         # loguuid = e["uuid"]
84         loguuid = ""
85
86         if e["event_type"] == "create" and e["object_uuid"][6:11] == "tpzed":
87             users.setdefault(e["object_uuid"], [])
88             users[e["object_uuid"]].append("%s User account created" % event_at)
89
90         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "tpzed":
91             pass
92
93         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "xvhdp":
94             if e["properties"]["new_attributes"]["requesting_container_uuid"] is None:
95                 users[owner].append("%s Ran container %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
96
97         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "xvhdp":
98             pass
99
100         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "j7d0g":
101             users[owner].append("%s Created project %s" %  (event_at, getname(e["properties"]["new_attributes"])))
102
103         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "j7d0g":
104             users[owner].append("%s Deleted project %s" % (event_at, getname(e["properties"]["old_attributes"])))
105
106         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "j7d0g":
107             users[owner].append("%s Updated project %s" % (event_at, getname(e["properties"]["new_attributes"])))
108
109         elif e["event_type"] in ("create", "update") and e["object_uuid"][6:11] == "gj3su":
110             since_last = None
111             if len(users[owner]) > 0 and users[owner][-1].endswith("activity"):
112                 sp = users[owner][-1].split(" ")
113                 start = sp[0]+" "+sp[1]
114                 since_last = ciso8601.parse_datetime(event_at) - ciso8601.parse_datetime(sp[3]+" "+sp[4])
115                 span = ciso8601.parse_datetime(event_at) - ciso8601.parse_datetime(start)
116
117             if since_last is not None and since_last < datetime.timedelta(minutes=61):
118                 users[owner][-1] = "%s to %s (%02d:%02d) Account activity" % (start, event_at, span.days*24 + int(span.seconds/3600), int((span.seconds % 3600)/60))
119             else:
120                 users[owner].append("%s to %s (0:00) Account activity" % (event_at, event_at))
121
122         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "o0j2j":
123             if e["properties"]["new_attributes"]["link_class"] == "tag":
124                 users[owner].append("%s Tagged %s" % (event_at, e["properties"]["new_attributes"]["head_uuid"]))
125             elif e["properties"]["new_attributes"]["link_class"] == "permission":
126                 users[owner].append("%s Shared %s with %s" % (event_at, e["properties"]["new_attributes"]["tail_uuid"], e["properties"]["new_attributes"]["head_uuid"]))
127             else:
128                 users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
129
130         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "o0j2j":
131             if e["properties"]["old_attributes"]["link_class"] == "tag":
132                 users[owner].append("%s Untagged %s" % (event_at, e["properties"]["old_attributes"]["head_uuid"]))
133             elif e["properties"]["old_attributes"]["link_class"] == "permission":
134                 users[owner].append("%s Unshared %s with %s" % (event_at, e["properties"]["old_attributes"]["tail_uuid"], e["properties"]["old_attributes"]["head_uuid"]))
135             else:
136                 users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
137
138         elif e["event_type"] == "create" and e["object_uuid"][6:11] == "4zz18":
139             if e["properties"]["new_attributes"]["properties"].get("type") in ("log", "output", "intermediate"):
140                 pass
141             else:
142                 users[owner].append("%s Created collection %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
143
144         elif e["event_type"] == "update" and e["object_uuid"][6:11] == "4zz18":
145             users[owner].append("%s Updated collection %s %s" % (event_at, getname(e["properties"]["new_attributes"]), loguuid))
146
147         elif e["event_type"] == "delete" and e["object_uuid"][6:11] == "4zz18":
148             if e["properties"]["old_attributes"]["properties"].get("type") in ("log", "output", "intermediate"):
149                 pass
150             else:
151                 users[owner].append("%s Deleted collection %s %s" % (event_at, getname(e["properties"]["old_attributes"]), loguuid))
152
153         elif e["event_type"] == "file_download":
154                 users.setdefault(e["object_uuid"], [])
155                 users[e["object_uuid"]].append("%s Downloaded file \"%s\" from \"%s\" (%s) (%s)" % (event_at,
156                                                                                        e["properties"].get("collection_file_path") or e["properties"].get("reqPath"),
157                                                                                        getCollectionName(arv, e["properties"].get("portable_data_hash")),
158                                                                                        e["properties"].get("collection_uuid"),
159                                                                                        e["properties"].get("portable_data_hash")))
160
161         elif e["event_type"] == "file_upload":
162                 users.setdefault(e["object_uuid"], [])
163                 users[e["object_uuid"]].append("%s Uploaded file \"%s\" to \"%s\" (%s)" % (event_at,
164                                                                                     e["properties"].get("collection_file_path") or e["properties"].get("reqPath"),
165                                                                                     getCollectionName(arv, e["properties"].get("portable_data_hash")),
166                                                                                     e["properties"].get("collection_uuid")))
167
168         else:
169             users[owner].append("%s %s %s %s %s" % (event_at, e["event_type"], e["object_kind"], e["object_uuid"], loguuid))
170
171     for k,v in users.items():
172         if k is None or k.endswith("-tpzed-000000000000000"):
173             continue
174         print(getuserinfo(arv, k))
175         for ev in v:
176             print("  %s" % ev)
177         print("")
178
179 if __name__ == "__main__":
180     main()