gitops 0.11.4__tar.gz → 0.11.6__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 (79) hide show
  1. {gitops-0.11.4 → gitops-0.11.6}/PKG-INFO +10 -5
  2. {gitops-0.11.4 → gitops-0.11.6}/README.md +9 -4
  3. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/Chart.yaml +1 -1
  4. {gitops-0.11.4 → gitops-0.11.6}/gitops/__init__.py +5 -1
  5. {gitops-0.11.4 → gitops-0.11.6}/gitops/common/app.py +24 -5
  6. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/apps.py +8 -3
  7. gitops-0.11.6/gitops_server/types.py +40 -0
  8. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/deploy.py +4 -2
  9. {gitops-0.11.4 → gitops-0.11.6}/pyproject.toml +1 -1
  10. gitops-0.11.6/tests/conftest.py +3 -0
  11. {gitops-0.11.4 → gitops-0.11.6}/tests/test_app.py +15 -0
  12. {gitops-0.11.4 → gitops-0.11.6}/tests/test_deploy.py +20 -6
  13. {gitops-0.11.4 → gitops-0.11.6}/tests/utils.py +8 -2
  14. gitops-0.11.4/gitops_server/types.py +0 -34
  15. {gitops-0.11.4 → gitops-0.11.6}/.dockerignore +0 -0
  16. {gitops-0.11.4 → gitops-0.11.6}/.github/workflows/publish.yml +0 -0
  17. {gitops-0.11.4 → gitops-0.11.6}/.github/workflows/test.yml +0 -0
  18. {gitops-0.11.4 → gitops-0.11.6}/.gitignore +0 -0
  19. {gitops-0.11.4 → gitops-0.11.6}/.mise.toml +0 -0
  20. {gitops-0.11.4 → gitops-0.11.6}/.pre-commit-config.yaml +0 -0
  21. {gitops-0.11.4 → gitops-0.11.6}/Dockerfile +0 -0
  22. {gitops-0.11.4 → gitops-0.11.6}/LICENSE +0 -0
  23. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/.helmignore +0 -0
  24. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/README.md +0 -0
  25. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/_helpers.tpl +0 -0
  26. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/certificate.yml +0 -0
  27. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/deployment.yml +0 -0
  28. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/environment.yml +0 -0
  29. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/ingress.yml +0 -0
  30. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/issuer.yml +0 -0
  31. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/secrets.yml +0 -0
  32. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/service.yml +0 -0
  33. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/serviceaccount.yaml +0 -0
  34. {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/values.yaml +0 -0
  35. {gitops-0.11.4 → gitops-0.11.6}/docker-compose.yml +0 -0
  36. {gitops-0.11.4 → gitops-0.11.6}/gitops/common/__init__.py +0 -0
  37. {gitops-0.11.4 → gitops-0.11.6}/gitops/common/utils.py +0 -0
  38. {gitops-0.11.4 → gitops-0.11.6}/gitops/core.py +0 -0
  39. {gitops-0.11.4 → gitops-0.11.6}/gitops/db.py +0 -0
  40. {gitops-0.11.4 → gitops-0.11.6}/gitops/main.py +0 -0
  41. {gitops-0.11.4 → gitops-0.11.6}/gitops/py.typed +0 -0
  42. {gitops-0.11.4 → gitops-0.11.6}/gitops/settings.py +0 -0
  43. {gitops-0.11.4 → gitops-0.11.6}/gitops/shorthands.py +0 -0
  44. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/__init__.py +0 -0
  45. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/async_runner.py +0 -0
  46. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/cli.py +0 -0
  47. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/exceptions.py +0 -0
  48. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/images.py +0 -0
  49. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/kube.py +0 -0
  50. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/tags.py +0 -0
  51. {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/yaml.py +0 -0
  52. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/README.md +0 -0
  53. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/__init__.py +0 -0
  54. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/app.py +0 -0
  55. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/main.py +0 -0
  56. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/settings.py +0 -0
  57. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/__init__.py +0 -0
  58. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/git.py +0 -0
  59. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/github.py +0 -0
  60. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/slack.py +0 -0
  61. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/__init__.py +0 -0
  62. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/__init__.py +0 -0
  63. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/hooks.py +0 -0
  64. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/worker.py +0 -0
  65. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/status_updater/__init__.py +0 -0
  66. {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/status_updater/worker.py +0 -0
  67. {gitops-0.11.4 → gitops-0.11.6}/poetry.lock +0 -0
  68. {gitops-0.11.4 → gitops-0.11.6}/release.py +0 -0
  69. {gitops-0.11.4 → gitops-0.11.6}/ruff.toml +0 -0
  70. {gitops-0.11.4 → gitops-0.11.6}/secrets.example.env +0 -0
  71. {gitops-0.11.4 → gitops-0.11.6}/setup.cfg +0 -0
  72. {gitops-0.11.4 → gitops-0.11.6}/tests/__init__.py +0 -0
  73. {gitops-0.11.4 → gitops-0.11.6}/tests/sample_data.py +0 -0
  74. {gitops-0.11.4 → gitops-0.11.6}/tests/test_core.py +0 -0
  75. {gitops-0.11.4 → gitops-0.11.6}/tests/test_git.py +0 -0
  76. {gitops-0.11.4 → gitops-0.11.6}/tests/test_gitops_server.py +0 -0
  77. {gitops-0.11.4 → gitops-0.11.6}/tests/test_utils.py +0 -0
  78. {gitops-0.11.4 → gitops-0.11.6}/tests/webhook_sample_data.py +0 -0
  79. {gitops-0.11.4 → gitops-0.11.6}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: gitops
3
- Version: 0.11.4
3
+ Version: 0.11.6
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
- # Making a release
88
-
89
- 1. Run `make release` to set tag version the multiple required locations.
90
- 2. Create a github release. Set the release tag to match the version number used. The github pipeline will do the following: Create a new version of the chart, create a docker image and release a new python library version to pypi.
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
- # Making a release
64
-
65
- 1. Run `make release` to set tag version the multiple required locations.
66
- 2. Create a github release. Set the release tag to match the version number used. The github pipeline will do the following: Create a new version of the chart, create a docker image and release a new python library version to pypi.
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.
@@ -2,4 +2,4 @@ apiVersion: v1
2
2
  appVersion: "1.0"
3
3
  description: GitOps Server Helm chart.
4
4
  name: gitops
5
- version: 0.11.4
5
+ version: 0.11.6
@@ -2,9 +2,11 @@ import os
2
2
  import sys
3
3
  from pathlib import Path
4
4
 
5
+ from gitops.utils.apps import App, get_app_details, get_apps
6
+
5
7
  from .utils.cli import success, warning
6
8
 
7
- __version__ = "0.11.4"
9
+ __version__ = "0.11.6"
8
10
 
9
11
 
10
12
  # Checking gitops version matches cluster repo version.
@@ -25,3 +27,5 @@ if versions_path.exists():
25
27
  f" requirement {success(min_gitops_version)}.",
26
28
  file=sys.stderr,
27
29
  )
30
+
31
+ __all__ = ["App", "get_app_details", "get_apps", "__version__"]
@@ -24,8 +24,19 @@ class App:
24
24
  deployments: dict | None = None,
25
25
  secrets: dict | None = None,
26
26
  load_secrets: bool = True,
27
+ encode_secrets: bool = True,
28
+ # deprecated
27
29
  account_id: str = "",
28
30
  ) -> None:
31
+ """
32
+ :params name: The name of the app
33
+ :params path: The path to the app directory
34
+ :params deployments: The deployments dictionary (injecting for testing)
35
+ :params secrets: The secrets dictionary (injecting for testing)
36
+ :params load_secrets: Whether to load the secrets file
37
+ :params encode_secrets: Whether to base64 encode the secrets
38
+ """
39
+ self.encode_secrets = encode_secrets
29
40
  self.name = name
30
41
  self.path = path
31
42
  self.account_id = account_id
@@ -47,6 +58,9 @@ class App:
47
58
  and json.dumps(self.values, sort_keys=True) == json.dumps(other.values, sort_keys=True)
48
59
  )
