qontract-development-cli 0.14.5__tar.gz → 0.15.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qontract_development_cli-0.15.2/LICENSE +21 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/PKG-INFO +34 -23
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/README.md +18 -4
- qontract_development_cli-0.15.2/pyproject.toml +101 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/cli.py +18 -11
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/config.py +2 -8
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/env.py +10 -7
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/profile.py +114 -98
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/completions.py +1 -4
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/config.py +30 -24
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/models.py +33 -32
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/shell.py +11 -16
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates.py +2 -4
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/utils.py +3 -6
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/watchdog.py +1 -5
- qontract_development_cli-0.14.5/pyproject.toml +0 -89
- qontract_development_cli-0.14.5/setup.py +0 -41
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/__init__.py +0 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/__main__.py +0 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/commands/__init__.py +0 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/compose.override.yml.j2 +0 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/compose.yml.j2 +0 -0
- {qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/templates/prep-worktree.sh.j2 +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Christian Assing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: qontract-development-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.2
|
|
4
4
|
Summary: Helper tool for qontract-reconcile development
|
|
5
|
-
|
|
5
|
+
Project-URL: homepage, http://github.com/chassing/qontract-development-cli
|
|
6
|
+
Project-URL: repository, http://github.com/chassing/qontract-development-cli
|
|
7
|
+
Project-URL: documentation, http://github.com/chassing/qontract-development-cli
|
|
8
|
+
Author-email: Christian Assing <cassing@redhat.com>
|
|
6
9
|
License: MIT
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Requires-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
Requires-Dist:
|
|
14
|
-
Requires-Dist:
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist:
|
|
17
|
-
Requires-Dist: pydantic (>=1.10.2,<2.0.0)
|
|
18
|
-
Requires-Dist: rich (>=13.7.0,<14.0.0)
|
|
19
|
-
Requires-Dist: typer (>=0.9.0,<0.10.0)
|
|
20
|
-
Requires-Dist: watchfiles (>=0.21.0,<0.22.0)
|
|
21
|
-
Project-URL: Repository, http://github.com/chassing/qontract-development-cli
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Requires-Dist: appdirs~=1.4.4
|
|
12
|
+
Requires-Dist: getkey~=0.6.5
|
|
13
|
+
Requires-Dist: jinja2~=3.1
|
|
14
|
+
Requires-Dist: pydantic-settings~=2.6
|
|
15
|
+
Requires-Dist: pydantic~=2.9
|
|
16
|
+
Requires-Dist: pyyaml~=6.0.2
|
|
17
|
+
Requires-Dist: rich~=13.9
|
|
18
|
+
Requires-Dist: typer~=0.12.5
|
|
19
|
+
Requires-Dist: watchfiles~=0.24.0
|
|
22
20
|
Description-Content-Type: text/markdown
|
|
23
21
|
|
|
24
22
|
# Qontract Development CLI
|
|
@@ -31,7 +29,21 @@ Qontract Development CLI supports your daily [qontract-reconcile][qontract-recon
|
|
|
31
29
|
|
|
32
30
|
[toc]
|
|
33
31
|
|
|
34
|
-
## Installation
|
|
32
|
+
## Recommended Installation Method
|
|
33
|
+
|
|
34
|
+
The recommended way to install `qontract-development-cli` is to use the [uv](https://docs.astral.sh/uv/) tool:
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
uv tool install qontract-development-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
and upgrade an existing installation with:
|
|
41
|
+
|
|
42
|
+
```shell
|
|
43
|
+
uv tool upgrade qontract-development-cli
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Alternative Installation Methods
|
|
35
47
|
|
|
36
48
|
You can install this library from [PyPI][pypi-link] with `pip`:
|
|
37
49
|
|
|
@@ -39,16 +51,16 @@ You can install this library from [PyPI][pypi-link] with `pip`:
|
|
|
39
51
|
python3 -m pip install qontract-development-cli
|
|
40
52
|
```
|
|
41
53
|
|
|
42
|
-
|
|
54
|
+
or install it with `pipx`:
|
|
43
55
|
|
|
44
56
|
```shell
|
|
45
57
|
pipx install qontract-development-cli
|
|
46
58
|
```
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
and upgrade an existing installation with:
|
|
49
61
|
|
|
50
62
|
```shell
|
|
51
|
-
pipx
|
|
63
|
+
pipx upgrade qontract-development-cli
|
|
52
64
|
```
|
|
53
65
|
|
|
54
66
|
## Quickstart
|
|
@@ -251,4 +263,3 @@ Running this profile will:
|
|
|
251
263
|
[pypi-link]: https://pypi.org/project/qontract-development-cli/
|
|
252
264
|
[pypi-platforms]: https://img.shields.io/pypi/pyversions/qontract-development-cli
|
|
253
265
|
[qontract-reconcile]: https://github.com/app-sre/qontract-reconcile
|
|
254
|
-
|
|
@@ -8,7 +8,21 @@ Qontract Development CLI supports your daily [qontract-reconcile][qontract-recon
|
|
|
8
8
|
|
|
9
9
|
[toc]
|
|
10
10
|
|
|
11
|
-
## Installation
|
|
11
|
+
## Recommended Installation Method
|
|
12
|
+
|
|
13
|
+
The recommended way to install `qontract-development-cli` is to use the [uv](https://docs.astral.sh/uv/) tool:
|
|
14
|
+
|
|
15
|
+
```shell
|
|
16
|
+
uv tool install qontract-development-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
and upgrade an existing installation with:
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
uv tool upgrade qontract-development-cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Alternative Installation Methods
|
|
12
26
|
|
|
13
27
|
You can install this library from [PyPI][pypi-link] with `pip`:
|
|
14
28
|
|
|
@@ -16,16 +30,16 @@ You can install this library from [PyPI][pypi-link] with `pip`:
|
|
|
16
30
|
python3 -m pip install qontract-development-cli
|
|
17
31
|
```
|
|
18
32
|
|
|
19
|
-
|
|
33
|
+
or install it with `pipx`:
|
|
20
34
|
|
|
21
35
|
```shell
|
|
22
36
|
pipx install qontract-development-cli
|
|
23
37
|
```
|
|
24
38
|
|
|
25
|
-
|
|
39
|
+
and upgrade an existing installation with:
|
|
26
40
|
|
|
27
41
|
```shell
|
|
28
|
-
pipx
|
|
42
|
+
pipx upgrade qontract-development-cli
|
|
29
43
|
```
|
|
30
44
|
|
|
31
45
|
## Quickstart
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qontract-development-cli"
|
|
3
|
+
version = "0.15.2"
|
|
4
|
+
description = "Helper tool for qontract-reconcile development"
|
|
5
|
+
authors = [{ name = "Christian Assing", email = "cassing@redhat.com" }]
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
requires-python = ">= 3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"appdirs ~=1.4.4",
|
|
11
|
+
"getkey ~=0.6.5",
|
|
12
|
+
"jinja2 ~=3.1",
|
|
13
|
+
"pydantic ~=2.9",
|
|
14
|
+
"pydantic-settings ~=2.6",
|
|
15
|
+
"pyyaml ~=6.0.2",
|
|
16
|
+
"rich ~=13.9",
|
|
17
|
+
"typer ~=0.12.5",
|
|
18
|
+
"watchfiles ~=0.24.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[dependency-groups]
|
|
22
|
+
dev = ["mypy ~=1.13", "ruff ~=0.7", "types-pyyaml ~=6.0"]
|
|
23
|
+
[project.urls]
|
|
24
|
+
homepage = "http://github.com/chassing/qontract-development-cli"
|
|
25
|
+
repository = "http://github.com/chassing/qontract-development-cli"
|
|
26
|
+
documentation = "http://github.com/chassing/qontract-development-cli"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
qd = "qontract_development_cli.__main__:app"
|
|
30
|
+
|
|
31
|
+
[build-system]
|
|
32
|
+
requires = ["hatchling"]
|
|
33
|
+
build-backend = "hatchling.build"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
only-include = ["qontract_development_cli"]
|
|
37
|
+
|
|
38
|
+
[tool.mypy]
|
|
39
|
+
plugins = "pydantic.mypy"
|
|
40
|
+
enable_error_code = "truthy-bool, redundant-expr"
|
|
41
|
+
no_implicit_optional = true
|
|
42
|
+
check_untyped_defs = true
|
|
43
|
+
warn_unused_ignores = true
|
|
44
|
+
show_error_codes = true
|
|
45
|
+
disallow_untyped_defs = true
|
|
46
|
+
disallow_incomplete_defs = true
|
|
47
|
+
|
|
48
|
+
[[tool.mypy.overrides]]
|
|
49
|
+
module = ["getkey.*", "appdirs.*"]
|
|
50
|
+
ignore_missing_imports = true
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
line-length = 88
|
|
54
|
+
target-version = 'py311'
|
|
55
|
+
src = ["qontract_development_cli"]
|
|
56
|
+
extend-exclude = [".local", ".cache"]
|
|
57
|
+
fix = true
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
preview = true
|
|
61
|
+
select = ["ALL"]
|
|
62
|
+
ignore = [
|
|
63
|
+
"ANN401", # Disallow *args, **kwargs with ANY
|
|
64
|
+
"ARG003", # Unusable arguments
|
|
65
|
+
"CPY", # Missing copyright header
|
|
66
|
+
"D10", # docstrings
|
|
67
|
+
"D203", # 1 blank line required before class docstring
|
|
68
|
+
"D211", # No blank lines allowed before class docstring
|
|
69
|
+
"D212", # multi-line-summary-first-line
|
|
70
|
+
"D213", # multi-line-summary-second-line
|
|
71
|
+
"D4", # Doc string style
|
|
72
|
+
"E501", # Line too long
|
|
73
|
+
"G004", # Logging statement uses string formatting
|
|
74
|
+
"S101", # Use of assert detected. Pytest uses assert
|
|
75
|
+
"EM101",
|
|
76
|
+
"EM102",
|
|
77
|
+
"S404", # subprocess possibly insecure
|
|
78
|
+
"S603", # subprocess check user input
|
|
79
|
+
"S607", # subprocess partical executable path
|
|
80
|
+
"TID252", # relative imports
|
|
81
|
+
"TRY003",
|
|
82
|
+
# pydoclint
|
|
83
|
+
"DOC",
|
|
84
|
+
# May conflict with the formatter, https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
|
85
|
+
"W191",
|
|
86
|
+
"E111",
|
|
87
|
+
"E114",
|
|
88
|
+
"E117",
|
|
89
|
+
"D206",
|
|
90
|
+
"D300",
|
|
91
|
+
"Q",
|
|
92
|
+
"COM812",
|
|
93
|
+
"COM819",
|
|
94
|
+
"ISC001",
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
[tool.ruff.format]
|
|
98
|
+
preview = true
|
|
99
|
+
|
|
100
|
+
[tool.ruff.lint.isort]
|
|
101
|
+
known-first-party = ["qontract_development_cli"]
|
{qontract_development_cli-0.14.5 → qontract_development_cli-0.15.2}/qontract_development_cli/cli.py
RENAMED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import atexit
|
|
2
2
|
import logging
|
|
3
3
|
import sys
|
|
4
|
+
from importlib.metadata import version
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
5
7
|
|
|
6
8
|
import typer
|
|
9
|
+
from rich import print as rich_print
|
|
7
10
|
from rich.logging import RichHandler
|
|
8
11
|
|
|
9
12
|
from .commands import config as config_cmd
|
|
10
|
-
from .commands import
|
|
11
|
-
env,
|
|
12
|
-
profile,
|
|
13
|
-
)
|
|
13
|
+
from .commands import env, profile
|
|
14
14
|
from .config import config
|
|
15
|
-
from .utils import
|
|
16
|
-
console,
|
|
17
|
-
screenshot,
|
|
18
|
-
)
|
|
15
|
+
from .utils import console, screenshot
|
|
19
16
|
|
|
20
17
|
app = typer.Typer()
|
|
21
18
|
app.add_typer(env.app, name="env", help="Environment related commands.")
|
|
@@ -25,10 +22,20 @@ app.add_typer(
|
|
|
25
22
|
app.add_typer(profile.app, name="profile", help="Profile related commands.")
|
|
26
23
|
|
|
27
24
|
|
|
25
|
+
def version_callback(value: bool) -> None: # noqa: FBT001
|
|
26
|
+
if value:
|
|
27
|
+
rich_print(f"Version: {version('qontract-development-cli')}")
|
|
28
|
+
raise typer.Exit
|
|
29
|
+
|
|
30
|
+
|
|
28
31
|
@app.callback(no_args_is_help=True)
|
|
29
32
|
def main(
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
*,
|
|
34
|
+
debug: Annotated[bool, typer.Option(help="Enable debug")] = False,
|
|
35
|
+
screen_capture_file: Annotated[Path | None, typer.Option(writable=True)] = None,
|
|
36
|
+
version: Annotated[ # noqa: ARG001
|
|
37
|
+
bool | None, typer.Option(callback=version_callback, help="Display version")
|
|
38
|
+
] = None,
|
|
32
39
|
) -> None:
|
|
33
40
|
logging.basicConfig(
|
|
34
41
|
level="DEBUG" if config.debug or debug else "INFO",
|
|
@@ -37,7 +44,7 @@ def main(
|
|
|
37
44
|
handlers=[RichHandler()],
|
|
38
45
|
)
|
|
39
46
|
if screen_capture_file is not None:
|
|
40
|
-
|
|
47
|
+
rich_print(f"Screen recording: {screen_capture_file}")
|
|
41
48
|
# strip $0 and screen_capture_file option
|
|
42
49
|
args = sys.argv[3:]
|
|
43
50
|
console.print(f"$ qd {' '.join(args)}")
|
|
@@ -5,14 +5,8 @@ from pathlib import Path
|
|
|
5
5
|
import typer
|
|
6
6
|
from rich.prompt import Prompt
|
|
7
7
|
|
|
8
|
-
from ..config import
|
|
9
|
-
|
|
10
|
-
user_config_file,
|
|
11
|
-
)
|
|
12
|
-
from ..models import (
|
|
13
|
-
DEFAULT_PROFILE,
|
|
14
|
-
Env,
|
|
15
|
-
)
|
|
8
|
+
from ..config import config, user_config_file
|
|
9
|
+
from ..models import DEFAULT_PROFILE, Env
|
|
16
10
|
from ..utils import console
|
|
17
11
|
|
|
18
12
|
app = typer.Typer()
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import subprocess
|
|
3
|
+
from typing import Annotated
|
|
3
4
|
|
|
4
5
|
import typer
|
|
5
6
|
from rich.prompt import Confirm
|
|
@@ -14,7 +15,9 @@ log = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@app.command()
|
|
17
|
-
def edit(
|
|
18
|
+
def edit(
|
|
19
|
+
env_name: Annotated[str, typer.Argument(help="Env to edit or create.")],
|
|
20
|
+
) -> None:
|
|
18
21
|
"""Create/edit an environment file in your editor."""
|
|
19
22
|
env = Env(name=env_name)
|
|
20
23
|
console.print(f"Opening [b]{env.name}[/] in your editor ...")
|
|
@@ -32,9 +35,9 @@ def ls() -> None:
|
|
|
32
35
|
|
|
33
36
|
@app.command()
|
|
34
37
|
def rm(
|
|
35
|
-
env_name:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
env_name: Annotated[
|
|
39
|
+
str, typer.Argument(help="Environment to remove.", autocompletion=complete_env)
|
|
40
|
+
],
|
|
38
41
|
) -> None:
|
|
39
42
|
"""Remove environment."""
|
|
40
43
|
env = Env(name=env_name)
|
|
@@ -44,9 +47,9 @@ def rm(
|
|
|
44
47
|
|
|
45
48
|
@app.command()
|
|
46
49
|
def show(
|
|
47
|
-
env_name:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
env_name: Annotated[
|
|
51
|
+
str, typer.Argument(help="Environment to display.", autocompletion=complete_env)
|
|
52
|
+
],
|
|
50
53
|
) -> None:
|
|
51
54
|
"""Display environment."""
|
|
52
55
|
env = Env(name=env_name)
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
+
# noqa: A005 - profile is shadowing python built-in 'profile'
|
|
1
2
|
import logging
|
|
2
3
|
import subprocess
|
|
3
4
|
import tempfile
|
|
4
|
-
from multiprocessing import Process
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import TYPE_CHECKING, Annotated
|
|
7
7
|
|
|
8
8
|
import typer
|
|
9
9
|
from getkey import getkey
|
|
10
|
-
from rich.prompt import
|
|
11
|
-
Confirm,
|
|
12
|
-
Prompt,
|
|
13
|
-
)
|
|
10
|
+
from rich.prompt import Confirm, Prompt
|
|
14
11
|
from rich.table import Table
|
|
15
12
|
|
|
16
13
|
from qontract_development_cli.watchdog import watch_files
|
|
@@ -37,57 +34,67 @@ from ..shell import (
|
|
|
37
34
|
from ..templates import template
|
|
38
35
|
from ..utils import console
|
|
39
36
|
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from multiprocessing import Process
|
|
39
|
+
|
|
40
40
|
app = typer.Typer()
|
|
41
41
|
log = logging.getLogger(__name__)
|
|
42
42
|
|
|
43
|
-
# see https://github.com/tiangolo/typer/issues/533
|
|
44
|
-
OptionalStr = Optional[str] # noqa: UP007
|
|
45
|
-
OptionalInt = Optional[int] # noqa: UP007
|
|
46
|
-
OptionalPath = Optional[Path] # noqa: UP007
|
|
47
|
-
|
|
48
43
|
|
|
49
44
|
@app.command()
|
|
50
45
|
def create( # noqa: PLR0913, PLR0917
|
|
51
46
|
profile_name: str = typer.Argument(..., help="Profile to create."),
|
|
52
|
-
integration_name:
|
|
53
|
-
integration_extra_args:
|
|
54
|
-
app_interface:
|
|
55
|
-
None,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
47
|
+
integration_name: Annotated[str | None, typer.Option()] = None,
|
|
48
|
+
integration_extra_args: Annotated[str | None, typer.Option()] = None,
|
|
49
|
+
app_interface: Annotated[
|
|
50
|
+
Path | None,
|
|
51
|
+
typer.Option(
|
|
52
|
+
file_okay=False,
|
|
53
|
+
dir_okay=True,
|
|
54
|
+
readable=True,
|
|
55
|
+
resolve_path=True,
|
|
56
|
+
exists=True,
|
|
57
|
+
help="Path to local app-interface instance git working copy.",
|
|
58
|
+
),
|
|
59
|
+
] = None,
|
|
60
|
+
app_interface_pr: Annotated[int | None, typer.Option(help="PR/MR to use")] = None,
|
|
61
|
+
app_interface_upstream: Annotated[
|
|
62
|
+
str, typer.Option(help="Upstream remote name")
|
|
63
|
+
] = "upstream",
|
|
64
|
+
qontract_schemas: Annotated[
|
|
65
|
+
Path | None,
|
|
66
|
+
typer.Option(
|
|
67
|
+
file_okay=False,
|
|
68
|
+
dir_okay=True,
|
|
69
|
+
readable=True,
|
|
70
|
+
resolve_path=True,
|
|
71
|
+
exists=True,
|
|
72
|
+
help="Path to local qontract-schemas git working copy.",
|
|
73
|
+
),
|
|
74
|
+
] = None,
|
|
75
|
+
qontract_schemas_pr: Annotated[
|
|
76
|
+
int | None, typer.Option(help="PR/MR to use")
|
|
77
|
+
] = None,
|
|
78
|
+
qontract_schemas_upstream: Annotated[
|
|
79
|
+
str, typer.Option(help="Upstream remote name")
|
|
80
|
+
] = "upstream",
|
|
81
|
+
qontract_reconcile: Annotated[
|
|
82
|
+
Path | None,
|
|
83
|
+
typer.Option(
|
|
84
|
+
file_okay=False,
|
|
85
|
+
dir_okay=True,
|
|
86
|
+
readable=True,
|
|
87
|
+
resolve_path=True,
|
|
88
|
+
exists=True,
|
|
89
|
+
help="Path to local qontract-reconcile git working copy.",
|
|
90
|
+
),
|
|
91
|
+
] = None,
|
|
92
|
+
qontract_reconcile_pr: Annotated[
|
|
93
|
+
int | None, typer.Option(help="PR/MR to use")
|
|
94
|
+
] = None,
|
|
95
|
+
qontract_reconcile_upstream: Annotated[
|
|
96
|
+
str, typer.Option(help="Upstream remote name")
|
|
97
|
+
] = "upstream",
|
|
91
98
|
) -> None:
|
|
92
99
|
"""Create a new profile to run an integration."""
|
|
93
100
|
if profile_name in [p.name for p in Profile.list_all()]:
|
|
@@ -132,9 +139,9 @@ def create( # noqa: PLR0913, PLR0917
|
|
|
132
139
|
|
|
133
140
|
@app.command()
|
|
134
141
|
def edit(
|
|
135
|
-
profile_name:
|
|
136
|
-
|
|
137
|
-
|
|
142
|
+
profile_name: Annotated[
|
|
143
|
+
str, typer.Argument(help="Profile to edit.", autocompletion=complete_profile)
|
|
144
|
+
],
|
|
138
145
|
) -> None:
|
|
139
146
|
"""Edit a profile in your editor."""
|
|
140
147
|
profile = Profile(name=profile_name)
|
|
@@ -153,10 +160,13 @@ def ls() -> None:
|
|
|
153
160
|
|
|
154
161
|
@app.command()
|
|
155
162
|
def rm(
|
|
156
|
-
profile_name:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
profile_name: Annotated[
|
|
164
|
+
str, typer.Argument(help="Profile to remove.", autocompletion=complete_profile)
|
|
165
|
+
],
|
|
166
|
+
*,
|
|
167
|
+
force: Annotated[
|
|
168
|
+
bool, typer.Option("--force", "-f", help="Do not ask any question.")
|
|
169
|
+
] = False,
|
|
160
170
|
) -> None:
|
|
161
171
|
"""Remove profile."""
|
|
162
172
|
profile = Profile(name=profile_name)
|
|
@@ -178,47 +188,53 @@ def show(
|
|
|
178
188
|
|
|
179
189
|
|
|
180
190
|
@app.command(no_args_is_help=True)
|
|
181
|
-
def run( # noqa:
|
|
182
|
-
env_name:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
profile_name:
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
help="
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
191
|
+
def run( # noqa: C901, PLR0912, PLR0913, PLR0915
|
|
192
|
+
env_name: Annotated[
|
|
193
|
+
str, typer.Argument(help="Environment to use.", autocompletion=complete_env)
|
|
194
|
+
],
|
|
195
|
+
profile_name: Annotated[
|
|
196
|
+
str, typer.Argument(help="Profile to run.", autocompletion=complete_profile)
|
|
197
|
+
],
|
|
198
|
+
*,
|
|
199
|
+
force_recreate: Annotated[
|
|
200
|
+
bool, typer.Option(help="Recreate all containers.")
|
|
201
|
+
] = False,
|
|
202
|
+
force_build: Annotated[bool, typer.Option(help="Rebuild all containers.")] = False,
|
|
203
|
+
qontract_reconcile_monitor_file_changes: Annotated[
|
|
204
|
+
bool,
|
|
205
|
+
typer.Option(
|
|
206
|
+
help="Restart integration when files changed in qontract-reconcile path"
|
|
207
|
+
),
|
|
208
|
+
] = True,
|
|
209
|
+
qontract_reconcile_monitor_file_extensions: Annotated[
|
|
210
|
+
str, typer.Option(help="Monitor these file extensions")
|
|
211
|
+
] = ".py .pyx .pyd",
|
|
212
|
+
qontract_schemas_monitor_file_changes: Annotated[
|
|
213
|
+
bool,
|
|
214
|
+
typer.Option(
|
|
215
|
+
help="Rebuild bundle and restart qontract-server when files changed in qontract-schemas path"
|
|
216
|
+
),
|
|
217
|
+
] = True,
|
|
218
|
+
qontract_schemas_monitor_file_extensions: Annotated[
|
|
219
|
+
str, typer.Option(help="Monitor these file extensions")
|
|
220
|
+
] = ".json .yml .yaml",
|
|
221
|
+
app_interface_monitor_file_changes: Annotated[
|
|
222
|
+
bool,
|
|
223
|
+
typer.Option(
|
|
224
|
+
help="Rebuild bundle and restart qontract-server when files changed in app-interface path"
|
|
225
|
+
),
|
|
226
|
+
] = True,
|
|
227
|
+
app_interface_monitor_file_extensions: Annotated[
|
|
228
|
+
str, typer.Option(help="Monitor these file extensions")
|
|
229
|
+
] = ".json .yml .yaml",
|
|
230
|
+
skip_initial_make_bundle: Annotated[
|
|
231
|
+
bool,
|
|
232
|
+
typer.Option(help="Do not run 'make bundle' before starting the integration"),
|
|
233
|
+
] = False,
|
|
234
|
+
no_dry_run: Annotated[
|
|
235
|
+
bool,
|
|
236
|
+
typer.Option(help="Disable dry-run mode"),
|
|
237
|
+
] = False,
|
|
222
238
|
) -> None:
|
|
223
239
|
"""Run a profile."""
|
|
224
240
|
env = Env(name=env_name)
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import platform
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
4
|
|
|
6
|
-
from appdirs import AppDirs
|
|
7
|
-
from
|
|
5
|
+
from appdirs import AppDirs
|
|
6
|
+
from pydantic_settings import (
|
|
7
|
+
BaseSettings,
|
|
8
|
+
PydanticBaseSettingsSource,
|
|
9
|
+
SettingsConfigDict,
|
|
10
|
+
YamlConfigSettingsSource,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
13
|
from .utils import yaml
|
|
10
14
|
|
|
@@ -20,14 +24,6 @@ user_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
|
20
24
|
is_mac = platform.system().lower() == "darwin"
|
|
21
25
|
|
|
22
26
|
|
|
23
|
-
def yaml_config_settings_source(settings: BaseSettings) -> dict[str, Any]:
|
|
24
|
-
encoding = settings.__config__.env_file_encoding
|
|
25
|
-
if user_config_file.exists():
|
|
26
|
-
cfg = yaml.safe_load(user_config_file.read_text(encoding))
|
|
27
|
-
return cfg if cfg else {}
|
|
28
|
-
return {}
|
|
29
|
-
|
|
30
|
-
|
|
31
27
|
class Config(BaseSettings):
|
|
32
28
|
debug: bool = False
|
|
33
29
|
defaults_profile: str = "defaults"
|
|
@@ -37,31 +33,41 @@ class Config(BaseSettings):
|
|
|
37
33
|
profiles_dir: Path = user_config_dir / "profiles"
|
|
38
34
|
worktrees_dir: Path = user_cache_dir / "worktrees"
|
|
39
35
|
|
|
40
|
-
def __init__(self
|
|
41
|
-
super().__init__(
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
super().__init__()
|
|
42
38
|
self.environments_dir.mkdir(parents=True, exist_ok=True)
|
|
43
39
|
self.profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
@classmethod
|
|
42
|
+
def settings_customise_sources(
|
|
43
|
+
cls,
|
|
44
|
+
settings_cls: type[BaseSettings],
|
|
45
|
+
init_settings: PydanticBaseSettingsSource,
|
|
46
|
+
env_settings: PydanticBaseSettingsSource,
|
|
47
|
+
dotenv_settings: PydanticBaseSettingsSource,
|
|
48
|
+
file_secret_settings: PydanticBaseSettingsSource,
|
|
49
|
+
) -> tuple[PydanticBaseSettingsSource, ...]:
|
|
50
|
+
return (
|
|
51
|
+
init_settings,
|
|
52
|
+
env_settings,
|
|
53
|
+
YamlConfigSettingsSource(settings_cls),
|
|
54
|
+
)
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
model_config = SettingsConfigDict(
|
|
57
|
+
yaml_file=user_config_file,
|
|
58
|
+
yaml_file_encoding="utf-8",
|
|
59
|
+
env_prefix="qontract_development_",
|
|
60
|
+
)
|
|
55
61
|
|
|
56
62
|
def save(self) -> None:
|
|
57
63
|
user_config_file.write_text(
|
|
58
64
|
yaml.dump(
|
|
59
|
-
self.
|
|
65
|
+
self.model_dump(),
|
|
60
66
|
explicit_start=True,
|
|
61
67
|
indent=4,
|
|
62
68
|
default_flow_style=False,
|
|
63
69
|
),
|
|
64
|
-
encoding=self.
|
|
70
|
+
encoding=self.model_config["yaml_file_encoding"],
|
|
65
71
|
)
|
|
66
72
|
|
|
67
73
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import contextlib
|
|
1
2
|
import copy
|
|
2
3
|
import os
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
|
+
from typing import Any, ClassVar, Self
|
|
5
6
|
|
|
6
|
-
from pydantic import
|
|
7
|
-
BaseModel,
|
|
8
|
-
validator,
|
|
9
|
-
)
|
|
7
|
+
from pydantic import BaseModel, model_validator
|
|
10
8
|
|
|
11
9
|
from .config import config
|
|
12
10
|
from .utils import yaml
|
|
@@ -24,12 +22,12 @@ class EnvSettings(BaseModel):
|
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
class ProfileSettings(BaseModel):
|
|
27
|
-
command: str = "
|
|
25
|
+
command: str = "run-integration"
|
|
28
26
|
command_extra_args: str = ""
|
|
29
27
|
container_uid: int = os.getuid()
|
|
30
28
|
debugger: str = "debugpy"
|
|
31
29
|
dry_run: bool = True
|
|
32
|
-
integration_extra_args: str = ""
|
|
30
|
+
integration_extra_args: str | int = ""
|
|
33
31
|
integration_name: str = ""
|
|
34
32
|
log_level: str = "INFO"
|
|
35
33
|
app_interface_path: Path | None = None
|
|
@@ -55,39 +53,42 @@ class ProfileSettings(BaseModel):
|
|
|
55
53
|
localstack_compose_file: Path | None
|
|
56
54
|
skip_initial_make_bundle: bool = False
|
|
57
55
|
|
|
58
|
-
@
|
|
59
|
-
def default_localstack_compose_file(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else values["qontract_reconcile_path"] / "dev/localstack/docker-compose.yml"
|
|
56
|
+
@model_validator(mode="after")
|
|
57
|
+
def default_localstack_compose_file(self) -> Self:
|
|
58
|
+
self.localstack_compose_file = (
|
|
59
|
+
self.localstack_compose_file
|
|
60
|
+
or self.qontract_reconcile_path / "dev/localstack/docker-compose.yml"
|
|
64
61
|
)
|
|
62
|
+
return self
|
|
65
63
|
|
|
66
64
|
|
|
67
65
|
class Base(BaseModel):
|
|
68
66
|
name: str
|
|
69
67
|
default: bool = False
|
|
70
|
-
|
|
68
|
+
root: ClassVar[Path]
|
|
71
69
|
|
|
72
70
|
def __lt__(self, other: "Base") -> bool:
|
|
73
71
|
return self.name < other.name
|
|
74
72
|
|
|
75
|
-
@
|
|
76
|
-
def name_remove_suffix(
|
|
77
|
-
p = Path(
|
|
78
|
-
|
|
73
|
+
@model_validator(mode="after")
|
|
74
|
+
def name_remove_suffix(self) -> Self:
|
|
75
|
+
p = Path(self.name)
|
|
76
|
+
self.name = str(p.parent / p.stem)
|
|
77
|
+
return self
|
|
79
78
|
|
|
80
79
|
@property
|
|
81
80
|
def file(self) -> Path:
|
|
82
|
-
p = self.
|
|
81
|
+
p = self.root / self.name
|
|
83
82
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
84
83
|
return p.with_suffix(".yml")
|
|
85
84
|
|
|
86
85
|
@classmethod
|
|
87
86
|
def list_all(cls) -> list["Base"]:
|
|
88
87
|
items = []
|
|
89
|
-
for f in [f for f in list(cls.
|
|
90
|
-
items.append(
|
|
88
|
+
for f in [f for f in list(cls.root.glob("**/*")) if f.is_file()]:
|
|
89
|
+
items.append( # noqa: PERF401
|
|
90
|
+
cls(name=str(f.relative_to(cls.root)))
|
|
91
|
+
)
|
|
91
92
|
return items
|
|
92
93
|
|
|
93
94
|
@property
|
|
@@ -101,18 +102,18 @@ class Base(BaseModel):
|
|
|
101
102
|
@property
|
|
102
103
|
def settings_as_dict(self) -> dict[str, Any]:
|
|
103
104
|
values = self.default_settings_as_dict
|
|
104
|
-
|
|
105
|
+
with contextlib.suppress(FileNotFoundError):
|
|
105
106
|
values.update(
|
|
106
107
|
**yaml.safe_load(
|
|
107
|
-
self.file.read_text(
|
|
108
|
+
self.file.read_text(
|
|
109
|
+
encoding=config.model_config["yaml_file_encoding"]
|
|
110
|
+
)
|
|
108
111
|
)
|
|
109
112
|
)
|
|
110
|
-
except FileNotFoundError:
|
|
111
|
-
pass
|
|
112
113
|
return values
|
|
113
114
|
|
|
114
115
|
def dump(self) -> None:
|
|
115
|
-
values = self.settings.dict( # type: ignore
|
|
116
|
+
values = self.settings.dict( # type: ignore[attr-defined]
|
|
116
117
|
exclude_defaults=not self.default, exclude_unset=not self.default
|
|
117
118
|
)
|
|
118
119
|
self.file.write_text(
|
|
@@ -126,7 +127,7 @@ class Base(BaseModel):
|
|
|
126
127
|
|
|
127
128
|
|
|
128
129
|
class Env(Base):
|
|
129
|
-
|
|
130
|
+
root: ClassVar[Path] = config.environments_dir
|
|
130
131
|
default: bool = True
|
|
131
132
|
settings: EnvSettings
|
|
132
133
|
|
|
@@ -141,7 +142,7 @@ class Env(Base):
|
|
|
141
142
|
|
|
142
143
|
|
|
143
144
|
class Profile(Base):
|
|
144
|
-
|
|
145
|
+
root: ClassVar[Path] = config.profiles_dir
|
|
145
146
|
settings: ProfileSettings
|
|
146
147
|
|
|
147
148
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
@@ -155,17 +156,17 @@ class Profile(Base):
|
|
|
155
156
|
defaults = {
|
|
156
157
|
"integration_name": "changeme",
|
|
157
158
|
"integration_extra_args": "",
|
|
159
|
+
"localstack_compose_file": None,
|
|
158
160
|
}
|
|
159
|
-
|
|
161
|
+
with contextlib.suppress(FileNotFoundError, AttributeError, NameError):
|
|
160
162
|
defaults.update(
|
|
161
163
|
yaml.safe_load(
|
|
162
164
|
DEFAULT_PROFILE.file.read_text(
|
|
163
|
-
encoding=config.
|
|
165
|
+
encoding=config.model_config["yaml_file_encoding"]
|
|
164
166
|
)
|
|
165
167
|
)
|
|
166
168
|
)
|
|
167
|
-
|
|
168
|
-
pass
|
|
169
|
+
|
|
169
170
|
return defaults
|
|
170
171
|
|
|
171
172
|
def dump(self) -> None:
|
|
@@ -12,10 +12,7 @@ from typing import Any
|
|
|
12
12
|
|
|
13
13
|
from .models import Profile
|
|
14
14
|
from .templates import template
|
|
15
|
-
from .utils import
|
|
16
|
-
EndlessProcess,
|
|
17
|
-
console,
|
|
18
|
-
)
|
|
15
|
+
from .utils import EndlessProcess, console
|
|
19
16
|
|
|
20
17
|
log = logging.getLogger(__name__)
|
|
21
18
|
|
|
@@ -26,12 +23,13 @@ _docker_compose_bin = (
|
|
|
26
23
|
|
|
27
24
|
def compose_up(
|
|
28
25
|
compose_file: Path,
|
|
26
|
+
*,
|
|
29
27
|
force_recreate: bool = False,
|
|
30
28
|
remove_orphan: bool = True,
|
|
31
29
|
build: bool = False,
|
|
32
30
|
) -> None:
|
|
33
31
|
log.info("Starting all containers")
|
|
34
|
-
compose_cmd = _docker_compose_bin
|
|
32
|
+
compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "up", "-d"]
|
|
35
33
|
if force_recreate:
|
|
36
34
|
compose_cmd.append("--force-recreate")
|
|
37
35
|
if remove_orphan:
|
|
@@ -43,12 +41,7 @@ def compose_up(
|
|
|
43
41
|
|
|
44
42
|
def compose_restart(compose_file: Path, container: str) -> None:
|
|
45
43
|
log.info(f"Restarting {container} container")
|
|
46
|
-
compose_cmd = _docker_compose_bin
|
|
47
|
-
"-f",
|
|
48
|
-
str(compose_file),
|
|
49
|
-
"restart",
|
|
50
|
-
container,
|
|
51
|
-
]
|
|
44
|
+
compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "restart", container]
|
|
52
45
|
subprocess.run(compose_cmd, check=True)
|
|
53
46
|
|
|
54
47
|
|
|
@@ -60,12 +53,12 @@ def container_restart(container: str) -> None:
|
|
|
60
53
|
|
|
61
54
|
def compose_down(compose_file: Path) -> None:
|
|
62
55
|
log.info("Stopping all containers")
|
|
63
|
-
compose_cmd = _docker_compose_bin
|
|
56
|
+
compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "down"]
|
|
64
57
|
subprocess.run(compose_cmd, check=True)
|
|
65
58
|
|
|
66
59
|
|
|
67
60
|
def compose_log_tail(compose_file: Path) -> Process:
|
|
68
|
-
compose_cmd = _docker_compose_bin
|
|
61
|
+
compose_cmd = [*_docker_compose_bin, "-f", str(compose_file), "logs", "--follow"]
|
|
69
62
|
p = EndlessProcess(target=subprocess.run, args=(compose_cmd,))
|
|
70
63
|
p.start()
|
|
71
64
|
return p
|
|
@@ -74,7 +67,7 @@ def compose_log_tail(compose_file: Path) -> Process:
|
|
|
74
67
|
def compose_list_projects() -> list[dict[str, Any]]:
|
|
75
68
|
return json.loads(
|
|
76
69
|
subprocess.run(
|
|
77
|
-
_docker_compose_bin
|
|
70
|
+
[*_docker_compose_bin, "ls", "--format", "json"],
|
|
78
71
|
capture_output=True,
|
|
79
72
|
check=True,
|
|
80
73
|
).stdout
|
|
@@ -86,7 +79,7 @@ def compose_stop_project(project_name: str) -> None:
|
|
|
86
79
|
for p in compose_list_projects():
|
|
87
80
|
if p["Name"] == project_name:
|
|
88
81
|
subprocess.run(
|
|
89
|
-
_docker_compose_bin
|
|
82
|
+
[*_docker_compose_bin, "-f", p["ConfigFiles"], "down"], check=True
|
|
90
83
|
)
|
|
91
84
|
|
|
92
85
|
|
|
@@ -97,7 +90,9 @@ def make_bundle(app_interface_path: Path, qontract_server_path: Path) -> None:
|
|
|
97
90
|
"APP_INTERFACE_PATH": str(app_interface_path.expanduser().absolute())
|
|
98
91
|
})
|
|
99
92
|
subprocess.run(
|
|
100
|
-
["make", "-C", str(qontract_server_path), "bundle"],
|
|
93
|
+
["make", "-C", str(qontract_server_path), "bundle"],
|
|
94
|
+
env=shell_env,
|
|
95
|
+
check=False,
|
|
101
96
|
)
|
|
102
97
|
|
|
103
98
|
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from jinja2 import
|
|
5
|
-
Environment,
|
|
6
|
-
FileSystemLoader,
|
|
7
|
-
)
|
|
4
|
+
from jinja2 import Environment, FileSystemLoader
|
|
8
5
|
|
|
9
6
|
environment = Environment(
|
|
10
7
|
loader=FileSystemLoader(str(Path(__file__).parent / "templates")),
|
|
11
8
|
trim_blocks=True,
|
|
12
9
|
lstrip_blocks=True,
|
|
13
10
|
keep_trailing_newline=True,
|
|
11
|
+
autoescape=True,
|
|
14
12
|
)
|
|
15
13
|
|
|
16
14
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
from multiprocessing import Process
|
|
2
|
-
from pathlib import
|
|
3
|
-
Path,
|
|
4
|
-
PosixPath,
|
|
5
|
-
)
|
|
2
|
+
from pathlib import Path, PosixPath
|
|
6
3
|
from typing import Any
|
|
7
4
|
|
|
8
5
|
import yaml
|
|
@@ -27,6 +24,6 @@ console = Console(record=True)
|
|
|
27
24
|
|
|
28
25
|
class EndlessProcess(Process):
|
|
29
26
|
def run(self) -> None:
|
|
30
|
-
if self._target: # type: ignore
|
|
27
|
+
if self._target: # type: ignore[attr-defined]
|
|
31
28
|
while True:
|
|
32
|
-
self._target(*self._args, **self._kwargs) # type: ignore
|
|
29
|
+
self._target(*self._args, **self._kwargs) # type: ignore[attr-defined]
|
|
@@ -4,11 +4,7 @@ from multiprocessing import Process
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
from rich.logging import RichHandler
|
|
7
|
-
from watchfiles import
|
|
8
|
-
Change,
|
|
9
|
-
DefaultFilter,
|
|
10
|
-
watch,
|
|
11
|
-
)
|
|
7
|
+
from watchfiles import Change, DefaultFilter, watch
|
|
12
8
|
|
|
13
9
|
log = logging.getLogger(__name__)
|
|
14
10
|
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "qontract-development-cli"
|
|
3
|
-
version = "0.14.5"
|
|
4
|
-
description = "Helper tool for qontract-reconcile development"
|
|
5
|
-
authors = ["Christian Assing <cassing@redhat.com>"]
|
|
6
|
-
license = "MIT"
|
|
7
|
-
readme = "README.md"
|
|
8
|
-
repository = "http://github.com/chassing/qontract-development-cli"
|
|
9
|
-
homepage = "http://github.com/chassing/qontract-development-cli"
|
|
10
|
-
|
|
11
|
-
[tool.poetry.dependencies]
|
|
12
|
-
python = ">=3.11,<4.0"
|
|
13
|
-
Jinja2 = "^3.1.2"
|
|
14
|
-
typer = "^0.9.0"
|
|
15
|
-
rich = "^13.7.0"
|
|
16
|
-
pydantic = "^1.10.2"
|
|
17
|
-
appdirs = "^1.4.4"
|
|
18
|
-
PyYAML = "^6.0.1"
|
|
19
|
-
getkey = "^0.6.5"
|
|
20
|
-
watchfiles = "^0.21.0"
|
|
21
|
-
|
|
22
|
-
[tool.poetry.dev-dependencies]
|
|
23
|
-
types-PyYAML = "^6.0.12"
|
|
24
|
-
|
|
25
|
-
[tool.poetry.group.dev.dependencies]
|
|
26
|
-
pytest = "^8.0.0"
|
|
27
|
-
mypy = "^1.8.0"
|
|
28
|
-
types-pyyaml = "^6.0.12.11"
|
|
29
|
-
ruff = "^0.4.8"
|
|
30
|
-
|
|
31
|
-
[build-system]
|
|
32
|
-
requires = ["poetry-core>=1.0.0"]
|
|
33
|
-
build-backend = "poetry.core.masonry.api"
|
|
34
|
-
|
|
35
|
-
[tool.poetry.scripts]
|
|
36
|
-
qd = "qontract_development_cli.__main__:app"
|
|
37
|
-
|
|
38
|
-
[tool.mypy]
|
|
39
|
-
plugins = "pydantic.mypy"
|
|
40
|
-
enable_error_code = "truthy-bool, redundant-expr"
|
|
41
|
-
no_implicit_optional = true
|
|
42
|
-
check_untyped_defs = true
|
|
43
|
-
warn_unused_ignores = true
|
|
44
|
-
show_error_codes = true
|
|
45
|
-
disallow_untyped_defs = true
|
|
46
|
-
disallow_incomplete_defs = true
|
|
47
|
-
|
|
48
|
-
[[tool.mypy.overrides]]
|
|
49
|
-
module = ["getkey.*"]
|
|
50
|
-
ignore_missing_imports = true
|
|
51
|
-
|
|
52
|
-
[tool.commitizen]
|
|
53
|
-
name = "cz_conventional_commits"
|
|
54
|
-
version = "0.11.1"
|
|
55
|
-
version_files = ["pyproject.toml:version"]
|
|
56
|
-
update_changelog_on_bump = true
|
|
57
|
-
major_version_zero = true
|
|
58
|
-
change_type_map = { "feat" = "Features", "fix" = "Bug Fixes" }
|
|
59
|
-
tag_format = "v$version"
|
|
60
|
-
|
|
61
|
-
[tool.ruff]
|
|
62
|
-
line-length = 88
|
|
63
|
-
target-version = 'py311'
|
|
64
|
-
required-version = "0.4.8"
|
|
65
|
-
src = ["qontract_development_cli"]
|
|
66
|
-
extend-exclude = [".local", ".cache"]
|
|
67
|
-
fix = true
|
|
68
|
-
|
|
69
|
-
[tool.ruff.lint]
|
|
70
|
-
preview = true
|
|
71
|
-
# defaults are ["E4", "E7", "E9", "F"]
|
|
72
|
-
extend-select = [
|
|
73
|
-
# flake8 default rules
|
|
74
|
-
"E1", # preview rule
|
|
75
|
-
"E2", # preview rule
|
|
76
|
-
"W",
|
|
77
|
-
# isort
|
|
78
|
-
"I",
|
|
79
|
-
# pylint
|
|
80
|
-
"PL",
|
|
81
|
-
# pyupgrade
|
|
82
|
-
"UP",
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
[tool.ruff.format]
|
|
86
|
-
preview = true
|
|
87
|
-
|
|
88
|
-
[tool.ruff.lint.isort]
|
|
89
|
-
known-first-party = ["qontract_development_cli"]
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
|
|
4
|
-
packages = \
|
|
5
|
-
['qontract_development_cli', 'qontract_development_cli.commands']
|
|
6
|
-
|
|
7
|
-
package_data = \
|
|
8
|
-
{'': ['*'], 'qontract_development_cli': ['templates/*']}
|
|
9
|
-
|
|
10
|
-
install_requires = \
|
|
11
|
-
['Jinja2>=3.1.2,<4.0.0',
|
|
12
|
-
'PyYAML>=6.0.1,<7.0.0',
|
|
13
|
-
'appdirs>=1.4.4,<2.0.0',
|
|
14
|
-
'getkey>=0.6.5,<0.7.0',
|
|
15
|
-
'pydantic>=1.10.2,<2.0.0',
|
|
16
|
-
'rich>=13.7.0,<14.0.0',
|
|
17
|
-
'typer>=0.9.0,<0.10.0',
|
|
18
|
-
'watchfiles>=0.21.0,<0.22.0']
|
|
19
|
-
|
|
20
|
-
entry_points = \
|
|
21
|
-
{'console_scripts': ['qd = qontract_development_cli.__main__:app']}
|
|
22
|
-
|
|
23
|
-
setup_kwargs = {
|
|
24
|
-
'name': 'qontract-development-cli',
|
|
25
|
-
'version': '0.14.5',
|
|
26
|
-
'description': 'Helper tool for qontract-reconcile development',
|
|
27
|
-
'long_description': "# Qontract Development CLI\n\n[][pypi-link]\n[![PyPI platforms][pypi-platforms]][pypi-link]\n\n\nQontract Development CLI supports your daily [qontract-reconcile][qontract-reconcile] development work.\n\n[toc]\n\n## Installation\n\nYou can install this library from [PyPI][pypi-link] with `pip`:\n\n```shell\npython3 -m pip install qontract-development-cli\n```\n\nOr install it with `pipx`:\n\n```shell\npipx install qontract-development-cli\n```\n\nYou can also use `pipx` to run the library without installing it:\n\n```shell\npipx run qontract-development-cli\n```\n\n## Quickstart\n\n* Create initial configuration\n\n ```shell\n qd config init\n ```\n\n \n\n* Create a profile `sql-query` to run the `sql-query` integration\n\n ```shell\n qd profile create sql-query\n ```\n\n \n\n* Run `sql-query` profile\n\n ```shell\n qd profile run dev sql-query\n ```\n\n \n\n## Features\n\nQontract Development CLI currently provides the following features (get help with `-h` or `--help`):\n\n* Run `qontract-reconcile` and `qontract-server` as docker containers on your local machine\n* Support for different App-Interface environments (dev, prod, ...) via the `env` command\n* Configure your [qontract-reconcile integration][qontract-reconcile] with the `profile` command\n* Support pull request reviews (see `profile create`)\n* Bootstrap your initial configurations with the `config` command\n* Shell auto-completion (see `qd --help`)\n* Automatically restart `qontract-reconcile` container when files change\n* Automatically rebuild the App-Interface bundle and restart the `qontract-server` container when files change\n\n## Examples\n\nTake a look at the [examples](examples) directory for different profile examples.\n\n## Usage\n\nThe following sections describe all available commands and their options.\n\n### Config\n\nManage global qontract-development-cli configuration.\n\n`qd config [sub-cmd] --help`\n\n* **edit**: open the configuration file in your favorite editor\n* **init**: create a default configuration\n\n#### Global Settings\n\n| **Key** | **Description** | **Default** |\n| --------------------------- | ------------------------------------- | ------------------------------------ |\n| debug | Enable/disable debug mode | false |\n| defaults_profile | Name of defaults profile | defaults |\n| docker_compose_project_name | Docker compose project name | qontract-development |\n| editor | Your favorite editor | $EDITOR or vim |\n| environments_dir | Directory to store environment files | User config directory / environments |\n| profiles_dir | Directory to store profile files | User config directory / profiles |\n| worktrees_dir | Directory to store git repo worktrees | User cache directory / worktrees |\n\n### Environments\n\nAn environment specifies app-interface instance settings, e.g., **dev** vs. **prod** config and path to the actual app-interface instance.\n\n`qd env [sub-cmd] --help`\n\n* **edit**: Create/edit an environment file in your editor.\n* **ls**: List all available environments.\n* **rm**: Remove environment.\n* **show**: Display environment.\n\n#### Environment Settings\n\n| **Key** | **Description** | **Default** |\n| ---------------------------------- | ------------------------------------ | ---------------------------------------------- |\n| **app_interface_path** | Path to local app-interface instance | ~/workspace/app-interface |\n| app_interface_state_bucket | S3 bucket | empty |\n| app_interface_state_bucket_account | AWS S3 account | empty |\n| **config** | app-interface config | ~/workspace/qontract-reconcile/config.dev.toml |\n| gitlab_pr_submitter_queue_url | Gitlab pr submitter queue url | |\n| run_qontract_reconcile | Run qontract-reconcile container | true |\n| run_qontract_server | Run qontract-server container | true |\n| run_vault | Run vault container | false |\n\n> :point_right: **Bold keys** are mandatory or should be customized.\n\n### Profiles\n\nA profile specifies all settings to run a qontract-reconcile integration (e.g., *sql-query*).\n\n`qd profile [sub-cmd] --help`\n\n* **create**: Create a new profile to run an integration.\n\n Supports the creation of a new profile from an open PR/MR. See `$ qd profile create --help` for all available options.\n\n* **edit**: Edit a profile in your editor.\n* **ls**: List all available profiles.\n* **rm**: Remove profile.\n* **run**: Run a profile.\n* **show**: Display profile.\n\n#### --no-dry-run\n\nBy default, `qd` runs integrations in `dry-run` mode if not explicitly specified differently in the profile config.\nThere is also a `--no-dry-run` flag which lets you disable `dry-run` mode from the command line.\n\n```shell\nqd profile run dev my-integration --no-dry-run\n```\n\nThe command line flag takes precedence over any configuration you might have in the profile settings.\n\n#### Profile Settings\n\n| **Key** | **Description** | **Default** |\n| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |\n| additional_environment | Dictionary of additional environment variables to pass to the qontract-reconcile container | `{}` |\n| container_uid | Change ownership of /recconcile files in container to this user id | current UID |\n| command | Command to run in the qontract-reconcile container. | `dockerfiles/hack/run-integration.py` |\n| command_extra_args | Additional arguments to pass to the command. | |\n| debugger | Python debugger | `debugpy` |\n| dry_run | Run --dry-run mode | `true` |\n| extra_hosts | List of 'HOSTNAME:IP' mapping entries for qontract-reconcile `/etc/hosts`. See [extra_hosts](https://docs.docker.com/compose/compose-file/#extra_hosts) docker compose file. | `[]` |\n| **integration_name** | Intergration name | |\n| **integration_extra_args** | Intergration extra arguments | |\n| **internal_redhat_ca** | Inject Red Hat internal CAs and `REQUESTS_CA_BUNDLE` environment variable | `false` |\n| log_level | Log level | `info` |\n| app_interface_path | App-interface instance path. (Overrides *env.app_interface_path*) | |\n| app_interface_pr | App-interface PR/MR number | |\n| app_interface_upstream | Upstream remote name | `upstream` |\n| qontract_reconcile_build_image | Build qontract-reconcile image | `true` |\n| qontract_reconcile_image | Qontract-reconcile image | `quay.io/app-sre/qontract-reconcile:latest` |\n| qontract_reconcile_path | Qontract-reconcile path | `~/workspace/qontract-reconcile` |\n| qontract_reconcile_pr | Qontract-reconcile PR/MR number | |\n| qontract_reconcile_upstream | Upstream remote name | `upstream` |\n| qontract_server_build_image | Build qontract-server image | `true` |\n| qontract_server_image | Qontract-server image | `quay.io/app-sre/qontract-server:lates`t |\n| qontract_server_path | Qontract-server path | `~/workspace/qontract-server` |\n| qontract_schemas_path | Qontract-schemas path | `~/workspace/qontract-schemas` |\n| qontract_schemas_pr | Qontract-schemas PR/MR number | |\n| qontract_schemas_upstream | Upstream remote name | `upstream` |\n| run_once | If 'true', execute the integration once and exit | `true` |\n| sleep_duration_secs | If not *run_once*, sleep duration until integration runs again | `10` |\n| localstack | Run localstack container and set AWS related environment variables | `false` |\n| localstack_compose_file | Path to your Localstack docker-compose file | `qontract_server_path` / `dev/localstack/docker-compose.yml` |\n| skip_initial_make_bundle | Skip initial make bundle step | `false` |\n\n> :point_right: **Bold keys** are mandatory or should be customized.\n\n### PR/MR support\n\nIt's a pretty handy feature to create a profile from a pull request (merge request). E.g.:\n\n```shell\nqd profile create --app-interface PATH_TO_YOUR_LOCAL/app-interface-dev-data --app-interface-pr NUMBER --qontract-schemas-pr NUMBER --qontract-reconcile-pr NUMBER --integration-name glitchtip --integration-extra-args '' glitchtip-pr-check\n```\n\nWhich results in this profile:\n\n```shell\n$ qd profile show glitchtip-pr-check\n---\napp_interface_path: PATH_TO_YOUR_LOCAL/app-interface-dev-data\napp_interface_pr: NUMBER\nintegration_name: glitchtip\nqontract_reconcile_pr: NUMBER\nqontract_schemas_pr: NUMBER\n```\n\nRunning this profile will:\n\n* Create new git worktrees (see `config.worktrees_dir`) for app-interface-dev-data, qontract-schema, and qontract-reconcile PRs\n* Start the containers with the adapted path to these worktrees\n* Restarting the profile will fetch PR updates\n\n> :point_right: A git worktree cleanup isn't implemented yet\n\n## Development\n\n[](https://github.com/astral-sh/ruff)\n[](https://mypy-lang.org/)\n\n### Release\n\n* Bump the version in `pyproject.toml`\n* Update the `CHANGELOG.md`\n* Commit the changes\n\n[pypi-link]: https://pypi.org/project/qontract-development-cli/\n[pypi-platforms]: https://img.shields.io/pypi/pyversions/qontract-development-cli\n[qontract-reconcile]: https://github.com/app-sre/qontract-reconcile\n",
|
|
28
|
-
'author': 'Christian Assing',
|
|
29
|
-
'author_email': 'cassing@redhat.com',
|
|
30
|
-
'maintainer': 'None',
|
|
31
|
-
'maintainer_email': 'None',
|
|
32
|
-
'url': 'http://github.com/chassing/qontract-development-cli',
|
|
33
|
-
'packages': packages,
|
|
34
|
-
'package_data': package_data,
|
|
35
|
-
'install_requires': install_requires,
|
|
36
|
-
'entry_points': entry_points,
|
|
37
|
-
'python_requires': '>=3.11,<4.0',
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
setup(**setup_kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|