Skip to content

Commit

Permalink
fix(service): return proper errors on migrations check
Browse files Browse the repository at this point in the history
  • Loading branch information
Panaetius authored and Ralf Grubenmann committed Feb 24, 2023
1 parent 4a52069 commit 0ffb67b
Show file tree
Hide file tree
Showing 25 changed files with 495 additions and 457 deletions.
177 changes: 143 additions & 34 deletions renku/command/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
# limitations under the License.
"""Migrate project to the latest Renku version."""

from typing import List
from dataclasses import dataclass
from enum import Enum, auto
from typing import Dict, List, Optional, Tuple

from pydantic import validate_arguments

from renku.command.command_builder.command import Command
from renku.core.errors import MinimumVersionError
from renku.core.migration.migrate import SUPPORTED_PROJECT_VERSION
from renku.domain_model.project_context import project_context

SUPPORTED_RENKU_PROJECT = 1
Expand All @@ -33,12 +37,96 @@
DOCKERFILE_UPDATE_POSSIBLE = 64


class MigrationType(Enum):
"""Enum for different migration types."""

CORE = auto()
DOCKERFILE = auto()
TEMPLATE = auto()


@dataclass
class CoreStatusResult:
"""Core migration status."""

migration_required: bool
project_metadata_version: Optional[int]
current_metadata_version: int


@dataclass
class DockerfileStatusResult:
"""Docker migration status."""

automated_dockerfile_update: bool
newer_renku_available: Optional[bool]
dockerfile_renku_version: Optional[str]
latest_renku_version: str


@dataclass
class TemplateStatusResult:
"""Template migration status."""

automated_template_update: bool
newer_template_available: bool
project_template_version: Optional[str]
latest_template_version: Optional[str]
template_source: Optional[str]
template_ref: Optional[str]
template_id: Optional[str]


@dataclass
class MigrationCheckResult:
"""Migration check output."""

project_supported: bool
core_renku_version: str
project_renku_version: Optional[str]
core_compatibility_status: Optional[CoreStatusResult]
dockerfile_renku_status: Optional[DockerfileStatusResult]
template_status: Optional[TemplateStatusResult]
errors: Optional[Dict[MigrationType, Exception]] = None

@staticmethod
def from_minimum_version_error(minimum_version_error: MinimumVersionError) -> "MigrationCheckResult":
"""Create a migration check when the project isn't supported yet."""
from renku import __version__

return MigrationCheckResult(
project_supported=False,
core_renku_version=str(minimum_version_error.current_version),
project_renku_version=f">={minimum_version_error.minimum_version}",
core_compatibility_status=CoreStatusResult(
migration_required=False,
project_metadata_version=None,
current_metadata_version=SUPPORTED_PROJECT_VERSION,
),
dockerfile_renku_status=DockerfileStatusResult(
dockerfile_renku_version="unknown",
latest_renku_version=__version__,
newer_renku_available=False,
automated_dockerfile_update=False,
),
template_status=TemplateStatusResult(
automated_template_update=False,
newer_template_available=False,
template_source="unknown",
template_ref="unknown",
template_id="unknown",
project_template_version="unknown",
latest_template_version="unknown",
),
)


def migrations_check():
"""Return a command for a migrations check."""
return Command().command(_migrations_check).with_database(write=False)


def _migrations_check():
def _migrations_check() -> MigrationCheckResult:
"""Check migration status of project.
Returns:
Expand All @@ -48,22 +136,43 @@ def _migrations_check():

core_version, latest_version = _migrations_versions()

return {
"project_supported": not is_project_unsupported(),
"core_renku_version": core_version,
"project_renku_version": latest_version,
"core_compatibility_status": _metadata_migration_check(),
"dockerfile_renku_status": _dockerfile_migration_check(),
"template_status": _template_migration_check(),
}
errors: Dict[MigrationType, Exception] = {}

try:
core_compatibility_status = _metadata_migration_check()
except Exception as e:
core_compatibility_status = None
errors[MigrationType.CORE] = e

try:
docker_status = _dockerfile_migration_check()
except Exception as e:
docker_status = None
errors[MigrationType.CORE] = e

try:
template_status = _template_migration_check()
except Exception as e:
template_status = None
errors[MigrationType.CORE] = e

return MigrationCheckResult(
project_supported=not is_project_unsupported(),
core_renku_version=core_version,
project_renku_version=latest_version,
core_compatibility_status=core_compatibility_status,
dockerfile_renku_status=docker_status,
template_status=template_status,
errors=errors,
)


def migrations_versions():
"""Return a command to get source and destination migration versions."""
return Command().command(_migrations_versions).lock_project().with_database()


def _migrations_versions():
def _migrations_versions() -> Tuple[str, Optional[str]]:
"""Return source and destination migration versions.
Returns:
Expand All @@ -82,7 +191,7 @@ def _migrations_versions():
return __version__, latest_agent


