Permalink
Fetching contributors…
Cannot retrieve contributors at this time
1085 lines (1002 sloc) 41.5 KB
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2016, 2017 Canonical Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import logging
import os
import re
import urllib.parse
import uuid
from pyramid import response
import pymacaroons
from snapcraft.tests.fake_servers import base
logger = logging.getLogger(__name__)
class FakeStoreAPIServer(base.BaseFakeServer):
_DEV_API_PATH = '/dev/api/'
def __init__(self, fake_store, server_address):
super().__init__(server_address)
self.fake_store = fake_store
self.account_keys = []
self.registered_names = {}
self.pushed_snaps = set()
def configure(self, configurator):
# POST
configurator.add_route(
'acl', urllib.parse.urljoin(self._DEV_API_PATH, 'acl/'),
request_method='POST')
configurator.add_view(self.acl, route_name='acl')
configurator.add_route(
'verify_acl', urllib.parse.urljoin(
self._DEV_API_PATH, 'acl/verify/'),
request_method='POST')
configurator.add_view(self.verify_acl, route_name='verify_acl')
configurator.add_route(
'account_key',
urllib.parse.urljoin(self._DEV_API_PATH, 'account/account-key'),
request_method='POST')
configurator.add_view(self.account_key, route_name='account_key')
configurator.add_route(
'snap_builds',
urllib.parse.urljoin(self._DEV_API_PATH, 'snaps/{dummy}/builds'),
request_method='POST')
configurator.add_view(self.snap_builds, route_name='snap_builds')
configurator.add_route(
'snap_close',
urllib.parse.urljoin(self._DEV_API_PATH, 'snaps/{dummy}/close'),
request_method='POST')
configurator.add_view(self.snap_close, route_name='snap_close')
configurator.add_route(
'snap_push', urllib.parse.urljoin(
self._DEV_API_PATH, 'snap-push/'),
request_method='POST')
configurator.add_view(self.snap_push, route_name='snap_push')
configurator.add_route(
'snap_release',
urllib.parse.urljoin(self._DEV_API_PATH, 'snap-release/'),
request_method='POST')
configurator.add_view(self.snap_release, route_name='snap_release')
configurator.add_route(
'register_name',
urllib.parse.urljoin(self._DEV_API_PATH, 'register-name/'),
request_method='POST')
configurator.add_view(self.register_name, route_name='register_name')
configurator.add_route(
'agreement', urllib.parse.urljoin(
self._DEV_API_PATH, 'agreement/'),
request_method='POST')
configurator.add_view(self.agreement, route_name='agreement')
configurator.add_route(
'snap_metadata_post',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/metadata'),
request_method='POST')
configurator.add_view(
self.snap_metadata, route_name='snap_metadata_post')
configurator.add_route(
'snap_binary_metadata_post',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/binary-metadata'),
request_method='POST')
configurator.add_view(
self.snap_binary_metadata, route_name='snap_binary_metadata_post')
# GET
configurator.add_route(
'details',
urllib.parse.urljoin(
self._DEV_API_PATH, '/details/upload-id/{snap}'),
request_method='GET')
configurator.add_view(self.details, route_name='details')
configurator.add_route(
'account', urllib.parse.urljoin(self._DEV_API_PATH, 'account'),
request_method='GET')
configurator.add_view(self.account, route_name='account')
configurator.add_route(
'snap_history',
urllib.parse.urljoin(self._DEV_API_PATH, 'snaps/{dummy}/history'),
request_method='GET')
configurator.add_view(self.snap_history, route_name='snap_history')
configurator.add_route(
'snap_state',
urllib.parse.urljoin(self._DEV_API_PATH, 'snaps/{dummy}/state'),
request_method='GET')
configurator.add_view(self.snap_state, route_name='snap_state')
configurator.add_route(
'snap_validations',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/validations'),
request_method='GET')
configurator.add_view(
self.snap_validations, route_name='snap_validations')
configurator.add_route(
'snap_developers',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/developers'),
request_method='GET')
configurator.add_view(
self.snap_developers, route_name='snap_developers')
configurator.add_route(
'snap_binary_metadata_get',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/binary-metadata'),
request_method='GET')
configurator.add_view(
self.snap_binary_metadata, route_name='snap_binary_metadata_get')
# PUT
configurator.add_route(
'put_snap_validations',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/validations'),
request_method='PUT')
configurator.add_view(
self.put_snap_validations, route_name='put_snap_validations')
configurator.add_route(
'put_snap_developers',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/developers'),
request_method='PUT')
configurator.add_view(
self.put_snap_developers, route_name='put_snap_developers')
configurator.add_route(
'snap_metadata_put',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/metadata'),
request_method='PUT')
configurator.add_view(
self.snap_metadata, route_name='snap_metadata_put')
configurator.add_route(
'snap_binary_metadata_put',
urllib.parse.urljoin(
self._DEV_API_PATH, 'snaps/{snap_id}/binary-metadata'),
request_method='PUT')
configurator.add_view(
self.snap_binary_metadata, route_name='snap_binary_metadata_put')
def _refresh_error(self):
error = {
'code': 'macaroon-permission-required',
'message': 'Authorization Required',
}
payload = json.dumps({'error_list': [error]}).encode()
response_code = 401
content_type = 'application/json'
headers = [
('Content-Type', content_type),
('WWW-Authenticate', 'Macaroon needs_refresh=1')
]
return response.Response(payload, response_code, headers)
# POST
def acl(self, request):
permission = request.path.split('/')[-1]
logger.debug(
'Handling ACL request for {}'.format(permission))
sso_host = urllib.parse.urlparse(os.environ.get(
'UBUNTU_SSO_API_ROOT_URL', "http://localhost")).netloc
macaroon = pymacaroons.Macaroon(
caveats=[
pymacaroons.Caveat(
caveat_id='test caveat',
location=sso_host,
verification_key_id='test verifiacion')
])
payload = json.dumps({'macaroon': macaroon.serialize()}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def verify_acl(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
print(request.json_body)
return self._verify_acl_wide_open()
def _verify_acl_wide_open(self):
acl = {
'snap_ids': None,
'channels': None,
'permissions': [
'package_upload', 'package_access', 'package_manage']
}
payload = json.dumps(acl).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def account_key(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
data = request.json_body
logger.debug(
'Handling account-key request with content {}'.format(data))
account_key_request = data['account_key_request']
if account_key_request == 'test-not-implemented':
return self._account_key_not_implemented()
elif account_key_request == 'test-invalid-data':
return self._account_key_invalid_field()
else:
return self._account_key_successful(account_key_request)
def _account_key_not_implemented(self):
payload = b'Not Implemented'
response_code = 501
content_type = 'text/plain'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _account_key_invalid_field(self):
error = {
'error_list': [{
'code': 'invalid-field',
'message': 'The account-key-request assertion is not valid.',
}]
}
payload = json.dumps(error).encode()
response_code = 400
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _account_key_successful(self, account_key_request):
# Extremely basic assertion parsing, just enough to make tests work.
# Don't copy this.
key_name = re.search(
'^name: (.*)$', account_key_request, flags=re.MULTILINE).group(1)
key_id = re.search(
'^public-key-sha3-384: (.*)$', account_key_request,
flags=re.MULTILINE).group(1)
self.account_keys.append(
{'name': key_name, 'public-key-sha3-384': key_id})
account_key = {
'account_key': {
'account-id': 'abcd',
'name': key_name,
'public-key-sha3-384': key_id,
},
}
payload = json.dumps(account_key).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_builds(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug('Handling sign-build request')
snap_build = request.json_body['assertion']
if snap_build == 'test-not-implemented':
payload = json.dumps({
'error_list': [
{'code': 'feature-disabled',
'message': ('The snap-build assertions are currently '
'disabled.')},
],
}).encode()
response_code = 501
content_type = 'application/json'
elif snap_build == 'test-invalid-data':
payload = json.dumps({
'error_list': [
{'code': 'invalid-field',
'message': 'The snap-build assertion is not valid.'},
],
}).encode()
response_code = 400
content_type = 'application/json'
elif snap_build == 'test-unexpected-data':
payload = b'unexpected chunk of data'
response_code = 500
content_type = 'text/plain'
else:
payload = json.dumps({
'type': 'snap-build',
'foo': 'bar',
}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_close(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug('Handling close request')
channels = request.json_body['channels']
if channels == ['invalid']:
payload = json.dumps({
'error_list': [
{'code': 'invalid-field',
'message': ('The \'channels\' field content is not '
'valid.')},
],
}).encode()
response_code = 400
content_type = 'application/json'
elif channels == ['unexpected']:
payload = b'unexpected chunk of data'
response_code = 500
content_type = 'text/plain'
elif channels == ['broken-plain']:
payload = b'plain data'
response_code = 200
content_type = 'text/plain'
elif channels == ['broken-json']:
payload = json.dumps({
'closed_channels': channels,
}).encode()
response_code = 200
content_type = 'application/json'
else:
payload = json.dumps({
'closed_channels': channels,
'channel_map_tree': {
'latest': {
'16': {
'amd64': [
{'channel': 'stable', 'info': 'none'},
{'channel': 'candidate', 'info': 'none'},
{'channel': 'beta', 'info': 'specific',
'version': '1.1', 'revision': 42},
{'channel': 'edge', 'info': 'tracking'}
]
}
}
},
}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_push(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug(
'Handling upload request with content {}'.format(
request.json_body))
name = request.json_body['name']
if name == 'test-snap-unregistered':
payload = b''
response_code = 404
content_type = 'text/plain'
else:
response_code = 202
content_type = 'application/json'
if name == 'test-review-snap':
details_path = 'details/upload-id/review-snap'
elif name == 'test-duplicate-snap':
details_path = 'details/upload-id/duplicate-snap'
else:
details_path = 'details/upload-id/good-snap'
if not request.json_body.get('dry_run', False):
snap_id = self.registered_names[name]['snap_id']
self.pushed_snaps.add(snap_id)
payload = json.dumps({
'status_details_url': urllib.parse.urljoin(
'http://localhost:{}/'.format(self.server_port),
details_path
),
}).encode()
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_release(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug(
'Handling release request with content {}'.format(
request.json_body))
response_code = 200
content_type = 'application/json'
name = request.json_body['name']
channels = request.json_body['channels']
revision = request.json_body['revision']
if name == 'test-snap-unregistered':
response_code = 404
content_type = 'text/plain'
payload = b''
elif 'alpha' in channels:
response_code = 400
payload = json.dumps({
'errors': 'Not a valid channel: alpha',
}).encode()
elif (
name == 'test-snap' or
name.startswith('snapcrafttest')):
payload = json.dumps({
'opened_channels': channels,
'channel_map': [
{'channel': 'stable', 'info': 'none'},
{'channel': 'candidate', 'info': 'none'},
{'revision': int(revision),
'channel': 'beta',
'version': '0', 'info': 'specific'},
{'channel': 'edge', 'info': 'tracking'}
]
}).encode()
elif name.startswith('arm-'):
payload = json.dumps({
'opened_channels': channels,
'channel_map_tree': {
'0.1': {
'16': {
'armhf':
[
{'channel': 'stable', 'info': 'none'},
{'channel': 'candidate', 'info': 'none'},
{'revision': int(revision),
'channel': 'beta',
'version': '0', 'info': 'specific'},
{'channel': 'edge', 'info': 'tracking'}
]
}
}
}
}).encode()
elif name.startswith('multiarch-'):
payload = json.dumps({
'opened_channels': channels,
'channel_map_tree': {
'0.1': {
'16': {
'amd64':
[
{'channel': 'stable', 'info': 'none'},
{'channel': 'candidate', 'info': 'none'},
{'revision': int(revision),
'channel': 'beta',
'version': '0', 'info': 'specific'},
{'channel': 'edge', 'info': 'tracking'}
],
'armhf':
[
{'channel': 'stable', 'info': 'none'},
{'channel': 'candidate', 'info': 'none'},
{'revision': int(revision),
'channel': 'beta',
'version': '0', 'info': 'specific'},
{'channel': 'edge', 'info': 'tracking'}
]
}
}
}
}).encode()
elif 'notanumber' in revision:
response_code = 400
payload = json.dumps({
'success': False,
'error_list': [{
'code': 'invalid-field',
'message': "The 'revision' field must be an integer"}],
'errors': {'revision': ['This field must be an integer.']}}
).encode()
else:
raise NotImplementedError(
'Cannot handle release request for {!r}'.format(name))
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def register_name(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug(
'Handling registration request with content {}'.format(
request.json_body))
snap_name = request.json_body['snap_name']
if snap_name == 'test-snap-name-already-registered':
return self._register_name_409_error('already_registered')
elif snap_name == 'test-reserved-snap-name':
return self._register_name_409_error('reserved_name')
elif snap_name == 'test-already-owned-snap-name':
return self._register_name_409_error('already_owned')
elif snap_name.startswith('test-too-fast'):
return self._register_name_429_error('register_window')
elif (
snap_name.startswith('test_invalid') or
len(snap_name) > 40):
return self._register_name_invalid(snap_name)
elif snap_name == 'snap-name-no-clear-error':
return self._register_name_unclear_error()
else:
return self._register_name_successful(
snap_name, request.json_body['is_private'])
def _register_name_409_error(self, error_code):
payload = json.dumps({
'status': 409,
'code': error_code,
'register_name_url': 'https://myapps.com/register-name/',
}).encode()
response_code = 409
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _register_name_429_error(self, error_code):
error = {
'status': 429,
'code': error_code,
}
if error_code == 'register_window':
error['retry_after'] = 177
payload = json.dumps(error).encode()
response_code = 429
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _register_name_invalid(self, snap_name):
# Emulates the current Store behaviour and never combines errors.
# It's either using invalid chars or too long, never both.
if len(snap_name) > 40:
msg = ('The name {} should not be longer than 40 characters.'
.format(snap_name))
else:
msg = (
'The name {!r} is not valid. It can only contain dashes, '
'numbers and lowercase ascii letters.'.format(snap_name))
payload = json.dumps({
'error_list': [
{'code': 'invalid', 'message': msg},
]
}).encode()
response_code = 400
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _register_name_unclear_error(self):
payload = json.dumps({
'status': 409,
'code': 'unexistent_error_code',
}).encode()
response_code = 409
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def _register_name_successful(self, name, is_private):
snap_id = uuid.uuid4().hex
self.registered_names[name] = dict(private=is_private, snap_id=snap_id)
payload = json.dumps({'snap_id': snap_id}).encode()
response_code = 201
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def agreement(self, request):
if 'STORE_DOWN' in os.environ:
response_code = 500
content_type = 'text/plain'
payload = b'Broken'
else:
if request.json_body['latest_tos_accepted'] is not True:
response_code = 400
content_type = 'application/json'
payload = json.dumps({
"error_list": [{
"message": "`latest_tos_accepted` must be `true`",
"code": "bad-request",
"extra": {"latest_tos_accepted": "true"}}]
}).encode()
else:
response_code = 200
content_type = 'application/json'
payload = json.dumps({"content": {
"latest_tos_accepted": True,
"tos_url": 'http://fake-url.com',
"latest_tos_date": '2000-01-01',
"accepted_tos_date": '2010-10-10'
}
}).encode()
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_metadata(self, request):
logger.debug('Handling metadata request')
snap_id = request.matchdict['snap_id']
# check if snap was previously pushed
if snap_id not in self.pushed_snaps:
err = {'error_list': [
{'message': 'Snap not found', 'code': 'not-found'}]}
payload = json.dumps(err).encode('utf8')
response_code = 404
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
if 'invalid' in request.json_body:
err = {'error_list': [{
'message': 'Invalid field: invalid',
'code': 'invalid-request',
}]}
payload = json.dumps(err).encode('utf8')
response_code = 400
content_type = 'application/json'
elif any('conflict' in field_name for field_name in request.json_body):
# conflicts!
if request.method == 'PUT':
# update anyway
payload = b''
response_code = 200
content_type = 'text/plain'
else:
# POST, return error
error_list = []
for name, value in request.json_body.items():
error_list.append({
'message': value + '-changed',
'code': 'conflict',
'extra': {'name': name},
})
payload = json.dumps({'error_list': error_list}).encode('utf8')
response_code = 409
content_type = 'application/json'
else:
# all fine by default
payload = b''
response_code = 200
content_type = 'text/plain'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_binary_metadata(self, request):
logger.debug('Handling binary metadata request')
if request.method == 'GET':
current = [
{'type': 'icon', 'hash': '1234567890', 'filename': 'icon.png'},
{'type': 'screenshot', 'hash': '0987654321',
'filename': 'ss1.png'},
{'type': 'screenshot', 'hash': '1122334455',
'filename': 'ss2.png'},
]
return response.Response(
json.dumps(current).encode('utf-8'), 200,
[('Content-Type', 'application/json')])
else:
# POST/PUT
info = json.loads(request.params['info'])
invalid = any([e.get('filename', '').endswith('invalid')
for e in info])
conflict = any([e.get('filename', '').endswith('conflict')
for e in info])
if invalid:
err = {'error_list': [{
'message': 'Invalid field: icon',
'code': 'invalid-request',
}]}
payload = json.dumps(err).encode('utf8')
response_code = 400
elif conflict and request.method == 'POST':
# POST, return error
error_list = [{
'message': 'original-icon',
'code': 'conflict',
'extra': {'name': 'icon'},
}]
payload = json.dumps({'error_list': error_list}).encode('utf8')
response_code = 409
else:
updated_info = []
for entry in info:
entry.pop('key', None)
updated_info.append(entry)
payload = json.dumps(updated_info).encode('utf-8')
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
# GET
def details(self, request):
snap = request.matchdict['snap']
if snap == 'duplicate-snap':
logger.debug('Handling duplicate snap request')
payload = json.dumps({
'code': 'processing_error',
'url': '/dev/click-apps/5349/rev/1',
'can_release': False,
'revision': '1',
'processed': True,
'errors': [
{'message': 'Duplicate snap already uploaded'},
]
}).encode()
else:
logger.debug('Handling scan complete request')
if snap == 'good-snap':
can_release = True
code = 'ready_to_release'
elif snap == 'review-snap':
can_release = False
code = 'need_manual_review'
payload = json.dumps({
'code': code,
'url': '/dev/click-apps/5349/rev/1',
'can_release': can_release,
'revision': '1',
'processed': True
}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def account(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug('Handling account request')
snaps = {
'basic': {'snap-id': 'snap-id', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'test-snap-with-no-validations': {
'snap-id': 'test-snap-id-with-no-validations',
'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'test-snap-with-dev': {'snap-id': 'test-snap-id-with-dev',
'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'core': {'snap-id': 'good', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'core-no-dev': {'snap-id': 'no-dev', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'badrequest': {'snap-id': 'badrequest', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'revoked': {'snap-id': 'revoked', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
'no-revoked': {'snap-id': 'no-revoked', 'status': 'Approved',
'private': False, 'price': None,
'since': '2016-12-12T01:01:01Z'},
}
snaps.update({
name: {'snap-id': snap_data['snap_id'], 'status': 'Approved',
'private': snap_data['private'], 'price': None,
'since': '2016-12-12T01:01:01Z'}
for name, snap_data in self.registered_names.items()})
payload = json.dumps({
'account_id': 'abcd',
'account_keys': self.account_keys,
'snaps': {'16': snaps},
}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_history(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug('Handling account request')
revisions = [{
'series': ['16'],
'channels': [],
'version': '2.0.1',
'timestamp': '2016-09-27T19:23:40Z',
'current_channels': ['beta', 'edge'],
'arch': 'i386',
'revision': 2
}, {
'series': ['16'],
'channels': ['stable', 'edge'],
'version': '2.0.2',
'timestamp': '2016-09-27T18:38:43Z',
'current_channels': ['stable', 'candidate', 'beta'],
'arch': 'amd64',
'revision': 1,
}]
parsed_qs = urllib.parse.parse_qs(
urllib.parse.urlparse(request.url).query)
if 'arch' in parsed_qs:
output = [
rev for rev in revisions if rev['arch'] in parsed_qs['arch']]
else:
output = revisions
response_code = 200
content_type = 'application/json'
return response.Response(
json.dumps(output).encode(), response_code,
[('Content-Type', content_type)])
def snap_state(self, request):
if self.fake_store.needs_refresh:
return self._refresh_error()
logger.debug('Handling snap state request')
channel_map = {
'channel_map_tree': {
'latest': {
'16': {
'i386': [
{
'info': 'none',
'channel': 'stable'
},
{
'info': 'none',
'channel': 'beta'
},
{
'info': 'specific',
'version': '1.0-i386',
'channel': 'edge',
'revision': 3
},
],
'amd64': [
{
'info': 'specific',
'version': '1.0-amd64',
'channel': 'stable',
'revision': 2
},
{
'info': 'specific',
'version': '1.1-amd64',
'channel': 'beta',
'revision': 4
},
{
'info': 'tracking',
'channel': 'edge'
},
],
}
}
}
}
parsed_qs = urllib.parse.parse_qs(
urllib.parse.urlparse(request.url).query)
if 'architecture' in parsed_qs:
arch = parsed_qs['architecture'][0]
series = channel_map['channel_map_tree']['latest']['16']
if arch in series:
output = {
'channel_map_tree': {
'latest': {
'16': {
arch: series[arch]
}
}
}
}
else:
output = {}
else:
output = channel_map
response_code = 200
content_type = 'application/json'
return response.Response(
json.dumps(output).encode(), response_code,
[('Content-Type', content_type)])
def snap_validations(self, request):
logger.debug('Handling validation request')
snap_id = request.matchdict['snap_id']
if snap_id == 'good':
validation = [{
"approved-snap-id": "snap-id-1",
"approved-snap-revision": "3",
"approved-snap-name": "snap-1",
"authority-id": "dev-1",
"series": "16",
"sign-key-sha3-384": "1234567890",
"snap-id": "snap-id-gating",
"timestamp": "2016-09-19T21:07:27.756001Z",
"type": "validation",
"revoked": "false",
"required": True,
}, {
"approved-snap-id": "snap-id-2",
"approved-snap-revision": "5",
"approved-snap-name": "snap-2",
"authority-id": "dev-1",
"series": "16",
"sign-key-sha3-384": "1234567890",
"snap-id": "snap-id-gating",
"timestamp": "2016-09-19T21:07:27.756001Z",
"type": "validation",
"revoked": "false",
"required": False,
}, {
"approved-snap-id": "snap-id-3",
"approved-snap-revision": "-",
"approved-snap-name": "snap-3",
"authority-id": "dev-1",
"series": "16",
"sign-key-sha3-384": "1234567890",
"snap-id": "snap-id-gating",
"timestamp": "2016-09-19T21:07:27.756001Z",
"type": "validation",
"revoked": "false",
"required": True,
}]
payload = json.dumps(validation).encode()
response_code = 200
elif snap_id == 'bad':
payload = 'foo'.encode()
response_code = 200
elif snap_id == 'test-snap-id-with-no-validations':
payload = json.dumps([]).encode()
response_code = 200
elif snap_id == 'err':
payload = json.dumps({
'error_list': [{'message': 'error'}]
}).encode()
response_code = 503
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def snap_developers(self, request):
logger.debug('Handling snap developers request')
snap_id = request.matchdict['snap_id']
if snap_id == 'good':
payload = json.dumps({'snap_developer': {}}).encode()
response_code = 200
elif snap_id in ('test-snap-id-with-dev', 'revoked', 'no-revoked'):
payload = json.dumps({
'snap_developer': {
'type': 'snap-developer',
'authority-id': 'dummy',
'publisher-id': 'dummy',
'snap-id': snap_id,
'developers': [{
'developer-id': 'test-dev-id',
'since': '2017-02-10T08:35:00.390258Z',
'until': '2018-02-10T08:35:00.390258Z'
}]
}
}).encode()
response_code = 200
elif snap_id == 'no-dev':
payload = json.dumps({'error_list': [
{'message': 'error',
'code': 'snap-developer-not-found'}]
}).encode()
response_code = 403
elif snap_id == 'badrequest':
payload = json.dumps({'snap_developer': {}}).encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
# PUT
def put_snap_validations(self, request):
snap_id = request.matchdict['snap_id']
if snap_id == 'good':
payload = request.body
response_code = 200
elif snap_id == 'err':
payload = json.dumps({
'error_list': [{'message': 'error'}]
}).encode()
response_code = 501
elif snap_id == 'bad':
payload = 'foo'.encode()
response_code = 200
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])
def put_snap_developers(self, request):
snap_id = request.matchdict['snap_id']
if snap_id in ('good', 'test-snap-id-with-dev'):
payload = request.body
response_code = 200
elif snap_id == 'no-dev':
payload = request.body
response_code = 200
elif snap_id == 'badrequest':
payload = json.dumps({'error_list': [
{'message': 'The given `snap-id` does not match the '
'assertion.',
'code': 'invalid-request'}]}).encode()
response_code = 400
elif snap_id == 'revoked':
payload = json.dumps({'error_list': [
{'message': "The assertion's `developers` would revoke "
"existing uploads.",
'code': 'revoked-uploads',
'extra': ['this']}]}).encode()
response_code = 409
elif snap_id == 'no-revoked':
payload = json.dumps({'error_list': [
{'message': "The collaborators for this snap haven't been "
"altered. Exiting... ",
'code': 'revoked-uploads',
'extra': ['this']}]}).encode()
response_code = 409
content_type = 'application/json'
return response.Response(
payload, response_code, [('Content-Type', content_type)])