support/retro_contest/rest.py (301 lines of code) (raw):

import argparse import docker import getpass import itertools import json import os import requests import sys import yaml from functools import wraps from requests.auth import HTTPBasicAuth config = {} def update_config(key, value): config[key] = value write_config() def clear_config(key): del config[key] write_config() def write_config(): os.makedirs(os.path.join(os.path.expanduser('~'), '.config'), exist_ok=True) c = yaml.dump(config, default_flow_style=False) with open(os.path.join(os.path.expanduser('~'), '.config/retro-contest.yml'), 'w') as f: f.write(c) def load_config(): global config try: with open(os.path.join(os.path.expanduser('~'), '.config/retro-contest.yml')) as f: config = yaml.safe_load(f) except FileNotFoundError: pass def login(email, password, server=None): if not server: server = config.get('server') if server and not (server.startswith('http://') or server.startswith('https://')): server = 'http://' + server r = requests.post(server + '/rest/login', json={'email': email, 'password': password}) if r.status_code // 100 == 2: update_config('cookies', dict(r.cookies)) update_config('server', server) return True return False def login_args(args): email = args.email if not email: email = input('Email address: ') password = args.password if args.password_stdin or not password: password = getpass.getpass() server = args.server if login(email, password, server): print('Login succeeded') else: print('Login failed') return False return True def leaderboard_args(args): server = config.get('server') r = requests.get(server + '/rest/leaderboard') if r.status_code // 100 == 2: try: info = r.json() board = info.get('leaderboard') except: return for place, score in zip(itertools.count(info.get('start', 1)), board): print('#%i:' % place) print('- User:', score['name']) print('- Score:', score['score']) else: return False return True def logout_args(args): clear_config('cookies') print('Logged out') return True def needs_login(f): @wraps(f) def wrapped(*args, **kwargs): server = config.get('server') cookies = config.get('cookies') if not server or not cookies: print('You are not logged in') return None return f(server=server, cookies=cookies, *args, **kwargs) return wrapped @needs_login def docker_login_args(args, server, cookies): r = requests.get(server + '/rest/user', cookies=cookies) if r.status_code != 200 or 'cr' not in r.json(): print('Failed to obtain container registry') return False cr = r.json()['cr'] client = docker.from_env() client.login(cr['username'], cr['password'], registry=cr['url']) print('Logged in') return True @needs_login def docker_show_args(args, server, cookies): r = requests.get(server + '/rest/user', cookies=cookies) if r.status_code != 200 or 'cr' not in r.json(): print('Failed to obtain container registry') return False cr = r.json()['cr'] print('Registry URL:', cr['url']) print('Username:', cr['username']) if args.show_password: print('Password:', cr['password']) return True @needs_login def docker_list_args(args, server, cookies): r = requests.get(server + '/rest/user', cookies=cookies) if r.status_code != 200 or 'cr' not in r.json(): print('Failed to obtain container registry') return False cr = r.json()['cr'] auth = HTTPBasicAuth(cr['username'], cr['password']) r = requests.get('https://%s/v2/_catalog' % cr['url'], auth=auth) repos = r.json()['repositories'] everything = {} for repo in repos: r = requests.get('https://%s/v2/%s/tags/list' % (cr['url'], repo), auth=auth) if r.status_code != 200: continue try: info = r.json() everything[repo] = info.get('tags') except: pass for k, v in everything.items(): print(k + ':') for tag in v: print(' ' + tag) return True @needs_login def show_args(args, server, cookies): endpoint = server + '/rest/job/status' if args.all: endpoint += '/all' elif args.id: endpoint += '/%d' % args.id r = requests.get(endpoint, cookies=cookies) if r.status_code == 404: print('No job found') return False elif r.status_code == 200: jobs = r.json() if not args.all: jobs = [jobs] for job in jobs: if args.verbose: print('ID:', job['id']) print('Status:', job['status']) if 'score' in job: print('Score:', job['score']) print('Workers:') for worker in job['workers']: print('- Task:', worker['task']) print(' Status:', worker['state']) if 'eta' in worker: print(' ETA (seconds):', worker['eta']) if 'progress' in worker: print(' Progress (percent):', worker['progress'] * 100) if 'score' in worker: print(' Score:', worker['score']) if 'error' in worker: print(' Error:', worker['error']) else: print('%i: %s' % (job['id'], job['status'])) else: print('Error %i occurred' % r.status_code) return False return True @needs_login def kill_args(args, server, cookies): if not args.yes: yn = input('Are you sure? [y/N] ') if yn.lower() not in ('y', 'yes'): print('Not canceled') return True r = requests.post(server + '/rest/job/kill', cookies=cookies) if r.status_code == 404: print('No job found') return False elif r.status_code // 100 == 2: print('Canceled') else: print('Error %i occurred' % r.status_code) return False return True @needs_login def restart_args(args, server, cookies): if not args.yes: yn = input('Are you sure? [y/N] ') if yn.lower() not in ('y', 'yes'): print('Not restarted') return True if args.id: suffix = '/%d' % args.id else: suffix = '' r = requests.post(server + '/rest/job/restart' + suffix, cookies=cookies) if r.status_code == 404: print('No job found') return False elif r.status_code // 100 == 2: print('Restarted') else: print('Error %i occurred' % r.status_code) return False return True @needs_login def submit_args(args, server, cookies): r = requests.get(server + '/rest/user', cookies=cookies) if r.status_code != 200 or 'cr' not in r.json(): print('Failed to obtain container registry') cr = r.json()['cr'] client = docker.APIClient() cr['registry'] = cr['url'] del cr['url'] tag = args.tag or 'agent:latest' try: client.tag(tag, cr['registry'] + '/' + tag) except requests.exceptions.HTTPError: print('Could not find local tag') return False print('Pushing container...') size = {} for line in client.push(cr['registry'] + '/' + tag, stream=True, auth_config=cr): for line in line.split(b'\r\n'): if not line: continue line = json.loads(line) if 'status' not in line: continue if line['status'] == 'Pushing': size[line['id']] = int(line['progressDetail']['current']), int(line['progressDetail']['total']) elif line['status'] == 'Pushed': size[line['id']] = size[line['id']][1], size[line['id']][1] current, total = 0, 0 for c, t in size.values(): current += c total += t if total > 0: print('\u001B[2K\r%i%%' % (100 * current / total), end='', flush=True) print('\u001B[2K\rPushed, submitting job') r = requests.post(server + '/rest/job/start', json={'tag': tag}, cookies=cookies) if r.status_code // 100 == 2: print('Done') else: print('Error %i occurred' % r.status_code) return False return True def init_parsers(subparsers): load_config() parser_login = subparsers.add_parser('login', description='Log into server') parser_login.set_defaults(func=login_args) parser_login.add_argument('--email', type=str, help='Your email address') parser_login.add_argument('--password', type=str, help='Your password (you should use --password-stdin instead)') parser_login.add_argument('--password-stdin', action='store_true', help='Read password from stdin') parser_login.add_argument('--server', type=str, help='Server to log into') parser_logout = subparsers.add_parser('logout', description='Log out of server') parser_logout.set_defaults(func=logout_args) parser_leaderboard = subparsers.add_parser('leaderboard', description='Get leaderboard') parser_leaderboard.set_defaults(func=leaderboard_args) parser_docker = subparsers.add_parser('docker', description='Docker support commands') parser_docker.set_defaults(func=lambda args: parser_docker.print_help()) subparsers_docker = parser_docker.add_subparsers() parser_docker_login = subparsers_docker.add_parser('login', description='Log into user Docker registry') parser_docker_login.set_defaults(func=docker_login_args) parser_docker_show = subparsers_docker.add_parser('show', description='Show information about user Docker registry') parser_docker_show.set_defaults(func=docker_show_args) parser_docker_show.add_argument('-p', '--show-password', action='store_true', help='Show login password') parser_docker_list = subparsers_docker.add_parser('list', description='List contents of Docker registry') parser_docker_list.set_defaults(func=docker_list_args) parser_job = subparsers.add_parser('job', description='Operations on jobs') parser_job.set_defaults(func=lambda args: parser_job.print_help()) subparsers_job = parser_job.add_subparsers() parser_job_show = subparsers_job.add_parser('show', description='Show current job, if it exists') parser_job_show.set_defaults(func=show_args) parser_job_show.add_argument('id', nargs='?', type=int, help='List a specific job ID') parser_job_show.add_argument('-v', '--verbose', action='store_true', help='Be more verbose') parser_job_show.add_argument('-a', '--all', action='store_true', help='Show all jobs') parser_job_kill = subparsers_job.add_parser('cancel', description='Cancel current job') parser_job_kill.set_defaults(func=kill_args) parser_job_kill.add_argument('-y', '--yes', action='store_true', help='Do not display confirmation') parser_job_restart = subparsers_job.add_parser('restart', description='Restart job') parser_job_restart.set_defaults(func=restart_args) parser_job_restart.add_argument('-y', '--yes', action='store_true', help='Do not display confirmation') parser_job_restart.add_argument('id', nargs='?', type=int, help='Job ID to restart (default: latest)') parser_job_submit = subparsers_job.add_parser('submit', description='Submit new job') parser_job_submit.set_defaults(func=submit_args) parser_job_submit.add_argument('-t', '--tag', type=str, help='Local tag to push') def main(argv=sys.argv[1:]): parser = argparse.ArgumentParser(description='Run OpenAI Retro Contest support code') parser.set_defaults(func=lambda args: parser.print_help()) init_parsers(parser.add_subparsers()) args = parser.parse_args(argv) if not args.func(args): sys.exit(1) if __name__ == '__main__': main()