49
60
 
61
+ def __repr__(self) -> str:
62
+ return f"App(name={self.name}, cluster={self.cluster}, tags={self.tags})"
63
+
50
64
  def is_inactive(self) -> bool:
51
65
  return "inactive" in self.values.get("tags", [])
52
66
 
@@ -61,11 +75,11 @@ class App:
61
75
  current_dict = current_dict.setdefault(key, {})
62
76
  current_dict[keys[-1]] = value
63
77
 
64
- def _make_values(self, deployments: dict, secrets: dict) -> dict:
65
- values = {
66
- **deployments,
67
- "secrets": {**{k: b64encode(v.encode()).decode() for k, v in secrets.items()}},
68
- }
78
+ def _make_values(self, deployments: dict, secrets: dict[str, str]) -> dict:
79
+ def encode(value: str) -> str:
80
+ return b64encode(str(value).encode()).decode() if self.encode_secrets else value
81
+
82
+ values = {**deployments, "secrets": {**{k: encode(v) for k, v in secrets.items()}}}
69
83
 
70
84
  image = self._make_image(deployments)
71
85
  if image:
@@ -127,6 +141,11 @@ class App:
127
141
  def service_account_name(self) -> str:
128
142
  return self.values.get("serviceAccount", {}).get("name") or self.values.get("serviceAccountName") or "default"
