#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
# ------------------------------------------------------------------------------
"""
NAME:
swarm_rename_users.py
DESCRIPTION:
This script renames Swarm users. Expects parameter to be a file containing:
old_username new_username (separated by whitespace)
You can run like this:
python3 swarm_rename_users.py -i users.list
Swarm key values updated:
p4 keys -e swarm-user-*
p4 keys -e swarm-workflow-*
Users/src/Model/Config.php: const KEY_PREFIX = 'swarm-user-';
swarm-user-jim = {"delayedComments":[],"follows":null,"user_notification_settings":null,"user_settings":null}
swarm-user-john = {"delayedComments":[],"follows":null,"user_notification_settings":null,"user_settings":null}
"""
# Python 2.7/3.3 compatibility.
from __future__ import print_function
import sys
import os
import textwrap
import argparse
import logging
import P4
import json
from collections import defaultdict
from six.moves import range
import six
script_name = os.path.basename(os.path.splitext(__file__)[0])
# Avoid utf encoding issues when writing to stdout
sys.stdout = os.fdopen(sys.stdout.buffer.fileno(), 'w', encoding='utf8')
LOGDIR = os.getenv('LOGS', '/p4/1/logs')
DEFAULT_LOG_FILE = "log-%s.log" % script_name
if os.path.exists(LOGDIR):
DEFAULT_LOG_FILE = os.path.join(LOGDIR, "%s.log" % script_name)
DEFAULT_VERBOSITY = 'DEBUG'
LOGGER_NAME = 'P4Triggers'
class SwarmObject(object):
"Wrapper around Swarm JSON"
updated = False
json = None
def __init__(self, jsonStr, keyword=None):
if keyword and keyword in jsonStr:
self.json = jsonStr[keyword]
else:
self.json = jsonStr
def getVal(self, k):
if k in self.json:
return self.json[k]
return None
def getKeys(self, k):
d = self.getVal(k)
if not d:
return None
results = []
for u, _ in six.iteritems(d):
results.append(u)
return results
def setVal(self, k, v):
self.json[k] = v
self.updated = True
def updateVal(self, k, usersToRename):
u = self.getVal(k)
if u and u in usersToRename:
self.setVal(k, usersToRename[u])
def updateDictKeyVal(self, k, usersToRename):
"Update 'key': {'u1': []}"
if not k in self.json:
return
users = []
for u, _ in six.iteritems(self.json[k]):
users.append(u)
for u in users:
if u in usersToRename:
# Copy old entry to new name and delete old
self.json[k][usersToRename[u]] = self.json[k][u]
del self.json[k][u]
self.updated = True
def updateArrayVal(self, k, usersToRename):
"Update 'key': [user array]"
if not k in self.json:
return
for i, u in enumerate(self.json[k]):
if u in usersToRename:
self.json[k][i] = usersToRename[u]
self.updated = True
def updateArrayDictVal(self, k, k2, usersToRename):
""" Entry is array of dicts with key
project = {"k": [{"k2": "u1"}]}
"""
if not k in self.json:
return
for i, _ in enumerate(self.json[k]):
if k2 in self.json[k][i]:
u = self.json[k][i][k2]
if u in usersToRename:
self.json[k][i][k2] = usersToRename[u]
self.updated = True
def updateDictArrayVal(self, k, k2, usersToRename):
""" Entry is a dict with value as array
project = {"k": {"k2": ["u1", "u2"]}}
"""
if not k in self.json or not self.json[k] or not k2 in self.json[k]:
return
for i, u in enumerate(self.json[k][k2]):
if u in usersToRename:
self.json[k][k2][i] = usersToRename[u]
self.updated = True
def updateDictArrayDictVal(self, k, k2, usersToRename):
""" Entry is an array of dicts with value as array
project = {"k": [{"j": ["u1", "u2"]}]}
"""
if not k in self.json:
return
for i, _ in enumerate(self.json[k]):
if k2 in self.json[k][i]:
for j, u in enumerate(self.json[k][i][k2]):
if u in usersToRename:
self.json[k][i][k2][j] = usersToRename[u]
self.updated = True
def updateDictArrayDictDictVal(self, k, k2, k3, usersToRename):
""" Entry is an array of dicts with value as a dict containing an array
project = {"k": [{"k2": {"k3": ["u1", "u2"]}}]}
"""
if not k in self.json:
return
for i, _ in enumerate(self.json[k]):
if k2 in self.json[k][i]:
if k3 in self.json[k][i][k2]:
for j, u in enumerate(self.json[k][i][k2][k3]):
if u in usersToRename:
self.json[k][i][k2][k3][j] = usersToRename[u]
self.updated = True
class Activity(SwarmObject):
"Swarm Activity"
def __init__(self, json):
SwarmObject.__init__(self, json, 'activity')
def user(self):
return self.getVal('user')
def behalfOf(self):
return self.getVal('behalfOf')
def Update(self, usersToRename):
self.updateVal("user", usersToRename)
self.updateVal("behalfOf", usersToRename)
class Comment(SwarmObject):
"Swarm Comment"
def __init__(self, json):
SwarmObject.__init__(self, json, 'comment')
def user(self):
return self.getVal('user')
def readBy(self):
return self.getVal('readBy')
def Update(self, usersToRename):
self.updateVal("user", usersToRename)
self.updateArrayVal("readBy", usersToRename)
class Project(SwarmObject):
"Swarm Project"
# 'members' - this is via group swarm-<project> and the Users: field
# swarm-project-test = {
# "name": "Test Swarm Project",
# "defaults": {
# "reviewers": ["jim"]
# },
# "description": "Test for demo purposes",
# "owners": [
# "fred",
# "bill"
# ],
# "moderators": [
# "fred",
# "bill"
# ],
# "branches": [
# {
# "id": "main",
# "name": "Main",
# "workflow": null,
# "paths": [
# "\/\/depot\/p4-test\/main\/..."
# ],
# "defaults": {
# "reviewers": []
# },
# "minimumUpVotes": null,
# "retainDefaultReviewers": false,
# "moderators": [],
# "moderators-groups": []
# },
def __init__(self, json):
SwarmObject.__init__(self, json, 'project')
def owners(self):
return self.getVal('owners')
def moderators(self):
return self.getVal('moderators')
def branches(self):
return self.getVal('branches')
def defaultReviewers(self):
defaults = self.getVal('defaults')
k = 'reviewers'
if defaults and k in defaults:
return defaults[k]
return None
def branchDefaultReviewers(self):
branches = self.getVal('branches')
if not branches:
return None
results = []
for b in branches:
k = 'defaults'
j = 'reviewers'
if b and k in b and j in b[k]:
results.append({b['id']: b[k][j]})
return results
def branchModerators(self):
branches = self.getVal('branches')
if not branches:
return None
results = []
for b in branches:
k = 'moderators'
if b and k in b:
results.append({b['id']: b[k]})
return results
def Update(self, usersToRename):
self.updateArrayVal("owners", usersToRename)
self.updateArrayVal("moderators", usersToRename)
self.updateDictArrayVal("defaults", "reviewers", usersToRename)
self.updateDictArrayDictVal("branches", "moderators", usersToRename)
self.updateDictArrayDictDictVal("branches", "defaults", "reviewers", usersToRename)
class Review(SwarmObject):
"Swarm Review"
# "author":"martin",
# "approvals":{"jane":[1]},
# "participants":{"martin":[],"jane":{"vote":{"value":1,"version":1}}
# "versions": [{"user": "fred", "difference": 1}]
def __init__(self, json):
SwarmObject.__init__(self, json, 'review')
def author(self):
return self.getVal('author')
def approvals(self):
return self.getKeys('approvals')
def participants(self):
return self.getKeys('participants')
def versionUsers(self):
d = self.getVal('versions')
if not d:
return None
results = []
for v in d:
if 'user' in v:
results.append(v['user'])
return results
def Update(self, usersToRename):
self.updateVal("author", usersToRename)
self.updateDictKeyVal("approvals", usersToRename)
self.updateDictKeyVal("participants", usersToRename)
self.updateArrayDictVal("versions", "user", usersToRename)
class FileInfo(SwarmObject):
# swarm-fileInfo-1018362-468732cb9f10670c2599d6844a82cd36 = {"readBy":{
# "jim":{"version":28,"digest":"EEFF792157ADBB2311938D7358F0588B"},
# "mike":{"version":30,"digest":"1E43325205374FBBE0E72DAB5930F8DB"}
def __init__(self, json):
SwarmObject.__init__(self, json, 'review')
def readBy(self):
return self.getKeys('readBy')
def Update(self, usersToRename):
self.updateDictKeyVal("readBy", usersToRename)
class Workflow(SwarmObject):
# swarm-workflow-fffffff5 = {"on_submit":{"with_review":{"rule":"approved","mode":"inherit"},"without_review":{"rule":"reject","mode":"inherit"}},
# "owners":["swarm-group-dev","dave", "scott"],
# "end_rules":{"update":{"rule":"no_checking","mode":"inherit"}},
# "auto_approve":{"rule":"never","mode":"inherit"},
# etc...
def __init__(self, json):
SwarmObject.__init__(self, json, 'workflow')
def owners(self):
return self.getVal('owners')
def Update(self, usersToRename):
self.updateArrayVal("owners", usersToRename)
class SwarmComments(object):
"""Parses all swarm comment objects - directly from p4 keys output to avoid overhead of
individual API calls
"""
def __init__(self, comments):
"Comments is output from p4.run_keys('-e', 'swarm-comment-*')"
self.review_comments = defaultdict(list)
for c in comments:
j = json.loads(c['value'])
if 'topic' in j and j['topic'].startswith('review'):
id = j['topic'].split('/')[1]
self.review_comments[id].append(j)
def getReviewComments(self, id):
if id in self.review_comments:
return self.review_comments[id]
return []
class SwarmRenameUsers(object):
"""See module doc string for details"""
def __init__(self, *args, **kwargs):
self.parse_args(__doc__, args)
def parse_args(self, doc, args):
"""Common parsing and setting up of args"""
desc = textwrap.dedent(doc)
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=desc,
epilog="Copyright (c) 2021 Perforce Software, Inc."
)
self.add_parse_args(parser) # Should be implemented by subclass
self.options = parser.parse_args(args=args)
self.init_logger()
self.logger.debug("Command Line Options: %s\n" % self.options)
def add_parse_args(self, parser, default_log_file=None, default_verbosity=None):
"""Default trigger arguments - common to all triggers
:param default_verbosity:
:param default_log_file:
:param parser:
"""
if not default_log_file:
default_log_file = DEFAULT_LOG_FILE
if not default_verbosity:
default_verbosity = DEFAULT_VERBOSITY
parser.add_argument('-p', '--port', default=None,
help="Perforce server port - set using %%serverport%%. Default: $P4PORT")
parser.add_argument('-u', '--user', default=None, help="Perforce user. Default: $P4USER")
parser.add_argument('-L', '--log', default=default_log_file, help="Default: " + default_log_file)
parser.add_argument('-i', '--input', help="Name of a file containing a list of users: oldname newname")
parser.add_argument('-v', '--verbosity',
nargs='?',
const="INFO",
default=default_verbosity,
choices=('DEBUG', 'WARNING', 'INFO', 'ERROR', 'FATAL'),
help="Output verbosity level. Default is: " + default_verbosity)
def init_logger(self, logger_name=None):
if not logger_name:
logger_name = LOGGER_NAME
self.logger = logging.getLogger(logger_name)
self.logger.setLevel(self.options.verbosity)
logformat = '%(levelname)s %(asctime)s %(filename)s %(lineno)d: %(message)s'
logging.basicConfig(format=logformat, filename=self.options.log, level=self.options.verbosity)
formatter = logging.Formatter('%(message)s')
ch = logging.StreamHandler(sys.stderr)
ch.setLevel(logging.INFO)
ch.setFormatter(formatter)
self.logger.addHandler(ch)
def renameObject(self, p4, usersToRename, p4key, klassName, klass):
updateCount = 0
values = p4.run_keys("-e", p4key)
self.logger.info("%s: %d" % (klassName, len(values)))
for v in values:
obj = klass(json.loads(v['value']))
obj.Update(usersToRename)
if obj.updated:
updateCount += 1
p4.run_key(v['key'], json.dumps(obj.json, separators=(',', ':')))
self.logger.info("%s Updated: %d" % (klassName, updateCount))
def renameUsers(self, p4, usersToRename):
self.renameObject(p4, usersToRename, "swarm-activity-*", "Activity", Activity)
self.renameObject(p4, usersToRename, "swarm-comment-*", "Comment", Comment)
self.renameObject(p4, usersToRename, "swarm-project-*", "Project", Project)
self.renameObject(p4, usersToRename, "swarm-review-*", "Review", Review)
self.renameObject(p4, usersToRename, "swarm-fileInfo-*", "FileInfo", FileInfo)
self.renameObject(p4, usersToRename, "swarm-workflow-*", "Workflow", Workflow)
# p4 keys -e swarm-user-* | wc -l
def run(self):
"""Runs script"""
try:
self.logger.debug("%s: starting" % script_name)
p4 = P4.P4()
if self.options.port:
p4.port = self.options.port
if self.options.user:
p4.user = self.options.user
p4.connect()
usersToRename = {}
if self.options.input:
with open(self.options.input, "r") as f:
for line in f.readlines():
oldUser, newUser = line.split()
usersToRename[oldUser] = newUser
self.renameUsers(p4, usersToRename)
except Exception as e:
self.logger.exception(e)
print(str(e))
if __name__ == '__main__':
""" Main Program"""
obj = SwarmRenameUsers(*sys.argv[1:])
sys.exit(obj.run())
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #5 | 27722 | C. Thomas Tyler |
Refinements to @27712: * Resolved one out-of-date file (verify_sdp.sh). * Added missing adoc file for which HTML file had a change (WorkflowEnforcementTriggers.adoc). * Updated revdate/revnumber in *.adoc files. * Additional content updates in Server/Unix/p4/common/etc/cron.d/ReadMe.md. * Bumped version numbers on scripts with Version= def'n. * Generated HTML, PDF, and doc/gen files: - Most HTML and all PDF are generated using Makefiles that call an AsciiDoc utility. - HTML for Perl scripts is generated with pod2html. - doc/gen/*.man.txt files are generated with .../tools/gen_script_man_pages.sh. #review-27712 |
||
| #4 | 27619 | Robert Cowham | Clarify help text/module description, adding warnings and current status | ||
| #3 | 27338 | Robert Cowham | Handle swarm-user-* keys | ||
| #2 | 27337 | Robert Cowham | Fix bugs found on site | ||
| #1 | 27336 | Robert Cowham | Implement rename of users for Swarm - backdoor version updating 'p4 keys' info |