gitops 1.1.2__tar.gz → 1.2.0__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.
Files changed (84) hide show
  1. {gitops-1.1.2 → gitops-1.2.0}/.release-please-manifest.json +1 -1
  2. {gitops-1.1.2 → gitops-1.2.0}/CHANGELOG.md +13 -0
  3. {gitops-1.1.2 → gitops-1.2.0}/PKG-INFO +1 -1
  4. {gitops-1.1.2 → gitops-1.2.0}/gitops/__init__.py +1 -1
  5. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/apps.py +15 -1
  6. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/main.py +1 -4
  7. gitops-1.2.0/gitops_server/version.py +1 -0
  8. gitops-1.2.0/gitops_server/workers/__init__.py +1 -0
  9. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/workers/deployer/hooks.py +4 -1
  10. {gitops-1.1.2 → gitops-1.2.0}/pyproject.toml +1 -1
  11. {gitops-1.1.2 → gitops-1.2.0}/tests/test_gitops_server.py +0 -11
  12. gitops-1.1.2/gitops_server/version.py +0 -1
  13. gitops-1.1.2/gitops_server/workers/__init__.py +0 -2
  14. gitops-1.1.2/gitops_server/workers/status_updater/__init__.py +0 -1
  15. gitops-1.1.2/gitops_server/workers/status_updater/worker.py +0 -126
  16. {gitops-1.1.2 → gitops-1.2.0}/.dockerignore +0 -0
  17. {gitops-1.1.2 → gitops-1.2.0}/.github/workflows/release-please.yaml +0 -0
  18. {gitops-1.1.2 → gitops-1.2.0}/.github/workflows/test.yml +0 -0
  19. {gitops-1.1.2 → gitops-1.2.0}/.gitignore +0 -0
  20. {gitops-1.1.2 → gitops-1.2.0}/.mise.toml +0 -0
  21. {gitops-1.1.2 → gitops-1.2.0}/.pre-commit-config.yaml +0 -0
  22. {gitops-1.1.2 → gitops-1.2.0}/Dockerfile +0 -0
  23. {gitops-1.1.2 → gitops-1.2.0}/LICENSE +0 -0
  24. {gitops-1.1.2 → gitops-1.2.0}/README.md +0 -0
  25. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/.helmignore +0 -0
  26. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/CHANGELOG.md +0 -0
  27. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/Chart.yaml +0 -0
  28. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/README.md +0 -0
  29. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/_helpers.tpl +0 -0
  30. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/certificate.yml +0 -0
  31. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/deployment.yml +0 -0
  32. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/environment.yml +0 -0
  33. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/ingress.yml +0 -0
  34. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/issuer.yml +0 -0
  35. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/secrets.yml +0 -0
  36. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/service.yml +0 -0
  37. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/templates/serviceaccount.yaml +0 -0
  38. {gitops-1.1.2 → gitops-1.2.0}/charts/gitops/values.yaml +0 -0
  39. {gitops-1.1.2 → gitops-1.2.0}/docker-compose.yml +0 -0
  40. {gitops-1.1.2 → gitops-1.2.0}/gitops/common/__init__.py +0 -0
  41. {gitops-1.1.2 → gitops-1.2.0}/gitops/common/app.py +0 -0
  42. {gitops-1.1.2 → gitops-1.2.0}/gitops/common/utils.py +0 -0
  43. {gitops-1.1.2 → gitops-1.2.0}/gitops/core.py +0 -0
  44. {gitops-1.1.2 → gitops-1.2.0}/gitops/main.py +0 -0
  45. {gitops-1.1.2 → gitops-1.2.0}/gitops/py.typed +0 -0
  46. {gitops-1.1.2 → gitops-1.2.0}/gitops/settings.py +0 -0
  47. {gitops-1.1.2 → gitops-1.2.0}/gitops/shorthands.py +0 -0
  48. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/__init__.py +0 -0
  49. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/async_runner.py +0 -0
  50. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/cli.py +0 -0
  51. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/exceptions.py +0 -0
  52. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/images.py +0 -0
  53. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/kube.py +0 -0
  54. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/tags.py +0 -0
  55. {gitops-1.1.2 → gitops-1.2.0}/gitops/utils/yaml.py +0 -0
  56. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/CHANGELOG.md +0 -0
  57. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/README.md +0 -0
  58. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/__init__.py +0 -0
  59. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/app.py +0 -0
  60. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/settings.py +0 -0
  61. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/types.py +0 -0
  62. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/utils/__init__.py +0 -0
  63. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/utils/git.py +0 -0
  64. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/utils/github.py +0 -0
  65. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/utils/slack.py +0 -0
  66. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/workers/deployer/__init__.py +0 -0
  67. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/workers/deployer/deploy.py +0 -0
  68. {gitops-1.1.2 → gitops-1.2.0}/gitops_server/workers/deployer/worker.py +0 -0
  69. {gitops-1.1.2 → gitops-1.2.0}/release-please-config.json +0 -0
  70. {gitops-1.1.2 → gitops-1.2.0}/release.py +0 -0
  71. {gitops-1.1.2 → gitops-1.2.0}/ruff.toml +0 -0
  72. {gitops-1.1.2 → gitops-1.2.0}/secrets.example.env +0 -0
  73. {gitops-1.1.2 → gitops-1.2.0}/setup.cfg +0 -0
  74. {gitops-1.1.2 → gitops-1.2.0}/tests/__init__.py +0 -0
  75. {gitops-1.1.2 → gitops-1.2.0}/tests/conftest.py +0 -0
  76. {gitops-1.1.2 → gitops-1.2.0}/tests/sample_data.py +0 -0
  77. {gitops-1.1.2 → gitops-1.2.0}/tests/test_app.py +0 -0
  78. {gitops-1.1.2 → gitops-1.2.0}/tests/test_core.py +0 -0
  79. {gitops-1.1.2 → gitops-1.2.0}/tests/test_deploy.py +0 -0
  80. {gitops-1.1.2 → gitops-1.2.0}/tests/test_git.py +0 -0
  81. {gitops-1.1.2 → gitops-1.2.0}/tests/test_utils.py +0 -0
  82. {gitops-1.1.2 → gitops-1.2.0}/tests/utils.py +0 -0
  83. {gitops-1.1.2 → gitops-1.2.0}/tests/webhook_sample_data.py +0 -0
  84. {gitops-1.1.2 → gitops-1.2.0}/uv.lock +0 -0
