gitops 0.11.3__tar.gz → 0.11.5__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.
- {gitops-0.11.3 → gitops-0.11.5}/PKG-INFO +10 -5
- {gitops-0.11.3 → gitops-0.11.5}/README.md +9 -4
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/Chart.yaml +1 -1
- {gitops-0.11.3 → gitops-0.11.5}/gitops/__init__.py +1 -1
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/utils/__init__.py +32 -4
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/utils/git.py +38 -5
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/deployer/deploy.py +5 -2
- {gitops-0.11.3 → gitops-0.11.5}/pyproject.toml +2 -1
- {gitops-0.11.3 → gitops-0.11.5}/release.py +2 -1
- {gitops-0.11.3 → gitops-0.11.5}/tests/test_deploy.py +5 -5
- gitops-0.11.5/tests/test_git.py +50 -0
- {gitops-0.11.3 → gitops-0.11.5}/uv.lock +15 -1
- {gitops-0.11.3 → gitops-0.11.5}/.dockerignore +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/.github/workflows/publish.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/.github/workflows/test.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/.gitignore +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/.mise.toml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/.pre-commit-config.yaml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/Dockerfile +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/LICENSE +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/.helmignore +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/README.md +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/_helpers.tpl +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/certificate.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/deployment.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/environment.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/ingress.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/issuer.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/secrets.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/service.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/templates/serviceaccount.yaml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/charts/gitops/values.yaml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/docker-compose.yml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/common/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/common/app.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/common/utils.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/core.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/db.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/main.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/py.typed +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/settings.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/shorthands.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/apps.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/async_runner.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/cli.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/exceptions.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/images.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/kube.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/tags.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops/utils/yaml.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/README.md +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/app.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/main.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/settings.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/types.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/utils/github.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/utils/slack.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/deployer/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/deployer/hooks.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/deployer/worker.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/status_updater/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/gitops_server/workers/status_updater/worker.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/poetry.lock +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/ruff.toml +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/secrets.example.env +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/setup.cfg +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/__init__.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/sample_data.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/test_app.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/test_core.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/test_gitops_server.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/test_utils.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/utils.py +0 -0
- {gitops-0.11.3 → gitops-0.11.5}/tests/webhook_sample_data.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: gitops
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.5
|
|
4
4
|
Summary: Manage multiple apps across one or more k8s clusters.
|
|
5
5
|
Author-email: Jarek Głowacki <jarekwg@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -84,7 +84,12 @@ subjects:
|
|
|
84
84
|
namespace: gitops
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
1.
|
|
90
|
-
|
|
87
|
+
## Contribution
|
|
88
|
+
We're using [releaseplease](https://github.com/googleapis/release-please), to publish a new version do the following:
|
|
89
|
+
1. Checkout a feature branch and make the changes
|
|
90
|
+
1. Make sure to follow instructions for writing commits by [releaseplease](https://github.com/googleapis/release-please)
|
|
91
|
+
1. fix: which represents bug fixes, and correlates to a SemVer patch.
|
|
92
|
+
2. feat: which represents a new feature, and correlates to a SemVer minor.
|
|
93
|
+
3. feat!:, or fix!:, refactor!:, etc., which represent a breaking change (indicated by the !) and will result in a SemVer major.
|
|
94
|
+
2. Push changes and get the PR approved
|
|
95
|
+
3. Once it is merged; an additional PR containing the release changes needs to be merged to create a release.
|
|
@@ -60,7 +60,12 @@ subjects:
|
|
|
60
60
|
namespace: gitops
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
1.
|
|
66
|
-
|
|
63
|
+
## Contribution
|
|
64
|
+
We're using [releaseplease](https://github.com/googleapis/release-please), to publish a new version do the following:
|
|
65
|
+
1. Checkout a feature branch and make the changes
|
|
66
|
+
1. Make sure to follow instructions for writing commits by [releaseplease](https://github.com/googleapis/release-please)
|
|
67
|
+
1. fix: which represents bug fixes, and correlates to a SemVer patch.
|
|
68
|
+
2. feat: which represents a new feature, and correlates to a SemVer minor.
|
|
69
|
+
3. feat!:, or fix!:, refactor!:, etc., which represent a breaking change (indicated by the !) and will result in a SemVer major.
|
|
70
|
+
2. Push changes and get the PR approved
|
|
71
|
+
3. Once it is merged; an additional PR containing the release changes needs to be merged to create a release.
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from collections.abc import Callable
|
|
3
5
|
|
|
4
6
|
from ..types import RunOutput
|
|
5
7
|
|
|
6
|
-
logger = logging.getLogger("gitops")
|
|
8
|
+
logger = logging.getLogger("gitops.run")
|
|
7
9
|
|
|
8
10
|
|
|
9
|
-
async def
|
|
11
|
+
async def _read_stream(stream: asyncio.StreamReader | None, cb: Callable[[bytes], None]) -> None:
|
|
12
|
+
if stream is None:
|
|
13
|
+
return
|
|
14
|
+
while True:
|
|
15
|
+
line = await stream.readline()
|
|
16
|
+
if line:
|
|
17
|
+
cb(line)
|
|
18
|
+
else:
|
|
19
|
+
break
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def run(command: str, suppress_errors: bool = False, cwd: str | Path | None = None) -> RunOutput:
|
|
10
23
|
"""Run a shell command.
|
|
11
24
|
|
|
12
25
|
Runs the command in an asyncio executor to keep things async. Will
|
|
@@ -15,10 +28,25 @@ async def run(command, suppress_errors=False) -> RunOutput:
|
|
|
15
28
|
exit_code = 0
|
|
16
29
|
logger.info(f'Running "{command}".')
|
|
17
30
|
proc = await asyncio.create_subprocess_shell(
|
|
18
|
-
command,
|
|
31
|
+
command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd
|
|
19
32
|
)
|
|
20
|
-
|
|
21
33
|
stdout, stderr = b"", b""
|
|
34
|
+
|
|
35
|
+
def log_stdout(line: bytes) -> None:
|
|
36
|
+
nonlocal stdout
|
|
37
|
+
stdout += line
|
|
38
|
+
logger.info(f"{line.decode()}")
|
|
39
|
+
|
|
40
|
+
def log_stderr(line: bytes) -> None:
|
|
41
|
+
nonlocal stderr
|
|
42
|
+
stderr += line
|
|
43
|
+
logger.info(f"{line.decode()}")
|
|
44
|
+
|
|
45
|
+
await asyncio.gather(
|
|
46
|
+
_read_stream(proc.stdout, log_stdout),
|
|
47
|
+
_read_stream(proc.stderr, log_stderr),
|
|
48
|
+
)
|
|
49
|
+
|
|
22
50
|
await proc.communicate()
|
|
23
51
|
exit_code = proc.returncode if proc.returncode not in (None, 128) else 1 # type: ignore
|
|
24
52
|
if exit_code == 0:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import re
|
|
4
5
|
import tempfile
|
|
5
6
|
from collections.abc import AsyncGenerator
|
|
6
7
|
from contextlib import asynccontextmanager
|
|
@@ -18,7 +19,7 @@ REPO_CACHE: dict[str, Path] = {}
|
|
|
18
19
|
repo_lock = asyncio.Semaphore(1)
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
async def clone_repo(git_repo_url: str, path: str, sha: str | None = None):
|
|
22
|
+
async def clone_repo(git_repo_url: str, path: str, sha: str | None = None, branch: str | None = None):
|
|
22
23
|
"""Shallow Clones a git repo url to path and git-crypt unlocks all encrypted files"""
|
|
23
24
|
logger.info(f'Cloning "{git_repo_url}".')
|
|
24
25
|
|
|
@@ -27,18 +28,37 @@ async def clone_repo(git_repo_url: str, path: str, sha: str | None = None):
|
|
|
27
28
|
with tracer.start_as_current_span("tempo_repo.clone_repo"):
|
|
28
29
|
if sha:
|
|
29
30
|
await run(f"git clone --depth 100 {url_with_oauth_token} {path}; cd {path}; git checkout {sha}")
|
|
31
|
+
elif branch:
|
|
32
|
+
await run(f"git clone --depth 1 {url_with_oauth_token} {path} --branch {branch}")
|
|
30
33
|
else:
|
|
31
34
|
await run(f"git clone --depth 100 {url_with_oauth_token} {path};")
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
if GIT_CRYPT_KEY_FILE := os.environ.get("GIT_CRYPT_KEY_FILE"):
|
|
37
|
+
with tracer.start_as_current_span("temp_repo.git_crypt_unlock"):
|
|
38
|
+
await run(f"cd {path}; git-crypt unlock {GIT_CRYPT_KEY_FILE}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_sha(sha_or_ref: str | None) -> bool:
|
|
42
|
+
"""Check if the given string is a valid SHA-1 hash or a short SHA."""
|
|
43
|
+
if not sha_or_ref:
|
|
44
|
+
return False
|
|
45
|
+
return bool(re.fullmatch(r"[a-fA-F0-9]{4,40}", sha_or_ref))
|
|
35
46
|
|
|
36
47
|
|
|
37
48
|
@asynccontextmanager
|
|
38
|
-
async def temp_repo(git_repo_url: str,
|
|
49
|
+
async def temp_repo(git_repo_url: str, ref: str | None) -> AsyncGenerator[str, None]:
|
|
39
50
|
"""Checks out a git_repo_url to a temporary folder location. Returns temporary folder location"""
|
|
51
|
+
# Someone passed us a branch name, not a sha
|
|
52
|
+
|
|
53
|
+
if not is_sha(ref):
|
|
54
|
+
async with temp_repo_branch(git_repo_url, branch=ref) as temp_repo_path:
|
|
55
|
+
yield temp_repo_path
|
|
56
|
+
return
|
|
57
|
+
|
|
40
58
|
with tracer.start_as_current_span("checkout_temp_repo"):
|
|
41
59
|
cache_path = REPO_CACHE_DIR / git_repo_url.split("/")[-1].split(".")[0]
|
|
60
|
+
|
|
61
|
+
# Prep the repo cache
|
|
42
62
|
if not (cache_path / ".git").exists():
|
|
43
63
|
logger.info("Repo %s not in cache, cloning", git_repo_url)
|
|
44
64
|
async with repo_lock:
|
|
@@ -48,8 +68,21 @@ async def temp_repo(git_repo_url: str, sha: str | None = None) -> AsyncGenerator
|
|
|
48
68
|
cache_path.mkdir(parents=True)
|
|
49
69
|
REPO_CACHE[git_repo_url] = cache_path
|
|
50
70
|
await clone_repo(git_repo_url, path=str(cache_path))
|
|
71
|
+
|
|
72
|
+
# Copy the repo cache to a temporary folder
|
|
51
73
|
with tempfile.TemporaryDirectory() as temporary_folder_path:
|
|
52
74
|
await run(f"rm -rf {temporary_folder_path}", suppress_errors=True)
|
|
53
75
|
await run(f"cp -r {cache_path} {temporary_folder_path}")
|
|
54
|
-
await run(f"cd {temporary_folder_path};git fetch; git checkout {
|
|
76
|
+
await run(f"cd {temporary_folder_path};git fetch; git checkout {ref}")
|
|
77
|
+
|
|
78
|
+
yield temporary_folder_path
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@asynccontextmanager
|
|
82
|
+
async def temp_repo_branch(git_repo_url: str, branch: str | None) -> AsyncGenerator[str, None]:
|
|
83
|
+
"""Checks out a git_repo_url to a temporary folder location. Returns temporary folder location"""
|
|
84
|
+
with tracer.start_as_current_span("checkout_temp_repo_branch"):
|
|
85
|
+
# Copy the repo cache to a temporary folder
|
|
86
|
+
with tempfile.TemporaryDirectory() as temporary_folder_path:
|
|
87
|
+
await clone_repo(git_repo_url, path=str(temporary_folder_path), branch=branch)
|
|
55
88
|
yield temporary_folder_path
|
|
@@ -21,6 +21,7 @@ BASE_REPO_DIR = "/var/gitops/repos"
|
|
|
21
21
|
ROLE_ARN = f"arn:aws:iam::{settings.ACCOUNT_ID}:role/GitopsAccess"
|
|
22
22
|
logger = logging.getLogger("gitops")
|
|
23
23
|
GITOPS_MAX_PARALLEL_DEPLOYS = os.environ.get("GITOPS_MAX_PARALLEL_DEPLOYS", "5")
|
|
24
|
+
MAX_HELM_HISTORY = 3
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
@tracer.start_as_current_span("post_init_summary")
|
|
@@ -65,7 +66,7 @@ async def post_result_summary(source: str, results: list[UpdateAppResult]):
|
|
|
65
66
|
@tracer.start_as_current_span("load_app_definitions")
|
|
66
67
|
async def load_app_definitions(url: str, sha: str) -> AppDefinitions:
|
|
67
68
|
logger.info(f'Loading app definitions at "{sha}".')
|
|
68
|
-
async with temp_repo(url,
|
|
69
|
+
async with temp_repo(url, ref=sha) as repo:
|
|
69
70
|
app_definitions = AppDefinitions(name=get_repo_name_from_url(url))
|
|
70
71
|
app_definitions.from_path(repo)
|
|
71
72
|
return app_definitions
|
|
@@ -173,7 +174,7 @@ class Deployer:
|
|
|
173
174
|
if app.chart.type == "git":
|
|
174
175
|
span.set_attribute("gitops.chart.type", "git")
|
|
175
176
|
assert app.chart.git_repo_url
|
|
176
|
-
async with temp_repo(app.chart.git_repo_url,
|
|
177
|
+
async with temp_repo(app.chart.git_repo_url, ref=app.chart.git_sha) as chart_folder_path:
|
|
177
178
|
with tracer.start_as_current_span("helm_dependency_build"):
|
|
178
179
|
await run(f"cd {chart_folder_path}; helm dependency build")
|
|
179
180
|
|
|
@@ -185,6 +186,7 @@ class Deployer:
|
|
|
185
186
|
with tracer.start_as_current_span("helm_upgrade"):
|
|
186
187
|
result = await run(
|
|
187
188
|
"helm secrets upgrade --create-namespace"
|
|
189
|
+
f" --history-max {MAX_HELM_HISTORY}"
|
|
188
190
|
" --install"
|
|
189
191
|
" --timeout=600s"
|
|
190
192
|
f"{' --set skip_migrations=true' if self.skip_migrations else ''}"
|
|
@@ -207,6 +209,7 @@ class Deployer:
|
|
|
207
209
|
with tracer.start_as_current_span("helm_upgrade"):
|
|
208
210
|
result = await run(
|
|
209
211
|
"helm secrets upgrade --create-namespace"
|
|
212
|
+
f" --history-max {MAX_HELM_HISTORY}"
|
|
210
213
|
" --install"
|
|
211
214
|
" --timeout=600s"
|
|
212
215
|
f"{' --set skip_migrations=true' if self.skip_migrations else ''}"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
[project]
|
|
3
3
|
name = "gitops"
|
|
4
|
-
version = "0.11.
|
|
4
|
+
version = "0.11.5"
|
|
5
5
|
description = "Manage multiple apps across one or more k8s clusters."
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
readme = "README.md"
|
|
@@ -44,6 +44,7 @@ dev-dependencies = [
|
|
|
44
44
|
"types-pyyaml>=6.0.12.20240808",
|
|
45
45
|
"types-tabulate>=0.9.0.20240106",
|
|
46
46
|
"boto3-stubs>=1.35.11",
|
|
47
|
+
"pytest-asyncio>=0.24.0",
|
|
47
48
|
]
|
|
48
49
|
|
|
49
50
|
|
|
@@ -4,8 +4,9 @@ import re
|
|
|
4
4
|
pyproject = pathlib.Path("pyproject.toml")
|
|
5
5
|
init = pathlib.Path("gitops/__init__.py")
|
|
6
6
|
chart = pathlib.Path("charts/gitops/Chart.yaml")
|
|
7
|
+
current = re.search(r'version = "(.*)"', pyproject.read_text()).group(1) # type: ignore
|
|
7
8
|
|
|
8
|
-
version = input("Enter a new version
|
|
9
|
+
version = input(f"Enter a new version. Currently at ({current}): ").strip()
|
|
9
10
|
|
|
10
11
|
pyproject_text = pyproject.read_text()
|
|
11
12
|
pyproject.write_text(re.sub(r'version = ".*"', f'version = "{version}"', pyproject_text, count=1))
|
|
@@ -29,13 +29,13 @@ class TestDeploy:
|
|
|
29
29
|
assert run_mock.call_count == 4
|
|
30
30
|
assert run_mock.call_args_list[0][0][0] == "cd mock-repo; helm dependency build"
|
|
31
31
|
assert re.match(
|
|
32
|
-
r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
|
|
32
|
+
r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
|
|
33
33
|
r" --namespace=mynamespace sample-app-\d mock-repo",
|
|
34
34
|
run_mock.call_args_list[1][0][0],
|
|
35
35
|
)
|
|
36
36
|
assert run_mock.call_args_list[2][0][0] == "cd mock-repo; helm dependency build"
|
|
37
37
|
assert re.match(
|
|
38
|
-
r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
|
|
38
|
+
r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
|
|
39
39
|
r" --namespace=mynamespace sample-app-\d mock-repo",
|
|
40
40
|
run_mock.call_args_list[3][0][0],
|
|
41
41
|
)
|
|
@@ -78,7 +78,7 @@ class TestDeploy:
|
|
|
78
78
|
assert run_mock.call_count == 2
|
|
79
79
|
assert run_mock.call_args_list[0][0][0] == "helm repo add brigade https://helm.charts"
|
|
80
80
|
assert re.match(
|
|
81
|
-
r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
|
|
81
|
+
r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
|
|
82
82
|
r" --namespace=mynamespace helm_app brigade/brigade",
|
|
83
83
|
run_mock.call_args_list[1][0][0],
|
|
84
84
|
)
|
|
@@ -99,13 +99,13 @@ class TestDeploy:
|
|
|
99
99
|
assert run_mock.call_count == 4
|
|
100
100
|
assert run_mock.call_args_list[0][0][0] == "cd mock-repo; helm dependency build"
|
|
101
101
|
assert re.match(
|
|
102
|
-
r"helm secrets upgrade --create-namespace --install --timeout=600s --set skip_migrations=true -f .+\.yml"
|
|
102
|
+
r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s --set skip_migrations=true -f .+\.yml"
|
|
103
103
|
r" --namespace=mynamespace sample-app-\d mock-repo",
|
|
104
104
|
run_mock.call_args_list[1][0][0],
|
|
105
105
|
)
|
|
106
106
|
assert run_mock.call_args_list[2][0][0] == "cd mock-repo; helm dependency build"
|
|
107
107
|
assert re.match(
|
|
108
|
-
r"helm secrets upgrade --create-namespace --install --timeout=600s --set skip_migrations=true -f .+\.yml"
|
|
108
|
+
r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s --set skip_migrations=true -f .+\.yml"
|
|
109
109
|
r" --namespace=mynamespace sample-app-\d mock-repo",
|
|
110
110
|
run_mock.call_args_list[3][0][0],
|
|
111
111
|
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import tempfile
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from gitops_server.utils import git, run
|
|
8
|
+
|
|
9
|
+
os.environ["GITHUB_OAUTH_TOKEN"] = ""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@asynccontextmanager
|
|
13
|
+
async def make_dummy_repo():
|
|
14
|
+
with tempfile.TemporaryDirectory() as temporary_folder_path:
|
|
15
|
+
if os.environ.get("CI"):
|
|
16
|
+
await run("git config --global user.email 'you@example.com'")
|
|
17
|
+
await run("git config --global user.name 'Your Name'")
|
|
18
|
+
await run("git config --global init.defaultBranch main")
|
|
19
|
+
await run("git init", cwd=temporary_folder_path)
|
|
20
|
+
for _ in range(10):
|
|
21
|
+
await run("git commit -m 'temporary commit' --allow-empty", cwd=temporary_folder_path)
|
|
22
|
+
await run("git checkout -b 'test'", cwd=temporary_folder_path)
|
|
23
|
+
for _ in range(10):
|
|
24
|
+
await run("git commit -m 'temporary commit' --allow-empty", cwd=temporary_folder_path)
|
|
25
|
+
await run("git checkout main", cwd=temporary_folder_path)
|
|
26
|
+
yield temporary_folder_path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.asyncio
|
|
30
|
+
class TestGit:
|
|
31
|
+
async def test_temp_repo_with_branch(self):
|
|
32
|
+
async with make_dummy_repo() as test_repo:
|
|
33
|
+
async with git.temp_repo(test_repo, ref="test"):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
async with git.temp_repo(test_repo, ref="main"):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
async def test_temp_repo_with_no_ref(self):
|
|
40
|
+
async with make_dummy_repo() as test_repo:
|
|
41
|
+
async with git.temp_repo(test_repo, ref=None):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
async def test_temp_repo_with_sha(self):
|
|
45
|
+
async with make_dummy_repo() as test_repo:
|
|
46
|
+
sha = (await run("git rev-parse HEAD", cwd=test_repo))["output"].strip()
|
|
47
|
+
assert git.is_sha(sha)
|
|
48
|
+
|
|
49
|
+
async with git.temp_repo(test_repo, ref=sha):
|
|
50
|
+
pass
|
|
@@ -277,7 +277,7 @@ wheels = [
|
|
|
277
277
|
|
|
278
278
|
[[package]]
|
|
279
279
|
name = "gitops"
|
|
280
|
-
version = "0.11.
|
|
280
|
+
version = "0.11.3"
|
|
281
281
|
source = { editable = "." }
|
|
282
282
|
dependencies = [
|
|
283
283
|
{ name = "boto3" },
|
|
@@ -305,6 +305,7 @@ dev = [
|
|
|
305
305
|
{ name = "boto3-stubs" },
|
|
306
306
|
{ name = "mypy" },
|
|
307
307
|
{ name = "pytest" },
|
|
308
|
+
{ name = "pytest-asyncio" },
|
|
308
309
|
{ name = "ruff" },
|
|
309
310
|
{ name = "types-colorama" },
|
|
310
311
|
{ name = "types-pyyaml" },
|
|
@@ -334,6 +335,7 @@ dev = [
|
|
|
334
335
|
{ name = "boto3-stubs", specifier = ">=1.35.11" },
|
|
335
336
|
{ name = "mypy", specifier = ">=1.11.2" },
|
|
336
337
|
{ name = "pytest", specifier = ">=8.3.2" },
|
|
338
|
+
{ name = "pytest-asyncio", specifier = ">=0.24.0" },
|
|
337
339
|
{ name = "ruff", specifier = ">=0.6.3" },
|
|
338
340
|
{ name = "types-colorama", specifier = ">=0.4.15.20240311" },
|
|
339
341
|
{ name = "types-pyyaml", specifier = ">=6.0.12.20240808" },
|
|
@@ -941,6 +943,18 @@ wheels = [
|
|
|
941
943
|
{ url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802 },
|
|
942
944
|
]
|
|
943
945
|
|
|
946
|
+
[[package]]
|
|
947
|
+
name = "pytest-asyncio"
|
|
948
|
+
version = "0.24.0"
|
|
949
|
+
source = { registry = "https://pypi.org/simple" }
|
|
950
|
+
dependencies = [
|
|
951
|
+
{ name = "pytest" },
|
|
952
|
+
]
|
|
953
|
+
sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 }
|
|
954
|
+
wheels = [
|
|
955
|
+
{ url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 },
|
|
956
|
+
]
|
|
957
|
+
|
|
944
958
|
[[package]]
|
|
945
959
|
name = "python-dateutil"
|
|
946
960
|
version = "2.9.0.post0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|