gitops 0.11.5__tar.gz → 0.12.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.
- {gitops-0.11.5 → gitops-0.12.0}/PKG-INFO +1 -1
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/Chart.yaml +1 -1
- {gitops-0.11.5 → gitops-0.12.0}/gitops/__init__.py +5 -1
- {gitops-0.11.5 → gitops-0.12.0}/gitops/common/app.py +29 -5
- {gitops-0.11.5 → gitops-0.12.0}/gitops/main.py +5 -4
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/apps.py +8 -3
- gitops-0.12.0/gitops/utils/images.py +113 -0
- gitops-0.12.0/gitops_server/types.py +40 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/deployer/deploy.py +1 -2
- {gitops-0.11.5 → gitops-0.12.0}/pyproject.toml +1 -1
- gitops-0.12.0/tests/conftest.py +3 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_app.py +15 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_deploy.py +15 -1
- {gitops-0.11.5 → gitops-0.12.0}/tests/utils.py +8 -2
- gitops-0.11.5/gitops/db.py +0 -254
- gitops-0.11.5/gitops/utils/images.py +0 -70
- gitops-0.11.5/gitops_server/types.py +0 -34
- gitops-0.11.5/poetry.lock +0 -1808
- {gitops-0.11.5 → gitops-0.12.0}/.dockerignore +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/.github/workflows/publish.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/.github/workflows/test.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/.gitignore +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/.mise.toml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/.pre-commit-config.yaml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/Dockerfile +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/LICENSE +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/README.md +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/.helmignore +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/README.md +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/_helpers.tpl +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/certificate.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/deployment.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/environment.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/ingress.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/issuer.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/secrets.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/service.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/templates/serviceaccount.yaml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/charts/gitops/values.yaml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/docker-compose.yml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/common/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/common/utils.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/core.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/py.typed +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/settings.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/shorthands.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/async_runner.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/cli.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/exceptions.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/kube.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/tags.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops/utils/yaml.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/README.md +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/app.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/main.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/settings.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/utils/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/utils/git.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/utils/github.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/utils/slack.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/deployer/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/deployer/hooks.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/deployer/worker.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/status_updater/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/gitops_server/workers/status_updater/worker.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/release.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/ruff.toml +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/secrets.example.env +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/setup.cfg +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/__init__.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/sample_data.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_core.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_git.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_gitops_server.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/test_utils.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/tests/webhook_sample_data.py +0 -0
- {gitops-0.11.5 → gitops-0.12.0}/uv.lock +0 -0
|
@@ -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.
|
|
9
|
+
__version__ = "0.12.0"
|
|
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:
|
|
@@ -99,6 +113,11 @@ class App:
|
|
|
99
113
|
"""305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/[uptick]:yoink-9f03ac80f3"""
|
|
100
114
|
return self.image.split(":")[0].split("/")[-1]
|
|
101
115
|
|
|
116
|
+
@property
|
|
117
|
+
def image_repository(self) -> str:
|
|
118
|
+
"""[305686791668.dkr.ecr.ap-southeast-2.amazonaws.com]/uptick:yoink-9f03ac80f3"""
|
|
119
|
+
return self.image.split("/")[0]
|
|
120
|
+
|
|
102
121
|
@property
|
|
103
122
|
def image_tag(self) -> str:
|
|
104
123
|
"""305686791668.dkr.ecr.ap-southeast-2.amazonaws.com/uptick:[yoink-9f03ac80f3]"""
|
|
@@ -127,6 +146,11 @@ class App:
|
|
|
127
146
|
def service_account_name(self) -> str:
|
|
128
147
|
return self.values.get("serviceAccount", {}).get("name") or self.values.get("serviceAccountName") or "default"
|
|
129
148
|
|
|
149
|
+
@property
|
|
150
|
+
def secrets(self) -> dict[str, str]:
|
|
151
|
+
# TODO: This should be a first class property
|
|
152
|
+
return self.values.get("secrets", {})
|
|
153
|
+
|
|
130
154
|
|
|
131
155
|
class Chart:
|
|
132
156
|
"""Represents a Helm chart
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
1
3
|
from invoke import Collection, Program, Task
|
|
2
4
|
|
|
3
|
-
from . import __version__, core,
|
|
5
|
+
from . import __version__, core, shorthands
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
4
8
|
|
|
5
9
|
version = __version__
|
|
6
10
|
|
|
@@ -12,7 +16,4 @@ for core_ns in [core, shorthands]:
|
|
|
12
16
|
for task in tasks:
|
|
13
17
|
namespace.add_task(task)
|
|
14
18
|
|
|
15
|
-
# Namespace the rarer ones.
|
|
16
|
-
namespace.add_collection(db) # type: ignore
|
|
17
|
-
|
|
18
19
|
program = Program(namespace=namespace, version=version)
|
|
@@ -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,113 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import logging
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
from hashlib import md5
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import boto3
|
|
8
|
+
import botocore
|
|
9
|
+
import botocore.exceptions
|
|
10
|
+
from colorama import Fore
|
|
11
|
+
|
|
12
|
+
from .cli import colourise
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from mypy_boto3_ecr.type_defs import ImageDetailTypeDef # type: ignore
|
|
16
|
+
BATCH_SIZE = 100
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_image(tag: str) -> str:
|
|
22
|
+
"""Finds a specific image in ECR."""
|
|
23
|
+
# TODO
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@lru_cache
|
|
28
|
+
def get_latest_image(repository_name: str, prefix: str, ecr_repository: str | None = None) -> str | None: # noqa:C901
|
|
29
|
+
"""Finds latest image in ECR with the given prefix and returns the image tag
|
|
30
|
+
|
|
31
|
+
param ecr_repository is expected in this format: 305686791668.dkr.ecr.ap-southeast-2.amazonaws.com
|
|
32
|
+
"""
|
|
33
|
+
describe_image_args = {}
|
|
34
|
+
region_name = None
|
|
35
|
+
if ecr_repository:
|
|
36
|
+
account_id = ecr_repository.split(".")[0]
|
|
37
|
+
describe_image_args["registryId"] = account_id
|
|
38
|
+
region_name = ecr_repository.split(".")[3]
|
|
39
|
+
|
|
40
|
+
ecr_client = boto3.client("ecr", region_name=region_name)
|
|
41
|
+
client_paginator = ecr_client.get_paginator("describe_images")
|
|
42
|
+
|
|
43
|
+
results = []
|
|
44
|
+
|
|
45
|
+
# First we try to find the image with `*latest`
|
|
46
|
+
image_tag = f"{prefix}-latest" if prefix else "latest"
|
|
47
|
+
|
|
48
|
+
def add_image_to_results(image: "ImageDetailTypeDef") -> None:
|
|
49
|
+
if prefix != "":
|
|
50
|
+
if prefix_tags := [
|
|
51
|
+
tag for tag in image["imageTags"] if tag.startswith(prefix + "-") and "latest" not in tag
|
|
52
|
+
]:
|
|
53
|
+
results.append((prefix_tags[0], image["imagePushedAt"]))
|
|
54
|
+
else:
|
|
55
|
+
if prefix_tags := [tag for tag in image["imageTags"] if "-" not in tag and "latest" not in tag]:
|
|
56
|
+
results.append((prefix_tags[0], image["imagePushedAt"]))
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
image = ecr_client.describe_images(
|
|
60
|
+
repositoryName=repository_name,
|
|
61
|
+
imageIds=[{"imageTag": image_tag}],
|
|
62
|
+
**describe_image_args, # type: ignore
|
|
63
|
+
)["imageDetails"][0]
|
|
64
|
+
# TODO: Remove this check after we've fully migrated to the new image tagging scheme
|
|
65
|
+
if image["imagePushedAt"] < datetime.datetime(2024, 9, 1, 0, 0, 0, tzinfo=datetime.timezone.utc):
|
|
66
|
+
logger.warning(f"Image {image_tag} is too old to be considered latest.")
|
|
67
|
+
raise ValueError("Image is too old")
|
|
68
|
+
|
|
69
|
+
add_image_to_results(image)
|
|
70
|
+
except (botocore.exceptions.ClientError, ValueError):
|
|
71
|
+
# Ok we couldn't find the -latest image; lets scan images manually
|
|
72
|
+
for ecr_response in client_paginator.paginate(
|
|
73
|
+
repositoryName=repository_name,
|
|
74
|
+
filter={
|
|
75
|
+
"tagStatus": "TAGGED",
|
|
76
|
+
},
|
|
77
|
+
**describe_image_args, # type: ignore
|
|
78
|
+
):
|
|
79
|
+
for image in ecr_response["imageDetails"]:
|
|
80
|
+
add_image_to_results(image)
|
|
81
|
+
if not results:
|
|
82
|
+
if prefix:
|
|
83
|
+
logger.info(f'No images found in repository: {repository_name} with tag "{prefix}-*".')
|
|
84
|
+
else:
|
|
85
|
+
logger.info(f"No images found in repository: {repository_name}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
latest_image_tag = sorted(results, key=lambda image: image[1], reverse=True)[0][0]
|
|
89
|
+
return latest_image_tag
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def colour_image(image_tag: str) -> str:
|
|
93
|
+
if not image_tag:
|
|
94
|
+
return image_tag
|
|
95
|
+
|
|
96
|
+
bits = image_tag.split("-")
|
|
97
|
+
if len(bits) > 1:
|
|
98
|
+
bits[0] = colourise(bits[0], color_hash(bits[1]))
|
|
99
|
+
return "-".join(bits)
|
|
100
|
+
else:
|
|
101
|
+
return colourise(bits[0], color_hash(bits[0]))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def color_hash(bit: str) -> str:
|
|
105
|
+
return [
|
|
106
|
+
Fore.RED,
|
|
107
|
+
Fore.GREEN,
|
|
108
|
+
Fore.YELLOW,
|
|
109
|
+
Fore.BLUE,
|
|
110
|
+
Fore.MAGENTA,
|
|
111
|
+
Fore.CYAN,
|
|
112
|
+
Fore.WHITE,
|
|
113
|
+
][int.from_bytes(md5(bit.encode()).digest(), "big") % 7] # noqa: S324
|
|
@@ -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)
|
|
@@ -67,8 +67,7 @@ async def post_result_summary(source: str, results: list[UpdateAppResult]):
|
|
|
67
67
|
async def load_app_definitions(url: str, sha: str) -> AppDefinitions:
|
|
68
68
|
logger.info(f'Loading app definitions at "{sha}".')
|
|
69
69
|
async with temp_repo(url, ref=sha) as repo:
|
|
70
|
-
app_definitions = AppDefinitions(name=get_repo_name_from_url(url))
|
|
71
|
-
app_definitions.from_path(repo)
|
|
70
|
+
app_definitions = AppDefinitions(name=get_repo_name_from_url(url), path=repo)
|
|
72
71
|
return app_definitions
|
|
73
72
|
|
|
74
73
|
|
|
@@ -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 +
|
|
@@ -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
|
|