Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

store: support registering to a specific store #2479

Merged
merged 5 commits into from
Mar 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions snapcraft/_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,11 +420,11 @@ def register_key(name):
)


def register(snap_name, is_private=False):
def register(snap_name: str, is_private: bool = False, store_id: str = None) -> None:
logger.info("Registering {}.".format(snap_name))
store = storeapi.StoreClient()
with _requires_login():
store.register(snap_name, is_private)
store.register(snap_name, is_private=is_private, store_id=store_id)


def _generate_snap_build(authority_id, snap_id, grade, key_name, snap_filename):
Expand Down
5 changes: 3 additions & 2 deletions snapcraft/cli/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def _human_readable_acls(store: storeapi.StoreClient) -> str:
@storecli.command()
@click.argument("snap-name", metavar="<snap-name>")
@click.option("--private", is_flag=True, help="Register the snap as a private one")
def register(snap_name, private):
@click.option("--store", metavar="<store>", help="Store to register with")
def register(snap_name, private, store):
"""Register <snap-name> with the store.

You can use this command to register an available <snap-name> and become
Expand All @@ -113,7 +114,7 @@ def register(snap_name, private):
if private:
click.echo(_MESSAGE_REGISTER_PRIVATE.format(snap_name))
if click.confirm(_MESSAGE_REGISTER_CONFIRM.format(snap_name)):
snapcraft.register(snap_name, private)
snapcraft.register(snap_name, is_private=private, store_id=store)
click.echo(_MESSAGE_REGISTER_SUCCESS.format(snap_name))
else:
click.echo(_MESSAGE_REGISTER_NO.format(snap_name))
Expand Down
6 changes: 5 additions & 1 deletion snapcraft/storeapi/_sca_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,13 @@ def register_key(self, account_key_request):
if not response.ok:
raise errors.StoreKeyRegistrationError(response)

def register(self, snap_name, is_private, series):
def register(
self, snap_name: str, *, is_private: bool, series: str, store_id: str
sergiusens marked this conversation as resolved.
Show resolved Hide resolved
) -> None:
auth = _macaroon_auth(self.conf)
data = dict(snap_name=snap_name, is_private=is_private, series=series)
if store_id is not None:
data["store"] = store_id
response = self.post(
"register-name/",
data=json.dumps(data),
Expand Down
8 changes: 6 additions & 2 deletions snapcraft/storeapi/_store_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,13 @@ def get_account_information(self):
def register_key(self, account_key_request):
return self._refresh_if_necessary(self.sca.register_key, account_key_request)

def register(self, snap_name, is_private=False):
def register(self, snap_name: str, is_private: bool = False, store_id: str = None):
return self._refresh_if_necessary(
self.sca.register, snap_name, is_private, constants.DEFAULT_SERIES
self.sca.register,
snap_name,
is_private=is_private,
store_id=store_id,
series=constants.DEFAULT_SERIES,
)

def push_precheck(self, snap_name):
Expand Down
7 changes: 6 additions & 1 deletion tests/fake_servers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,8 +644,13 @@ def register_name(self, request):
"Handling registration request with content {}".format(request.json_body)
)
snap_name = request.json_body["snap_name"]
store = request.json_body.get("store")

if snap_name == "test-snap-name-already-registered":
if store == "snapcraft-test" and not snap_name.startswith(
"test-snapcraft-in-store"
):
return self._register_name_409_error("reserved_name")
elif snap_name == "test-snap-name-already-registered":
return self._register_name_409_error("already_registered")
elif snap_name == "test-reserved-snap-name":
return self._register_name_409_error("reserved_name")
Expand Down
8 changes: 5 additions & 3 deletions tests/integration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,10 +502,12 @@ def logout(self):
expected = r".*Credentials cleared.\n.*"
self.assertThat(output, MatchesRegex(expected, flags=re.DOTALL))

def register(self, snap_name, private=False, wait=True):
def register(self, snap_name, private=False, store=None, wait=True):
command = ["register", snap_name]
if private:
command.append("--private")
if store:
command.extend(["--store", store])
process = self.spawn_snapcraft(command)
process.expect(r".*\[y/N\]: ")
process.sendline("y")
Expand Down Expand Up @@ -579,7 +581,7 @@ def list_registered(self, expected_snaps):
process.expect(pexpect.EOF)
return process.wait()

def get_unique_name(self, prefix=""):
def get_unique_name(self, name_prefix="test-snapcraft", prefix=""):
"""Return a unique snap name.