@@ -1,5 +1,5 @@
1
1
  {
2
- ".": "1.1.2",
2
+ ".": "1.2.0",
3
3
  "gitops_server": "0.15.1",
4
4
  "charts/gitops": "1.1.2"
5
5
  }
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0](https://github.com/uptick/gitops/compare/gitops-v1.1.2...gitops-v1.2.0) (2025-03-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * **cli:** allow filtering apps via a csv file ([f465d9a](https://github.com/uptick/gitops/commit/f465d9a8ad565afde1f9bf7f9d1232281352882c))
9
+ * **cli:** allow filtering apps via a csv file ([7fa06cd](https://github.com/uptick/gitops/commit/7fa06cd6d8ca42a2d1e50304ad7cba7e688650e9))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * remove status updater and replace with a post deploy hook ([e72c2ad](https://github.com/uptick/gitops/commit/e72c2ad2b46304f2258d7b3d1bb04e4200ce1602))
15
+
3
16
  ## [1.1.2](https://github.com/uptick/gitops/compare/gitops-v1.1.1...gitops-v1.1.2) (2025-02-14)
4
17
 
5
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitops
3
- Version: 1.1.2
3
+ Version: 1.2.0
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
@@ -6,7 +6,7 @@ from gitops.utils.apps import App, get_app_details, get_apps
6
6
 
7
7
  from .utils.cli import success, warning
8
8
 
9
- __version__ = "1.1.2"
9
+ __version__ = "1.2.0"
10
10
 
11
11
 
12
12
  # Checking gitops version matches cluster repo version.
@@ -1,3 +1,4 @@
1
+ import pathlib
1
2
  import sys
2
3
  from pathlib import Path
3
4
  from typing import Literal
@@ -77,6 +78,9 @@ def get_apps( # noqa: C901
77
78
  `exclude`. The incoming filter and exclude params may come in as a list or commastring.
78
79
  For the purpose of this filtering, app names and image tag prefixes are also considered as
79
80
  tags. For instance, you can do get_apps(tags=[emeriss, production], exclude=[arafire]).
81
+
82
+ If filter is a file like name; read a list of app names from the file.
83
+
80
84
  Calling this method without any args returns all apps.
81
85
  There are three modes for communicating selected apps to the user:
82
86
  - PROMPT: Prints selected apps and asks for confirmation to proceed.
@@ -84,8 +88,13 @@ def get_apps( # noqa: C901
84
88
  - SILENT: Proceeds without printing.
85
89
  Apps with the `inactive` tag are excluded by default, unless requested otherwise.
86
90
  """
91
+ app_names: list[str] = []
92
+
87
93
  if filter == "all":
88
94
  filter = set()
95
+ elif isinstance(filter, str) and pathlib.Path(filter).is_file():
96
+ app_names = [f.strip() for f in pathlib.Path(filter).read_text().splitlines() if f.strip()]
97
+ filter = set()
89
98
  else:
90
99
  filter = set(filter.split(",") if filter and isinstance(filter, str) else filter)
91
100
 
@@ -114,7 +123,12 @@ def get_apps( # noqa: C901
114
123
 
115
124
  tags = set(app.tags + pseudotags)
116
125
  existing_tags |= tags
117
- if filter <= tags and not exclude & tags:
126
+
127
+ # Decide whether to include the app based on the filter and exclude tags.
128
+ if app_names:
129
+ if app.name in app_names:
130
+ apps.append(app)
131
+ elif filter <= tags and not exclude & tags:
118
132
  apps.append(app)
119
133
 
120
134
  validate_tags(filter | exclude, existing_tags)
@@ -13,7 +13,7 @@ from uptick_observability.logging import ( # type: ignore[import-untyped]
13
13
 
14
14
  from gitops_server import settings
15
15
  from gitops_server.app import app
16
- from gitops_server.workers import DeploymentStatusWorker, DeployQueueWorker
16
+ from gitops_server.workers import DeployQueueWorker
17
17
 
18
18
  manually_instrument_logging()
19
19
  manually_instrument_fastapi()
@@ -64,9 +64,6 @@ async def startup_event():
64
64
  deploy_queue_worker = DeployQueueWorker.get_worker()
65
65
  deploy_queue_worker.task = asyncio.ensure_future(deploy_queue_worker.run(), loop=loop)
66
66
 
67
- deployment_status_worker = DeploymentStatusWorker.get_worker()
68
- deployment_status_worker.task = asyncio.ensure_future(deployment_status_worker.run(), loop=loop)
69
-
70
67
 
71
68
  def get_digest(data: bytes) -> str:
72
69
  """Calculate the digest of a webhook body.
@@ -0,0 +1 @@
1
+ __version__ = "1.2.0"
@@ -0,0 +1 @@
1
+ from .deployer import DeployQueueWorker # NOQA
@@ -50,10 +50,13 @@ async def update_issue_from_deployment_url(app: App, deployment_url: str, **kwar
50
50
 
51
51
  async def handle_successful_deploy(app: App, result, deployer, **kwargs) -> UpdateAppResult:
52
52
  github_deployment_url = str(app.values.get("github/deployment_url", ""))
53
+ # I know this shouldn't be uptick specific but for now it is.
54
+ environment_url = app.values.get("github/environment_url", "") or f"https://{app.name}.onuptick.com"
53
55
  await github.update_deployment(
54
56
  github_deployment_url,
55
- status=github.STATUSES.in_progress,
57
+ status=github.STATUSES.success,
56
58
  description="Helm installed app into cluster. Waiting for pods to deploy.",
59
+ environment_url=environment_url,
57
60
  )
58
61
  return result
59
62
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  [project]
3
3
  name = "gitops"
4
- version = "1.1.2"
4
+ version = "1.2.0"
5
5
  description = "Manage multiple apps across one or more k8s clusters."
6
6
  requires-python = ">=3.12"
7
7
  readme = "README.md"
@@ -31,14 +31,3 @@ def test_webhook_returns_200_if_hmac_is_correct():
31
31
 
32
32
  assert response.status_code == 200
33
33
  get_worker_mock.assert_called()
34
-
35
-
36
- @patch("gitops_server.main.settings.GITHUB_WEBHOOK_KEY", "test_key")
37
- def test_webhook_returns_400_if_hmac_is_invalid():
38
- sha_encoding = "INVALID HMAC ENCODING"
39
- headers["X-Hub-Signature"] = f"sha1={sha_encoding}"
40
-
41
- with patch.object(gitops_server.workers.DeploymentStatusWorker, "get_worker", AsyncMock):
42
- response = client.post("/webhook", headers=headers, json=payload)
43
-
44
- assert response.status_code == 400
@@ -1 +0,0 @@
1
- __version__ = "1.1.2"
@@ -1,2 +0,0 @@
1
- from .deployer import DeployQueueWorker # NOQA
2
- from .status_updater import DeploymentStatusWorker # NOQA
@@ -1 +0,0 @@
1
- from .worker import DeploymentStatusWorker # NOQA
@@ -1,126 +0,0 @@
1
- """
2
- This async worker keeps tracks of ongoing deployments by polling k8s space (CLUSTER_NAMESPACE).
3
-
4
- The worker polls and checks for deployments with the following labels: gitops/deploy_id and gitops/status=in_progress
5
-
6
- Any failing/successfully deployed deployment is notified to github.
7
- This mechanism will only work if the annotation: github/deployment_url is applied to the deployment.
8
-
9
- TODO
10
- - Update the slack summary message from deploy.py
11
- - @notify the user if the deployment failed
12
- """
13
-
14
- import asyncio
15
- import logging
16
-
17
- import kubernetes_asyncio # type:ignore[import-untyped]
18
- import kubernetes_asyncio.client # type:ignore[import-untyped]
19
- import kubernetes_asyncio.config # type:ignore[import-untyped]
20
-
21
- from gitops_server.settings import CLUSTER_NAMESPACE
22
- from gitops_server.utils import github
23
-
24
- logger = logging.getLogger("deployment_status")
25
-
26
-
27
- async def get_ingress_url(api, namespace: str, app: str):
28
- """Attempts to get domain for the ingress associated with the app"""
29
- ingresses = await kubernetes_asyncio.client.NetworkingV1Api(api).list_namespaced_ingress(
30
- namespace=namespace, label_selector=f"app={app}"
31
- )
32
- environment_url = ""
33
- if ingresses.items:
34
- try:
35
- environment_url = "https://" + ingresses.items[0].spec.rules[0].host
36
- except Exception:
37
- logger.warning(f"Could not find ingress for {app=}")
38
- return environment_url
39
-
40
-
41
- class DeploymentStatusWorker:
42
- """Watches for deployments and updates the github deployment status"""
43
-
44
- _worker = None
45
-
46
- @classmethod
47
- def get_worker(cls):
48
- if not cls._worker:
49
- loop = asyncio.get_running_loop()
50
- cls._worker = cls(loop)
51
- return cls._worker
52
-
53
- def __init__(self, loop):
54
- self.loop = loop
55
-
56
- async def load_config(self):
57
- try:
58
- kubernetes_asyncio.config.load_incluster_config()
59
- except kubernetes_asyncio.config.config_exception.ConfigException:
60
- await kubernetes_asyncio.config.load_kube_config()
61
-
62
- async def process_work(self):
63
- await asyncio.sleep(5)
64
- async with kubernetes_asyncio.client.ApiClient() as api:
65
- apps_api = kubernetes_asyncio.client.AppsV1Api(api)
66
- deployments = await apps_api.list_namespaced_deployment(
67
- # Only things that have gitops/deploy_id aka was deployed
68
- namespace=CLUSTER_NAMESPACE,
69
- label_selector="gitops/deploy_id,gitops/status=in_progress",
70
- )
71
- await asyncio.sleep(5)
72
-
73
- for deployment in deployments.items:
74
- # Deployment status may not exist yet
75
- if not deployment.status:
76
- continue
77
- app = deployment.metadata.labels["app"]
78
- namespace = deployment.metadata.namespace
79
- github_deployment_url = deployment.metadata.annotations.get("github/deployment_url")
80
- conds = {}
81
- for x in deployment.status.conditions:
82
- conds[x.type] = x
83
- status = None
84
- if (
85
- len(conds) == 2
86
- and conds["Available"].status == "True"
87
- and conds["Progressing"].status == "True"
88
- and conds["Progressing"].reason == "NewReplicaSetAvailable"
89
- ):
90
- status = github.STATUSES.success
91
- await github.update_deployment(
92
- github_deployment_url,
93
- status=status,
94
- description="Deployed successfully",
95
- environment_url=await get_ingress_url(api, namespace, app),
96
- )
97
- elif (
98
- "Progressing" in conds
99
- and conds["Progressing"].status == "False"
100
- and conds["Progressing"].reason == "ProgressDeadlineExceeded"
101
- ):
102
- status = github.STATUSES.failure
103
- await github.update_deployment(
104
- github_deployment_url,
105
- status=status,
106
- description="Failed to deploy. Check the pod or migrations.",
107
- )
108
- if status:
109
- logger.info(f"Patching {deployment.metadata.name}.label.gitops/status to {status}")
110
- patch = {"metadata": {"labels": {"gitops/status": status}}}
111
- try:
112
- await apps_api.patch_namespaced_deployment(
113
- deployment.metadata.name, deployment.metadata.namespace, patch
114
- )
115
- except kubernetes_asyncio.client.exceptions.ApiException as e:
116
- logger.warning(e, exc_info=True)
117
-
118
- async def run(self):
119
- logger.info("Starting deployment status watching loop")
120
- logger.info("Loading kubernetes asyncio api")
121
- while True:
122
- try:
123
- await self.load_config()
124
- await self.process_work()
125
- except Exception as e:
126
- logger.error(str(e), exc_info=True)
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