Skip to content

Commit 870717b

Browse files
author
Matthew Fisher
committed
Merge pull request #1165 from deis/554-client-version
feat(client): check client version against the server
2 parents 3a566e3 + 5a78f58 commit 870717b

5 files changed

Lines changed: 88 additions & 3 deletions

File tree

client/deis.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,18 +362,25 @@ def __init__(self):
362362
self._session = Session()
363363
self._settings = Settings()
364364

365-
def _dispatch(self, method, path, body=None,
366-
headers={'content-type': 'application/json'}, **kwargs):
365+
def _dispatch(self, method, path, body=None, **kwargs):
367366
"""
368367
Dispatch an API request to the active Deis controller
369368
"""
369+
headers = {
370+
'content-type': 'application/json',
371+
'X-Deis-Version': __version__.rsplit('.', 1)[0],
372+
}
370373
func = getattr(self._session, method.lower())
371374
controller = self._settings['controller']
372375
if not controller:
373376
raise EnvironmentError(
374377
'No active controller. Use `deis login` or `deis register` to get started.')
375378
url = urlparse.urljoin(controller, path, **kwargs)
376379
response = func(url, data=body, headers=headers)
380+
# check for errors
381+
if response.json().get('error') is not None:
382+
print(response.json()['error'])
383+
sys.exit(1)
377384
return response
378385

379386
def apps(self, args):
@@ -1605,7 +1612,7 @@ def main():
16051612
call the appropriate method on the client.
16061613
"""
16071614
cli = DeisClient()
1608-
args = docopt(__doc__, version='Deis CLI {}'.format(__version__),
1615+
args = docopt(__doc__, version=__version__,
16091616
options_first=True)
16101617
cmd = args['<command>']
16111618
cmd, help_flag = parse_args(cmd)

controller/api/middleware.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import json
2+
3+
from django.http import HttpResponse
4+
from rest_framework import status
5+
6+
from deis import __version__
7+
8+
9+
class VersionMiddleware:
10+
11+
def process_request(self, request):
12+
try:
13+
# server and client version must match "x.y"
14+
client_version = request.META['HTTP_X_DEIS_VERSION']
15+
server_version = __version__.rsplit('.', 1)[0]
16+
if client_version != server_version:
17+
message = {
18+
'error': 'Client and server versions do not match.\n' +
19+
'Client version: {}\n'.format(client_version) +
20+
'Server version: {}'.format(server_version)
21+
}
22+
return HttpResponse(
23+
json.dumps(message),
24+
content_type='application/json',
25+
status=status.HTTP_405_METHOD_NOT_ALLOWED
26+
)
27+
except KeyError:
28+
pass

controller/api/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def run_tests(self, test_labels, extra_tests=None, **kwargs):
4141
test_labels, extra_tests, **kwargs)
4242

4343

44+
from .test_api_middleware import * # noqa
4445
from .test_app import * # noqa
4546
from .test_auth import * # noqa
4647
from .test_build import * # noqa
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
Unit tests for the Deis api app.
3+
4+
Run the tests with "./manage.py test api"
5+
"""
6+
from __future__ import unicode_literals
7+
8+
from django.test import TestCase
9+
10+
from deis import __version__
11+
12+
13+
class APIMiddlewareTest(TestCase):
14+
15+
"""Tests middleware.py's business logic"""
16+
17+
fixtures = ['tests.json']
18+
19+
def setUp(self):
20+
self.assertTrue(
21+
self.client.login(username='autotest', password='password'))
22+
23+
def test_x_deis_version_header_good(self):
24+
"""
25+
Test that when the version header is sent, the request is accepted.
26+
"""
27+
response = self.client.get(
28+
'/api/apps',
29+
HTTP_X_DEIS_VERSION=__version__.rsplit('.', 1)[0]
30+
)
31+
self.assertEqual(response.status_code, 200)
32+
33+
def test_x_deis_version_header_bad(self):
34+
"""
35+
Test that when an improper version header is sent, the request is declined.
36+
"""
37+
response = self.client.get(
38+
'/api/apps',
39+
HTTP_X_DEIS_VERSION='1234.5678'
40+
)
41+
self.assertEqual(response.status_code, 405)
42+
43+
def test_x_deis_version_header_not_present(self):
44+
"""
45+
Test that when the version header is not present, the request is accepted.
46+
"""
47+
response = self.client.get('/api/apps')
48+
self.assertEqual(response.status_code, 200)

controller/deis/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
'django.middleware.csrf.CsrfViewMiddleware',
109109
'django.contrib.auth.middleware.AuthenticationMiddleware',
110110
'django.contrib.messages.middleware.MessageMiddleware',
111+
'api.middleware.VersionMiddleware',
111112
# Uncomment the next line for simple clickjacking protection:
112113
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
113114
)

0 commit comments

Comments
 (0)