diff --git a/snapcraft/_store.py b/snapcraft/_store.py index 58ced4fc8ae..89045902ac9 100644 --- a/snapcraft/_store.py +++ b/snapcraft/_store.py @@ -409,11 +409,14 @@ def push(snap_filename, release_channels=None): raise FileNotFoundError( 'The file {!r} does not exist.'.format(snap_filename)) - logger.info('Uploading {}.'.format(snap_filename)) - snap_yaml = _get_data_from_snap_file(snap_filename) snap_name = snap_yaml['name'] store = storeapi.StoreClient() + + with _requires_login(): + store.push_precheck(snap_name) + + logger.info('Uploading {}.'.format(snap_filename)) with _requires_login(): tracker = store.upload(snap_name, snap_filename) diff --git a/snapcraft/storeapi/__init__.py b/snapcraft/storeapi/__init__.py index fbdfecd550f..515e5b65575 100644 --- a/snapcraft/storeapi/__init__.py +++ b/snapcraft/storeapi/__init__.py @@ -185,6 +185,10 @@ def register(self, snap_name, is_private=False): return self._refresh_if_necessary( self.sca.register, snap_name, is_private, constants.DEFAULT_SERIES) + def push_precheck(self, snap_name): + return self._refresh_if_necessary( + self.sca.snap_push_precheck, snap_name) + def push_snap_build(self, snap_id, snap_build): return self._refresh_if_necessary( self.sca.push_snap_build, snap_id, snap_build) @@ -494,6 +498,20 @@ def register(self, snap_name, is_private, series): if not response.ok: raise errors.StoreRegistrationError(snap_name, response) + def snap_push_precheck(self, snap_name): + data = { + 'name': snap_name, + 'dry_run': True, + } + auth = _macaroon_auth(self.conf) + response = self.post( + 'snap-push/', data=json.dumps(data), + headers={'Authorization': auth, + 'Content-Type': 'application/json', + 'Accept': 'application/json'}) + if not response.ok: + raise errors.StorePushError(data['name'], response) + def snap_push_metadata(self, snap_name, updown_data): data = { 'name': snap_name, diff --git a/snapcraft/tests/test_commands_push.py b/snapcraft/tests/test_commands_push.py index e25261c9b08..717816efdb1 100644 --- a/snapcraft/tests/test_commands_push.py +++ b/snapcraft/tests/test_commands_push.py @@ -30,7 +30,10 @@ ) from snapcraft.internal.cache._snap import _rewrite_snap_filename_with_revision from snapcraft.main import main -from snapcraft.storeapi.errors import StoreUploadError +from snapcraft.storeapi.errors import ( + StorePushError, + StoreUploadError, +) from snapcraft.tests import fixture_setup @@ -45,6 +48,10 @@ def setUp(self): patcher.start() self.addCleanup(patcher.stop) + patcher = mock.patch('snapcraft.storeapi.StoreClient.push_precheck') + patcher.start() + self.addCleanup(patcher.stop) + def test_push_without_snap_must_raise_exception(self): raised = self.assertRaises( docopt.DocoptExit, @@ -101,6 +108,34 @@ def test_push_nonexisting_snap_must_raise_exception(self): SystemExit, main, ['push', 'test-unexisting-snap']) + def test_push_unregistered_snap_must_raise_exception(self): + self.useFixture(fixture_setup.FakeTerminal()) + + class MockResponse: + status_code = 404 + error_list = [{'code': 'resource-not-found', + 'message': 'Snap not found for name=my-snap-name'}] + + patcher = mock.patch.object(storeapi.StoreClient, 'push_precheck') + mock_precheck = patcher.start() + self.addCleanup(patcher.stop) + mock_precheck.side_effect = StorePushError( + 'my-snap-name', MockResponse()) + + # Create a snap + main(['init']) + main(['snap']) + snap_file = glob.glob('*.snap')[0] + + self.assertRaises( + SystemExit, + main, ['push', snap_file]) + + self.assertIn( + 'Sorry, try `snapcraft register my-snap-name` ' + 'before pushing again.', + self.fake_logger.output) + def test_push_with_updown_error(self): # We really don't know of a reason why this would fail # aside from a 5xx style error on the server. @@ -265,6 +300,13 @@ class PushCommandDeltasTestCase(tests.TestCase): ('without deltas', dict(enable_deltas=False)), ] + def setUp(self): + super().setUp() + + patcher = mock.patch('snapcraft.storeapi.StoreClient.push_precheck') + patcher.start() + self.addCleanup(patcher.stop) + def test_push_revision_cached_with_experimental_deltas(self): self.useFixture(fixture_setup.FakeTerminal()) if self.enable_deltas: @@ -328,6 +370,10 @@ def test_push_revision_prune_snap_cache(self): snap_revision = 9 + patcher = mock.patch('snapcraft.storeapi.StoreClient.push_precheck') + patcher.start() + self.addCleanup(patcher.stop) + mock_tracker = mock.Mock(storeapi.StatusTracker) mock_tracker.track.return_value = { 'code': 'ready_to_release',