From a53b268f3f3d30b35c1641d01626203f2572b4d1 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Wed, 8 May 2019 17:25:06 -0400 Subject: [PATCH] 15061: add federation-migrate script Produces a report, users can edit the report to choose which cluster each user should belong to, then run the report back through to perform the migration. Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- .../federation-migrate/federation-migrate.py | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100755 tools/federation-migrate/federation-migrate.py diff --git a/tools/federation-migrate/federation-migrate.py b/tools/federation-migrate/federation-migrate.py new file mode 100755 index 0000000000..9f612b24b5 --- /dev/null +++ b/tools/federation-migrate/federation-migrate.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + +import arvados +import arvados.util +import csv +import sys +import argparse +import hmac + +def main(): + + parser = argparse.ArgumentParser(description='Migrate users to federated identity') + parser.add_argument('--tokens', type=str) + group = parser.add_mutually_exclusive_group() + group.add_argument('--report', type=str) + group.add_argument('--migrate', type=str) + args = parser.parse_args() + + clusters = {} + + with open(args.tokens, "rt") as f: + for r in csv.reader(f): + host = r[0] + token = r[1] + arv = arvados.api(host=host, token=token) + clusters[arv._rootDesc["uuidPrefix"]] = arv + cur = arv.users().current().execute() + if not cur["is_admin"]: + raise Exception("Not admin of %s" % host) + + if args.report: + users = [] + for c, arv in clusters.items(): + ul = arvados.util.list_all(arv.users().list) + for l in ul: + if l["uuid"].startswith(c): + users.append(l) + + out = csv.writer(open(args.report, "wt")) + + out.writerow(("email", "user uuid", "primary cluster/user")) + + users = sorted(users, key=lambda u: u["email"]+"::"+u["uuid"]) + + accum = [] + lastemail = None + for u in users: + if u["uuid"].endswith("-anonymouspublic") or u["uuid"].endswith("-000000000000000"): + continue + if lastemail == None: + lastemail = u["email"] + if u["email"] == lastemail: + accum.append(u) + else: + homeuuid = None + for a in accum: + if homeuuid is None: + homeuuid = a["uuid"] + if a["uuid"] != homeuuid: + homeuuid = "" + for a in accum: + out.writerow((a["email"], a["uuid"], homeuuid[0:5])) + lastemail = u["email"] + accum = [u] + + homeuuid = None + for a in accum: + if homeuuid is None: + homeuuid = a["uuid"] + if a["uuid"] != homeuuid: + homeuuid = "" + for a in accum: + out.writerow((a["email"], a["uuid"], homeuuid[0:5])) + + if args.migrate: + rows = [] + by_email = {} + with open(args.migrate, "rt") as f: + for r in csv.reader(f): + if r[0] == "email": + continue + by_email.setdefault(r[0], []) + by_email[r[0]].append(r) + rows.append(r) + for r in rows: + if r[2] == "": + print("(%s) Skipping %s, no home cluster specified" % (r[0], r[1])) + if r[1].startswith(r[2]): + continue + candidates = [] + for b in by_email[r[0]]: + if b[1].startswith(r[2]): + candidates.append(b) + if len(candidates) == 0: + print("(%s) No user listed to migrate %s to %s" % (r[0], r[1], r[2])) + continue + if len(candidates) > 1: + print("(%s) Multiple users listed to migrate %s to %s, use full uuid" % (r[0], r[1], r[2])) + continue + new_user_uuid = candidates[0][1] + print("(%s) Will migrate %s to %s" % (r[0], r[1], new_user_uuid)) + oldcluster = r[1][0:5] + newhomecluster = r[2][0:5] + homearv = clusters[newhomecluster] + # create a token + newtok = homearv.api_client_authorizations().create(body={"api_client_authorization": {'owner_uuid': new_user_uuid}}).execute() + salted = 'v2/' + newtok["uuid"] + '/' + hmac.new(newtok["api_token"].encode(), msg=oldcluster.encode(), digestmod='sha1').hexdigest() + arvados.api(host=arv._rootDesc["rootUrl"][8:-1], token=salted).users().current().execute() + + # now migrate from local user to remote user. + arv = clusters[oldcluster] + + grp = arv.groups().create(body={ + "owner_uuid": new_user_uuid, + "name": "Migrated from %s (%s)" % (r[0], r[1]), + "group_class": "project" + }, ensure_unique_name=True).execute() + arv.users().merge(old_user_uuid=r[1], + new_user_uuid=new_user_uuid, + new_owner_uuid=grp["uuid"], + redirect_to_new_user=True).execute() + +if __name__ == "__main__": + main() -- 2.30.2