diff --git a/renku/core/errors.py b/renku/core/errors.py index 46976d5852..a3872e94c1 100644 --- a/renku/core/errors.py +++ b/renku/core/errors.py @@ -485,11 +485,11 @@ class TemplateError(RenkuException): class InvalidTemplateError(TemplateError): - """Raised when using a non-valid template.""" + """Raised when using an invalid template.""" class TemplateMissingReferenceError(TemplateError): - """Raised when using a non-valid template.""" + """Raised when a reference cannot be found in the template's repository.""" class TemplateUpdateError(TemplateError): diff --git a/renku/core/init.py b/renku/core/init.py index 166978d22f..8146291d5f 100644 --- a/renku/core/init.py +++ b/renku/core/init.py @@ -110,6 +110,7 @@ def init_project( input_parameters: Dict[str, str], custom_metadata: Optional[Dict[str, Any]], force: bool, + ignore_template_errors: bool, data_dir: Optional[Path], initial_branch: Optional[str], install_mergetool: bool, @@ -129,6 +130,7 @@ def init_project( input_parameters: Template parameters. custom_metadata: Custom JSON-LD metadata for project. force: Whether to overwrite existing files and delete existing metadata. + ignore_template_errors: Create project anyway even if template rendering fails. data_dir: Where to store dataset data. initial_branch: Default git branch. install_mergetool(bool): Whether to set up the renku metadata mergetool in the created project. @@ -181,7 +183,7 @@ def init_project( # TODO: Validate input_parameters to make sure they don't contain __\w+__ keys set_template_parameters(template=template, template_metadata=template_metadata, input_parameters=input_parameters) - rendered_template = template.render(metadata=template_metadata) + rendered_template = template.render(metadata=template_metadata, ignore_template_errors=ignore_template_errors) actions = get_file_actions( rendered_template=rendered_template, template_action=TemplateAction.INITIALIZE, interactive=False ) diff --git a/renku/domain_model/template.py b/renku/domain_model/template.py index 3cf8e9a057..6d7a6273e5 100644 --- a/renku/domain_model/template.py +++ b/renku/domain_model/template.py @@ -28,6 +28,7 @@ from renku.core import errors from renku.core.constant import RENKU_HOME +from renku.core.util import communication from renku.core.util.os import get_safe_relative_path, hash_file, hash_string from renku.core.util.util import to_string @@ -343,7 +344,7 @@ def get_files(self) -> Generator[str, None, None]: if subpath.is_file(): yield str(subpath.relative_to(self.path)) - def render(self, metadata: "TemplateMetadata") -> "RenderedTemplate": + def render(self, metadata: "TemplateMetadata", ignore_template_errors: bool = False) -> "RenderedTemplate": """Render template files in a new directory.""" if self.path is None: raise ValueError("Template path not set") @@ -351,8 +352,15 @@ def render(self, metadata: "TemplateMetadata") -> "RenderedTemplate": render_base = Path(tempfile.mkdtemp()) for relative_path in self.get_files(): - # NOTE: The path could contain template variables, we need to template it - rendered_relative_path = jinja2.Template(relative_path).render(metadata.metadata) + try: + # NOTE: The path could contain template variables, we need to template it + rendered_relative_path = jinja2.Template(relative_path).render(metadata.metadata) + except jinja2.TemplateError as e: + if ignore_template_errors: + rendered_relative_path = relative_path + communication.warn(f"Ignoring template error when rendering path '{relative_path}'") + else: + raise errors.InvalidTemplateError(f"Cannot render template file path '{relative_path}': {e}") destination = render_base / rendered_relative_path destination.parent.mkdir(parents=True, exist_ok=True) @@ -365,9 +373,20 @@ def render(self, metadata: "TemplateMetadata") -> "RenderedTemplate": content_bytes = source.read_bytes() destination.write_bytes(content_bytes) else: - template = jinja2.Template(content, keep_trailing_newline=True) - rendered_content = template.render(metadata.metadata) - destination.write_text(rendered_content) + try: + rendered_content = jinja2.Template(content, keep_trailing_newline=True).render(metadata.metadata) + except jinja2.TemplateError as e: + if ignore_template_errors: + destination.write_text(content) + communication.warn( + f"Ignoring template rendering error when creating '{rendered_relative_path}'" + ) + else: + raise errors.InvalidTemplateError( + f"Cannot render template file '{rendered_relative_path}': {e}" + ) + else: + destination.write_text(rendered_content) return RenderedTemplate(path=render_base, template=self, metadata=metadata.metadata) @@ -432,8 +451,8 @@ def from_dict(cls, name: str, value: Dict[str, Any]): @property def has_default(self) -> bool: """Return True if a default value is set.""" - # NOTE: ``None`` cannot be used as the default value but it's ok since no variable type accepts it and it's not - # a valid value anyways + # NOTE: ``None`` cannot be used as the default value, but it's ok since no variable type accepts it, and it's + # not a valid value anyway return self.default is not None def validate(self, raise_errors: bool = True) -> List[str]: diff --git a/renku/ui/cli/init.py b/renku/ui/cli/init.py index dd2f06ecee..e41821ded7 100644 --- a/renku/ui/cli/init.py +++ b/renku/ui/cli/init.py @@ -53,6 +53,10 @@ source ``--template-source`` (both local path and remote url are supported) and the reference ``--template-ref`` (branch, tag or commit). +If the template contains an error that prevents it from being rendered, you can +ignore this error by passing ``--ignore-template-errors`` to the ``init`` +command. + You can take inspiration from the `official Renku template repository `_ @@ -259,6 +263,7 @@ def resolve_data_directory(data_dir, path): @click.option("-l", "--list-templates", is_flag=True, help="List templates available in the template-source.") @click.option("-d", "--describe", is_flag=True, help="Show description for templates and parameters") @click.option("--force", is_flag=True, help="Override target path.") +@click.option("--ignore-template-errors", is_flag=True, help="Ignore template rendering errors.") @click.option("--initial-branch", help="Initial git branch to create.") @option_external_storage_requested @click.pass_context @@ -277,6 +282,7 @@ def init( metadata, list_templates, force, + ignore_template_errors, describe, datadir, initial_branch, @@ -319,6 +325,7 @@ def init( input_parameters=parameters, custom_metadata=custom_metadata, force=force, + ignore_template_errors=ignore_template_errors, data_dir=datadir, initial_branch=initial_branch, install_mergetool=True,