|
1 | | -""" |
2 | | -Unit tests for the Deis api app. |
3 | | -
|
4 | | -Run the tests with "./manage.py test api" |
5 | | -""" |
6 | | - |
7 | | - |
| 1 | +import hashlib |
| 2 | +import hmac |
8 | 3 | import json |
| 4 | +import logging |
| 5 | +import requests |
9 | 6 | import uuid |
10 | 7 |
|
11 | 8 | from django.contrib.auth.models import User |
@@ -390,6 +387,98 @@ def test_release_get_port(self, mock_requests): |
390 | 387 |
|
391 | 388 | # TODO(bacongobbler): test dockerfile ports |
392 | 389 |
|
| 390 | + @override_settings(DEIS_DEPLOY_HOOK_URLS=['http://deis.rocks']) |
| 391 | + @mock.patch('api.models.logger') |
| 392 | + def test_deploy_hooks_logged(self, mock_requests, mock_logger): |
| 393 | + """ |
| 394 | + Verifies that a configured deploy hook is dumped into the logs when a release is created. |
| 395 | + """ |
| 396 | + app_id = 'foo' |
| 397 | + body = {'sha': '123456', 'image': 'autotest/example'} |
| 398 | + |
| 399 | + mr_rocks = mock_requests.post('http://deis.rocks?app={app_id}&user={self.user.username}&sha=&release=v1&release_summary={self.user.username}+created+initial+release'.format(**locals())) # noqa |
| 400 | + self.create_app(app_id) |
| 401 | + # check app logs |
| 402 | + exp_msg = "[{app_id}]: Sent deploy hook to http://deis.rocks".format(**locals()) |
| 403 | + mock_logger.log.has_calls(logging.INFO, exp_msg) |
| 404 | + self.assertTrue(mr_rocks.called) |
| 405 | + self.assertEqual(mr_rocks.call_count, 1) |
| 406 | + |
| 407 | + # override DEIS_DEPLOY_HOOK_URLS again, ensuring that the new deploy hooks get the same |
| 408 | + # treatment |
| 409 | + url = '/v2/apps/{app_id}/builds'.format(**locals()) |
| 410 | + with self.settings(DEIS_DEPLOY_HOOK_URLS=['http://deis.ninja', 'http://cat.dog']): |
| 411 | + mr_ninja = mock_requests.post("http://deis.ninja?app={app_id}&user={self.user.username}&sha=123456&release=v2&release_summary={self.user.username}+deployed+123456".format(**locals())) # noqa |
| 412 | + mr_catdog = mock_requests.post("http://cat.dog?app={app_id}&user={self.user.username}&sha=123456&release=v2&release_summary={self.user.username}+deployed+123456".format(**locals())) # noqa |
| 413 | + response = self.client.post(url, body) |
| 414 | + self.assertEqual(response.status_code, 201, response.data) |
| 415 | + |
| 416 | + # check app logs |
| 417 | + exp_msg = "[{app_id}]: Sent deploy hook to http://deis.ninja".format(**locals()) |
| 418 | + mock_logger.log.has_calls(logging.INFO, exp_msg) |
| 419 | + self.assertTrue(mr_ninja.called) |
| 420 | + self.assertEqual(mr_ninja.call_count, 1) |
| 421 | + exp_msg = "[{app_id}]: Sent deploy hook to http://cat.dog".format(**locals()) |
| 422 | + mock_logger.log.has_calls(logging.INFO, exp_msg) |
| 423 | + self.assertTrue(mr_catdog.called) |
| 424 | + self.assertEqual(mr_catdog.call_count, 1) |
| 425 | + sha = '2345678' |
| 426 | + body['sha'] = sha |
| 427 | + # Ensure that when requests.Exception is raised, the error is noted and life carries on. |
| 428 | + with self.settings(DEIS_DEPLOY_HOOK_URLS=['http://cat.ninja', 'http://deis.dog']): |
| 429 | + def raise_callback(request, context): |
| 430 | + raise requests.ConnectionError('poop') |
| 431 | + mr_ninja = mock_requests.post("http://cat.ninja?app={app_id}&user={self.user.username}&sha={sha}&release=v3&release_summary={self.user.username}+deployed+{sha}". format(**locals()), text=raise_callback) # noqa |
| 432 | + mr_catdog = mock_requests.post("http://deis.dog?app={app_id}&user={self.user.username}&sha={sha}&release=v3&release_summary={self.user.username}+deployed+{sha}". format(**locals())) # noqa |
| 433 | + response = self.client.post(url, body) |
| 434 | + self.assertEqual(response.status_code, 201, response.data) |
| 435 | + |
| 436 | + # check app logs |
| 437 | + exp_msg = "[{app_id}]: An error occurred while sending the deploy hook to http://cat.ninja: poop".format(**locals()) # noqa |
| 438 | + mock_logger.log.has_calls(logging.ERROR, exp_msg) |
| 439 | + self.assertTrue(mr_ninja.called) |
| 440 | + self.assertEqual(mr_ninja.call_count, 1) |
| 441 | + exp_msg = "[{app_id}]: Sent deploy hook to http://deis.dog".format(**locals()) |
| 442 | + mock_logger.log.has_calls(logging.INFO, exp_msg) |
| 443 | + self.assertTrue(mr_catdog.called) |
| 444 | + self.assertEqual(mr_catdog.call_count, 1) |
| 445 | + |
| 446 | + # ensure that when a secret key is used, a Deis-Signature header is present |
| 447 | + # which was generated by using HMAC-SHA1 against the target URL |
| 448 | + secret = 'Hasta la vista, baby.' |
| 449 | + hook_url = 'http://deis.com' |
| 450 | + sha = '3456789' |
| 451 | + body['sha'] = sha |
| 452 | + # target URL MUST be in the exact alphabetized order when calculating the HMAC signature. |
| 453 | + target_url = '{}?app={}&release=v4&release_summary={}+deployed+{}&sha={}&user={}'.format( |
| 454 | + hook_url, |
| 455 | + app_id, |
| 456 | + self.user.username, |
| 457 | + sha, |
| 458 | + sha, |
| 459 | + self.user.username, |
| 460 | + ) |
| 461 | + signature = hmac.new( |
| 462 | + secret.encode('utf-8'), |
| 463 | + target_url.encode('utf-8'), |
| 464 | + hashlib.sha1, |
| 465 | + ).hexdigest() |
| 466 | + request_headers = {'Authorization': signature} |
| 467 | + |
| 468 | + with self.settings(DEIS_DEPLOY_HOOK_SECRET_KEY=secret, DEIS_DEPLOY_HOOK_URLS=[hook_url]): |
| 469 | + mr_terminator = mock_requests.post( |
| 470 | + target_url, |
| 471 | + request_headers=request_headers, |
| 472 | + ) |
| 473 | + response = self.client.post(url, body) |
| 474 | + self.assertEqual(response.status_code, 201, response.data) |
| 475 | + |
| 476 | + # check app logs |
| 477 | + exp_msg = "[{app_id}]: Sent deploy hook to {hook_url}".format(**locals()) |
| 478 | + mock_logger.log.has_calls(logging.INFO, exp_msg) |
| 479 | + self.assertTrue(mr_terminator.called) |
| 480 | + self.assertEqual(mr_terminator.call_count, 1) |
| 481 | + |
393 | 482 | @override_settings(REGISTRY_LOCATION="off-cluster") |
394 | 483 | def test_release_external_registry(self, mock_requests): |
395 | 484 | """ |
|
0 commit comments