2013-09-23 16:37:40 +00:00
|
|
|
import logging
|
2013-10-02 04:48:03 +00:00
|
|
|
import stripe
|
2013-10-18 18:31:14 +00:00
|
|
|
import re
|
2013-10-25 19:13:11 +00:00
|
|
|
import requests
|
2013-10-25 22:17:43 +00:00
|
|
|
import urlparse
|
|
|
|
import json
|
2013-09-23 16:37:40 +00:00
|
|
|
|
2013-10-25 05:14:38 +00:00
|
|
|
from flask import request, make_response, jsonify, abort, url_for
|
2013-10-14 21:50:07 +00:00
|
|
|
from flask.ext.login import login_required, current_user, logout_user
|
|
|
|
from flask.ext.principal import identity_changed, AnonymousIdentity
|
2013-09-23 16:37:40 +00:00
|
|
|
from functools import wraps
|
2013-10-10 04:40:25 +00:00
|
|
|
from collections import defaultdict
|
2013-09-23 16:37:40 +00:00
|
|
|
|
2013-10-18 18:31:14 +00:00
|
|
|
import storage
|
|
|
|
|
2013-09-25 16:45:12 +00:00
|
|
|
from data import model
|
2013-10-25 05:14:38 +00:00
|
|
|
from data.userfiles import UserRequestFiles
|
|
|
|
from data.queue import dockerfile_build_queue
|
2013-09-23 16:37:40 +00:00
|
|
|
from app import app
|
2013-10-14 21:50:07 +00:00
|
|
|
from util.email import send_confirmation_email, send_recovery_email
|
2013-09-25 20:46:28 +00:00
|
|
|
from util.names import parse_repository_name
|
2013-09-27 22:15:31 +00:00
|
|
|
from util.gravatar import compute_hash
|
2013-09-26 21:59:20 +00:00
|
|
|
from auth.permissions import (ReadRepositoryPermission,
|
2013-09-27 17:24:07 +00:00
|
|
|
ModifyRepositoryPermission,
|
|
|
|
AdministerRepositoryPermission)
|
2013-10-01 18:46:44 +00:00
|
|
|
from endpoints import registry
|
2013-10-14 21:50:07 +00:00
|
|
|
from endpoints.web import common_login
|
2013-10-20 05:18:31 +00:00
|
|
|
from util.cache import cache_control
|
2013-09-23 16:37:40 +00:00
|
|
|
|
2013-10-18 18:31:14 +00:00
|
|
|
|
|
|
|
store = storage.load()
|
2013-09-23 16:37:40 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2013-09-30 21:51:07 +00:00
|
|
|
def api_login_required(f):
|
|
|
|
@wraps(f)
|
|
|
|
def decorated_view(*args, **kwargs):
|
|
|
|
if not current_user.is_authenticated():
|
|
|
|
abort(401)
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
return decorated_view
|
|
|
|
|
|
|
|
|
2013-09-27 22:16:26 +00:00
|
|
|
@app.errorhandler(model.DataModelException)
|
|
|
|
def handle_dme(ex):
|
|
|
|
return make_response(ex.message, 400)
|
|
|
|
|
|
|
|
|
2013-09-23 16:37:40 +00:00
|
|
|
@app.route('/api/')
|
|
|
|
def welcome():
|
|
|
|
return make_response('welcome', 200)
|
|
|
|
|
|
|
|
|
2013-10-01 23:37:33 +00:00
|
|
|
@app.route('/api/user/', methods=['GET'])
|
2013-09-26 23:59:58 +00:00
|
|
|
def get_logged_in_user():
|
2013-10-31 22:17:26 +00:00
|
|
|
def org_view(o):
|
|
|
|
return {
|
|
|
|
'name': o.username,
|
|
|
|
'gravatar': compute_hash(o.email),
|
|
|
|
}
|
|
|
|
|
2013-09-30 21:32:03 +00:00
|
|
|
if current_user.is_anonymous():
|
|
|
|
return jsonify({'anonymous': True})
|
|
|
|
|
2013-10-15 18:48:49 +00:00
|
|
|
user = current_user.db_user()
|
2013-10-31 22:17:26 +00:00
|
|
|
organizations = model.get_user_organizations(user.username)
|
|
|
|
|
2013-09-26 23:59:58 +00:00
|
|
|
return jsonify({
|
|
|
|
'verified': user.verified,
|
|
|
|
'anonymous': False,
|
|
|
|
'username': user.username,
|
|
|
|
'email': user.email,
|
2013-09-27 22:15:31 +00:00
|
|
|
'gravatar': compute_hash(user.email),
|
2013-10-10 17:44:34 +00:00
|
|
|
'askForPassword': user.password_hash is None,
|
2013-10-31 22:17:26 +00:00
|
|
|
'organizations': [org_view(o) for o in organizations]
|
2013-09-26 23:59:58 +00:00
|
|
|
})
|
|
|
|
|
2013-10-31 22:17:26 +00:00
|
|
|
|
2013-10-10 17:44:34 +00:00
|
|
|
@app.route('/api/user/', methods=['PUT'])
|
|
|
|
@api_login_required
|
|
|
|
def change_user_details():
|
2013-10-15 18:48:49 +00:00
|
|
|
user = current_user.db_user()
|
2013-10-10 17:44:34 +00:00
|
|
|
|
|
|
|
user_data = request.get_json();
|
|
|
|
|
|
|
|
try:
|
|
|
|
if user_data['password']:
|
|
|
|
logger.debug('Changing password for user: %s', user.username)
|
|
|
|
model.change_password(user, user_data['password'])
|
|
|
|
except model.InvalidPasswordException, ex:
|
|
|
|
error_resp = jsonify({
|
|
|
|
'message': ex.message,
|
|
|
|
})
|
|
|
|
error_resp.status_code = 400
|
|
|
|
return error_resp
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'verified': user.verified,
|
|
|
|
'anonymous': False,
|
|
|
|
'username': user.username,
|
|
|
|
'email': user.email,
|
|
|
|
'gravatar': compute_hash(user.email),
|
|
|
|
'askForPassword': user.password_hash is None,
|
|
|
|
})
|
2013-09-26 23:59:58 +00:00
|
|
|
|
2013-10-01 23:37:33 +00:00
|
|
|
@app.route('/api/user/', methods=['POST'])
|
|
|
|
def create_user_api():
|
|
|
|
user_data = request.get_json()
|
2013-10-02 02:13:43 +00:00
|
|
|
existing_user = model.get_user(user_data['username'])
|
|
|
|
if existing_user:
|
|
|
|
error_resp = jsonify({
|
2013-10-10 17:44:34 +00:00
|
|
|
'message': 'The username already exists'
|
2013-10-02 02:13:43 +00:00
|
|
|
})
|
|
|
|
error_resp.status_code = 400
|
|
|
|
return error_resp
|
|
|
|
|
2013-10-01 23:37:33 +00:00
|
|
|
try:
|
|
|
|
new_user = model.create_user(user_data['username'], user_data['password'],
|
|
|
|
user_data['email'])
|
|
|
|
code = model.create_confirm_email_code(new_user)
|
|
|
|
send_confirmation_email(new_user.username, new_user.email, code.code)
|
|
|
|
return make_response('Created', 201)
|
|
|
|
except model.DataModelException as ex:
|
|
|
|
error_resp = jsonify({
|
2013-10-10 16:55:03 +00:00
|
|
|
'message': ex.message,
|
2013-10-01 23:37:33 +00:00
|
|
|
})
|
|
|
|
error_resp.status_code = 400
|
|
|
|
return error_resp
|
|
|
|
|
|
|
|
|
2013-10-14 21:50:07 +00:00
|
|
|
@app.route('/api/signin', methods=['POST'])
|
|
|
|
def signin_api():
|
|
|
|
signin_data = request.get_json()
|
|
|
|
|
|
|
|
username = signin_data['username']
|
|
|
|
password = signin_data['password']
|
|
|
|
|
|
|
|
#TODO Allow email login
|
|
|
|
needs_email_verification = False
|
|
|
|
invalid_credentials = False
|
|
|
|
|
|
|
|
verified = model.verify_user(username, password)
|
|
|
|
if verified:
|
|
|
|
if common_login(verified):
|
|
|
|
return make_response('Success', 200)
|
|
|
|
else:
|
|
|
|
needs_email_verification = True
|
|
|
|
|
|
|
|
else:
|
|
|
|
invalid_credentials = True
|
|
|
|
|
|
|
|
response = jsonify({
|
|
|
|
'needsEmailVerification': needs_email_verification,
|
|
|
|
'invalidCredentials': invalid_credentials,
|
|
|
|
})
|
|
|
|
response.status_code = 403
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/signout", methods=['POST'])
|
|
|
|
@api_login_required
|
|
|
|
def logout():
|
|
|
|
logout_user()
|
|
|
|
|
|
|
|
identity_changed.send(app, identity=AnonymousIdentity())
|
|
|
|
|
|
|
|
return make_response('Success', 200)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/api/recovery", methods=['POST'])
|
|
|
|
def send_recovery():
|
|
|
|
email = request.get_json()['email']
|
|
|
|
code = model.create_reset_password_email_code(email)
|
|
|
|
send_recovery_email(email, code.code)
|
|
|
|
return make_response('Created', 201)
|
|
|
|
|
|
|
|
|
2013-09-27 23:21:54 +00:00
|
|
|
@app.route('/api/users/<prefix>', methods=['GET'])
|
2013-09-30 21:54:06 +00:00
|
|
|
@api_login_required
|
2013-09-27 23:21:54 +00:00
|
|
|
def get_matching_users(prefix):
|
|
|
|
users = model.get_matching_users(prefix)
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'users': [user.username for user in users]
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2013-10-25 05:14:38 +00:00
|
|
|
user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'],
|
|
|
|
app.config['AWS_SECRET_KEY'],
|
|
|
|
app.config['REGISTRY_S3_BUCKET'])
|
|
|
|
|
2013-10-31 22:17:26 +00:00
|
|
|
@app.route('/api/organization/<orgname>', methods=['GET'])
|
|
|
|
def get_organization(orgname):
|
|
|
|
def team_view(t):
|
|
|
|
return {
|
|
|
|
'id': t.id,
|
|
|
|
'name': t.name
|
|
|
|
}
|
|
|
|
|
|
|
|
def org_view(o, teams):
|
|
|
|
return {
|
|
|
|
'name': o.username,
|
|
|
|
'gravatar': compute_hash(o.email),
|
|
|
|
'teams': [team_view(t) for t in teams]
|
|
|
|
}
|
|
|
|
|
|
|
|
if current_user.is_anonymous():
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
user = current_user.db_user()
|
2013-11-01 23:34:17 +00:00
|
|
|
org = model.get_organization(orgname)
|
|
|
|
if not org:
|
2013-10-31 22:17:26 +00:00
|
|
|
abort(404)
|
|
|
|
|
2013-11-01 23:34:17 +00:00
|
|
|
teams = model.get_user_teams_within_org(user.username, org)
|
2013-10-31 22:17:26 +00:00
|
|
|
return jsonify(org_view(organization, teams))
|
|
|
|
|
2013-10-25 05:14:38 +00:00
|
|
|
|
2013-10-26 20:03:11 +00:00
|
|
|
@app.route('/api/repository', methods=['POST'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-23 16:37:40 +00:00
|
|
|
def create_repo_api():
|
2013-10-25 19:13:11 +00:00
|
|
|
owner = current_user.db_user()
|
|
|
|
|
|
|
|
namespace_name = owner.username
|
2013-10-26 20:03:11 +00:00
|
|
|
repository_name = request.get_json()['repository']
|
|
|
|
visibility = request.get_json()['visibility']
|
2013-10-25 05:14:38 +00:00
|
|
|
|
|
|
|
repo = model.create_repository(namespace_name, repository_name, owner,
|
|
|
|
visibility)
|
2013-10-30 00:21:18 +00:00
|
|
|
repo.description = request.get_json()['description']
|
|
|
|
repo.save()
|
2013-10-25 05:14:38 +00:00
|
|
|
|
2013-10-26 20:03:11 +00:00
|
|
|
return jsonify({
|
|
|
|
'namespace': namespace_name,
|
|
|
|
'name': repository_name
|
|
|
|
})
|
2013-09-23 16:37:40 +00:00
|
|
|
|
|
|
|
|
2013-10-01 21:02:49 +00:00
|
|
|
@app.route('/api/find/repository', methods=['GET'])
|
|
|
|
def match_repos_api():
|
|
|
|
prefix = request.args.get('query', '')
|
2013-10-08 15:29:42 +00:00
|
|
|
|
2013-09-27 23:21:54 +00:00
|
|
|
def repo_view(repo):
|
|
|
|
return {
|
|
|
|
'namespace': repo.namespace,
|
|
|
|
'name': repo.name,
|
2013-09-28 21:11:10 +00:00
|
|
|
'description': repo.description
|
2013-09-27 23:21:54 +00:00
|
|
|
}
|
|
|
|
|
2013-10-08 15:29:42 +00:00
|
|
|
username = None
|
|
|
|
if current_user.is_authenticated():
|
2013-10-15 18:48:49 +00:00
|
|
|
username = current_user.db_user().username
|
2013-10-08 15:29:42 +00:00
|
|
|
|
2013-09-28 03:25:57 +00:00
|
|
|
matching = model.get_matching_repositories(prefix, username)
|
2013-09-27 23:21:54 +00:00
|
|
|
response = {
|
2013-09-28 03:25:57 +00:00
|
|
|
'repositories': [repo_view(repo) for repo in matching]
|
2013-09-27 23:21:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return jsonify(response)
|
|
|
|
|
2013-09-26 21:59:20 +00:00
|
|
|
|
2013-09-23 16:37:40 +00:00
|
|
|
@app.route('/api/repository/', methods=['GET'])
|
|
|
|
def list_repos_api():
|
2013-09-28 04:05:32 +00:00
|
|
|
def repo_view(repo_obj):
|
2013-10-22 05:26:14 +00:00
|
|
|
is_public = model.repository_is_public(repo_obj.namespace, repo_obj.name)
|
|
|
|
|
2013-09-23 16:37:40 +00:00
|
|
|
return {
|
2013-09-28 04:05:32 +00:00
|
|
|
'namespace': repo_obj.namespace,
|
|
|
|
'name': repo_obj.name,
|
|
|
|
'description': repo_obj.description,
|
2013-10-22 05:26:14 +00:00
|
|
|
'is_public': is_public
|
2013-09-23 16:37:40 +00:00
|
|
|
}
|
2013-10-08 15:29:42 +00:00
|
|
|
|
2013-10-02 04:28:24 +00:00
|
|
|
limit = request.args.get('limit', None)
|
2013-10-31 22:17:26 +00:00
|
|
|
namespace_filter = request.args.get('namespace', None)
|
2013-10-02 04:28:24 +00:00
|
|
|
include_public = request.args.get('public', 'true')
|
|
|
|
include_private = request.args.get('private', 'true')
|
|
|
|
sort = request.args.get('sort', 'false')
|
2013-09-23 16:37:40 +00:00
|
|
|
|
2013-10-02 04:28:24 +00:00
|
|
|
try:
|
|
|
|
limit = int(limit) if limit else None
|
|
|
|
except:
|
|
|
|
limit = None
|
|
|
|
|
|
|
|
include_public = include_public == 'true'
|
|
|
|
include_private = include_private == 'true'
|
|
|
|
sort = sort == 'true'
|
|
|
|
|
2013-10-08 15:29:42 +00:00
|
|
|
username = None
|
|
|
|
if current_user.is_authenticated() and include_private:
|
2013-10-15 18:48:49 +00:00
|
|
|
username = current_user.db_user().username
|
2013-10-08 15:29:42 +00:00
|
|
|
|
|
|
|
repo_query = model.get_visible_repositories(username, limit=limit,
|
|
|
|
include_public=include_public,
|
2013-10-31 22:17:26 +00:00
|
|
|
sort=sort, namespace=namespace_filter)
|
2013-10-08 15:29:42 +00:00
|
|
|
repos = [repo_view(repo) for repo in repo_query]
|
2013-09-23 16:37:40 +00:00
|
|
|
response = {
|
|
|
|
'repositories': repos
|
|
|
|
}
|
|
|
|
|
|
|
|
return jsonify(response)
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>', methods=['PUT'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-23 16:37:40 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def update_repo_api(namespace, repository):
|
2013-09-26 21:59:20 +00:00
|
|
|
permission = ModifyRepositoryPermission(namespace, repository)
|
2013-09-28 00:03:07 +00:00
|
|
|
if permission.can():
|
2013-09-26 21:59:20 +00:00
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
values = request.get_json()
|
|
|
|
repo.description = values['description']
|
|
|
|
repo.save()
|
|
|
|
return jsonify({
|
|
|
|
'success': True
|
|
|
|
})
|
2013-09-28 00:03:07 +00:00
|
|
|
|
2013-09-26 21:59:20 +00:00
|
|
|
abort(404)
|
2013-09-23 16:37:40 +00:00
|
|
|
|
|
|
|
|
2013-10-08 15:29:42 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/changevisibility',
|
|
|
|
methods=['POST'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-28 21:11:10 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def change_repo_visibility_api(namespace, repository):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
values = request.get_json()
|
|
|
|
model.set_repository_visibility(repo, values['visibility'])
|
|
|
|
return jsonify({
|
|
|
|
'success': True
|
|
|
|
})
|
|
|
|
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
2013-10-01 16:13:25 +00:00
|
|
|
@app.route('/api/repository/<path:repository>', methods=['DELETE'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def delete_repository(namespace, repository):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
model.purge_repository(namespace, repository)
|
2013-10-01 18:46:44 +00:00
|
|
|
registry.delete_repository_storage(namespace, repository)
|
2013-10-01 16:13:25 +00:00
|
|
|
return make_response('Deleted', 204)
|
|
|
|
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
2013-09-27 21:01:45 +00:00
|
|
|
def image_view(image):
|
|
|
|
return {
|
2013-10-01 18:14:39 +00:00
|
|
|
'id': image.docker_image_id,
|
2013-09-27 21:01:45 +00:00
|
|
|
'created': image.created,
|
|
|
|
'comment': image.comment,
|
2013-10-10 04:40:18 +00:00
|
|
|
'ancestors': image.ancestors,
|
|
|
|
'dbid': image.id,
|
2013-09-27 21:01:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-09-23 16:37:40 +00:00
|
|
|
@app.route('/api/repository/<path:repository>', methods=['GET'])
|
|
|
|
@parse_repository_name
|
|
|
|
def get_repo_api(namespace, repository):
|
2013-09-27 18:56:14 +00:00
|
|
|
logger.debug('Get repo: %s/%s' % (namespace, repository))
|
2013-09-28 00:03:07 +00:00
|
|
|
|
2013-09-26 21:59:20 +00:00
|
|
|
def tag_view(tag):
|
|
|
|
image = model.get_tag_image(namespace, repository, tag.name)
|
|
|
|
if not image:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'name': tag.name,
|
2013-09-27 17:24:07 +00:00
|
|
|
'image': image_view(image),
|
2013-09-26 21:59:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
permission = ReadRepositoryPermission(namespace, repository)
|
2013-09-28 21:11:10 +00:00
|
|
|
is_public = model.repository_is_public(namespace, repository)
|
|
|
|
if permission.can() or is_public:
|
2013-09-26 21:59:20 +00:00
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
tags = model.list_repository_tags(namespace, repository)
|
2013-09-27 17:24:07 +00:00
|
|
|
tag_dict = {tag.name: tag_view(tag) for tag in tags}
|
|
|
|
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
2013-09-27 18:56:14 +00:00
|
|
|
can_admin = AdministerRepositoryPermission(namespace, repository).can()
|
2013-10-27 20:00:44 +00:00
|
|
|
active_builds = model.list_repository_builds(namespace, repository,
|
|
|
|
include_inactive=False)
|
2013-10-25 19:13:11 +00:00
|
|
|
|
2013-09-27 17:24:07 +00:00
|
|
|
return jsonify({
|
|
|
|
'namespace': namespace,
|
|
|
|
'name': repository,
|
|
|
|
'description': repo.description,
|
|
|
|
'tags': tag_dict,
|
|
|
|
'can_write': can_write,
|
2013-09-27 18:56:14 +00:00
|
|
|
'can_admin': can_admin,
|
2013-10-25 19:13:11 +00:00
|
|
|
'is_public': is_public,
|
2013-10-27 20:00:44 +00:00
|
|
|
'is_building': len(active_builds) > 0,
|
2013-09-27 17:24:07 +00:00
|
|
|
})
|
2013-09-26 21:59:20 +00:00
|
|
|
|
2013-09-27 17:24:07 +00:00
|
|
|
abort(404) # Not fount
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
2013-10-25 19:13:11 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/build/', methods=['GET'])
|
2013-10-26 21:41:29 +00:00
|
|
|
@api_login_required
|
2013-10-25 19:13:11 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def get_repo_builds(namespace, repository):
|
2013-10-25 22:17:43 +00:00
|
|
|
permission = ModifyRepositoryPermission(namespace, repository)
|
2013-10-25 19:13:11 +00:00
|
|
|
if permission.can():
|
|
|
|
def build_view(build_obj):
|
|
|
|
if build_obj.status_url:
|
|
|
|
# Delegate the status to the build node
|
|
|
|
node_status = requests.get(build_obj.status_url).json()
|
|
|
|
node_status['id'] = build_obj.id
|
|
|
|
return node_status
|
|
|
|
|
|
|
|
# If there was no status url, do the best we can
|
|
|
|
return {
|
|
|
|
'id': build_obj.id,
|
|
|
|
'total_commands': None,
|
|
|
|
'total_images': None,
|
|
|
|
'current_command': None,
|
|
|
|
'current_image': None,
|
|
|
|
'image_completion_percent': None,
|
|
|
|
'status': build_obj.phase,
|
|
|
|
'message': None,
|
|
|
|
}
|
|
|
|
|
|
|
|
builds = model.list_repository_builds(namespace, repository)
|
|
|
|
return jsonify({
|
|
|
|
'builds': [build_view(build) for build in builds]
|
|
|
|
})
|
|
|
|
|
2013-10-25 19:47:34 +00:00
|
|
|
abort(403) # Permissions denied
|
|
|
|
|
2013-10-25 19:13:11 +00:00
|
|
|
|
2013-10-26 21:20:59 +00:00
|
|
|
|
|
|
|
@app.route('/api/filedrop/', methods=['POST'])
|
|
|
|
def get_filedrop_url():
|
2013-10-26 22:37:53 +00:00
|
|
|
mime_type = request.get_json()['mimeType']
|
|
|
|
(url, file_id) = user_files.prepare_for_drop(mime_type)
|
2013-10-26 21:20:59 +00:00
|
|
|
return jsonify({
|
|
|
|
'url': url,
|
|
|
|
'file_id': file_id
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2013-10-25 22:17:43 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
|
2013-10-26 21:41:29 +00:00
|
|
|
@api_login_required
|
2013-10-25 22:17:43 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def request_repo_build(namespace, repository):
|
|
|
|
permission = ModifyRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
logger.debug('User requested repository initialization.')
|
2013-10-26 21:20:59 +00:00
|
|
|
dockerfile_id = request.get_json()['file_id']
|
2013-10-25 22:17:43 +00:00
|
|
|
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
token = model.create_access_token(repo, 'write')
|
|
|
|
|
|
|
|
host = urlparse.urlparse(request.url).netloc
|
|
|
|
tag = '%s/%s/%s' % (host, repo.namespace, repo.name)
|
|
|
|
build_request = model.create_repository_build(repo, token, dockerfile_id,
|
|
|
|
tag)
|
2013-10-27 23:06:20 +00:00
|
|
|
dockerfile_build_queue.put(json.dumps({'build_id': build_request.id}))
|
2013-10-25 22:17:43 +00:00
|
|
|
|
2013-10-26 21:20:59 +00:00
|
|
|
return jsonify({
|
|
|
|
'started': True
|
|
|
|
})
|
2013-10-25 22:17:43 +00:00
|
|
|
|
|
|
|
abort(403) # Permissions denied
|
|
|
|
|
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
def role_view(repo_perm_obj):
|
|
|
|
return {
|
|
|
|
'role': repo_perm_obj.role.name
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-10 04:40:25 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/image/', methods=['GET'])
|
|
|
|
@parse_repository_name
|
|
|
|
def list_repository_images(namespace, repository):
|
|
|
|
permission = ReadRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can() or model.repository_is_public(namespace, repository):
|
|
|
|
all_images = model.get_repository_images(namespace, repository)
|
|
|
|
all_tags = model.list_repository_tags(namespace, repository)
|
|
|
|
|
|
|
|
tags_by_image_id = defaultdict(list)
|
|
|
|
for tag in all_tags:
|
|
|
|
tags_by_image_id[tag.image.docker_image_id].append(tag.name)
|
|
|
|
|
|
|
|
|
|
|
|
def add_tags(image_json):
|
|
|
|
image_json['tags'] = tags_by_image_id[image_json['id']]
|
|
|
|
return image_json
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'images': [add_tags(image_view(image)) for image in all_images]
|
|
|
|
})
|
|
|
|
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
|
2013-10-19 02:28:46 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/image/<image_id>',
|
|
|
|
methods=['GET'])
|
|
|
|
@parse_repository_name
|
|
|
|
def get_image(namespace, repository, image_id):
|
|
|
|
permission = ReadRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can() or model.repository_is_public(namespace, repository):
|
|
|
|
image = model.get_repo_image(namespace, repository, image_id)
|
|
|
|
if not image:
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
return jsonify(image_view(image))
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
|
2013-10-17 22:25:19 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/image/<image_id>/changes',
|
|
|
|
methods=['GET'])
|
2013-10-20 05:18:31 +00:00
|
|
|
@cache_control(max_age=60*60) # Cache for one hour
|
2013-10-17 22:25:19 +00:00
|
|
|
@parse_repository_name
|
2013-10-19 02:28:46 +00:00
|
|
|
def get_image_changes(namespace, repository, image_id):
|
2013-10-17 22:25:19 +00:00
|
|
|
permission = ReadRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can() or model.repository_is_public(namespace, repository):
|
2013-10-18 18:31:14 +00:00
|
|
|
diffs_path = store.image_file_diffs_path(namespace, repository, image_id)
|
|
|
|
|
|
|
|
try:
|
|
|
|
response_json = store.get_content(diffs_path)
|
|
|
|
return make_response(response_json)
|
|
|
|
except IOError:
|
|
|
|
abort(404)
|
2013-10-17 22:25:19 +00:00
|
|
|
|
|
|
|
abort(403)
|
|
|
|
|
|
|
|
|
2013-09-28 00:03:07 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/tag/<tag>/images',
|
|
|
|
methods=['GET'])
|
2013-09-27 21:01:45 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def list_tag_images(namespace, repository, tag):
|
|
|
|
permission = ReadRepositoryPermission(namespace, repository)
|
2013-09-28 04:05:32 +00:00
|
|
|
if permission.can() or model.repository_is_public(namespace, repository):
|
2013-09-30 19:30:00 +00:00
|
|
|
tag_image = model.get_tag_image(namespace, repository, tag)
|
|
|
|
parent_images = model.get_parent_images(tag_image)
|
|
|
|
|
|
|
|
parents = list(parent_images)
|
|
|
|
parents.reverse()
|
|
|
|
all_images = [tag_image] + parents
|
2013-09-28 00:03:07 +00:00
|
|
|
|
2013-09-27 21:01:45 +00:00
|
|
|
return jsonify({
|
2013-09-30 19:30:00 +00:00
|
|
|
'images': [image_view(image) for image in all_images]
|
2013-09-27 21:01:45 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
2013-09-27 17:24:07 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/permissions/', methods=['GET'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-27 17:24:07 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def list_repo_permissions(namespace, repository):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
repo_perms = model.get_all_repo_users(namespace, repository)
|
|
|
|
|
|
|
|
return jsonify({
|
2013-09-28 00:03:07 +00:00
|
|
|
'permissions': {repo_perm.user.username: role_view(repo_perm)
|
2013-09-27 17:24:07 +00:00
|
|
|
for repo_perm in repo_perms}
|
|
|
|
})
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
|
|
|
methods=['GET'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-27 18:56:14 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def get_permissions(namespace, repository, username):
|
|
|
|
logger.debug('Get repo: %s/%s permissions for user %s' %
|
|
|
|
(namespace, repository, username))
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
2013-09-27 19:53:39 +00:00
|
|
|
perm = model.get_user_reponame_permission(username, namespace, repository)
|
2013-09-27 18:56:14 +00:00
|
|
|
return jsonify(role_view(perm))
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
2013-09-27 17:24:07 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
2013-09-27 18:56:14 +00:00
|
|
|
methods=['PUT', 'POST'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-27 17:24:07 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def change_permissions(namespace, repository, username):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
new_permission = request.get_json()
|
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
logger.debug('Setting permission to: %s for user %s' %
|
|
|
|
(new_permission['role'], username))
|
2013-09-27 19:53:39 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
perm = model.set_user_repo_permission(username, namespace, repository,
|
|
|
|
new_permission['role'])
|
|
|
|
except model.DataModelException:
|
|
|
|
logger.warning('User tried to remove themselves as admin.')
|
|
|
|
abort(409)
|
2013-09-27 18:56:14 +00:00
|
|
|
|
|
|
|
resp = jsonify(role_view(perm))
|
|
|
|
if request.method == 'POST':
|
|
|
|
resp.status_code = 201
|
|
|
|
return resp
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
2013-09-28 00:03:07 +00:00
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
@app.route('/api/repository/<path:repository>/permissions/<username>',
|
|
|
|
methods=['DELETE'])
|
2013-09-30 21:51:07 +00:00
|
|
|
@api_login_required
|
2013-09-27 18:56:14 +00:00
|
|
|
@parse_repository_name
|
|
|
|
def delete_permissions(namespace, repository, username):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
2013-09-27 19:53:39 +00:00
|
|
|
try:
|
|
|
|
model.delete_user_permission(username, namespace, repository)
|
|
|
|
except model.DataModelException:
|
|
|
|
logger.warning('User tried to remove themselves as admin.')
|
|
|
|
abort(409)
|
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
return make_response('Deleted', 204)
|
2013-09-27 17:24:07 +00:00
|
|
|
|
2013-09-27 18:56:14 +00:00
|
|
|
abort(403) # Permission denied
|
2013-10-02 04:48:03 +00:00
|
|
|
|
|
|
|
|
2013-10-16 18:24:10 +00:00
|
|
|
def token_view(token_obj):
|
|
|
|
return {
|
|
|
|
'friendlyName': token_obj.friendly_name,
|
|
|
|
'code': token_obj.code,
|
|
|
|
'role': token_obj.role.name,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/tokens/', methods=['GET'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def list_repo_tokens(namespace, repository):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
tokens = model.get_repository_delegate_tokens(namespace, repository)
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'tokens': {token.code: token_view(token) for token in tokens}
|
|
|
|
})
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['GET'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def get_tokens(namespace, repository, code):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
perm = model.get_repo_delegate_token(namespace, repository, code)
|
|
|
|
return jsonify(token_view(perm))
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/tokens/', methods=['POST'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def create_token(namespace, repository):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
token_params = request.get_json()
|
|
|
|
|
|
|
|
token = model.create_delegate_token(namespace, repository,
|
|
|
|
token_params['friendlyName'])
|
|
|
|
|
|
|
|
resp = jsonify(token_view(token))
|
|
|
|
resp.status_code = 201
|
|
|
|
return resp
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['PUT'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def change_token(namespace, repository, code):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
new_permission = request.get_json()
|
|
|
|
|
|
|
|
logger.debug('Setting permission to: %s for code %s' %
|
|
|
|
(new_permission['role'], code))
|
|
|
|
|
|
|
|
token = model.set_repo_delegate_token_role(namespace, repository, code,
|
|
|
|
new_permission['role'])
|
|
|
|
|
|
|
|
resp = jsonify(token_view(token))
|
|
|
|
return resp
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/repository/<path:repository>/tokens/<code>',
|
|
|
|
methods=['DELETE'])
|
|
|
|
@api_login_required
|
|
|
|
@parse_repository_name
|
|
|
|
def delete_token(namespace, repository, code):
|
|
|
|
permission = AdministerRepositoryPermission(namespace, repository)
|
|
|
|
if permission.can():
|
|
|
|
model.delete_delegate_token(namespace, repository, code)
|
|
|
|
return make_response('Deleted', 204)
|
|
|
|
|
|
|
|
abort(403) # Permission denied
|
|
|
|
|
|
|
|
|
2013-10-08 15:29:42 +00:00
|
|
|
def subscription_view(stripe_subscription, used_repos):
|
2013-10-02 04:48:03 +00:00
|
|
|
return {
|
2013-10-02 05:40:11 +00:00
|
|
|
'currentPeriodStart': stripe_subscription.current_period_start,
|
|
|
|
'currentPeriodEnd': stripe_subscription.current_period_end,
|
2013-10-02 04:48:03 +00:00
|
|
|
'plan': stripe_subscription.plan.id,
|
2013-10-02 05:40:11 +00:00
|
|
|
'usedPrivateRepos': used_repos,
|
2013-10-02 04:48:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/api/user/plan', methods=['PUT'])
|
|
|
|
@api_login_required
|
|
|
|
def subscribe():
|
|
|
|
# Amount in cents
|
|
|
|
amount = 500
|
|
|
|
|
|
|
|
request_data = request.get_json()
|
|
|
|
plan = request_data['plan']
|
|
|
|
|
2013-10-15 18:48:49 +00:00
|
|
|
user = current_user.db_user()
|
2013-10-02 05:40:11 +00:00
|
|
|
private_repos = model.get_private_repo_count(user.username)
|
2013-10-02 04:48:03 +00:00
|
|
|
|
|
|
|
if not user.stripe_id:
|
|
|
|
# Create the customer and plan simultaneously
|
2013-10-02 06:05:53 +00:00
|
|
|
card = request_data['token']
|
2013-10-02 04:48:03 +00:00
|
|
|
cus = stripe.Customer.create(email=user.email, plan=plan, card=card)
|
|
|
|
user.stripe_id = cus.id
|
|
|
|
user.save()
|
2013-10-08 15:29:42 +00:00
|
|
|
|
2013-10-02 05:40:11 +00:00
|
|
|
resp = jsonify(subscription_view(cus.subscription, private_repos))
|
2013-10-02 04:48:03 +00:00
|
|
|
resp.status_code = 201
|
|
|
|
return resp
|
|
|
|
|
|
|
|
else:
|
|
|
|
# Change the plan
|
|
|
|
cus = stripe.Customer.retrieve(user.stripe_id)
|
2013-10-02 06:05:53 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
if plan == 'free':
|
|
|
|
cus.cancel_subscription()
|
|
|
|
cus.save()
|
2013-10-02 06:05:53 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
response_json = {
|
|
|
|
'plan': 'free',
|
|
|
|
'usedPrivateRepos': private_repos,
|
|
|
|
}
|
2013-10-02 04:48:03 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
else:
|
|
|
|
cus.plan = plan
|
2013-10-02 04:48:03 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
# User may have been a previous customer who is resubscribing
|
|
|
|
if 'token' in request_data:
|
|
|
|
cus.card = request_data['token']
|
2013-10-02 16:43:25 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
cus.save()
|
2013-10-02 04:48:03 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
response_json = subscription_view(cus.subscription, private_repos)
|
2013-10-02 06:05:53 +00:00
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
return jsonify(response_json)
|
2013-10-02 06:05:53 +00:00
|
|
|
|
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
@app.route('/api/user/plan', methods=['GET'])
|
2013-10-02 06:05:53 +00:00
|
|
|
@api_login_required
|
2013-10-04 18:35:51 +00:00
|
|
|
def get_subscription():
|
2013-10-15 18:48:49 +00:00
|
|
|
user = current_user.db_user()
|
2013-10-04 18:35:51 +00:00
|
|
|
private_repos = model.get_private_repo_count(user.username)
|
2013-10-02 06:05:53 +00:00
|
|
|
|
|
|
|
if user.stripe_id:
|
|
|
|
cus = stripe.Customer.retrieve(user.stripe_id)
|
|
|
|
|
2013-10-04 18:35:51 +00:00
|
|
|
if cus.subscription:
|
|
|
|
return jsonify(subscription_view(cus.subscription, private_repos))
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
'plan': 'free',
|
|
|
|
'usedPrivateRepos': private_repos,
|
2013-10-08 15:29:42 +00:00
|
|
|
})
|