299 lines
7.3 KiB
Python
299 lines
7.3 KiB
Python
import code
|
|
import json, requests, sys
|
|
from getpass import getpass
|
|
|
|
if sys.platform == 'darwin':
|
|
import gnureadline
|
|
|
|
USE_COLORS = '-nocolors' not in sys.argv
|
|
|
|
# colours
|
|
if USE_COLORS:
|
|
ENDC = '\033[37m'
|
|
color = lambda n, bright=False: lambda s: f'\033[{3 + 6*bright}{n}m{s}{ENDC}'
|
|
else:
|
|
ENDC = ''
|
|
color = lambda n, bright=False: lambda s: s
|
|
|
|
warn = color(3)
|
|
error = color(1)
|
|
name_color = color(2)
|
|
|
|
#########
|
|
|
|
|
|
|
|
ROBOMERGE_URL = 'https://robomerge.epicgames.net'
|
|
|
|
|
|
|
|
context = dict(
|
|
_bots = {},
|
|
ops = {}
|
|
)
|
|
|
|
def get_bot(name):
|
|
try:
|
|
return context['_bots'][name.lower()]
|
|
except KeyError:
|
|
raise f"Unknown bot '{name}'"
|
|
|
|
def request(url, method, data=None):
|
|
|
|
auth_token = context['_auth_token']
|
|
if method == 'GET':
|
|
return requests.get(url, cookies = dict(auth=auth_token))
|
|
|
|
return requests.post(url, cookies = dict(auth=auth_token), data=data)
|
|
|
|
def get(url): return request(url, 'GET')
|
|
def post(url, data=None): return request(url, 'POST', data)
|
|
|
|
def export(category):
|
|
def impl(f):
|
|
context[f.__name__] = f
|
|
ops = context['ops'].setdefault(category, [])
|
|
ops.append(f.__name__)
|
|
ops.sort()
|
|
return f
|
|
return impl
|
|
|
|
class Bot(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
self.nodes = {}
|
|
self.edges = {}
|
|
|
|
class Branch(object):
|
|
def __init__(self, bot, json_data):
|
|
self.bot = bot
|
|
|
|
def_obj = json_data['def']
|
|
self.name = def_obj['name']
|
|
self.flows_to = def_obj.get('flowsTo', [])
|
|
self.force_flow = def_obj.get('forceFlowTo', [])
|
|
|
|
self.aliases = def_obj.get('aliases', [])
|
|
|
|
class Blockage(object):
|
|
def __init__(self, blockage_json):
|
|
self.cl = blockage_json.get('change')
|
|
self.owner = blockage_json.get('owner')
|
|
|
|
class Node(object):
|
|
def __init__(self, bot, name):
|
|
self.bot = bot
|
|
self.name = name
|
|
|
|
def __repr__(self):
|
|
return f"Node({name_color(self.name)})"
|
|
|
|
class Edge(object):
|
|
def __init__(self, bot, source, target, merge_mode):
|
|
self.bot = bot
|
|
self.source = source
|
|
self.target = target
|
|
self.merge_mode = merge_mode
|
|
|
|
self.is_paused, self.blockage = False, None
|
|
|
|
def __repr__(self):
|
|
return f'Edge({self.source}, {self.target}, {self.merge_mode})'
|
|
|
|
def update_status(self, status_json):
|
|
self.is_paused = bool(status_json.get('is_paused'))
|
|
self.blockage = bool(status_json.get('is_blocked')) and Blockage(status_json.get('blockage'))
|
|
|
|
if self.is_paused:
|
|
manual_pause_json = status_json.get('manual_pause')
|
|
self.paused_since = manual_pause_json and manual_pause_json.get('startedAt')
|
|
|
|
def fetch_branch_data():
|
|
|
|
response = get(f'{ROBOMERGE_URL}/api/branches')
|
|
|
|
bots = context['_bots']
|
|
bots.clear()
|
|
|
|
try:
|
|
branches = []
|
|
edge_statuses = []
|
|
for branch_json in json.loads(response.text)['branches']:
|
|
bot_name = branch_json['bot']
|
|
bot = bots.setdefault(bot_name.lower(), Bot(bot_name))
|
|
|
|
branch = Branch(bot, branch_json)
|
|
branches.append(branch)
|
|
|
|
edges = branch_json.get('edges')
|
|
if edges:
|
|
edge_statuses += [(bot, branch.name, target, status) for target, status in edges.items()]
|
|
|
|
node = Node(bot, branch.name)
|
|
|
|
bot.nodes[branch.name.lower()] = node
|
|
for alias in branch.aliases:
|
|
bot.nodes[alias.lower()] = node
|
|
|
|
for branch in branches:
|
|
for target in branch.flows_to:
|
|
source_node = branch.bot.nodes[branch.name.lower()]
|
|
target_node = branch.bot.nodes[target.lower()]
|
|
branch.bot.edges[(source_node, target_node)] \
|
|
= Edge(branch.bot, source_node, target_node,
|
|
'auto' if target in branch.force_flow else 'normal'
|
|
# not quite right if alias used for force list
|
|
)
|
|
|
|
for bot, branch_name, target, status_json in edge_statuses:
|
|
find_edge(bot.name, branch_name, target).update_status(status_json)
|
|
except:
|
|
return
|
|
|
|
@export('login')
|
|
def login(user, password=None):
|
|
if not password:
|
|
password = getpass('Enter your password: ')
|
|
|
|
r = requests.post(f'{ROBOMERGE_URL}/dologin', data = dict(
|
|
user = user,
|
|
password = password
|
|
))
|
|
|
|
if r.status_code == 200:
|
|
print(user + ' logged in')
|
|
context['_auth_token'] = r.text
|
|
fetch_branch_data()
|
|
else:
|
|
print(r.text)
|
|
|
|
@export('setAuthToken')
|
|
def setAuthToken(token):
|
|
context['_auth_token'] = token
|
|
fetch_branch_data()
|
|
|
|
if len(context['_bots']) > 0:
|
|
print("Token set successfully")
|
|
else:
|
|
print("Token did not allow for branch data to be fetched")
|
|
|
|
# discoverability
|
|
|
|
# query branches once for now - maybe provide way to refresh status
|
|
|
|
|
|
|
|
# print([0]['def']['upperName'])
|
|
@export('discovery')
|
|
def list_bots():
|
|
return list(context['_bots'].keys())
|
|
|
|
@export('discovery')
|
|
def find_edge(bot, source, target):
|
|
'''Find an edge in a specific bot
|
|
:param str bot: Name of bot to look in (not case-sensitive)
|
|
:param str source: Name or part of name of source node (not case-sensitive)
|
|
:param str source: Name or part of name of target node (not case-sensitive)
|
|
'''
|
|
source_node = find_node(bot, source)
|
|
target_node = find_node(bot, target)
|
|
|
|
bot = context['_bots'][bot.lower()]
|
|
return source_node and target_node and bot.edges.get((source_node, target_node))
|
|
|
|
@export('discovery')
|
|
def find_node(bot, name):
|
|
'''Find an edge in a specific bot
|
|
:param str bot: Name of bot to look in (not case-sensitive)
|
|
:param str name: Name or part of name of node to find (not case-sensitive)
|
|
'''
|
|
name = name.lower()
|
|
bot = context['_bots'][bot.lower()]
|
|
from_alias = bot.nodes.get(name)
|
|
if from_alias:
|
|
return from_alias
|
|
|
|
# fuzzier match
|
|
for alias, node in bot.nodes.items():
|
|
if name in alias:
|
|
return node
|
|
|
|
@export('discovery')
|
|
def list_ops():
|
|
return context['_ops']
|
|
|
|
|
|
# status
|
|
@export('status')
|
|
def list_blockages(bot):
|
|
bot = context['_bots'][bot.lower()]
|
|
for edge in bot.edges.values():
|
|
# next, store CL and owner
|
|
if edge.is_paused:
|
|
print(f"{edge} {warn('paused')} since {edge.paused_since}")
|
|
|
|
if edge.blockage:
|
|
print(f"{edge} {error('blocked')} at CL {edge.blockage.cl} (owner {edge.blockage.owner})")
|
|
|
|
@export('status')
|
|
def is_running(bot):
|
|
result = get(f"{ROBOMERGE_URL}/api/control/isrunning/{bot}")
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
print(f"{result.text}")
|
|
|
|
# operations
|
|
|
|
def op_url(node_or_edge, op):
|
|
prefix = f'{ROBOMERGE_URL}/api/op/bot/{node_or_edge.bot.name}/'
|
|
suffix = '/op/' + op
|
|
|
|
if isinstance(node_or_edge, Node):
|
|
return prefix + f'node/{node_or_edge.name}' + suffix
|
|
|
|
edge = node_or_edge
|
|
return prefix + f'node/{edge.source.name.upper()}/edge/{edge.target.name.upper()}' + suffix
|
|
|
|
@export('ops')
|
|
def retry(edge):
|
|
'''Send a retry request to RoboMerge
|
|
|
|
:param Edge edge: Blocked edge that should retry integration
|
|
'''
|
|
result = post(op_url(edge, 'retry'))
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
|
|
@export('ops')
|
|
def reconsider(node_or_edge, cl):
|
|
'''Send a reconsider request to RoboMerge
|
|
|
|
:param Node|Edge target: Edge to integrate along, or source node to integrate to all outgoing edges
|
|
:param int cl: Changelist to integrate
|
|
'''
|
|
result = post(op_url(node_or_edge, 'reconsider') + f'?cl={cl}')
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
|
|
@export('control')
|
|
def stop():
|
|
result = post(f"{ROBOMERGE_URL}/api/control/stop")
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
|
|
@export('control')
|
|
def start():
|
|
result = post(f"{ROBOMERGE_URL}/api/control/start")
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
|
|
@export('control')
|
|
def restart_bot(bot):
|
|
result = post(f"{ROBOMERGE_URL}/api/control/restart-bot/{bot}")
|
|
if result.status_code != 200:
|
|
raise Exception(result.text)
|
|
|
|
if __name__ == '__main__':
|
|
code.interact('RoboMerge REPL v0.1' + ENDC, None, context)
|