def _template_migration_check():
def _template_migration_check() -> TemplateStatusResult:
"""Return template migration status.
Returns:
Expand All @@ -103,23 +212,23 @@ def _template_migration_check():

update_available, update_allowed, current_version, new_version = check_for_template_update(project)

return {
"automated_template_update": update_allowed,
"newer_template_available": update_available,
"project_template_version": current_version,
"latest_template_version": new_version,
"template_source": template_source,
"template_ref": template_ref,
"template_id": template_id,
}
return TemplateStatusResult(
automated_template_update=update_allowed,
newer_template_available=update_available,
project_template_version=current_version,
latest_template_version=new_version,
template_source=template_source,
template_ref=template_ref,
template_id=template_id,
)


def dockerfile_migration_check():
"""Return a command for a Dockerfile migrations check."""
return Command().command(_dockerfile_migration_check)


def _dockerfile_migration_check():
def _dockerfile_migration_check() -> DockerfileStatusResult:
"""Return Dockerfile migration status.
Returns:
Expand All @@ -130,32 +239,32 @@ def _dockerfile_migration_check():

automated_dockerfile_update, newer_renku_available, dockerfile_renku_version = is_docker_update_possible()

return {
"automated_dockerfile_update": automated_dockerfile_update,
"newer_renku_available": newer_renku_available,
"dockerfile_renku_version": dockerfile_renku_version,
"latest_renku_version": __version__,
}
return DockerfileStatusResult(
automated_dockerfile_update=automated_dockerfile_update,
newer_renku_available=newer_renku_available,
dockerfile_renku_version=dockerfile_renku_version,
latest_renku_version=__version__,
)


def metadata_migration_check():
"""Return a command for a metadata migrations check."""
return Command().command(_metadata_migration_check)


def _metadata_migration_check():
def _metadata_migration_check() -> CoreStatusResult:
"""Return metadata migration status.
Returns:
Dictionary of metadata migration status.
"""
from renku.core.migration.migrate import SUPPORTED_PROJECT_VERSION, get_project_version, is_migration_required

return {
"migration_required": is_migration_required(),
"project_metadata_version": get_project_version(),
"current_metadata_version": SUPPORTED_PROJECT_VERSION,
}
return CoreStatusResult(
migration_required=is_migration_required(),
project_metadata_version=get_project_version(),
current_metadata_version=SUPPORTED_PROJECT_VERSION,
)


def migrate_project_command():
Expand Down
9 changes: 5 additions & 4 deletions renku/core/migration/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import posixpath
import threading
import uuid
from typing import Optional, cast
from urllib.parse import ParseResult, quote, urljoin, urlparse

from renku.core.util.yaml import read_yaml
Expand Down Expand Up @@ -164,7 +165,7 @@ def read_project_version() -> str:
return read_project_version_from_yaml(yaml_data)


def read_latest_agent():
def read_latest_agent() -> Optional[str]:
"""Read project version from metadata file."""
import pyld

Expand All @@ -178,16 +179,16 @@ def read_latest_agent():
yaml_data = read_yaml(metadata_path)
jsonld = pyld.jsonld.expand(yaml_data)[0]
jsonld = normalize(jsonld)
return _get_jsonld_property(jsonld, "http://schema.org/agent", "pre-0.11.0")
return cast(str, _get_jsonld_property(jsonld, "http://schema.org/agent", "pre-0.11.0"))


def read_project_version_from_yaml(yaml_data):
def read_project_version_from_yaml(yaml_data) -> str:
"""Read project version from YAML data."""
import pyld

