diff --git a/data/database.py b/data/database.py index 6a1544e76..c12d59f60 100644 --- a/data/database.py +++ b/data/database.py @@ -245,6 +245,24 @@ class Repository(BaseModel): (('namespace_user', 'name'), True), ) + def delete_instance(self, recursive=False, delete_nullable=False): + # Note: peewee generates extra nested deletion statements here that are slow and unnecessary. + # Therefore, we define our own deletion order here and use the dependency system to verify it. + ordered_dependencies = [RepositoryAuthorizedEmail, RepositoryTag, Image, LogEntry, + RepositoryBuild, RepositoryBuildTrigger, RepositoryNotification, + RepositoryPermission, AccessToken] + + for query, fk in self.dependencies(search_nullable=True): + model = fk.model_class + if not model in ordered_dependencies: + raise Exception('Missing repository deletion dependency: %s', model) + + for model in ordered_dependencies: + model.delete().where(model.repository == self).execute() + + # Delete the repository itself. + super(Repository, self).delete_instance(recursive=False, delete_nullable=False) + class Role(BaseModel): name = CharField(index=True, unique=True) @@ -441,7 +459,6 @@ class LogEntry(BaseModel): performer = QuayUserField(allows_robots=True, index=True, null=True, related_name='performer') repository = ForeignKeyField(Repository, index=True, null=True) - access_token = ForeignKeyField(AccessToken, null=True) datetime = DateTimeField(default=datetime.now, index=True) ip = CharField(null=True) metadata_json = TextField(default='{}') diff --git a/data/model/legacy.py b/data/model/legacy.py index 4511769d8..89fdff74a 100644 --- a/data/model/legacy.py +++ b/data/model/legacy.py @@ -1774,7 +1774,7 @@ def purge_repository(namespace_name, repository_name): # Delete the rest of the repository metadata fetched = _get_repository(namespace_name, repository_name) - fetched.delete_instance(recursive=True) + fetched.delete_instance(recursive=True, delete_nullable=True) def get_private_repo_count(username): @@ -2004,7 +2004,7 @@ def log_action(kind_name, user_or_organization_name, performer=None, kind = LogEntryKind.get(LogEntryKind.name == kind_name) account = User.get(User.username == user_or_organization_name) LogEntry.create(kind=kind, account=account, performer=performer, - repository=repository, access_token=access_token, ip=ip, + repository=repository, ip=ip, metadata_json=json.dumps(metadata), datetime=timestamp) diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 295030005..6ae9cdff8 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -1140,6 +1140,8 @@ class TestChangeRepoVisibility(ApiTestCase): class TestDeleteRepository(ApiTestCase): SIMPLE_REPO = ADMIN_ACCESS_USER + '/simple' + COMPLEX_REPO = ADMIN_ACCESS_USER + '/complex' + def test_deleterepo(self): self.login(ADMIN_ACCESS_USER) @@ -1151,6 +1153,64 @@ class TestDeleteRepository(ApiTestCase): params=dict(repository=self.SIMPLE_REPO), expected_code=404) + def test_deleterepo2(self): + self.login(ADMIN_ACCESS_USER) + + self.deleteResponse(Repository, + params=dict(repository=self.COMPLEX_REPO)) + + # Verify the repo was deleted. + self.getResponse(Repository, + params=dict(repository=self.COMPLEX_REPO), + expected_code=404) + + def test_populate_and_delete_repo(self): + self.login(ADMIN_ACCESS_USER) + + # Make sure the repository has come images and tags. + self.assertTrue(len(list(model.get_repository_images(ADMIN_ACCESS_USER, 'complex'))) > 0) + self.assertTrue(len(list(model.list_repository_tags(ADMIN_ACCESS_USER, 'complex'))) > 0) + + # Add some data for the repository, in addition to is already existing images and tags. + repository = model.get_repository(ADMIN_ACCESS_USER, 'complex') + + # Create some access tokens. + access_token = model.create_access_token(repository, 'read') + model.create_access_token(repository, 'write') + + delegate_token = model.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken', 'read') + model.create_delegate_token(ADMIN_ACCESS_USER, 'complex', 'sometoken', 'write') + + # Create some repository builds. + model.create_repository_build(repository, access_token, {}, 'someid', 'foobar') + model.create_repository_build(repository, delegate_token, {}, 'someid2', 'foobar2') + + # Create some notifications. + model.create_repo_notification(repository, 'repo_push', 'hipchat', {}) + model.create_repo_notification(repository, 'build_queued', 'slack', {}) + + # Create some logs. + model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository) + model.log_action('push_repo', ADMIN_ACCESS_USER, repository=repository) + + # Create some build triggers. + user = model.get_user(ADMIN_ACCESS_USER) + model.create_build_trigger(repository, 'github', 'sometoken', user) + model.create_build_trigger(repository, 'github', 'anothertoken', user) + + # Create some email authorizations. + model.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'a@b.com') + model.create_email_authorization_for_repo(ADMIN_ACCESS_USER, 'complex', 'b@c.com') + + # Delete the repository. + self.deleteResponse(Repository, + params=dict(repository=self.COMPLEX_REPO)) + + # Verify the repo was deleted. + self.getResponse(Repository, + params=dict(repository=self.COMPLEX_REPO), + expected_code=404) + class TestGetRepository(ApiTestCase): PUBLIC_REPO = PUBLIC_USER + '/publicrepo'