Skip to content

Commit ea0dc35

Browse files
Merge pull request #137 from baloise/fixes
fix: some minor fixes and logging improvements
2 parents 6714244 + 2037f68 commit ea0dc35

File tree

10 files changed

+171
-19
lines changed

10 files changed

+171
-19
lines changed

gitopscli/__main__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ def main() -> None:
1717
logging.exception(ex)
1818
else:
1919
logging.error(ex)
20+
logging.error("Provide verbose flag '-v' for more error details...")
2021
sys.exit(1)
2122

2223

gitopscli/cliparser.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
VersionCommand,
1414
)
1515
from gitopscli.git import GitProvider
16-
from gitopscli.io.yaml_util import yaml_load
16+
from gitopscli.io.yaml_util import yaml_load, YAMLException
1717

1818

1919
def parse_args(raw_args: List[str]) -> Tuple[bool, CommandArgs]:
@@ -69,7 +69,7 @@ def __create_deploy_parser() -> ArgumentParser:
6969
parser.add_argument(
7070
"--values",
7171
help="YAML/JSON object with the YAML path as key and the desired value as value",
72-
type=yaml_load,
72+
type=__parse_yaml,
7373
required=True,
7474
)
7575
parser.add_argument(
@@ -236,6 +236,13 @@ def __parse_bool(value: str) -> bool:
236236
raise ArgumentTypeError(f"invalid bool value: '{value}'")
237237

238238

239+
def __parse_yaml(value: str) -> Any:
240+
try:
241+
return yaml_load(value)
242+
except YAMLException as ex:
243+
raise ArgumentTypeError(f"invalid YAML value: '{value}'") from ex
244+
245+
239246
def __parse_git_provider(value: str) -> GitProvider:
240247
mapping = {"github": GitProvider.GITHUB, "bitbucket-server": GitProvider.BITBUCKET}
241248
assert set(mapping.values()) == set(GitProvider), "git provider mapping not exhaustive"

gitopscli/commands/create_preview.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from dataclasses import dataclass
55
from typing import Any, Callable, Dict
66
from gitopscli.git import GitApiConfig, GitRepo, GitRepoApi, GitRepoApiFactory
7-
from gitopscli.io.yaml_util import update_yaml_file
7+
from gitopscli.io.yaml_util import update_yaml_file, YAMLException
88
from gitopscli.gitops_config import GitOpsConfig
99
from gitopscli.gitops_exception import GitOpsException
1010
from .common import load_gitops_config
@@ -126,5 +126,9 @@ def __update_yaml_file(git_repo: GitRepo, file_path: str, key: str, value: Any)
126126
full_file_path = git_repo.get_full_file_path(file_path)
127127
try:
128128
return update_yaml_file(full_file_path, key, value)
129+
except (FileNotFoundError, IsADirectoryError) as ex:
130+
raise GitOpsException(f"No such file: {file_path}") from ex
131+
except YAMLException as ex:
132+
raise GitOpsException(f"Error loading file: {file_path}") from ex
129133
except KeyError as ex:
130-
raise GitOpsException(f"Key '{key}' not found in '{file_path}'") from ex
134+
raise GitOpsException(f"Key '{key}' not found in file: {file_path}") from ex

gitopscli/commands/deploy.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from dataclasses import dataclass
44
from typing import Any, Dict, Optional, Tuple
55
from gitopscli.git import GitApiConfig, GitRepo, GitRepoApi, GitRepoApiFactory
6-
from gitopscli.io.yaml_util import update_yaml_file, yaml_dump
6+
from gitopscli.io.yaml_util import update_yaml_file, yaml_dump, YAMLException
77
from gitopscli.gitops_exception import GitOpsException
88
from .command import Command
99

@@ -67,8 +67,10 @@ def __update_values(self, git_repo: GitRepo) -> Dict[str, Any]:
6767
updated_value = update_yaml_file(full_file_path, key, value)
6868
except (FileNotFoundError, IsADirectoryError) as ex:
6969
raise GitOpsException(f"No such file: {args.file}") from ex
70+
except YAMLException as ex:
71+
raise GitOpsException(f"Error loading file: {args.file}") from ex
7072
except KeyError as ex:
71-
raise GitOpsException(f"Key '{key}' not found in {args.file}") from ex
73+
raise GitOpsException(f"Key '{key}' not found in file: {args.file}") from ex
7274

7375
if not updated_value:
7476
logging.info("Yaml property %s already up-to-date", key)

gitopscli/git/git_repo.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import os
22
import logging
33
from types import TracebackType
4-
from typing import Optional, Type
5-
from typing_extensions import Literal
4+
from typing import Optional, Type, Literal
65
from git import Repo, GitError, GitCommandError
76
from gitopscli.gitops_exception import GitOpsException
87
from gitopscli.io.tmp_dir import create_tmp_dir, delete_tmp_dir

gitopscli/io/yaml_util.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import re
22
from io import StringIO
33
from typing import Any
4-
from ruamel.yaml import YAML
4+
from ruamel.yaml import YAML, YAMLError
55

6-
ARRAY_KEY_SEGMENT_PATTERN = re.compile(r"\[(\d+)\]")
6+
7+
_ARRAY_KEY_SEGMENT_PATTERN = re.compile(r"\[(\d+)\]")
8+
9+
10+
class YAMLException(Exception):
11+
pass
712

813

914
def yaml_file_load(file_path: str) -> Any:
1015
with open(file_path, "r") as stream:
11-
return YAML().load(stream)
16+
try:
17+
return YAML().load(stream)
18+
except YAMLError as ex:
19+
raise YAMLException(f"Error parsing YAML file: {file_path}") from ex
1220

1321

1422
def yaml_file_dump(yaml: Any, file_path: str) -> None:
@@ -17,7 +25,10 @@ def yaml_file_dump(yaml: Any, file_path: str) -> None:
1725

1826

1927
def yaml_load(yaml_str: str) -> Any:
20-
return YAML().load(yaml_str)
28+
try:
29+
return YAML().load(yaml_str)
30+
except YAMLError as ex:
31+
raise YAMLException(f"Error parsing YAML string '{yaml_str}'") from ex
2132

2233

2334
def yaml_dump(yaml: Any) -> str:
@@ -35,7 +46,7 @@ def update_yaml_file(file_path: str, key: str, value: Any) -> bool:
3546
for current_key_segment in key_segments:
3647
current_key_segments.append(current_key_segment)
3748
current_key = ".".join(current_key_segments)
38-
is_array = ARRAY_KEY_SEGMENT_PATTERN.match(current_key_segment)
49+
is_array = _ARRAY_KEY_SEGMENT_PATTERN.match(current_key_segment)
3950
if is_array:
4051
current_array_index = int(is_array.group(1))
4152
if not isinstance(parent_item, list) or current_array_index >= len(parent_item):

tests/commands/test_create_preview.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import shutil
44
import logging
55
from unittest.mock import call, Mock
6-
from gitopscli.io.yaml_util import update_yaml_file
6+
from gitopscli.io.yaml_util import update_yaml_file, YAMLException
77
from gitopscli.git import GitRepo, GitRepoApi, GitRepoApiFactory, GitProvider
88
from gitopscli.gitops_config import GitOpsConfig
99
from gitopscli.gitops_exception import GitOpsException
@@ -241,14 +241,64 @@ def test_create_preview_for_unknown_template(self):
241241
call.os.path.isdir("/tmp/created-tmp-dir/.preview-templates/my-app"),
242242
]
243243