129
143
 
144
+ @property
145
+ def secrets(self) -> dict[str, str]:
146
+ # TODO: This should be a first class property
147
+ return self.values.get("secrets", {})
148
+
130
149
 
131
150
  class Chart:
132
151
  """Represents a Helm chart
@@ -1,5 +1,6 @@
1
1
  import sys
2
2
  from pathlib import Path
3
+ from typing import Literal
3
4
 
4
5
  from colorama import Fore
5
6
  from tabulate import tabulate
@@ -21,13 +22,16 @@ def is_valid_app_directory(directory: Path) -> bool:
21
22
  return all(file_paths)
22
23
 
23
24
 
24
- def get_app_details(app_name: str, load_secrets: bool = True, exit_if_not_found: bool = True) -> App:
25
+ def get_app_details(
26
+ app_name: str, load_secrets: bool = True, encode_secrets: bool = True, exit_if_not_found: bool = True
27
+ ) -> App:
25
28
  account_id = get_account_id() if load_secrets else "UNKNOWN"
26
29
  try:
27
30
  app = App(
28
31
  app_name,
29
32
  path=str(get_apps_directory() / app_name),
30
33
  load_secrets=load_secrets,
34
+ encode_secrets=encode_secrets,
31
35
  account_id=account_id,
32
36
  )
33
37
  except FileNotFoundError as e:
@@ -63,10 +67,11 @@ def update_app(app_name: str, **kwargs: object) -> None:
63
67
  def get_apps( # noqa: C901
64
68
  filter: set[str] | list[str] | str = "",
65
69
  exclude: set[str] | list[str] | str = "",
66
- mode: str = "PROMPT",
70
+ mode: Literal["PROMPT", "PREVIEW", "SILENT"] | None = "PROMPT",
67
71
  autoexclude_inactive: bool = True,
68
72
  message: str | None = None,
69
73
  load_secrets: bool = True,
74
+ encode_secrets: bool = True,
70
75
  ) -> list[App]:
71
76
  """Return apps that contain ALL of the tags listed in `filter` and NONE of the tags listed in
72
77
  `exclude`. The incoming filter and exclude params may come in as a list or commastring.
