diff --git a/README.md b/README.md index 844ad279c..067679dbe 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,33 @@ 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 +``` + + +### 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 + +# Migrations get run when you create a docker image or you can run them +# manually with the following command. +PYTHONPATH=. alembic upgrade head +``` + ## Documentation * [Quay Enterprise Documentation](https://tectonic.com/quay-enterprise/docs/latest) diff --git a/data/database.py b/data/database.py index d93bba30f..47bd79fc2 100644 --- a/data/database.py +++ b/data/database.py @@ -774,6 +774,8 @@ class LogEntry(BaseModel): (('repository', 'datetime', 'kind'), False), ) +class Messages(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..420733548 --- /dev/null +++ b/data/model/message.py @@ -0,0 +1,9 @@ +from data.database import Messages + + +def get_messages(): + return Messages.select() + +def create(messages): + for message in messages: + Messages.create(content=message['content']) \ No newline at end of file diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index baa5b1a86..f25e04d43 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -819,3 +819,74 @@ class SuperUserServiceKeyApproval(ApiResource): return make_response('', 201) abort(403) + + +@resource('/v1/messages') +@show_if(features.SUPER_USERS) +class SuperUserMessages(ApiResource): + """ Resource for getting a list of super user messages """ + + schemas = { + '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': { + '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()) + return { + 'messages': [message_view(m) for m in messages], + } + + @require_scope(scopes.SUPERUSER) + @verify_not_prod + @nickname('createMessages') + @validate_json_request('CreateMessage') + def post(self): + """ Create a message """ + body = request.get_json() + 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 69ccb38e5..07fa92039 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..04e94dbd7 100644 --- a/static/js/directives/quay-message-bar.js +++ b/static/js/directives/quay-message-bar.js @@ -11,6 +11,11 @@ angular.module('quay').directive('quayMessageBar', function () { scope: {}, controller: function ($scope, $element, ApiService) { $scope.messages = []; + try { + ApiService.getMessages().then(function (data) { + $scope.messages = data['messages'] || []; + }); + } } }; }); diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 7a55fee38..cbb248c63 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, + SuperUserMessages) from endpoints.api.secscan import RepositoryImageSecurity from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, SuperUserCreateInitialSuperUser) @@ -4278,6 +4279,18 @@ 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(SuperUserMessages, data=dict(message={"content": "new message"})) + + json = self.getJsonResponse(SuperUserMessages) + + 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__': unittest.main()