diff --git a/endpoints/api/__init__.py b/endpoints/api/__init__.py index eedb94e93..afe620fca 100644 --- a/endpoints/api/__init__.py +++ b/endpoints/api/__init__.py @@ -177,10 +177,12 @@ import endpoints.api.billing import endpoints.api.build import endpoints.api.discovery import endpoints.api.image +import endpoints.api.logs import endpoints.api.permission import endpoints.api.prototype import endpoints.api.repository import endpoints.api.repotoken +import endpoints.api.robot import endpoints.api.search import endpoints.api.tag import endpoints.api.team diff --git a/endpoints/api/legacy.py b/endpoints/api/legacy.py index 674084b7e..957affcca 100644 --- a/endpoints/api/legacy.py +++ b/endpoints/api/legacy.py @@ -2394,6 +2394,7 @@ def robot_view(name, token): } +# Ported @api_bp.route('/user/robots', methods=['GET']) @api_login_required def get_user_robots(): @@ -2404,6 +2405,7 @@ def get_user_robots(): }) +# Ported @api_bp.route('/organization//robots', methods=['GET']) @api_login_required @org_api_call('get_user_robots') @@ -2418,6 +2420,7 @@ def get_org_robots(orgname): abort(403) +# Ported @api_bp.route('/user/robots/', methods=['PUT']) @api_login_required def create_user_robot(robot_shortname): @@ -2429,6 +2432,7 @@ def create_user_robot(robot_shortname): return resp +# Ported @api_bp.route('/organization//robots/', methods=['PUT']) @api_login_required @@ -2446,6 +2450,7 @@ def create_org_robot(orgname, robot_shortname): abort(403) +# Ported @api_bp.route('/user/robots/', methods=['DELETE']) @api_login_required def delete_user_robot(robot_shortname): @@ -2455,6 +2460,7 @@ def delete_user_robot(robot_shortname): return make_response('Deleted', 204) +# Ported @api_bp.route('/organization//robots/', methods=['DELETE']) @api_login_required @@ -2487,7 +2493,7 @@ def log_view(log): return view - +# Ported @api_bp.route('/repository//logs', methods=['GET']) @api_login_required @parse_repository_name @@ -2505,6 +2511,7 @@ def list_repo_logs(namespace, repository): abort(403) +# Ported @api_bp.route('/organization//logs', methods=['GET']) @api_login_required @org_api_call('list_user_logs') @@ -2521,6 +2528,7 @@ def list_org_logs(orgname): abort(403) +# Ported @api_bp.route('/user/logs', methods=['GET']) @api_login_required def list_user_logs(): diff --git a/endpoints/api/logs.py b/endpoints/api/logs.py new file mode 100644 index 000000000..912eab531 --- /dev/null +++ b/endpoints/api/logs.py @@ -0,0 +1,121 @@ +import json + +from datetime import datetime, timedelta +from flask.ext.restful import abort + +from endpoints.api import (resource, nickname, ApiResource, query_param, parse_args, + RepositoryParamResource, require_repo_admin) +from auth.permissions import AdministerOrganizationPermission, AdministerOrganizationPermission +from auth.auth_context import get_authenticated_user +from data import model + + +def log_view(log): + view = { + 'kind': log.kind.name, + 'metadata': json.loads(log.metadata_json), + 'ip': log.ip, + 'datetime': log.datetime, + } + + if log.performer: + view['performer'] = { + 'kind': 'user', + 'name': log.performer.username, + 'is_robot': log.performer.robot, + } + + return view + + +def get_logs(namespace, start_time, end_time, performer_name=None, + repository=None): + performer = None + if performer_name: + performer = model.get_user(performer_name) + + if start_time: + try: + start_time = datetime.strptime(start_time + ' UTC', '%m/%d/%Y %Z') + except ValueError: + start_time = None + + if not start_time: + start_time = datetime.today() - timedelta(7) # One week + + if end_time: + try: + end_time = datetime.strptime(end_time + ' UTC', '%m/%d/%Y %Z') + end_time = end_time + timedelta(days=1) + except ValueError: + end_time = None + + if not end_time: + end_time = datetime.today() + + logs = model.list_logs(namespace, start_time, end_time, performer=performer, + repository=repository) + return { + 'start_time': start_time, + 'end_time': end_time, + 'logs': [log_view(log) for log in logs] + } + + +@resource('/v1/repository//logs') +class RepositoryLogs(RepositoryParamResource): + """ Resource for fetching logs for the specific repository. """ + @require_repo_admin + @nickname('listRepoLogs') + @parse_args + @query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str) + @query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str) + def get(self, args, namespace, repository): + """ List the logs for the specified repository. """ + repo = model.get_repository(namespace, repository) + if not repo: + abort(404) + + start_time = args['starttime'] + end_time = args['endtime'] + return get_logs(namespace, start_time, end_time, repository=repo) + + +@resource('/v1/organization//logs') +class OrgLogs(ApiResource): + """ Resource for fetching logs for the entire organization. """ + @nickname('listOrgLogs') + @parse_args + @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) + @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) + @query_param('performer', 'Username for which to filter logs.', type=str) + # @org_api_call('list_user_logs') + def get(self, args, orgname): + """ List the logs for the specified organization. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + performer_name = args['performer'] + start_time = args['starttime'] + end_time = args['endtime'] + + return get_logs(orgname, start_time, end_time, performer_name=performer_name) + + abort(403) + + +@resource('/v1/user/logs') +class UserLogs(ApiResource): + """ Resource for fetching logs for the current user. """ + @nickname('listUserLogs') + @parse_args + @query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str) + @query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str) + @query_param('performer', 'Username for which to filter logs.', type=str) + def get(self, args): + """ List the logs for the current user. """ + performer_name = args['performer'] + start_time = args['starttime'] + end_time = args['endtime'] + + return get_logs(get_authenticated_user().username, start_time, end_time, + performer_name=performer_name) \ No newline at end of file diff --git a/endpoints/api/robot.py b/endpoints/api/robot.py new file mode 100644 index 000000000..74e469245 --- /dev/null +++ b/endpoints/api/robot.py @@ -0,0 +1,97 @@ +from flask.ext.restful import abort + +from endpoints.api import resource, nickname, ApiResource, log_action +from auth.permissions import AdministerOrganizationPermission, OrganizationMemberPermission +from auth.auth_context import get_authenticated_user +from data import model +from util.names import format_robot_username + + +def robot_view(name, token): + return { + 'name': name, + 'token': token, + } + + +@resource('/user/robots') +class UserRobotList(ApiResource): + """ Resource for listing user robots. """ + @nickname('getUserRobots') + def get(self): + """ List the available robots for the user. """ + user = get_authenticated_user() + robots = model.list_entity_robots(user.username) + return { + 'robots': [robot_view(name, password) for name, password in robots] + } + + +@resource('/user/robots/') +class UserRobot(ApiResource): + """ Resource for managing a user's robots. """ + @nickname('createUserRobot') + def put(self, robot_shortname): + """ Create a new user robot with the specified name. """ + parent = get_authenticated_user() + robot, password = model.create_robot(robot_shortname, parent) + resp = robot_view(robot.username, password) + log_action('create_robot', parent.username, {'robot': robot_shortname}) + resp.status_code = 201 + return resp + + @nickname('deleteUserRobot') + def delete(self, robot_shortname): + """ Delete an existing robot. """ + parent = get_authenticated_user() + model.delete_robot(format_robot_username(parent.username, robot_shortname)) + log_action('delete_robot', parent.username, {'robot': robot_shortname}) + return 'Deleted', 204 + + +@resource('/organization//robots') +class OrgRobotList(ApiResource): + """ Resource for listing an organization's robots. """ + @nickname('getOrgRobots') + #@org_api_call('get_user_robots') + def get(self, orgname): + """ List the organization's robots. """ + permission = OrganizationMemberPermission(orgname) + if permission.can(): + robots = model.list_entity_robots(orgname) + return { + 'robots': [robot_view(name, password) for name, password in robots] + } + + abort(403) + + +@resource('/organization//robots/') +class OrgRobot(ApiResource): + """ Resource for managing an organization's robots. """ + @nickname('createOrgRobot') + #@org_api_call('create_user_robot') + def put(self, orgname, robot_shortname): + """ Create a new robot in the organization. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + parent = model.get_organization(orgname) + robot, password = model.create_robot(robot_shortname, parent) + resp = robot_view(robot.username, password) + log_action('create_robot', orgname, {'robot': robot_shortname}) + resp.status_code = 201 + return resp + + abort(403) + + @nickname('deleteOrgRobot') + #@org_api_call('delete_user_robot') + def delete(self, orgname, robot_shortname): + """ Delete an existing organization robot. """ + permission = AdministerOrganizationPermission(orgname) + if permission.can(): + model.delete_robot(format_robot_username(orgname, robot_shortname)) + log_action('delete_robot', orgname, {'robot': robot_shortname}) + return 'Deleted', 204 + + abort(403)