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.
- {gitops-0.11.4 → gitops-0.11.6}/PKG-INFO +10 -5
- {gitops-0.11.4 → gitops-0.11.6}/README.md +9 -4
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/Chart.yaml +1 -1
- {gitops-0.11.4 → gitops-0.11.6}/gitops/__init__.py +5 -1
- {gitops-0.11.4 → gitops-0.11.6}/gitops/common/app.py +24 -5
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/apps.py +8 -3
- gitops-0.11.6/gitops_server/types.py +40 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/deploy.py +4 -2
- {gitops-0.11.4 → gitops-0.11.6}/pyproject.toml +1 -1
- gitops-0.11.6/tests/conftest.py +3 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_app.py +15 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_deploy.py +20 -6
- {gitops-0.11.4 → gitops-0.11.6}/tests/utils.py +8 -2
- gitops-0.11.4/gitops_server/types.py +0 -34
- {gitops-0.11.4 → gitops-0.11.6}/.dockerignore +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/.github/workflows/publish.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/.github/workflows/test.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/.gitignore +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/.mise.toml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/.pre-commit-config.yaml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/Dockerfile +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/LICENSE +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/.helmignore +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/README.md +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/_helpers.tpl +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/certificate.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/deployment.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/environment.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/ingress.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/issuer.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/secrets.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/service.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/templates/serviceaccount.yaml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/charts/gitops/values.yaml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/docker-compose.yml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/common/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/common/utils.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/core.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/db.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/main.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/py.typed +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/settings.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/shorthands.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/async_runner.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/cli.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/exceptions.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/images.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/kube.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/tags.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops/utils/yaml.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/README.md +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/app.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/main.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/settings.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/git.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/github.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/utils/slack.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/hooks.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/deployer/worker.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/status_updater/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/gitops_server/workers/status_updater/worker.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/poetry.lock +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/release.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/ruff.toml +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/secrets.example.env +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/setup.cfg +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/__init__.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/sample_data.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_core.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_git.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_gitops_server.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/test_utils.py +0 -0
- {gitops-0.11.4 → gitops-0.11.6}/tests/webhook_sample_data.py +0 -0
- {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.
|
|
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
|
-
|
|
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.
|
|
@@ -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.
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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(
|
|
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:
|
|
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 ''}"
|
|
@@ -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": "
|
|
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
|
|
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
|