jsonld = pyld.jsonld.expand(yaml_data)[0]
jsonld = normalize(jsonld)
return _get_jsonld_property(jsonld, "http://schema.org/schemaVersion", "1")
return cast(str, _get_jsonld_property(jsonld, "http://schema.org/schemaVersion", "1"))


def _get_jsonld_property(jsonld, property_name, default=None):
Expand Down
7 changes: 6 additions & 1 deletion renku/ui/cli/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,12 @@ def migrationscheck():
from renku.command.migrate import migrations_check

result = migrations_check().lock_project().build().execute().output
click.echo(json.dumps(result))
result_dict = result.as_dict()

if result_dict.get("errors"):
for key, value in result_dict["errors"]:
result_dict["errors"][key] = str(value)
click.echo(json.dumps(result_dict))


@click.command(hidden=True)
Expand Down
41 changes: 12 additions & 29 deletions renku/ui/service/controllers/cache_migrations_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,17 @@
"""Renku service migrations check controller."""

import tempfile
from dataclasses import asdict
from pathlib import Path

from renku.command.migrate import migrations_check
from renku.command.migrate import MigrationCheckResult, migrations_check
from renku.core.errors import AuthenticationError, MinimumVersionError, ProjectNotFound, RenkuException
from renku.core.migration.migrate import SUPPORTED_PROJECT_VERSION
from renku.core.util.contexts import renku_project_context
from renku.ui.service.controllers.api.abstract import ServiceCtrl
from renku.ui.service.controllers.api.mixins import RenkuOperationMixin
from renku.ui.service.interfaces.git_api_provider import IGitAPIProvider
from renku.ui.service.serializers.cache import ProjectMigrationCheckRequest, ProjectMigrationCheckResponseRPC
from renku.ui.service.views import result_response
from renku.version import __version__


class MigrationsCheckCtrl(ServiceCtrl, RenkuOperationMixin):
Expand Down Expand Up @@ -72,34 +71,12 @@ def renku_op(self):
try:
return migrations_check().build().execute().output
except MinimumVersionError as e:
return {
"project_supported": False,
"core_renku_version": e.current_version,
"project_renku_version": f">={e.minimum_version}",
"core_compatibility_status": {
"migration_required": False,
"project_metadata_version": f">={SUPPORTED_PROJECT_VERSION}",
"current_metadata_version": SUPPORTED_PROJECT_VERSION,
},
"dockerfile_renku_status": {
"dockerfile_renku_version": "unknown",
"latest_renku_version": __version__,
"newer_renku_available": False,
"automated_dockerfile_update": False,
},
"template_status": {
"automated_template_update": False,
"newer_template_available": False,
"template_source": "unknown",
"template_ref": "unknown",
"template_id": "unknown",
"project_template_version": "unknown",
"latest_template_version": "unknown",
},
}
return MigrationCheckResult.from_minimum_version_error(e)

def to_response(self):
"""Execute controller flow and serialize to service response."""
from renku.ui.service.views.error_handlers import pretty_print_error

if "project_id" in self.context:
result = self.execute_op()
else:
Expand All @@ -111,4 +88,10 @@ def to_response(self):
except BaseException:
result = self.execute_op()

return result_response(self.RESPONSE_SERIALIZER, result)
result_dict = asdict(result)

if result.errors:
for key, value in result.errors.items():
result_dict["errors"][key] = pretty_print_error(value)

return result_response(self.RESPONSE_SERIALIZER, result_dict)
6 changes: 6 additions & 0 deletions renku/ui/service/serializers/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from marshmallow import Schema, ValidationError, fields, post_load, pre_load, validates_schema
from werkzeug.utils import secure_filename

from renku.command.migrate import MigrationType
from renku.core import errors
from renku.core.util.os import normalize_to_ascii
from renku.domain_model.git import GitURL
Expand Down Expand Up @@ -364,6 +365,11 @@ class ProjectMigrationCheckResponse(Schema):
TemplateStatusResponse,
metadata={"description": "Fields detailing the status of the project template used by this project."},
)
errors = fields.Dict(
fields.Enum(MigrationType),
fields.Dict,
metadata={"description": "Errors if there were any (corresponding entry will be empty)."},
)


class ProjectMigrationCheckResponseRPC(JsonRPCResponse):
Expand Down
Loading

0 comments on commit 0ffb67b

Please sign in to comment.