Skip to content

Commit

Permalink
feat: handle missing reference separately
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzo-cavazzi committed Apr 12, 2022
1 parent 7249e0b commit fe666ec
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 4 deletions.
4 changes: 4 additions & 0 deletions renku/core/errors.py
Expand Up @@ -458,6 +458,10 @@ class InvalidTemplateError(TemplateError):
"""Raised when using a non-valid template."""


class TemplateMissingReferenceError(TemplateError):
"""Raised when using a non-valid template."""


class TemplateUpdateError(TemplateError):
"""Raised when a project couldn't be updated from its template."""

Expand Down
4 changes: 4 additions & 0 deletions renku/core/template/template.py
Expand Up @@ -420,6 +420,10 @@ def fetch(cls, source: Optional[str], reference: Optional[str]) -> "RepositoryTe
try:
repository = clone_repository(url=source, path=path, checkout_revision=reference, install_lfs=False)
except errors.GitError as e:
if "Cannot checkout reference" in str(e):
raise errors.TemplateMissingReferenceError(
f"Cannot find the reference {reference} in the template repository from {source}"
) from e
raise errors.InvalidTemplateError(f"Cannot clone template repository from {source}") from e

version = repository.head.commit.hexsha
Expand Down
3 changes: 3 additions & 0 deletions renku/core/template/usecase.py
Expand Up @@ -136,6 +136,9 @@ def update_template(
templates_source = fetch_templates_source(
source=template_metadata.source, reference=template_metadata.reference
)
except errors.TemplateMissingReferenceError as e:
message = f"{str(e)}; You can still manually update the template and set a difference reference."
raise errors.TemplateUpdateError(message)
except errors.InvalidTemplateError:
raise errors.TemplateUpdateError("Template cannot be fetched.")

Expand Down
23 changes: 23 additions & 0 deletions renku/ui/service/errors.py
Expand Up @@ -342,6 +342,29 @@ def __init__(self, exception=None):
super().__init__(exception=exception)


class UserProjectTemplateReferenceError(ServiceError):
"""The project's template original reference cannot be found anymore.
The reference has probably been removed, either on purpose or as a side effect of a
forced push.
"""

code = SVC_ERROR_USER + 141
userMessage = (
"The project's template original reference has been removed or overwritten."
" Manually changing it in a session may fix the problem."
" Further details: {message}."
)
devMessage = "Template reference is not available anymore. Details: {message}."

def __init__(self, exception):
super().__init__(
userMessage=self.userMessage.format(message=str(exception)),
devMessage=self.devMessage.format(message=str(exception)),
exception=exception,
)


class ProgramInvalidGenericFieldsError(ServiceError):
"""One or more fields are unexpected.
Expand Down
4 changes: 4 additions & 0 deletions renku/ui/service/views/error_handlers.py
Expand Up @@ -36,6 +36,7 @@
MigrationRequired,
ParameterError,
RenkuException,
TemplateMissingReferenceError,
TemplateUpdateError,
UninitializedProject,
)
Expand Down Expand Up @@ -66,6 +67,7 @@
UserNonRenkuProjectError,
UserOutdatedProjectError,
UserProjectCreationError,
UserProjectTemplateReferenceError,
UserRepoBranchInvalidError,
UserRepoNoAccessError,
UserRepoUrlInvalidError,
Expand Down Expand Up @@ -385,6 +387,8 @@ def decorated_function(*args, **kwargs):
# NOTE: verify if this may better go in MigrationsCheckCtrl as try/except in to_response()
try:
return f(*args, **kwargs)
except TemplateMissingReferenceError as e:
raise UserProjectTemplateReferenceError(e)
except (InvalidTemplateError, TemplateUpdateError) as e:
raise IntermittentProjectTemplateUnavailable(e)

Expand Down
30 changes: 26 additions & 4 deletions tests/service/views/test_cache_views.py
Expand Up @@ -33,6 +33,7 @@
IntermittentFileExistsError,
IntermittentProjectTemplateUnavailable,
UserAnonymousError,
UserProjectTemplateReferenceError,
)
from renku.ui.service.serializers.headers import JWT_TOKEN_SECRET
from tests.utils import assert_rpc_response, retry_failed
Expand Down Expand Up @@ -686,42 +687,63 @@ def test_check_migrations_remote(svc_client, identity_headers, it_remote_repo_ur

@pytest.mark.service
@pytest.mark.integration
def test_mirgate_wrong_template_failure(svc_client_with_repo, monkeypatch):
def test_mirgate_wrong_template_failure(svc_client_with_repo, template, monkeypatch):
"""Check if migrations gracefully fail when the project template is not available."""
import renku.core.template.usecase
from renku.core.template.template import fetch_templates_source

svc_client, headers, project_id, _ = svc_client_with_repo

class DummyTemplateMetadata(TemplateMetadata):
def __init__(self, metadata, immutable_files):
super().__init__(metadata=metadata, immutable_files=immutable_files)

def set_fake_source(self, value):
"""Toggle source between fake and real"""
self.fake_source = value

@property
def source(self):
"""Template source."""
return "https://github.com/SwissDataScienceCenter/renku-FAKE-REMPO"
template_url = template["url"]
if self.fake_source:
return f"{template_url}FAKE_URL"
return template_url

@property
def reference(self):
"""Template reference."""
return "master"
return "FAKE_REF"

def dummy_check_for_template_update(client):
metadata = DummyTemplateMetadata.from_client(client=client)
metadata.set_fake_source(fake_source)
templates_source = fetch_templates_source(source=metadata.source, reference=metadata.reference)
update_available, latest_reference = templates_source.is_update_available(
id=metadata.id, reference=metadata.reference, version=metadata.version
)
return update_available, metadata.allow_update, metadata.reference, latest_reference

# NOTE: fake URL and fake REF
fake_source = True
with monkeypatch.context() as monkey:
monkey.setattr(renku.command.migrate, "check_for_template_update", dummy_check_for_template_update)

svc_client, headers, project_id, _ = svc_client_with_repo
response = svc_client.get("/cache.migrations_check", query_string=dict(project_id=project_id), headers=headers)

assert_rpc_response(response, "error")
assert IntermittentProjectTemplateUnavailable.code == response.json["error"]["code"]

# NOTE: valid URL but fake REF
fake_source = False
with monkeypatch.context() as monkey:
monkey.setattr(renku.command.migrate, "check_for_template_update", dummy_check_for_template_update)

response = svc_client.get("/cache.migrations_check", query_string=dict(project_id=project_id), headers=headers)

assert_rpc_response(response, "error")
assert UserProjectTemplateReferenceError.code == response.json["error"]["code"]


@pytest.mark.service
@pytest.mark.integration
Expand Down

0 comments on commit fe666ec

Please sign in to comment.