@@ -101,7 +106,7 @@ def get_apps( # noqa: C901
101
106
  continue
102
107
  elif not is_valid_app_directory(entry):
103
108
  continue
104
- app = get_app_details(entry.name, load_secrets=load_secrets)
109
+ app = get_app_details(entry.name, load_secrets=load_secrets, encode_secrets=encode_secrets)
105
110
 
106
111
  pseudotags = [app.name, app.cluster]
107
112
  if app.image and app.image_prefix:
@@ -0,0 +1,40 @@
1
+ import logging
2
+ import os
3
+ from typing import TypedDict
4
+
5
+ from gitops.common.app import App
6
+ from gitops_server import settings
7
+
8
+ logger = logging.getLogger("gitops")
9
+
10
+
11
+ class RunOutput(TypedDict):
12
+ exit_code: int
13
+ output: str
14
+
15
+
16
+ class UpdateAppResult(RunOutput):
17
+ app_name: str
18
+ slack_message: str
19
+
20
+
21
+ class AppDefinitions:
22
+ def __init__(self, name, apps: dict[str, App] | None = None, path: str | None = None):
23
+ self.name = name
24
+ self.apps = apps or {}
25
+
26
+ if path:
27
+ path = os.path.join(path, "apps")
28
+
29
+ for entry in os.listdir(path):
30
+ entry_path = os.path.join(path, entry)
31
+ if entry[0] != "." and not os.path.isfile(entry_path):
32
+ app = App(entry, entry_path, account_id=settings.ACCOUNT_ID)
33
+ self.apps[entry] = app
34
+
35
+ # Removing apps that are suspended or not part of this cluster
36
+ for app in list(self.apps.values()):
37
+ # We only care for apps pertaining to our current cluster.
38
+ if app.values["cluster"] != settings.CLUSTER_NAME or "suspended" in app.tags:
39
+ # and suspended apps are considered removed from the cluster.
40
+ self.apps.pop(app.name)
@@ -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")
@@ -66,8 +67,7 @@ async def post_result_summary(source: str, results: list[UpdateAppResult]):
66
67
  async def load_app_definitions(url: str, sha: str) -> AppDefinitions:
67
68
  logger.info(f'Loading app definitions at "{sha}".')
68
69
  async with temp_repo(url, ref=sha) as repo:
69
- app_definitions = AppDefinitions(name=get_repo_name_from_url(url))
70
- app_definitions.from_path(repo)
70
+ app_definitions = AppDefinitions(name=get_repo_name_from_url(url), path=repo)
71
71
  return app_definitions
72
72
 
73
73
 
@@ -185,6 +185,7 @@ class Deployer:
185
185
  with tracer.start_as_current_span("helm_upgrade"):
186
186
  result = await run(
187
187
  "helm secrets upgrade --create-namespace"
188
+ f" --history-max {MAX_HELM_HISTORY}"
188
189
  " --install"
189
190
  " --timeout=600s"
190
191
  f"{' --set skip_migrations=true' if self.skip_migrations else ''}"
@@ -207,6 +208,7 @@ class Deployer:
207
208
  with tracer.start_as_current_span("helm_upgrade"):
208
209
  result = await run(
209
210
  "helm secrets upgrade --create-namespace"
211
+ f" --history-max {MAX_HELM_HISTORY}"
210
212
  " --install"
211
213
  " --timeout=600s"
212
214
  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"
4
+ version = "0.11.6"
5
5
  description = "Manage multiple apps across one or more k8s clusters."
6
6
  requires-python = ">=3.12"
7
7
  readme = "README.md"
@@ -0,0 +1,3 @@
1
+ import gitops_server.settings
2
+
3
+ gitops_server.settings.CLUSTER_NAME = "test-cluster"
@@ -105,3 +105,18 @@ class TestChart:
105
105
 
106
106
  assert chart.type == "local"
107
107
  assert chart.path == "."
108
+
109
+ def test_app_namespace_property(self):
110
+ path = create_test_yaml()
111
+ app = App("test", path, encode_secrets=True)
112
+
113
+ assert app.namespace
114
+
115
+ def test_app_encode_secrets_works(self):
116
+ path = create_test_yaml()
117
+ app = App("test", path, encode_secrets=True)
118
+
119
+ assert app.secrets["SNAPE"] != "KILLS_DUMBLEDORE"
120
+
121
+ app_decoded = App("test", path, encode_secrets=False)
122
+ assert app_decoded.secrets["SNAPE"] == "KILLS_DUMBLEDORE"
@@ -4,10 +4,11 @@ from unittest.mock import patch
4
4
  import pytest
5
5
 
6
6
  from gitops.common.app import App
7
+ from gitops_server.types import AppDefinitions
7
8
  from gitops_server.workers.deployer import Deployer
8
9
 
9
10
  from .sample_data import SAMPLE_GITHUB_PAYLOAD, SAMPLE_GITHUB_PAYLOAD_SKIP_MIGRATIONS
10
- from .utils import mock_load_app_definitions
11
+ from .utils import create_test_yaml, mock_load_app_definitions
11
12
 
12
13
  # Patch gitops_server.git.run & check correct commands + order
13
14
  # Patch command that reads yaml from cluster repo +
@@ -29,13 +30,13 @@ class TestDeploy:
29
30
  assert run_mock.call_count == 4
30
31
  assert run_mock.call_args_list[0][0][0] == "cd mock-repo; helm dependency build"
31
32
  assert re.match(
32
- r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
33
+ r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
33
34
  r" --namespace=mynamespace sample-app-\d mock-repo",
34
35
  run_mock.call_args_list[1][0][0],
35
36
  )
36
37
  assert run_mock.call_args_list[2][0][0] == "cd mock-repo; helm dependency build"
37
38
  assert re.match(
38
- r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
39
+ r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
39
40
  r" --namespace=mynamespace sample-app-\d mock-repo",
40
41
  run_mock.call_args_list[3][0][0],
41
42
  )
@@ -78,7 +79,7 @@ class TestDeploy:
78
79
  assert run_mock.call_count == 2
79
80
  assert run_mock.call_args_list[0][0][0] == "helm repo add brigade https://helm.charts"
80
81
  assert re.match(
81
- r"helm secrets upgrade --create-namespace --install --timeout=600s -f .+\.yml"
82
+ r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s -f .+\.yml"
82
83
  r" --namespace=mynamespace helm_app brigade/brigade",
83
84
  run_mock.call_args_list[1][0][0],
84
85
  )
@@ -99,13 +100,13 @@ class TestDeploy:
99
100
  assert run_mock.call_count == 4
100
101
  assert run_mock.call_args_list[0][0][0] == "cd mock-repo; helm dependency build"
101
102
  assert re.match(
102
- r"helm secrets upgrade --create-namespace --install --timeout=600s --set skip_migrations=true -f .+\.yml"
103
+ r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s --set skip_migrations=true -f .+\.yml"
103
104
  r" --namespace=mynamespace sample-app-\d mock-repo",
104
105
  run_mock.call_args_list[1][0][0],
105
106
  )
106
107
  assert run_mock.call_args_list[2][0][0] == "cd mock-repo; helm dependency build"
107
108
  assert re.match(
108
- r"helm secrets upgrade --create-namespace --install --timeout=600s --set skip_migrations=true -f .+\.yml"
109
+ r"helm secrets upgrade --create-namespace --history-max 3 --install --timeout=600s --set skip_migrations=true -f .+\.yml"
109
110
  r" --namespace=mynamespace sample-app-\d mock-repo",
110
111
  run_mock.call_args_list[3][0][0],
111
112
  )
@@ -120,3 +121,16 @@ class TestDeploy:
120
121
  ]
121
122
  for where, check in check_in_run_mock:
122
123
  assert check in post_mock.call_args_list[where][0][0]
124
+
125
+
126
+ class TestLoadAppDefinitions:
127
+ def test_load_app_definitions_ignores_suspended_apps(self):
128
+ app_definitions = AppDefinitions(
129
+ "mock-repo",
130
+ apps={
131
+ "in-cluster": App("in-cluster", path=create_test_yaml(tags=[])),
132
+ "suspended": App("suspended", path=create_test_yaml(tags=["suspended"])),
133
+ "not-in-cluster": App("not-in-cluster", path=create_test_yaml(tags=[], cluster="other-cluster")),
134
+ },
135
+ )
136
+ assert len(app_definitions.apps) == 1
@@ -1,3 +1,5 @@
1
+ from typing import Any
2
+
1
3
  import yaml
2
4
 
3
5
  from gitops.common.app import App
@@ -20,14 +22,14 @@ async def mock_load_app_definitions(url, sha):
20
22
  return app_definitions
21
23
 
22
24
 
23
- def create_test_yaml(fg=4, bg=2):
25
+ def create_test_yaml(fg=4, bg=2, **kwargs: Any):
24
26
  data = {
25
27
  "chart": "https://github.com/some/chart",
26
28
  "images": {"template": "template-tag-{tag}"},
27
29
  "namespace": "mynamespace",
28
30
  "tags": ["tag1", "tag2"],
29
31
  "image-tag": "myimagetag",
30
- "cluster": "UNKNOWN",
32
+ "cluster": "test-cluster",
31
33
  "containers": {"fg": {"replicas": fg}, "bg": {"replicas": bg}},
32
34
  "environment": {
33
35
  "DJANGO_SETTINGS_MODULE": "my.settings.module",
@@ -36,6 +38,10 @@ def create_test_yaml(fg=4, bg=2):
36
38
  "MEDIA_CLOUDINARY_PREFIX": "cloudinaryprefix",
37
39
  },
38
40
  }
41
+
42
+ for k, v in kwargs.items():
43
+ data[k] = v
44
+
39
45
  with open("/tmp/deployment.yml", "w+") as fh:
40
46
  fh.write(yaml.dump(data))
41
47
 
@@ -1,34 +0,0 @@
1
- import logging
2
- import os
3
- from typing import TypedDict
4
-
5
- from gitops.common.app import App
6
- from gitops_server import settings
7
-
8
- logger = logging.getLogger("gitops")
9
-
10
-
11
- class RunOutput(TypedDict):
12
- exit_code: int
13
- output: str
14
-
15
-
16
- class UpdateAppResult(RunOutput):
17
- app_name: str
18
- slack_message: str
19
-
20
-
21
- class AppDefinitions:
22
- def __init__(self, name, apps: dict | None = None):
23
- self.name = name
24
- self.apps = apps or {}
25
-
26
- def from_path(self, path: str):
27
- path = os.path.join(path, "apps")
28
- for entry in os.listdir(path):
29
- entry_path = os.path.join(path, entry)
30
- if entry[0] != "." and not os.path.isfile(entry_path):
31
- app = App(entry, entry_path, account_id=settings.ACCOUNT_ID)
32
- # We only care for apps pertaining to our current cluster.
33
- if app.values["cluster"] == settings.CLUSTER_NAME:
34
- self.apps[entry] = app
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