From 9f804de23d15597eacd136bc48a80b7b52140f53 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 18 Oct 2017 17:03:27 -0400 Subject: [PATCH] Fix bug in deletion of repos with OCI-style linked tags MySQL does not allow rows in the same table referencing other rows to be deleted in a single statement. We now do a two-pass deletion, and add a test to make sure. Fixes https://jira.prod.coreos.systems/browse/QS-18 --- data/model/repository.py | 8 +++++++- test/test_api_usage.py | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/data/model/repository.py b/data/model/repository.py index 7700dde4b..109a9a25e 100644 --- a/data/model/repository.py +++ b/data/model/repository.py @@ -10,7 +10,7 @@ from cachetools import ttl_cache from data.model import ( config, DataModelException, tag, db_transaction, storage, permission, _basequery) from data.database import ( - Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, Visibility, + Repository, Namespace, RepositoryTag, Star, Image, ImageStorage, User, Visibility, Tag, RepositoryPermission, RepositoryActionCount, Role, RepositoryAuthorizedEmail, TagManifest, DerivedStorageForImage, Label, TagManifestLabel, db_for_update, get_epoch_timestamp, db_random_func, db_concat_func, RepositorySearchScore) @@ -81,6 +81,12 @@ def purge_repository(namespace_name, repository_name): except Repository.DoesNotExist: return False + # Delete the repository of all OCI-referenced entries. + # Note that new-model Tag's must be deleted in *two* passes, as they can reference parent tags, + # and MySQL is... particular... about such relationships when deleting. + Tag.delete().where(Tag.repository == repo, ~(Tag.linked_tag >> None)).execute() + Tag.delete().where(Tag.repository == repo).execute() + # Delete all tags to allow gc to reclaim storage previously_referenced = tag.purge_all_tags(repo) unreferenced_image_q = Image.select(Image.id).where(Image.repository == repo) diff --git a/test/test_api_usage.py b/test/test_api_usage.py index 49259e7a3..5f9a22f15 100644 --- a/test/test_api_usage.py +++ b/test/test_api_usage.py @@ -26,7 +26,7 @@ from endpoints.webhooks import webhooks from app import app, config_provider, all_queues, dockerfile_build_queue, notification_queue from buildtrigger.basehandler import BuildTriggerHandler from initdb import setup_database_for_testing, finished_database_for_testing -from data import database, model +from data import database, model, oci_model from data.database import RepositoryActionCount, Repository as RepositoryTable from test.helpers import assert_action_logged from util.secscan.fake import fake_security_scanner @@ -2148,6 +2148,12 @@ class TestDeleteRepository(ApiTestCase): # Add some data for the repository, in addition to is already existing images and tags. repository = model.repository.get_repository(ADMIN_ACCESS_USER, 'complex') + # Add some new-style tags and linked tags. + base_tag = oci_model.tag.create_or_update_tag(repository, 'somebasetag') + base_tag2 = oci_model.tag.create_or_update_tag(repository, 'somebasetag2') + oci_model.tag.create_or_update_tag(repository, 'somelinkedtag', linked_tag=base_tag) + oci_model.tag.create_or_update_tag(repository, 'somelinkedtag2', linked_tag=base_tag2) + # Create some access tokens. access_token = model.token.create_access_token(repository, 'read') model.token.create_access_token(repository, 'write')