gitops 1.6.0__tar.gz → 1.6.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.
- gitops-1.6.2/.release-please-manifest.json +5 -0
- {gitops-1.6.0 → gitops-1.6.2}/CHANGELOG.md +17 -0
- {gitops-1.6.0 → gitops-1.6.2}/PKG-INFO +10 -3
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/CHANGELOG.md +9 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/Chart.yaml +1 -1
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/deployment.yml +2 -2
- {gitops-1.6.0 → gitops-1.6.2}/gitops/__init__.py +1 -1
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/apps.py +4 -1
- gitops-1.6.2/gitops_server/version.py +1 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/deployer/deploy.py +38 -12
- {gitops-1.6.0 → gitops-1.6.2}/pyproject.toml +18 -3
- {gitops-1.6.0 → gitops-1.6.2}/release-please-config.json +7 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_deploy.py +73 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_gitops_server.py +4 -2
- {gitops-1.6.0 → gitops-1.6.2}/uv.lock +301 -202
- gitops-1.6.0/.release-please-manifest.json +0 -5
- gitops-1.6.0/gitops_server/version.py +0 -1
- {gitops-1.6.0 → gitops-1.6.2}/.dockerignore +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/.github/workflows/release-please.yaml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/.github/workflows/test.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/.gitignore +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/.mise.toml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/.pre-commit-config.yaml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/CLAUDE.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/Dockerfile +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/LICENSE +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/README.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/.helmignore +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/README.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/_helpers.tpl +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/certificate.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/environment.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/ingress.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/issuer.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/pdb.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/secrets.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/service.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/templates/serviceaccount.yaml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/charts/gitops/values.yaml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/docker-compose.yml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/docs/plans/2026-03-13-kms-encryption-design.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/common/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/common/app.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/common/utils.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/core.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/main.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/py.typed +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/settings.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/shorthands.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/async_runner.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/cli.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/exceptions.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/images.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/kube.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/tags.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops/utils/yaml.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/CHANGELOG.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/README.md +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/app.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/main.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/settings.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/types.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/utils/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/utils/git.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/utils/github.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/utils/slack.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/deployer/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/deployer/hooks.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/deployer/semaphore_manager.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/gitops_server/workers/deployer/worker.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/release.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/ruff.toml +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/secrets.example.env +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/setup.cfg +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/__init__.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/conftest.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/sample_data.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_app.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_core.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_deploy_concurrent.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_git.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/test_utils.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/utils.py +0 -0
- {gitops-1.6.0 → gitops-1.6.2}/tests/webhook_sample_data.py +0 -0
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.2](https://github.com/uptick/gitops/compare/gitops-v1.6.1...gitops-v1.6.2) (2026-06-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **deploy:** isolate per-app failures so one bad app doesn't abort the batch ([29ff9c3](https://github.com/uptick/gitops/commit/29ff9c378ab8d003fc862f274b39bc131ada5871))
|
|
9
|
+
* **deploy:** isolate per-app failures so one bad app doesn't abort the batch ([8261b7b](https://github.com/uptick/gitops/commit/8261b7bb335678a029586b2238e81cb783988009))
|
|
10
|
+
* **deploy:** make post_result fail-proof so reporting errors don't abort the batch ([83dbc8e](https://github.com/uptick/gitops/commit/83dbc8e217e358ea51168eb955007ac76bceb9aa))
|
|
11
|
+
|
|
12
|
+
## [1.6.1](https://github.com/uptick/gitops/compare/gitops-v1.6.0...gitops-v1.6.1) (2026-05-28)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* improve app not found error message ([2c2a5a6](https://github.com/uptick/gitops/commit/2c2a5a6398ec6f7e7609982e6105b87cc6fd0d9d))
|
|
18
|
+
* improve app not found error message ([e0f93b4](https://github.com/uptick/gitops/commit/e0f93b40f14c732ac1661c6a61e981eee05e32b2))
|
|
19
|
+
|
|
3
20
|
## [1.6.0](https://github.com/uptick/gitops/compare/gitops-v1.5.1...gitops-v1.6.0) (2026-05-06)
|
|
4
21
|
|
|
5
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitops
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
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
|
|
@@ -13,12 +13,19 @@ Requires-Dist: invoke>=2.2.0
|
|
|
13
13
|
Requires-Dist: packaging>=24.1
|
|
14
14
|
Requires-Dist: pyyaml>=6.0.2
|
|
15
15
|
Requires-Dist: tabulate>=0.8.9
|
|
16
|
+
Requires-Dist: urllib3>=2.7.0
|
|
16
17
|
Provides-Extra: server
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: aiohttp>=3.14.0; extra == 'server'
|
|
19
|
+
Requires-Dist: fastapi>=0.133.0; extra == 'server'
|
|
20
|
+
Requires-Dist: h11>=0.16.0; extra == 'server'
|
|
21
|
+
Requires-Dist: httpx>=0.27.0; extra == 'server'
|
|
22
|
+
Requires-Dist: idna>=3.15; extra == 'server'
|
|
19
23
|
Requires-Dist: kubernetes-asyncio>=25.1.2; extra == 'server'
|
|
24
|
+
Requires-Dist: protobuf>=5.29.6; extra == 'server'
|
|
25
|
+
Requires-Dist: requests>=2.33.0; extra == 'server'
|
|
20
26
|
Requires-Dist: sentry-sdk>=1.3.0; extra == 'server'
|
|
21
27
|
Requires-Dist: slack-sdk>=3.33.5; extra == 'server'
|
|
28
|
+
Requires-Dist: starlette>=1.3.1; extra == 'server'
|
|
22
29
|
Requires-Dist: uptick-observability[fastapi]>=0.2.2; extra == 'server'
|
|
23
30
|
Requires-Dist: uvicorn>=0.34.0; extra == 'server'
|
|
24
31
|
Description-Content-Type: text/markdown
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.2](https://github.com/uptick/gitops/compare/helm-v1.2.1...helm-v1.2.2) (2026-05-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **helm:** remove trailing colon from OTEL_RESOURCE_ATTRIBUTES env var name ([0c23326](https://github.com/uptick/gitops/commit/0c23326d62ed331e5ca24b9c3a04bbc3a58b2931))
|
|
9
|
+
* **helm:** remove trailing colon from OTEL_RESOURCE_ATTRIBUTES env var name ([f454c45](https://github.com/uptick/gitops/commit/f454c4587cd79fda5ac8ad30fd9a65541f0f4636))
|
|
10
|
+
* **helm:** use correct values reference for OTEL_RESOURCE_ATTRIBUTES cluster name ([2184d70](https://github.com/uptick/gitops/commit/2184d7040c36b802af7c86a1b6c074c92940b269))
|
|
11
|
+
|
|
3
12
|
## [1.2.1](https://github.com/uptick/gitops/compare/helm-v1.2.0...helm-v1.2.1) (2026-05-06)
|
|
4
13
|
|
|
5
14
|
|
|
@@ -90,8 +90,8 @@ spec:
|
|
|
90
90
|
value: "gitops"
|
|
91
91
|
- name: OTEL_EXPORTER_OTLP_TIMEOUT
|
|
92
92
|
value: "1"
|
|
93
|
-
- name: OTEL_RESOURCE_ATTRIBUTES
|
|
94
|
-
value: "cluster={{.Values.
|
|
93
|
+
- name: OTEL_RESOURCE_ATTRIBUTES
|
|
94
|
+
value: "cluster={{.Values.environment.CLUSTER_NAME}}"
|
|
95
95
|
# We want to limit the length of the attributes so that we don't store too much
|
|
96
96
|
- name: OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
|
|
97
97
|
value: "512"
|
|
@@ -38,7 +38,10 @@ def get_app_details(
|
|
|
38
38
|
except FileNotFoundError as e:
|
|
39
39
|
msg, exc = "", Exception
|
|
40
40
|
if get_apps_directory().exists():
|
|
41
|
-
msg, exc =
|
|
41
|
+
msg, exc = (
|
|
42
|
+
f"There's no app with the name '{app_name}', silly. is your cluster repo up to date?",
|
|
43
|
+
AppDoesNotExist,
|
|
44
|
+
)
|
|
42
45
|
else:
|
|
43
46
|
msg, exc = "Could not find an 'apps' directory. Are you in a cluster repo?", AppDirectoryDoesNotExist
|
|
44
47
|
if exit_if_not_found:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.6.2"
|
|
@@ -162,9 +162,27 @@ class Deployer:
|
|
|
162
162
|
)
|
|
163
163
|
|
|
164
164
|
async def update_app_deployment(self, app: App) -> UpdateAppResult | None:
|
|
165
|
+
from_timestamp = time.time()
|
|
165
166
|
async with self.semaphore_manager.app_semaphore(app.name):
|
|
166
167
|
async with helm_parallel_semaphore:
|
|
167
|
-
|
|
168
|
+
try:
|
|
169
|
+
return await self._update_app_deployment(app)
|
|
170
|
+
except Exception as e:
|
|
171
|
+
# Isolate per-app failures so one broken app doesn't abort the
|
|
172
|
+
# whole batch. Steps before the helm upgrade (e.g. cloning a
|
|
173
|
+
# custom git chart from a branch that doesn't exist, or
|
|
174
|
+
# `helm dependency build`) raise rather than returning a
|
|
175
|
+
# non-zero result, and would otherwise propagate up through
|
|
176
|
+
# asyncio.gather() and cancel every other app's deployment.
|
|
177
|
+
logger.exception(f"Failed to deploy app {app.name!r}.")
|
|
178
|
+
result = UpdateAppResult(
|
|
179
|
+
app_name=app.name,
|
|
180
|
+
slack_message="",
|
|
181
|
+
exit_code=1,
|
|
182
|
+
output=str(e),
|
|
183
|
+
)
|
|
184
|
+
await self.post_result(app=app, result=result, deployer=self, from_timestamp=from_timestamp)
|
|
185
|
+
return result
|
|
168
186
|
|
|
169
187
|
async def _update_app_deployment(self, app: App) -> UpdateAppResult | None:
|
|
170
188
|
app.set_value("deployment.labels.gitops/deploy_id", self.deploy_id)
|
|
@@ -275,17 +293,25 @@ class Deployer:
|
|
|
275
293
|
|
|
276
294
|
@tracer.start_as_current_span("post_result")
|
|
277
295
|
async def post_result(self, app: App, result: UpdateAppResult, deployer: "Deployer", **kwargs: Any) -> None:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
# Reporting a result makes Slack/GitHub network calls and runs inside the
|
|
297
|
+
# asyncio.gather() in deploy(). A failure here (e.g. a Slack or GitHub API
|
|
298
|
+
# error) must not propagate, or it would cancel every other app's
|
|
299
|
+
# deployment in the batch. The success/failure bookkeeping is recorded
|
|
300
|
+
# before any network call so it survives a notification failure.
|
|
301
|
+
try:
|
|
302
|
+
if result["exit_code"] != 0:
|
|
303
|
+
self.failed_apps.add(app.name)
|
|
304
|
+
deploy_result = await handle_failed_deploy(app, result, deployer, **kwargs)
|
|
305
|
+
message = (
|
|
306
|
+
deploy_result["slack_message"]
|
|
307
|
+
or f"Failed to deploy app `{result['app_name']}` for cluster `{settings.CLUSTER_NAME}`:\n>>>{result['output']}"
|
|
308
|
+
)
|
|
309
|
+
await slack.post_alert(message)
|
|
310
|
+
else:
|
|
311
|
+
self.successful_apps.add(app.name)
|
|
312
|
+
await handle_successful_deploy(app, result, deployer)
|
|
313
|
+
except Exception:
|
|
314
|
+
logger.exception(f"Failed to post deploy result for app {app.name!r}.")
|
|
289
315
|
|
|
290
316
|
# TODO: Fix this in the future. We want to update the message with the latest status.
|
|
291
317
|
# But we can't because of the messge length (when messaging > 4000 characters)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
[project]
|
|
3
3
|
name = "gitops"
|
|
4
|
-
version = "1.6.
|
|
4
|
+
version = "1.6.2"
|
|
5
5
|
description = "Manage multiple apps across one or more k8s clusters."
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
7
|
readme = "README.md"
|
|
@@ -15,6 +15,8 @@ dependencies = [
|
|
|
15
15
|
"humanize>=3.5.0",
|
|
16
16
|
"tabulate>=0.8.9",
|
|
17
17
|
"packaging>=24.1",
|
|
18
|
+
# Transitive security pins
|
|
19
|
+
"urllib3>=2.7.0",
|
|
18
20
|
]
|
|
19
21
|
|
|
20
22
|
[project.scripts]
|
|
@@ -23,13 +25,20 @@ gitops = "gitops.main:program.run"
|
|
|
23
25
|
|
|
24
26
|
[project.optional-dependencies]
|
|
25
27
|
server = [
|
|
26
|
-
"httpx>=0.
|
|
28
|
+
"httpx>=0.27.0",
|
|
27
29
|
"uvicorn>=0.34.0",
|
|
28
|
-
"fastapi
|
|
30
|
+
"fastapi>=0.133.0",
|
|
29
31
|
"kubernetes_asyncio>=25.1.2",
|
|
30
32
|
"sentry-sdk>=1.3.0",
|
|
31
33
|
"uptick-observability[fastapi]>=0.2.2",
|
|
32
34
|
"slack-sdk>=3.33.5",
|
|
35
|
+
# Transitive security pins
|
|
36
|
+
"h11>=0.16.0",
|
|
37
|
+
"aiohttp>=3.14.0",
|
|
38
|
+
"starlette>=1.3.1",
|
|
39
|
+
"protobuf>=5.29.6",
|
|
40
|
+
"requests>=2.33.0",
|
|
41
|
+
"idna>=3.15",
|
|
33
42
|
]
|
|
34
43
|
|
|
35
44
|
[build-system]
|
|
@@ -51,6 +60,12 @@ dev-dependencies = [
|
|
|
51
60
|
|
|
52
61
|
|
|
53
62
|
[tool.uv.sources]
|
|
63
|
+
uptick-observability = { index = "uptick" }
|
|
64
|
+
|
|
65
|
+
[[tool.uv.index]]
|
|
66
|
+
name = "uptick"
|
|
67
|
+
url = "https://pypi.deployments.onuptick.com"
|
|
68
|
+
explicit = true
|
|
54
69
|
|
|
55
70
|
[tool.nitpick]
|
|
56
71
|
# Generated by the 'nitpick init' command
|
|
@@ -145,6 +145,79 @@ class TestDeploy:
|
|
|
145
145
|
)
|
|
146
146
|
assert post_mock.call_count == 2
|
|
147
147
|
|
|
148
|
+
@patch("gitops_server.workers.deployer.deploy.run")
|
|
149
|
+
@patch("gitops_server.utils.slack.post")
|
|
150
|
+
@patch("gitops_server.workers.deployer.deploy.Deployer.post_result")
|
|
151
|
+
@patch("gitops_server.workers.deployer.deploy.load_app_definitions", mock_load_app_definitions)
|
|
152
|
+
@patch("gitops_server.workers.deployer.deploy.temp_repo")
|
|
153
|
+
async def test_one_app_failing_does_not_abort_the_batch(
|
|
154
|
+
self, temp_repo_mock, post_result_mock, post_mock, run_mock
|
|
155
|
+
):
|
|
156
|
+
"""A single app failing (e.g. cloning a custom chart from a branch that
|
|
157
|
+
doesn't exist) must not prevent the rest of the batch from deploying."""
|
|
158
|
+
run_mock.return_value = {"exit_code": 0, "output": ""}
|
|
159
|
+
|
|
160
|
+
class _FakeTempRepo:
|
|
161
|
+
async def __aenter__(self):
|
|
162
|
+
return "mock-repo"
|
|
163
|
+
|
|
164
|
+
async def __aexit__(self, *args):
|
|
165
|
+
return False
|
|
166
|
+
|
|
167
|
+
# SAMPLE_GITHUB_PAYLOAD deploys two apps. Make the first chart fetch blow
|
|
168
|
+
# up (as a non-existent git branch would), the second succeed.
|
|
169
|
+
calls = {"n": 0}
|
|
170
|
+
|
|
171
|
+
def temp_repo_side_effect(*args, **kwargs):
|
|
172
|
+
calls["n"] += 1
|
|
173
|
+
if calls["n"] == 1:
|
|
174
|
+
raise Exception("git clone failed: branch 'does-not-exist' not found")
|
|
175
|
+
return _FakeTempRepo()
|
|
176
|
+
|
|
177
|
+
temp_repo_mock.side_effect = temp_repo_side_effect
|
|
178
|
+
|
|
179
|
+
semaphore_manager = AppSemaphoreManager()
|
|
180
|
+
deployer = await Deployer.from_push_event(SAMPLE_GITHUB_PAYLOAD, semaphore_manager)
|
|
181
|
+
|
|
182
|
+
# Must not raise even though one app errors out.
|
|
183
|
+
await deployer.deploy()
|
|
184
|
+
|
|
185
|
+
# The surviving app still ran its full helm flow (dependency build + upgrade).
|
|
186
|
+
assert run_mock.call_count == 2
|
|
187
|
+
# post_result was called for both apps: one failure, one success.
|
|
188
|
+
assert post_result_mock.call_count == 2
|
|
189
|
+
exit_codes = sorted(call.kwargs["result"]["exit_code"] for call in post_result_mock.call_args_list)
|
|
190
|
+
assert exit_codes == [0, 1]
|
|
191
|
+
|
|
192
|
+
@patch("gitops_server.workers.deployer.deploy.handle_failed_deploy")
|
|
193
|
+
@patch("gitops_server.workers.deployer.deploy.handle_successful_deploy")
|
|
194
|
+
@patch("gitops_server.workers.deployer.deploy.load_app_definitions", mock_load_app_definitions)
|
|
195
|
+
async def test_post_result_swallows_reporting_failures(self, handle_success_mock, handle_failed_mock):
|
|
196
|
+
"""Reporting a result makes Slack/GitHub network calls inside the gather()
|
|
197
|
+
in deploy(); a failure there must never propagate, or it would abort the
|
|
198
|
+
rest of the batch. post_result must swallow such errors itself, since it
|
|
199
|
+
is also invoked from places not wrapped by update_app_deployment's guard
|
|
200
|
+
(its own except handler and uninstall_app)."""
|
|
201
|
+
handle_success_mock.side_effect = Exception("slack/github exploded")
|
|
202
|
+
handle_failed_mock.side_effect = Exception("slack/github exploded")
|
|
203
|
+
|
|
204
|
+
semaphore_manager = AppSemaphoreManager()
|
|
205
|
+
deployer = await Deployer.from_push_event(SAMPLE_GITHUB_PAYLOAD, semaphore_manager)
|
|
206
|
+
app = App("some-app", path=create_test_yaml())
|
|
207
|
+
|
|
208
|
+
# Neither the success nor the failure reporting path may raise...
|
|
209
|
+
await deployer.post_result(
|
|
210
|
+
app, {"app_name": app.name, "slack_message": "", "exit_code": 0, "output": ""}, deployer
|
|
211
|
+
)
|
|
212
|
+
await deployer.post_result(
|
|
213
|
+
app, {"app_name": app.name, "slack_message": "", "exit_code": 1, "output": "boom"}, deployer
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
# ...but the success/failure bookkeeping is still recorded (it happens
|
|
217
|
+
# before the network call that blows up).
|
|
218
|
+
assert "some-app" in deployer.successful_apps
|
|
219
|
+
assert "some-app" in deployer.failed_apps
|
|
220
|
+
|
|
148
221
|
@patch("gitops_server.workers.deployer.deploy.run")
|
|
149
222
|
@patch("gitops_server.utils.slack.post")
|
|
150
223
|
@patch("gitops_server.workers.deployer.deploy.load_app_definitions", mock_load_app_definitions)
|
|
@@ -22,12 +22,14 @@ def test_get_on_index_returns_200():
|
|
|
22
22
|
|
|
23
23
|
@patch("gitops_server.main.settings.GITHUB_WEBHOOK_KEY", "test_key")
|
|
24
24
|
def test_webhook_returns_200_if_hmac_is_correct():
|
|
25
|
-
|
|
25
|
+
body = json.dumps(payload).encode()
|
|
26
|
+
sha_encoding = hmac.new(b"test_key", body, hashlib.sha1).hexdigest()
|
|
26
27
|
headers["X-Hub-Signature"] = f"sha1={sha_encoding}"
|
|
28
|
+
headers["Content-Type"] = "application/json"
|
|
27
29
|
|
|
28
30
|
with patch.object(gitops_server.workers.DeployQueueWorker, "get_worker") as get_worker_mock:
|
|
29
31
|
get_worker_mock.return_value = AsyncMock()
|
|
30
|
-
response = client.post("/webhook", headers=headers,
|
|
32
|
+
response = client.post("/webhook", headers=headers, content=body)
|
|
31
33
|
|
|
32
34
|
assert response.status_code == 200
|
|
33
35
|
get_worker_mock.assert_called()
|