Skip to content

Commit bc56b8b

Browse files
authored
feat(cli): add renku template validate command (#2936)
1 parent 31f9514 commit bc56b8b

File tree

21 files changed

+742
-381
lines changed

21 files changed

+742
-381
lines changed

.pre-commit-config.yaml

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,54 @@
1+
default_language_version:
2+
python: python3.8
13
repos:
2-
- repo: https://github.com/pre-commit/pre-commit-hooks
3-
rev: v2.3.0
4-
hooks:
5-
- id: check-yaml
6-
- id: end-of-file-fixer
7-
- id: trailing-whitespace
8-
- repo: https://github.com/psf/black
9-
rev: 22.3.0
10-
hooks:
11-
- id: black
12-
additional_dependencies: ['click==8.0.4']
13-
- repo: https://github.com/pycqa/isort
14-
rev: 5.10.1
15-
hooks:
16-
- id: isort
17-
name: isort (python)
18-
- repo: https://github.com/pycqa/flake8
19-
rev: '3.9.2'
20-
hooks:
21-
- id: flake8
22-
exclude: ^docs/
23-
args:
24-
- "--max-line-length=120"
25-
- "--show-source"
26-
- "--ignore=E121,E126,E203,E226,E231,W503,W504"
27-
- repo: https://github.com/pycqa/pydocstyle
28-
rev: 4.0.1
29-
hooks:
30-
- id: pydocstyle
31-
args:
32-
- --ignore=D105,D107,D202,D203,D212,D213,D401,D406,D407,D410,D411,D413
33-
- repo: https://github.com/koalaman/shellcheck-precommit
34-
rev: v0.8.0
35-
hooks:
36-
- id: shellcheck
4+
- repo: https://github.com/pre-commit/pre-commit-hooks
5+
rev: v2.3.0
6+
hooks:
7+
- id: check-yaml
8+
- id: end-of-file-fixer
9+
- id: trailing-whitespace
10+
- repo: https://github.com/psf/black
11+
rev: 22.3.0
12+
hooks:
13+
- id: black
14+
additional_dependencies: ["click==8.0.4"]
15+
- repo: https://github.com/pycqa/isort
16+
rev: 5.10.1
17+
hooks:
18+
- id: isort
19+
name: isort (python)
20+
- repo: https://github.com/pycqa/flake8
21+
rev: "3.9.2"
22+
hooks:
23+
- id: flake8
24+
exclude: ^docs/
25+
args:
26+
- "--max-line-length=120"
27+
- "--show-source"
28+
- "--ignore=E121,E126,E203,E226,E231,W503,W504"
29+
- repo: https://github.com/pycqa/pydocstyle
30+
rev: 4.0.1
31+
hooks:
32+
- id: pydocstyle
33+
args:
34+
- --ignore=D105,D107,D202,D203,D212,D213,D401,D406,D407,D410,D411,D413
35+
- repo: https://github.com/koalaman/shellcheck-precommit
36+
rev: v0.8.0
37+
hooks:
38+
- id: shellcheck
39+
- repo: https://github.com/pre-commit/mirrors-mypy
40+
rev: "v0.961"
41+
hooks:
42+
- id: mypy
43+
language_version: '3.8'
44+
args:
45+
- --no-strict-optional
46+
- --ignore-missing-imports
47+
additional_dependencies:
48+
- types-python-dateutil>=2.8.10
49+
- types-PyYAML<6.1.0,>=5.4
50+
- types-redis>=3.5.3,<4.1.0
51+
- types-requests<2.27.2,>=2.23.0
52+
- types-tabulate<0.8.10,>=0.7.7
53+
- attrs<21.5.0,>=21.4.0
54+
- filelock>=3.3.0,<3.6.1

poetry.lock

Lines changed: 269 additions & 251 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ deepmerge = "==1.0.1"
7171
docker = "<6,>=3.7.2"
7272
environ-config = ">=18.2.0,<22.2.0"
7373
fakeredis = { version = ">=1.4.1,<1.7.2", optional = true }
74-
filelock = ">=3.0.0,<3.6.1"
74+
filelock = ">=3.3.0,<3.6.1"
7575
flake8 = { version = "<4.0,>=3.8", optional = true } #wait for https://github.com/flakehell/flakehell/pull/23 to be merged before bumping
7676
flakehell = { version = ">=0.9.0,<1.0.*", optional = true }
7777
flaky = { version = "==3.7.0", optional = true }
@@ -132,7 +132,7 @@ responses = { version = ">=0.7.0,<0.21.0", optional = true }
132132
rich = ">=9.3.0,<12.3.0"
133133
rq = { version = "==1.10.1", optional = true }
134134
rq-scheduler = { version = "==0.11.0", optional = true }
135-
sentry-sdk = { version = ">=1.0.0,<1.5.12,!=1.5.10", extras = ["flask"], optional = true }
135+
sentry-sdk = { version = ">=1.5.11,<1.5.12", extras = ["flask"], optional = true }
136136
shellingham = "1.4.0"
137137
sphinxcontrib-spelling = { version = "7.*", optional = true }
138138
sphinx-rtd-theme = { version = "<1.1,>=0.5.0", optional = true }

renku/command/template.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"""Template management commands."""
1919

2020
from renku.command.command_builder.command import Command
21-
from renku.core.template.usecase import list_templates, set_template, show_template, update_template
21+
from renku.core.template.usecase import list_templates, set_template, show_template, update_template, validate_templates
2222

2323

2424
def list_templates_command():
@@ -55,3 +55,8 @@ def update_template_command():
5555
.with_database(write=True)
5656
.with_commit()
5757
)
58+
59+
60+
def validate_templates_command():
61+
"""Command to validate a template repository."""
62+
return Command().command(validate_templates)

