From 5264b649993cfaebb91f9e7f06cabeccd073c2be Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Thu, 6 Oct 2016 17:33:15 -0400 Subject: [PATCH 01/11] Adding in an endpoint for super user messages. --- endpoints/api/superuser.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index baa5b1a86..cde035723 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -819,3 +819,41 @@ class SuperUserServiceKeyApproval(ApiResource): return make_response('', 201) abort(403) + +@resource('/v1/superuser/messages') +@show_if(features.SUPER_USERS) +class ListMessages(ApiResource): + """ Resource for getting a list of super user messages """ + + schemas = { + 'messages': { + 'id': 'messages', + 'type': 'object', + 'description': 'Messages that a super user has saved in the past', + 'properties': { + 'messages': { + 'type': 'array', + 'description': 'Array of message', + 'items': { + 'type': 'object', + 'description': 'A single message', + 'properties': { + 'content': { + 'type': 'string', + 'description': 'The actual message', + }, + }, + } + }, + }, + }, + } + + @nickname('getMessages') + @require_scope(scopes.SUPERUSER) + def get(self): + """ Return a super users messages """ + messages = [] + return { + 'messages': messages, + } From 651639bb76bf01ed9e577da8a9677ce59a72dec5 Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Fri, 7 Oct 2016 10:19:03 -0400 Subject: [PATCH 02/11] Updating the README so that it is easier for people to know how to run individual tests. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 844ad279c..41f98395f 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,16 @@ TODO docker run -d -p 6379:6379 quay.io/quay/redis ``` +### To run individual tests + +``` +# To run a specific suite +TEST=true python -m test.test_api_usage -f + +# To run a specific test in a suite +TEST=true python -m test.test_api_usage -f SuiteName +``` + ## Documentation * [Quay Enterprise Documentation](https://tectonic.com/quay-enterprise/docs/latest) From 002f533bf8ee84a727ca85d02e5d7edaf7d1b088 Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Fri, 7 Oct 2016 10:22:30 -0400 Subject: [PATCH 03/11] Creating message api. --- endpoints/api/superuser.py | 8 ++++++-- test/test_api_usage.py | 8 +++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index cde035723..429eae2a7 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -822,7 +822,7 @@ class SuperUserServiceKeyApproval(ApiResource): @resource('/v1/superuser/messages') @show_if(features.SUPER_USERS) -class ListMessages(ApiResource): +class Messages(ApiResource): """ Resource for getting a list of super user messages """ schemas = { @@ -850,10 +850,14 @@ class ListMessages(ApiResource): } @nickname('getMessages') - @require_scope(scopes.SUPERUSER) def get(self): """ Return a super users messages """ messages = [] return { 'messages': messages, } + + @require_scope(scopes.SUPERUSER) + def post(self): + """ Create a message """ + pass diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 7a55fee38..53d19bb01 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -65,7 +65,8 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe RepositoryTeamPermissionList, RepositoryUserPermissionList) from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement, SuperUserServiceKeyManagement, SuperUserServiceKey, - SuperUserServiceKeyApproval, SuperUserTakeOwnership) + SuperUserServiceKeyApproval, SuperUserTakeOwnership, + Messages) from endpoints.api.secscan import RepositoryImageSecurity from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, SuperUserCreateInitialSuperUser) @@ -4278,6 +4279,11 @@ class TestSuperUserManagement(ApiTestCase): self.assertEquals('freshuser', json['username']) self.assertEquals('foo@bar.com', json['email']) + def test_set_message(self): + self.login(ADMIN_ACCESS_USER) + + # Create a message + self.postJsonResponse(Messages) if __name__ == '__main__': unittest.main() From 7d8cc1fc3405c70eec9ff87ab799f5a607fce31c Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Fri, 7 Oct 2016 14:20:00 -0400 Subject: [PATCH 04/11] Adding in some more updates to the docs for easier use. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 41f98395f..13c8bfc1e 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,19 @@ TEST=true python -m test.test_api_usage -f TEST=true python -m test.test_api_usage -f SuiteName ``` + +### Running migrations + +``` +# To create a new migration with this description. +# Note there might be some errors about unique id being to long +# That's okay as long as the migration file is created +./data/migrations/migration.sh "Description goes here" + +# To test the up and down of the migration +./data/migrations/migration.sh # without params +``` + ## Documentation * [Quay Enterprise Documentation](https://tectonic.com/quay-enterprise/docs/latest) From 1e733ddffb52c24cf102326c91051bc4fc166c62 Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Fri, 7 Oct 2016 15:56:58 -0400 Subject: [PATCH 05/11] Adding in a new message data model and the corresponding methods to in the API. --- data/database.py | 2 ++ .../a3002f7638d5_adding_in_messages_table.py | 26 +++++++++++++++++++ data/model/__init__.py | 1 + data/model/message.py | 12 +++++++++ endpoints/api/superuser.py | 14 +++++++--- initdb.py | 2 ++ static/js/directives/quay-message-bar.js | 1 + test/test_api_usage.py | 6 ++++- 8 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 data/migrations/versions/a3002f7638d5_adding_in_messages_table.py create mode 100644 data/model/message.py diff --git a/data/database.py b/data/database.py index d93bba30f..fe59c9171 100644 --- a/data/database.py +++ b/data/database.py @@ -774,6 +774,8 @@ class LogEntry(BaseModel): (('repository', 'datetime', 'kind'), False), ) +class Message(BaseModel): + content = TextField() class RepositoryActionCount(BaseModel): repository = ForeignKeyField(Repository) diff --git a/data/migrations/versions/a3002f7638d5_adding_in_messages_table.py b/data/migrations/versions/a3002f7638d5_adding_in_messages_table.py new file mode 100644 index 000000000..c16ea7339 --- /dev/null +++ b/data/migrations/versions/a3002f7638d5_adding_in_messages_table.py @@ -0,0 +1,26 @@ +"""Adding in messages table + +Revision ID: a3002f7638d5 +Revises: c9b91bee7554 +Create Date: 2016-10-07 11:14:15.054546 + +""" + +# revision identifiers, used by Alembic. +revision = 'a3002f7638d5' +down_revision = 'c9b91bee7554' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +def upgrade(tables): + op.create_table('messages', + sa.Column("id", sa.INTEGER, primary_key=True), + sa.Column("content", sa.UnicodeText, nullable=False), + sa.PrimaryKeyConstraint('id', name=op.f('pk_messages')) + ) + + +def downgrade(tables): + op.drop_table('messages') diff --git a/data/model/__init__.py b/data/model/__init__.py index 9845a6014..6c35a6ae9 100644 --- a/data/model/__init__.py +++ b/data/model/__init__.py @@ -124,6 +124,7 @@ from data.model import ( image, label, log, + message, modelutil, notification, oauth, diff --git a/data/model/message.py b/data/model/message.py new file mode 100644 index 000000000..6fec3a73f --- /dev/null +++ b/data/model/message.py @@ -0,0 +1,12 @@ +from data.database import Message + + +def get_messages(): + messages = [] + for message in Message.select(): + messages.append({'id': message.id, 'content': message.content}) + return messages + +def create(messages): + for message in messages: + Message.create(content=message['content']) \ No newline at end of file diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index 429eae2a7..ab37c1b02 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -820,7 +820,7 @@ class SuperUserServiceKeyApproval(ApiResource): abort(403) -@resource('/v1/superuser/messages') +@resource('/v1/messages') @show_if(features.SUPER_USERS) class Messages(ApiResource): """ Resource for getting a list of super user messages """ @@ -838,6 +838,10 @@ class Messages(ApiResource): 'type': 'object', 'description': 'A single message', 'properties': { + 'id':{ + 'type': 'integer', + 'description': 'The message id', + }, 'content': { 'type': 'string', 'description': 'The actual message', @@ -852,12 +856,16 @@ class Messages(ApiResource): @nickname('getMessages') def get(self): """ Return a super users messages """ - messages = [] + messages = model.message.get_messages() return { 'messages': messages, } @require_scope(scopes.SUPERUSER) + @verify_not_prod + @nickname('createMessages') def post(self): """ Create a message """ - pass + body = request.get_json() + print body + model.message.create(body['messages']) diff --git a/initdb.py b/initdb.py index 69ccb38e5..3ce6b487b 100644 --- a/initdb.py +++ b/initdb.py @@ -776,6 +776,8 @@ def populate_database(minimal=False, with_storage=False): 'trigger_id': trigger.uuid, 'config': json.loads(trigger.config), 'service': trigger.service.name}) + model.message.create([{'content': 'We love you Quay customers!'}]) + fake_queue = WorkQueue('fakequeue', tf) fake_queue.put(['canonical', 'job', 'name'], '{}') diff --git a/static/js/directives/quay-message-bar.js b/static/js/directives/quay-message-bar.js index a85ae0b17..4c05893b9 100644 --- a/static/js/directives/quay-message-bar.js +++ b/static/js/directives/quay-message-bar.js @@ -11,6 +11,7 @@ angular.module('quay').directive('quayMessageBar', function () { scope: {}, controller: function ($scope, $element, ApiService) { $scope.messages = []; + ApiService.getMessages().then(function(data){ $scope.messages = data['messages'] || []; }); } }; }); diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 53d19bb01..90665aff9 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -4283,7 +4283,11 @@ class TestSuperUserManagement(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Create a message - self.postJsonResponse(Messages) + self.postJsonResponse(Messages, data=dict(messages=[{"content": "new message"}])) + + json = self.getJsonResponse(Messages) + + self.assertEquals(len(json['messages']), 2) if __name__ == '__main__': unittest.main() From 075e87089fefbe28b7ee967c0c4184386612dfcb Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 09:36:59 -0400 Subject: [PATCH 06/11] removing debug print statement --- endpoints/api/superuser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index ab37c1b02..fa6af3803 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -867,5 +867,4 @@ class Messages(ApiResource): def post(self): """ Create a message """ body = request.get_json() - print body model.message.create(body['messages']) From 4ae6e6efa9395da7faed8919d4f3a74d849d76ff Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 10:51:30 -0400 Subject: [PATCH 07/11] Fixing some database integration errors --- data/database.py | 2 +- data/model/message.py | 6 +++--- static/js/directives/quay-message-bar.js | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/data/database.py b/data/database.py index fe59c9171..47bd79fc2 100644 --- a/data/database.py +++ b/data/database.py @@ -774,7 +774,7 @@ class LogEntry(BaseModel): (('repository', 'datetime', 'kind'), False), ) -class Message(BaseModel): +class Messages(BaseModel): content = TextField() class RepositoryActionCount(BaseModel): diff --git a/data/model/message.py b/data/model/message.py index 6fec3a73f..779595746 100644 --- a/data/model/message.py +++ b/data/model/message.py @@ -1,12 +1,12 @@ -from data.database import Message +from data.database import Messages def get_messages(): messages = [] - for message in Message.select(): + for message in Messages.select(): messages.append({'id': message.id, 'content': message.content}) return messages def create(messages): for message in messages: - Message.create(content=message['content']) \ No newline at end of file + Messages.create(content=message['content']) \ No newline at end of file diff --git a/static/js/directives/quay-message-bar.js b/static/js/directives/quay-message-bar.js index 4c05893b9..04e94dbd7 100644 --- a/static/js/directives/quay-message-bar.js +++ b/static/js/directives/quay-message-bar.js @@ -11,7 +11,11 @@ angular.module('quay').directive('quayMessageBar', function () { scope: {}, controller: function ($scope, $element, ApiService) { $scope.messages = []; - ApiService.getMessages().then(function(data){ $scope.messages = data['messages'] || []; }); + try { + ApiService.getMessages().then(function (data) { + $scope.messages = data['messages'] || []; + }); + } } }; }); From f15b3d5b84cc1183bdf540309fdf4d6b6368bf74 Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 12:26:44 -0400 Subject: [PATCH 08/11] Adding in some extra documentation. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 13c8bfc1e..067679dbe 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,10 @@ TEST=true python -m test.test_api_usage -f SuiteName # To test the up and down of the migration ./data/migrations/migration.sh # without params + +# Migrations get run when you create a docker image or you can run them +# manually with the following command. +PYTHONPATH=. alembic upgrade head ``` ## Documentation From 14eb3005b689174fa84ec62ccc8cdd1f60b1a35c Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 12:55:00 -0400 Subject: [PATCH 09/11] Some fixes for code review. --- data/model/message.py | 5 +---- endpoints/api/superuser.py | 38 +++++++++++++++++++------------------- initdb.py | 2 +- test/test_api_usage.py | 8 +++++--- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/data/model/message.py b/data/model/message.py index 779595746..420733548 100644 --- a/data/model/message.py +++ b/data/model/message.py @@ -2,10 +2,7 @@ from data.database import Messages def get_messages(): - messages = [] - for message in Messages.select(): - messages.append({'id': message.id, 'content': message.content}) - return messages + return Messages.select() def create(messages): for message in messages: diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index fa6af3803..0de8ae78f 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -820,9 +820,10 @@ class SuperUserServiceKeyApproval(ApiResource): abort(403) + @resource('/v1/messages') @show_if(features.SUPER_USERS) -class Messages(ApiResource): +class SuperUserMessages(ApiResource): """ Resource for getting a list of super user messages """ schemas = { @@ -831,21 +832,17 @@ class Messages(ApiResource): 'type': 'object', 'description': 'Messages that a super user has saved in the past', 'properties': { - 'messages': { - 'type': 'array', - 'description': 'Array of message', - 'items': { - 'type': 'object', - 'description': 'A single message', - 'properties': { - 'id':{ - 'type': 'integer', - 'description': 'The message id', - }, - 'content': { - 'type': 'string', - 'description': 'The actual message', - }, + 'message': { + 'type': 'object', + 'description': 'A single message', + 'properties': { + 'id': { + 'type': 'integer', + 'description': 'The message id', + }, + 'content': { + 'type': 'string', + 'description': 'The actual message', }, } }, @@ -856,9 +853,9 @@ class Messages(ApiResource): @nickname('getMessages') def get(self): """ Return a super users messages """ - messages = model.message.get_messages() + messages = list(model.message.get_messages()) return { - 'messages': messages, + 'messages': [message_view(m) for m in messages], } @require_scope(scopes.SUPERUSER) @@ -867,4 +864,7 @@ class Messages(ApiResource): def post(self): """ Create a message """ body = request.get_json() - model.message.create(body['messages']) + model.message.create([body['message']]) + +def message_view(message): + return {'id': message.id, 'content': message.content} diff --git a/initdb.py b/initdb.py index 3ce6b487b..07fa92039 100644 --- a/initdb.py +++ b/initdb.py @@ -776,7 +776,7 @@ def populate_database(minimal=False, with_storage=False): 'trigger_id': trigger.uuid, 'config': json.loads(trigger.config), 'service': trigger.service.name}) - model.message.create([{'content': 'We love you Quay customers!'}]) + model.message.create([{'content': 'We love you, Quay customers!'}]) fake_queue = WorkQueue('fakequeue', tf) fake_queue.put(['canonical', 'job', 'name'], '{}') diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 90665aff9..382719af7 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -66,7 +66,7 @@ from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPe from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement, SuperUserServiceKeyManagement, SuperUserServiceKey, SuperUserServiceKeyApproval, SuperUserTakeOwnership, - Messages) + SuperUserMessages) from endpoints.api.secscan import RepositoryImageSecurity from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, SuperUserCreateInitialSuperUser) @@ -4283,11 +4283,13 @@ class TestSuperUserManagement(ApiTestCase): self.login(ADMIN_ACCESS_USER) # Create a message - self.postJsonResponse(Messages, data=dict(messages=[{"content": "new message"}])) + self.postJsonResponse(SuperUserMessages, data=dict(message={"content": "new message"})) - json = self.getJsonResponse(Messages) + json = self.getJsonResponse(SuperUserMessages) self.assertEquals(len(json['messages']), 2) + self.assertEquals(json['messages'][1]["content"], "new message") + self.assertEquals(json['messages'][1]["id"], 2) if __name__ == '__main__': unittest.main() From fa10d799b2b8aaaedf33960f02f5a3fbad2004be Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 14:00:20 -0400 Subject: [PATCH 10/11] Adding in one more unit test. --- test/test_api_usage.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 382719af7..cbb248c63 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -4289,6 +4289,7 @@ class TestSuperUserManagement(ApiTestCase): self.assertEquals(len(json['messages']), 2) self.assertEquals(json['messages'][1]["content"], "new message") + self.assertNotEqual(json['messages'][0]["content"], json['messages'][1]["content"]) self.assertEquals(json['messages'][1]["id"], 2) if __name__ == '__main__': From f1793209444a1bb4cd632137ac5c0e5372cdb373 Mon Sep 17 00:00:00 2001 From: charltonaustin Date: Mon, 10 Oct 2016 14:15:09 -0400 Subject: [PATCH 11/11] Adding in validate son request. --- endpoints/api/superuser.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index 0de8ae78f..f25e04d43 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -827,30 +827,51 @@ class SuperUserMessages(ApiResource): """ Resource for getting a list of super user messages """ schemas = { - 'messages': { - 'id': 'messages', + 'GetMessage': { + 'id': 'GetMessage', 'type': 'object', 'description': 'Messages that a super user has saved in the past', + 'properties': { + 'message': { + 'type': 'array', + 'description': 'A list of messages', + 'itemType': { + 'type': 'object', + 'properties': { + 'id': { + 'type': 'integer', + 'description': 'The message id', + }, + 'content': { + 'type': 'string', + 'description': 'The actual message', + }, + }, + }, + }, + }, + }, + 'CreateMessage': { + 'id': 'CreateMessage', + 'type': 'object', + 'description': 'Create a new message', 'properties': { 'message': { 'type': 'object', 'description': 'A single message', 'properties': { - 'id': { - 'type': 'integer', - 'description': 'The message id', - }, 'content': { 'type': 'string', 'description': 'The actual message', }, - } + }, }, }, - }, + } } @nickname('getMessages') + @validate_json_request('GetMessage') def get(self): """ Return a super users messages """ messages = list(model.message.get_messages()) @@ -861,6 +882,7 @@ class SuperUserMessages(ApiResource): @require_scope(scopes.SUPERUSER) @verify_not_prod @nickname('createMessages') + @validate_json_request('CreateMessage') def post(self): """ Create a message """ body = request.get_json()