It uses a UUIDv4 to create unique names and limits its full size
Expand All @@ -590,7 +592,7 @@ def get_unique_name(self, prefix=""):
# Do not change the test-snapcraft- prefix. Ensure that you
# notify the store team if you need to use a different value when
# working with the production store.
return "test-snapcraft-{}{}".format(prefix, unique_id)[:40]
return "{}-{}{}".format(name_prefix, prefix, unique_id)[:40]

def get_unique_version(self):
"""Return a unique snap version.
Expand Down
23 changes: 23 additions & 0 deletions tests/integration/store/test_store_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ def test_successful_private_registration(self):
snap_name = self.get_unique_name()
self.register(snap_name, private=True)

def test_successful_registration_with_store(self):
self.login()
snap_name = self.get_unique_name(name_prefix="test-snapcraft-in-store")
self.register(snap_name, store="snapcraft-test")

def test_failed_registration_already_registered(self):
self.login()
# The snap name is already registered.
Expand Down Expand Up @@ -90,6 +95,24 @@ def test_registration_of_reserved_name(self):
),
)

def test_registration_of_reserved_name_with_store(self):
self.login()
snap_name = self.get_unique_name(name_prefix="not-test-snapcraft-in-store")
# The snap name is already registered.
error = self.assertRaises(
integration.RegisterError, self.register, snap_name, store="snapcraft-test"
)
self.assertThat(
str(error), Contains("The name {0!r} is reserved".format(snap_name))
)
self.assertThat(
str(error),
Contains(
"If you are the publisher most users expect for {0!r} "
"then please claim the name at".format(snap_name)
),
)

def test_registration_of_invalid_name(self):
self.login()
name = "test_invalid"
Expand Down
30 changes: 28 additions & 2 deletions tests/unit/commands/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,31 @@ def test_register_name_successfully(self):
result.output,
Not(Contains("Congratulations! You're now the publisher for 'test-snap'.")),
)
mock_register.assert_called_once_with("test-snap", False, "16")
mock_register.assert_called_once_with(
"test-snap", is_private=False, series="16", store_id=None
)

def test_register_name_to_specific_store_successfully(self):
with mock.patch.object(
storeapi._sca_client.SCAClient, "register"
) as mock_register:
result = self.run_command(
["register", "test-snap", "--store", "my-brand"], input="y\n"
)

self.assertThat(result.exit_code, Equals(0))
self.assertThat(result.output, Contains("Registering test-snap"))
self.assertThat(
result.output,
Contains("Congrats! You are now the publisher of 'test-snap'."),
)
self.assertThat(
result.output,
Not(Contains("Congratulations! You're now the publisher for 'test-snap'.")),
)
mock_register.assert_called_once_with(
"test-snap", is_private=False, series="16", store_id="my-brand"
)

def test_register_private_name_successfully(self):
with mock.patch.object(
Expand All @@ -78,7 +102,9 @@ def test_register_private_name_successfully(self):
result.output,
Not(Contains("Congratulations! You're now the publisher for 'test-snap'.")),
)
mock_register.assert_called_once_with("test-snap", True, "16")
mock_register.assert_called_once_with(
"test-snap", is_private=True, series="16", store_id=None
)

def test_registration_failed(self):
response = mock.Mock()
Expand Down
5 changes: 5 additions & 0 deletions tests/unit/store/test_store_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,11 @@ def test_register_name_successfully(self):
# No exception will be raised if this is successful
self.client.register("test-good-snap-name")

def test_register_name_successfully_to_store_id(self):
self.client.login("dummy", "test correct password")
# No exception will be raised if this is successful
self.client.register("test-good-snap-name", store_id="my-brand")

def test_register_private_name_successfully(self):
self.client.login("dummy", "test correct password")
# No exception will be raised if this is successful
Expand Down