qontract-reconcile 0.10.2.dev456__py3-none-any.whl → 0.10.2.dev473__py3-none-any.whl
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.
Potentially problematic release.
This version of qontract-reconcile might be problematic. Click here for more details.
- {qontract_reconcile-0.10.2.dev456.dist-info → qontract_reconcile-0.10.2.dev473.dist-info}/METADATA +2 -2
- {qontract_reconcile-0.10.2.dev456.dist-info → qontract_reconcile-0.10.2.dev473.dist-info}/RECORD +26 -26
- reconcile/aus/base.py +0 -3
- reconcile/aws_account_manager/integration.py +13 -1
- reconcile/aws_account_manager/utils.py +1 -1
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/change_owners.py +9 -9
- reconcile/change_owners/decision.py +1 -1
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +9 -0
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +3 -1
- reconcile/gql_definitions/introspection.json +15 -7
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +3 -1
- reconcile/quay_base.py +25 -6
- reconcile/quay_membership.py +35 -28
- reconcile/quay_mirror_org.py +6 -4
- reconcile/quay_permissions.py +81 -75
- reconcile/quay_repos.py +35 -37
- reconcile/queries.py +1 -1
- reconcile/templating/validator.py +4 -4
- reconcile/terraform_vpc_resources/merge_request.py +12 -2
- reconcile/terraform_vpc_resources/merge_request_manager.py +43 -19
- reconcile/typed_queries/saas_files.py +1 -1
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/quay_api.py +74 -87
- {qontract_reconcile-0.10.2.dev456.dist-info → qontract_reconcile-0.10.2.dev473.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.2.dev456.dist-info → qontract_reconcile-0.10.2.dev473.dist-info}/entry_points.txt +0 -0
reconcile/quay_mirror_org.py
CHANGED
|
@@ -17,7 +17,7 @@ from sretoolbox.container.image import (
|
|
|
17
17
|
)
|
|
18
18
|
from sretoolbox.container.skopeo import SkopeoCmdError
|
|
19
19
|
|
|
20
|
-
from reconcile.quay_base import
|
|
20
|
+
from reconcile.quay_base import QuayApiStore
|
|
21
21
|
from reconcile.quay_mirror import QuayMirror
|
|
22
22
|
from reconcile.utils.quay_mirror import record_timestamp, sync_tag
|
|
23
23
|
|
|
@@ -45,7 +45,7 @@ class QuayMirrorOrg:
|
|
|
45
45
|
) -> None:
|
|
46
46
|
self.dry_run = dry_run
|
|
47
47
|
self.skopeo_cli = Skopeo(dry_run)
|
|
48
|
-
self.quay_api_store =
|
|
48
|
+
self.quay_api_store = QuayApiStore()
|
|
49
49
|
self.compare_tags = compare_tags
|
|
50
50
|
self.compare_tags_interval = compare_tags_interval
|
|
51
51
|
self.orgs = orgs
|
|
@@ -71,6 +71,7 @@ class QuayMirrorOrg:
|
|
|
71
71
|
|
|
72
72
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
73
73
|
self.session.close()
|
|
74
|
+
self.quay_api_store.cleanup()
|
|
74
75
|
|
|
75
76
|
def run(self) -> None:
|
|
76
77
|
sync_tasks = self.process_sync_tasks()
|
|
@@ -101,11 +102,9 @@ class QuayMirrorOrg:
|
|
|
101
102
|
if self.orgs and org_key.org_name not in self.orgs:
|
|
102
103
|
continue
|
|
103
104
|
|
|
104
|
-
quay_api = org_info["api"]
|
|
105
105
|
upstream_org_key = org_info["mirror"]
|
|
106
106
|
assert upstream_org_key is not None
|
|
107
107
|
upstream_org = self.quay_api_store[upstream_org_key]
|
|
108
|
-
upstream_quay_api = upstream_org["api"]
|
|
109
108
|
|
|
110
109
|
push_token = upstream_org["push_token"]
|
|
111
110
|
|
|
@@ -114,7 +113,10 @@ class QuayMirrorOrg:
|
|
|
114
113
|
username = push_token["user"]
|
|
115
114
|
token = push_token["token"]
|
|
116
115
|
|
|
116
|
+
quay_api = org_info["api"]
|
|
117
117
|
org_repos = [item["name"] for item in quay_api.list_images()]
|
|
118
|
+
|
|
119
|
+
upstream_quay_api = upstream_org["api"]
|
|
118
120
|
for repo in upstream_quay_api.list_images():
|
|
119
121
|
if repo["name"] not in org_repos:
|
|
120
122
|
continue
|
reconcile/quay_permissions.py
CHANGED
|
@@ -2,7 +2,10 @@ import logging
|
|
|
2
2
|
import sys
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from reconcile.quay_base import
|
|
5
|
+
from reconcile.quay_base import (
|
|
6
|
+
OrgKey,
|
|
7
|
+
QuayApiStore,
|
|
8
|
+
)
|
|
6
9
|
from reconcile.status import ExitCodes
|
|
7
10
|
from reconcile.utils import gql
|
|
8
11
|
|
|
@@ -57,85 +60,88 @@ def run(dry_run: bool) -> None:
|
|
|
57
60
|
return
|
|
58
61
|
|
|
59
62
|
apps: list[dict[str, Any]] = result.get("apps") or []
|
|
60
|
-
quay_api_store = get_quay_api_store()
|
|
61
63
|
error = False
|
|
62
|
-
for app in apps:
|
|
63
|
-
quay_repo_configs = app.get("quayRepos")
|
|
64
|
-
if not quay_repo_configs:
|
|
65
|
-
continue
|
|
66
|
-
for quay_repo_config in quay_repo_configs:
|
|
67
|
-
instance_name = quay_repo_config["org"]["instance"]["name"]
|
|
68
|
-
org_name = quay_repo_config["org"]["name"]
|
|
69
|
-
org_key = OrgKey(instance_name, org_name)
|
|
70
|
-
|
|
71
|
-
if not quay_repo_config["org"]["managedRepos"]:
|
|
72
|
-
logging.error(
|
|
73
|
-
f"[{app['name']}] Can not manage repo permissions in {org_name} "
|
|
74
|
-
"since managedRepos is set to false."
|
|
75
|
-
)
|
|
76
|
-
error = True
|
|
77
|
-
continue
|
|
78
|
-
|
|
79
|
-
# processing quayRepos section
|
|
80
|
-
logging.debug(["app", app["name"], instance_name, org_name])
|
|
81
64
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
with QuayApiStore() as quay_api_store:
|
|
66
|
+
for app in apps:
|
|
67
|
+
quay_repo_configs = app.get("quayRepos")
|
|
68
|
+
if not quay_repo_configs:
|
|
85
69
|
continue
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
70
|
+
for quay_repo_config in quay_repo_configs:
|
|
71
|
+
instance_name = quay_repo_config["org"]["instance"]["name"]
|
|
72
|
+
org_name = quay_repo_config["org"]["name"]
|
|
73
|
+
org_key = OrgKey(instance_name, org_name)
|
|
74
|
+
|
|
75
|
+
if not quay_repo_config["org"]["managedRepos"]:
|
|
76
|
+
logging.error(
|
|
77
|
+
f"[{app['name']}] Can not manage repo permissions in {org_name} "
|
|
78
|
+
"since managedRepos is set to false."
|
|
79
|
+
)
|
|
80
|
+
error = True
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# processing quayRepos section
|
|
84
|
+
logging.debug(["app", app["name"], instance_name, org_name])
|
|
85
|
+
|
|
86
|
+
org_data = quay_api_store[org_key]
|
|
87
|
+
teams = quay_repo_config.get("teams")
|
|
88
|
+
if not teams:
|
|
89
|
+
continue
|
|
90
|
+
repos = quay_repo_config["items"]
|
|
91
|
+
quay_api = org_data["api"]
|
|
92
|
+
|
|
93
|
+
for repo in repos:
|
|
94
|
+
repo_name = repo["name"]
|
|
95
|
+
|
|
96
|
+
# processing repo section
|
|
97
|
+
logging.debug(["repo", repo_name])
|
|
98
|
+
|
|
99
|
+
for team in teams:
|
|
100
|
+
permissions = team["permissions"]
|
|
101
|
+
role = team["role"]
|
|
102
|
+
for permission in permissions:
|
|
103
|
+
if permission["service"] != "quay-membership":
|
|
104
|
+
logging.warning(
|
|
105
|
+
"wrong service kind, should be quay-membership"
|
|
106
|
+
)
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
perm_org_key = OrgKey(
|
|
110
|
+
permission["quayOrg"]["instance"]["name"],
|
|
111
|
+
permission["quayOrg"]["name"],
|
|
100
112
|
)
|
|
101
|
-
continue
|
|
102
|
-
|
|
103
|
-
perm_org_key = OrgKey(
|
|
104
|
-
permission["quayOrg"]["instance"]["name"],
|
|
105
|
-
permission["quayOrg"]["name"],
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
if perm_org_key != org_key:
|
|
109
|
-
logging.warning(f"wrong org, should be {org_key}")
|
|
110
|
-
continue
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
114
|
+
if perm_org_key != org_key:
|
|
115
|
+
logging.warning(f"wrong org, should be {org_key}")
|
|
116
|
+
continue
|
|
117
|
+
|
|
118
|
+
team_name = permission["team"]
|
|
119
|
+
|
|
120
|
+
# processing team section
|
|
121
|
+
logging.debug(["team", team_name])
|
|
122
|
+
try:
|
|
123
|
+
current_role = quay_api.get_repo_team_permissions(
|
|
124
|
+
repo_name, team_name
|
|
125
|
+
)
|
|
126
|
+
if current_role != role:
|
|
127
|
+
logging.info([
|
|
128
|
+
"update_role",
|
|
129
|
+
org_key,
|
|
130
|
+
repo_name,
|
|
131
|
+
team_name,
|
|
132
|
+
role,
|
|
133
|
+
])
|
|
134
|
+
if not dry_run:
|
|
135
|
+
quay_api.set_repo_team_permissions(
|
|
136
|
+
repo_name, team_name, role
|
|
137
|
+
)
|
|
138
|
+
except Exception:
|
|
139
|
+
error = True
|
|
140
|
+
logging.exception(
|
|
141
|
+
"could not manage repo permissions: "
|
|
142
|
+
f"repo name: {repo_name}, "
|
|
143
|
+
f"team name: {team_name}"
|
|
144
|
+
)
|
|
139
145
|
|
|
140
146
|
if error:
|
|
141
147
|
sys.exit(ExitCodes.ERROR)
|
reconcile/quay_repos.py
CHANGED
|
@@ -3,18 +3,14 @@ from __future__ import annotations
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
5
|
from collections import namedtuple
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
6
|
|
|
8
7
|
from reconcile.quay_base import (
|
|
9
8
|
OrgKey,
|
|
10
|
-
|
|
9
|
+
QuayApiStore,
|
|
11
10
|
)
|
|
12
11
|
from reconcile.status import ExitCodes
|
|
13
12
|
from reconcile.utils import gql
|
|
14
13
|
|
|
15
|
-
if TYPE_CHECKING:
|
|
16
|
-
from reconcile.quay_base import QuayApiStore
|
|
17
|
-
|
|
18
14
|
QUAY_REPOS_QUERY = """
|
|
19
15
|
{
|
|
20
16
|
apps: apps_v1 {
|
|
@@ -51,7 +47,6 @@ def fetch_current_state(quay_api_store: QuayApiStore) -> list[RepoInfo]:
|
|
|
51
47
|
continue
|
|
52
48
|
|
|
53
49
|
quay_api = org_info["api"]
|
|
54
|
-
|
|
55
50
|
for repo in quay_api.list_images():
|
|
56
51
|
name = repo["name"]
|
|
57
52
|
public = repo["is_public"]
|
|
@@ -150,7 +145,8 @@ def act_delete(
|
|
|
150
145
|
current_repo.name,
|
|
151
146
|
])
|
|
152
147
|
if not dry_run:
|
|
153
|
-
|
|
148
|
+
org_data = quay_api_store[current_repo.org_key]
|
|
149
|
+
api = org_data["api"]
|
|
154
150
|
api.repo_delete(current_repo.name)
|
|
155
151
|
|
|
156
152
|
|
|
@@ -164,7 +160,8 @@ def act_create(
|
|
|
164
160
|
desired_repo.name,
|
|
165
161
|
])
|
|
166
162
|
if not dry_run:
|
|
167
|
-
|
|
163
|
+
org_data = quay_api_store[desired_repo.org_key]
|
|
164
|
+
api = org_data["api"]
|
|
168
165
|
api.repo_create(
|
|
169
166
|
desired_repo.name, desired_repo.description, desired_repo.public
|
|
170
167
|
)
|
|
@@ -180,7 +177,8 @@ def act_description(
|
|
|
180
177
|
desired_repo.description,
|
|
181
178
|
])
|
|
182
179
|
if not dry_run:
|
|
183
|
-
|
|
180
|
+
org_data = quay_api_store[desired_repo.org_key]
|
|
181
|
+
api = org_data["api"]
|
|
184
182
|
api.repo_update_description(desired_repo.name, desired_repo.description)
|
|
185
183
|
|
|
186
184
|
|
|
@@ -194,7 +192,8 @@ def act_public(
|
|
|
194
192
|
desired_repo.name,
|
|
195
193
|
])
|
|
196
194
|
if not dry_run:
|
|
197
|
-
|
|
195
|
+
org_data = quay_api_store[desired_repo.org_key]
|
|
196
|
+
api = org_data["api"]
|
|
198
197
|
if desired_repo.public:
|
|
199
198
|
api.repo_make_public(desired_repo.name)
|
|
200
199
|
else:
|
|
@@ -223,32 +222,31 @@ def act(
|
|
|
223
222
|
|
|
224
223
|
|
|
225
224
|
def run(dry_run: bool) -> None:
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
sys.exit(ExitCodes.ERROR)
|
|
225
|
+
with QuayApiStore() as quay_api_store:
|
|
226
|
+
# consistency checks
|
|
227
|
+
for org_key, org_info in quay_api_store.items():
|
|
228
|
+
if org_info.get("mirror"):
|
|
229
|
+
# ensure there are no circular mirror dependencies
|
|
230
|
+
mirror_org_key = org_info["mirror"]
|
|
231
|
+
assert mirror_org_key is not None
|
|
232
|
+
mirror_org = quay_api_store[mirror_org_key]
|
|
233
|
+
if mirror_org.get("mirror"):
|
|
234
|
+
logging.error(
|
|
235
|
+
f"{mirror_org_key.instance}/"
|
|
236
|
+
+ f"{mirror_org_key.org_name} "
|
|
237
|
+
+ "can't have mirrors and be a mirror"
|
|
238
|
+
)
|
|
239
|
+
sys.exit(ExitCodes.ERROR)
|
|
242
240
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
241
|
+
# ensure no org defines `managedRepos` and `mirror` at the same
|
|
242
|
+
if org_info.get("managedRepos"):
|
|
243
|
+
logging.error(
|
|
244
|
+
f"{org_key.instance}/{org_key.org_name} "
|
|
245
|
+
+ "has defined mirror and managedRepos"
|
|
246
|
+
)
|
|
247
|
+
sys.exit(ExitCodes.ERROR)
|
|
250
248
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
# run integration
|
|
250
|
+
current_state = fetch_current_state(quay_api_store)
|
|
251
|
+
desired_state = fetch_desired_state(quay_api_store)
|
|
252
|
+
act(dry_run, quay_api_store, current_state, desired_state)
|
reconcile/queries.py
CHANGED
|
@@ -141,11 +141,11 @@ class TemplateValidatorIntegration(QontractReconcileIntegration):
|
|
|
141
141
|
if diffs:
|
|
142
142
|
for diff in diffs:
|
|
143
143
|
logging.error(f"template: {diff.template}, test: {diff.test}")
|
|
144
|
-
# This log should never be added except for local debugging.
|
|
145
|
-
# Credentials could be leaked, i.e. creating an MR with a diff,
|
|
146
|
-
# using a template, that uses the vault function.
|
|
147
|
-
# Use template-validator CLI instead.
|
|
148
144
|
# logging.debug(diff.diff)
|
|
145
|
+
|
|
146
|
+
logging.error(
|
|
147
|
+
"The diff is never logged to avoid accidental credential leaks. Use template-validator CLI locally for debugging templates."
|
|
148
|
+
)
|
|
149
149
|
raise ValueError("Template validation failed")
|
|
150
150
|
|
|
151
151
|
@property
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import string
|
|
3
|
+
from enum import StrEnum
|
|
3
4
|
|
|
4
5
|
from pydantic import BaseModel
|
|
5
6
|
|
|
@@ -9,6 +10,12 @@ PROMOTION_DATA_SEPARATOR = "**DO NOT MANUALLY CHANGE ANYTHING BELOW THIS LINE**"
|
|
|
9
10
|
VERSION = "0.1.0"
|
|
10
11
|
LABEL = "terraform-vpc-resources"
|
|
11
12
|
|
|
13
|
+
|
|
14
|
+
class Action(StrEnum):
|
|
15
|
+
CREATE = "create"
|
|
16
|
+
UPDATE = "update"
|
|
17
|
+
|
|
18
|
+
|
|
12
19
|
VERSION_REF = "tf_vpc_resources_version"
|
|
13
20
|
ACCOUNT_REF = "account"
|
|
14
21
|
COMPILED_REGEXES = {
|
|
@@ -53,5 +60,8 @@ class Renderer:
|
|
|
53
60
|
def render_description(self, account: str) -> str:
|
|
54
61
|
return DESC.safe_substitute(account=account)
|
|
55
62
|
|
|
56
|
-
def render_title(self, account: str) -> str:
|
|
57
|
-
return f"[auto] VPC data file
|
|
63
|
+
def render_title(self, account: str, action: Action) -> str:
|
|
64
|
+
return f"[auto] {action} VPC data file for {account}"
|
|
65
|
+
|
|
66
|
+
def render_update_title(self, account: str) -> str:
|
|
67
|
+
return f"[auto] VPC data file update for {account}"
|
|
@@ -5,6 +5,7 @@ from pydantic import BaseModel
|
|
|
5
5
|
|
|
6
6
|
from reconcile.terraform_vpc_resources.merge_request import (
|
|
7
7
|
LABEL,
|
|
8
|
+
Action,
|
|
8
9
|
Info,
|
|
9
10
|
Renderer,
|
|
10
11
|
)
|
|
@@ -28,6 +29,7 @@ class VPCRequestMR(MergeRequestBase):
|
|
|
28
29
|
vpc_tmpl_file_path: str,
|
|
29
30
|
vpc_tmpl_file_content: str,
|
|
30
31
|
labels: list[str],
|
|
32
|
+
action: Action,
|
|
31
33
|
):
|
|
32
34
|
super().__init__()
|
|
33
35
|
self._title = title
|
|
@@ -35,6 +37,7 @@ class VPCRequestMR(MergeRequestBase):
|
|
|
35
37
|
self._vpc_tmpl_file_path = vpc_tmpl_file_path
|
|
36
38
|
self._vpc_tmpl_file_content = vpc_tmpl_file_content
|
|
37
39
|
self.labels = labels
|
|
40
|
+
self._action = action
|
|
38
41
|
|
|
39
42
|
@property
|
|
40
43
|
def title(self) -> str:
|
|
@@ -45,12 +48,21 @@ class VPCRequestMR(MergeRequestBase):
|
|
|
45
48
|
return self._description
|
|
46
49
|
|
|
47
50
|
def process(self, gitlab_cli: GitLabApi) -> None:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
# Create or update file based on whether it already exists
|
|
52
|
+
if self._action == Action.UPDATE:
|
|
53
|
+
gitlab_cli.update_file(
|
|
54
|
+
branch_name=self.branch,
|
|
55
|
+
file_path=self._vpc_tmpl_file_path,
|
|
56
|
+
commit_message="update vpc datafile",
|
|
57
|
+
content=self._vpc_tmpl_file_content,
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
gitlab_cli.create_file(
|
|
61
|
+
branch_name=self.branch,
|
|
62
|
+
file_path=self._vpc_tmpl_file_path,
|
|
63
|
+
commit_message="add vpc datafile",
|
|
64
|
+
content=self._vpc_tmpl_file_content,
|
|
65
|
+
)
|
|
54
66
|
|
|
55
67
|
|
|
56
68
|
class MrData(BaseModel):
|
|
@@ -73,26 +85,37 @@ class MergeRequestManager(MergeRequestManagerBase[Info]):
|
|
|
73
85
|
self._renderer = renderer
|
|
74
86
|
self._auto_merge_enabled = auto_merge_enabled
|
|
75
87
|
|
|
76
|
-
def
|
|
77
|
-
"""Open a new MR, if not already present, for a VPC datafile and close any outdated before."""
|
|
78
|
-
if not self._housekeeping_ran:
|
|
79
|
-
self.housekeeping()
|
|
80
|
-
|
|
88
|
+
def _create_action(self, data: MrData) -> Action | None:
|
|
81
89
|
if self._merge_request_already_exists({"account": data.account}):
|
|
82
90
|
logging.info("MR already exists for %s", data.account)
|
|
83
91
|
return None
|
|
84
|
-
|
|
85
92
|
try:
|
|
86
|
-
self._vcs.get_file_content_from_app_interface_ref(
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
existing_content = self._vcs.get_file_content_from_app_interface_ref(
|
|
94
|
+
file_path=data.path
|
|
95
|
+
)
|
|
89
96
|
except GitlabGetError as e:
|
|
90
|
-
if e.response_code
|
|
91
|
-
|
|
97
|
+
if e.response_code == 404:
|
|
98
|
+
return Action.CREATE
|
|
99
|
+
raise
|
|
100
|
+
|
|
101
|
+
if existing_content.strip() != data.content.strip():
|
|
102
|
+
return Action.UPDATE
|
|
103
|
+
|
|
104
|
+
logging.info("VPC data file exists and is up-to-date for %s", data.account)
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
def create_merge_request(self, data: MrData) -> None:
|
|
108
|
+
"""Open a new MR for VPC datafile updates, or update existing if changed."""
|
|
109
|
+
if not self._housekeeping_ran:
|
|
110
|
+
self.housekeeping()
|
|
111
|
+
action = self._create_action(data)
|
|
112
|
+
if action is None:
|
|
113
|
+
return
|
|
92
114
|
|
|
93
115
|
description = self._renderer.render_description(account=data.account)
|
|
94
|
-
title = self._renderer.render_title(account=data.account)
|
|
95
|
-
|
|
116
|
+
title = self._renderer.render_title(account=data.account, action=action)
|
|
117
|
+
|
|
118
|
+
logging.info("Open MR for %s (%s)", data.account, action)
|
|
96
119
|
mr_labels = [LABEL]
|
|
97
120
|
if self._auto_merge_enabled:
|
|
98
121
|
mr_labels.append(AUTO_MERGE)
|
|
@@ -103,5 +126,6 @@ class MergeRequestManager(MergeRequestManagerBase[Info]):
|
|
|
103
126
|
description=description,
|
|
104
127
|
vpc_tmpl_file_content=data.content,
|
|
105
128
|
labels=mr_labels,
|
|
129
|
+
action=action,
|
|
106
130
|
)
|
|
107
131
|
)
|
|
@@ -83,7 +83,7 @@ class SaasResourceTemplateTarget(
|
|
|
83
83
|
if used_for_security_is_enabled():
|
|
84
84
|
# When USED_FOR_SECURITY is enabled, use blake2s without digest_size and truncate to 20 bytes
|
|
85
85
|
# This is needed for FIPS compliance where digest_size parameter is not supported
|
|
86
|
-
return hashlib.
|
|
86
|
+
return hashlib.sha256(data).digest()[:20].hex()
|
|
87
87
|
else:
|
|
88
88
|
# Default behavior: use blake2s with digest_size=20
|
|
89
89
|
return hashlib.blake2s(data, digest_size=20).hexdigest()
|
|
@@ -157,6 +157,8 @@ class ExternalResourceSpec:
|
|
|
157
157
|
tags["cost-center"] = cost_center
|
|
158
158
|
if service_phase := self.namespace["environment"].get("servicePhase"):
|
|
159
159
|
tags["service-phase"] = service_phase
|
|
160
|
+
if cost_center := self.namespace["environment"].get("costCenter"):
|
|
161
|
+
tags["cost-center"] = cost_center
|
|
160
162
|
|
|
161
163
|
resource_tags_str = self.resource.get("tags")
|
|
162
164
|
if resource_tags_str:
|