renku/core/management/git.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,16 +150,15 @@ def prepare_worktree(
150150
# TODO sys.argv
151151

152152
if commit is NULL_TREE:
153-
args = ["add", "--detach", path]
154-
original_client.repository.run_git_command("worktree", *args)
153+
original_client.repository.create_worktree(path, detach=True)
155154
client = attr.evolve(original_client, path=path)
156155
client.repository.run_git_command("checkout", "--orphan", branch_name)
157156
client.repository.remove("*", recursive=True, force=True)
158157
else:
159-
args = ["add", "-b", branch_name, path]
158+
revision = None
160159
if commit:
161-
args.append(commit.hexsha)
162-
original_client.repository.run_git_command("worktree", *args)
160+
revision = commit.hexsha
161+
original_client.repository.create_worktree(path, branch=branch_name, reference=revision)
163162
client = attr.evolve(original_client, path=path)
164163

165164
client.repository.get_configuration = original_client.repository.get_configuration
@@ -207,7 +206,7 @@ def finalize_worktree(
207206
raise errors.FailedMerge(client.repository, branch_name, merge_args)
208207

209208
if delete:
210-
client.repository.run_git_command("worktree", "remove", path)
209+
client.repository.remove_worktree(path)
211210

212211
if new_branch:
213212
# delete the created temporary branch

renku/core/management/repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _path_converter(path) -> Path:
5858
class PathMixin:
5959
"""Define a default path attribute."""
6060

61-
path: Path = attr.ib(default=default_path, converter=_path_converter)
61+
path: Path = attr.ib(default=default_path, converter=_path_converter) # type: ignore
6262

6363
@path.validator
6464
def _check_path(self, _, value):

renku/core/template/template.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,10 @@ class RepositoryTemplates(TemplatesSource):
407407
get available versions of templates.
408408
"""
409409

410-
def __init__(self, path, source, reference, version, repository: Repository):
411-
super().__init__(path=path, source=source, reference=reference, version=version)
410+
def __init__(self, path, source, reference, version, repository: Repository, skip_validation: bool = False):
411+
super().__init__(
412+
path=path, source=source, reference=reference, version=version, skip_validation=skip_validation
413+
)
412414
self.repository: Repository = repository
413415

414416
@classmethod

renku/core/template/usecase.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
# limitations under the License.
1818
"""Template use cases."""
1919

20-
from typing import Dict, List, NamedTuple, Optional, Tuple
20+
import os
21+
import tempfile
22+
from pathlib import Path
23+
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
2124

2225
import click
2326

@@ -29,6 +32,7 @@
2932
from renku.core.management.migrate import is_renku_project
3033
from renku.core.template.template import (
3134
FileAction,
35+
RepositoryTemplates,
3236
TemplateAction,
3337
copy_template_to_client,
3438
fetch_templates_source,
@@ -38,6 +42,7 @@
3842
from renku.core.util import communication
3943
from renku.domain_model.tabulate import tabulate
4044
from renku.domain_model.template import RenderedTemplate, Template, TemplateMetadata, TemplatesSource
45+
from renku.infrastructure.repository import Repository
4146

4247

4348
def list_templates(source, reference) -> List[TemplateViewModel]:
@@ -242,3 +247,53 @@ def prompt_to_select_template():
242247
return templates_source.templates[0]
243248

244249
return prompt_to_select_template()
250+
251+
252+
def validate_templates(
253+
source: Optional[str] = None, reference: Optional[str] = None
254+
) -> Dict[str, Union[str, Dict[str, List[str]]]]:
255+
"""Validate a template repository.
256+
257+
Args:
258+
source(str, optional): Remote repository URL to clone and check (Default value = None).
259+
reference(str, optional): Git commit/branch/tag to check (Default value = None).
260+
Returns:
261+
Dict[str, Union[str, Dict[str, List[str]]]]: Dictionary containing errors and warnings for manifest and
262+
templates, along with a ``valid`` field telling if all checks passed.
263+
"""
264+
265+
if source is not None:
266+
path = Path(tempfile.mkdtemp())
267+
repo = Repository.clone_from(path=path, url=source)
268+
repo.checkout(reference=reference)
269+
else:
270+
path = Path(os.getcwd())
271+
repo = Repository(path=path)
272+
273+
if reference is not None:
274+
path = Path(tempfile.mkdtemp())
275+
repo.create_worktree(path, reference=reference)
276+
repo = Repository(path=path)
277+
278+
version = repo.head.commit.hexsha
279+
280+
result: Dict[str, Any] = {"manifest": None, "templates": {}, "warnings": [], "valid": True}
281+
282+
try:
283+
template_source = RepositoryTemplates(
284+
path=path, source=path, reference="", version=version, repository=repo, skip_validation=True
285+
)
286+
result["warnings"] = template_source.manifest.validate(manifest_only=True)
287+
except errors.InvalidTemplateError as e:
288+
result["manifest"] = e.args[0] if e.args else str(e)
289+
result["valid"] = False
290+
return result
291+
292+
for template in template_source.manifest.templates:
293+
template.templates_source = template_source
294+
issues = template.validate(skip_files=False, raise_errors=False)
295+
if issues:
296+
result["templates"][template.id] = issues
297+
result["valid"] = False
298+
299+
return result

0 commit comments

Comments
 (0)