244+
def test_create_preview_values_yaml_not_found(self):
245+
self.update_yaml_file_mock.side_effect = FileNotFoundError()
246+
247+
try:
248+
CreatePreviewCommand(ARGS).execute()
249+
self.fail()
250+
except GitOpsException as ex:
251+
self.assertEqual("No such file: my-app-685912d3-preview/values.yaml", str(ex))
252+
253+
assert self.mock_manager.method_calls == [
254+
call.load_gitops_config(ARGS, "ORGA", "REPO",),
255+
call.GitRepoApiFactory.create(ARGS, "TEAM_CONFIG_ORG", "TEAM_CONFIG_REPO",),
256+
call.GitRepo(self.git_repo_api_mock),
257+
call.GitRepo.checkout("master"),
258+
call.GitRepo.get_full_file_path("my-app-685912d3-preview"),
259+
call.os.path.isdir("/tmp/created-tmp-dir/my-app-685912d3-preview"),
260+
call.logging.info("Use existing folder for preview: %s", "my-app-685912d3-preview"),
261+
call.GitRepo.get_full_file_path("my-app-685912d3-preview/values.yaml"),
262+
call.update_yaml_file(
263+
"/tmp/created-tmp-dir/my-app-685912d3-preview/values.yaml",
264+
"image.tag",
265+
"3361723dbd91fcfae7b5b8b8b7d462fbc14187a9",
266+
),
267+
]
268+
269+
def test_create_preview_values_yaml_parse_error(self):
270+
self.update_yaml_file_mock.side_effect = YAMLException()
271+
272+
try:
273+
CreatePreviewCommand(ARGS).execute()
274+
self.fail()
275+
except GitOpsException as ex:
276+
self.assertEqual("Error loading file: my-app-685912d3-preview/values.yaml", str(ex))
277+
278+
assert self.mock_manager.method_calls == [
279+
call.load_gitops_config(ARGS, "ORGA", "REPO",),
280+
call.GitRepoApiFactory.create(ARGS, "TEAM_CONFIG_ORG", "TEAM_CONFIG_REPO",),
281+
call.GitRepo(self.git_repo_api_mock),
282+
call.GitRepo.checkout("master"),
283+
call.GitRepo.get_full_file_path("my-app-685912d3-preview"),
284+
call.os.path.isdir("/tmp/created-tmp-dir/my-app-685912d3-preview"),
285+
call.logging.info("Use existing folder for preview: %s", "my-app-685912d3-preview"),
286+
call.GitRepo.get_full_file_path("my-app-685912d3-preview/values.yaml"),
287+
call.update_yaml_file(
288+
"/tmp/created-tmp-dir/my-app-685912d3-preview/values.yaml",
289+
"image.tag",
290+
"3361723dbd91fcfae7b5b8b8b7d462fbc14187a9",
291+
),
292+
]
293+
244294
def test_create_preview_with_invalid_replacement_path(self):
245295
self.update_yaml_file_mock.side_effect = KeyError()
246296

247297
try:
248298
CreatePreviewCommand(ARGS).execute()
249299
self.fail()
250300
except GitOpsException as ex:
251-
self.assertEqual("Key 'image.tag' not found in 'my-app-685912d3-preview/values.yaml'", str(ex))
301+
self.assertEqual("Key 'image.tag' not found in file: my-app-685912d3-preview/values.yaml", str(ex))
252302

253303
assert self.mock_manager.method_calls == [
254304
call.load_gitops_config(ARGS, "ORGA", "REPO",),
@@ -278,7 +328,7 @@ def test_create_new_preview_invalid_chart_template(self):
278328
CreatePreviewCommand(ARGS).execute()
279329
self.fail()
280330
except GitOpsException as ex:
281-
self.assertEqual("Key 'name' not found in 'my-app-685912d3-preview/Chart.yaml'", str(ex))
331+
self.assertEqual("Key 'name' not found in file: my-app-685912d3-preview/Chart.yaml", str(ex))
282332

283333
assert self.mock_manager.method_calls == [
284334
call.load_gitops_config(ARGS, "ORGA", "REPO",),

tests/commands/test_deploy.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from gitopscli.gitops_exception import GitOpsException
88
from gitopscli.commands.deploy import DeployCommand
99
from gitopscli.git import GitRepoApi, GitProvider, GitRepoApiFactory, GitRepo
10-
from gitopscli.io.yaml_util import update_yaml_file
10+
from gitopscli.io.yaml_util import update_yaml_file, YAMLException
1111
from .mock_mixin import MockMixin
1212

1313

@@ -356,6 +356,37 @@ def test_file_not_found(self):
356356
call.update_yaml_file("/tmp/created-tmp-dir/test/file.yml", "a.b.c", "foo"),
357357
]
358358

359+
def test_file_parse_error(self):
360+
self.update_yaml_file_mock.side_effect = YAMLException
361+
362+
args = DeployCommand.Args(
363+
file="test/file.yml",
364+
values={"a.b.c": "foo", "a.b.d": "bar"},
365+
username="USERNAME",
366+
password="PASSWORD",
367+
git_user="GIT_USER",
368+
git_email="GIT_EMAIL",
369+
create_pr=False,
370+
auto_merge=False,
371+
single_commit=False,
372+
organisation="ORGA",
373+
repository_name="REPO",
374+
git_provider=GitProvider.GITHUB,
375+
git_provider_url=None,
376+
commit_message=None,
377+
)
378+
with pytest.raises(GitOpsException) as ex:
379+
DeployCommand(args).execute()
380+
self.assertEqual(str(ex.value), "Error loading file: test/file.yml")
381+
382+
assert self.mock_manager.method_calls == [
383+
call.GitRepoApiFactory.create(args, "ORGA", "REPO"),
384+
call.GitRepo(self.git_repo_api_mock),
385+
call.GitRepo.checkout("master"),
386+
call.GitRepo.get_full_file_path("test/file.yml"),
387+
call.update_yaml_file("/tmp/created-tmp-dir/test/file.yml", "a.b.c", "foo"),
388+
]
389+
359390
def test_key_not_found(self):
360391
self.update_yaml_file_mock.side_effect = KeyError("dummy key error")
361392

@@ -377,7 +408,7 @@ def test_key_not_found(self):
377408
)
378409
with pytest.raises(GitOpsException) as ex:
379410
DeployCommand(args).execute()
380-
self.assertEqual(str(ex.value), "Key 'a.b.c' not found in test/file.yml")
411+
self.assertEqual(str(ex.value), "Key 'a.b.c' not found in file: test/file.yml")
381412

382413
assert self.mock_manager.method_calls == [
383414
call.GitRepoApiFactory.create(args, "ORGA", "REPO"),

tests/io/test_yaml_util.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
yaml_file_dump,
1010
yaml_load,
1111
yaml_dump,
12+
YAMLException,
1213
update_yaml_file,
1314
merge_yaml_element,
1415
)
@@ -43,11 +44,19 @@ def test_yaml_file_load(self):
4344

4445
def test_yaml_file_load_file_not_found(self):
4546
try:
46-
self.assertEqual(yaml_file_load("unknown"), {"answer": {"is": "42"}})
47+
yaml_file_load("unknown")
4748
self.fail()
4849
except FileNotFoundError:
4950
pass
5051

52+
def test_yaml_file_load_yaml_exception(self):
53+
path = self._create_file("{ INVALID YAML")
54+
try:
55+
yaml_file_load(path)
56+
self.fail()
57+
except YAMLException as ex:
58+
self.assertEqual(f"Error parsing YAML file: {path}", str(ex))
59+
5160
def test_yaml_file_dump(self):
5261
path = self._create_tmp_file_path()
5362
yaml_file_dump({"answer": {"is": "42"}}, path)
@@ -75,6 +84,13 @@ def test_yaml_load(self):
7584
self.assertEqual(yaml_load("{answer: 42}"), {"answer": 42})
7685
self.assertEqual(yaml_load("answer: 42"), {"answer": 42})
7786

87+
def test_yaml_load_yaml_exception(self):
88+
try:
89+
yaml_load("{ INVALID YAML")
90+
self.fail()
91+
except YAMLException as ex:
92+
self.assertEqual("Error parsing YAML string '{ INVALID YAML'", str(ex))
93+
7894
def test_yaml_dump(self):
7995
self.assertEqual(yaml_dump({"answer": "42"}), "answer: '42'")
8096
self.assertEqual(yaml_dump({"answer": 42}), "answer: 42")

tests/test_cliparser.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,37 @@ def test_invalid_int(self):
11801180
"gitopscli add-pr-comment: error: argument --pr-id: invalid int value: 'INVALID_INT'", last_stderr_line
11811181
)
11821182

1183+
def test_invalid_yaml(self):
1184+
exit_code, stdout, stderr = self._capture_parse_args(
1185+
[
1186+
"deploy",
1187+
"--git-provider",
1188+
"github",
1189+
"--username",
1190+
"x",
1191+
"--password",
1192+
"x",
1193+
"--organisation",
1194+
"x",
1195+
"--repository-name",
1196+
"x",
1197+
"--git-user",
1198+
"x",
1199+
"--git-email",
1200+
"x",
1201+
"--file",
1202+
"x",
1203+
"--values",
1204+
"{ INVALID YAML",
1205+
]
1206+
)
1207+
self.assertEqual(exit_code, 2)
1208+
self.assertEqual("", stdout)
1209+
last_stderr_line = stderr.splitlines()[-1]
1210+
self.assertEqual(
1211+
"gitopscli deploy: error: argument --values: invalid YAML value: '{ INVALID YAML'", last_stderr_line,
1212+
)
1213+
11831214
def test_invalid_git_provider(self):
11841215
exit_code, stdout, stderr = self._capture_parse_args(
11851216
[

0 commit comments

Comments
 (0)