qontract-reconcile 0.10.2.dev427__py3-none-any.whl → 0.10.2.dev465__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.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/METADATA +2 -2
- {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/RECORD +41 -40
- {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/WHEEL +1 -1
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +9 -8
- reconcile/aus/version_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -125
- reconcile/aws_account_manager/integration.py +13 -1
- reconcile/aws_account_manager/utils.py +1 -1
- reconcile/aws_ecr_image_pull_secrets.py +1 -1
- reconcile/change_owners/README.md +1 -1
- reconcile/change_owners/change_owners.py +108 -42
- reconcile/change_owners/decision.py +1 -1
- reconcile/cli.py +1 -1
- reconcile/external_resources/secrets_sync.py +2 -3
- reconcile/gql_definitions/aws_account_manager/aws_accounts.py +9 -0
- reconcile/gql_definitions/common/aws_vpc_requests.py +3 -0
- reconcile/gql_definitions/common/clusters.py +2 -0
- reconcile/gql_definitions/external_resources/external_resources_namespaces.py +5 -1
- reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -0
- reconcile/gql_definitions/introspection.json +60 -0
- reconcile/gql_definitions/rhcs/certs.py +1 -0
- reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +1 -0
- reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +7 -1
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +3 -0
- reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +1 -0
- reconcile/openshift_namespaces.py +3 -4
- reconcile/openshift_rhcs_certs.py +51 -12
- reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
- reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
- reconcile/terraform_vpc_resources/integration.py +10 -7
- reconcile/typed_queries/saas_files.py +9 -4
- reconcile/utils/environ.py +5 -0
- reconcile/utils/external_resource_spec.py +2 -0
- reconcile/utils/gitlab_api.py +12 -0
- reconcile/utils/jjb_client.py +19 -3
- reconcile/utils/oc.py +8 -2
- reconcile/utils/rhcsv2_certs.py +87 -21
- reconcile/utils/terrascript_aws_client.py +140 -50
- reconcile/vpc_peerings_validator.py +13 -0
- {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/entry_points.txt +0 -0
|
@@ -24,7 +24,7 @@ def validate(account: AWSAccountV1) -> bool:
|
|
|
24
24
|
raise ExceptionGroup("Multiple quotas are referenced in the account", errors)
|
|
25
25
|
|
|
26
26
|
if account.organization_accounts or account.account_requests:
|
|
27
|
-
# it's payer account
|
|
27
|
+
# it's a "payer account"
|
|
28
28
|
if not account.premium_support:
|
|
29
29
|
raise ValueError(
|
|
30
30
|
f"Premium support is required for payer account {account.name}"
|
|
@@ -21,7 +21,7 @@ def get_password(token: str) -> str:
|
|
|
21
21
|
|
|
22
22
|
def construct_dockercfg_secret_data(data: Mapping[str, Any]) -> dict[str, str]:
|
|
23
23
|
auth_data = data["authorizationData"][0]
|
|
24
|
-
server = auth_data["proxyEndpoint"]
|
|
24
|
+
server = auth_data["proxyEndpoint"].replace("https://", "")
|
|
25
25
|
token = auth_data["authorizationToken"]
|
|
26
26
|
password = get_password(token)
|
|
27
27
|
data = {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
`change_owners` uses the `qontract-server` diff endpoint to get a highlevel overview what changed in an MR. It leverages `change_types` to find fine grained differences in datafiles and resourcefiles and build `BundleFileChange` objects that hold the state of diffs and diff coverage.
|
|
17
17
|
|
|
18
|
-
`change_owners` checks `BundleFileChange` objects for `change-types` that are `restrictive`. If the MR was created by a user, that has this `change-type` not assigned, the integration will fail. A user with this role assigned could issue an `/
|
|
18
|
+
`change_owners` checks `BundleFileChange` objects for `change-types` that are `restrictive`. If the MR was created by a user, that has this `change-type` not assigned, the integration will fail. A user with this role assigned could issue an `/ok-to-test` command to override this restriction.
|
|
19
19
|
|
|
20
20
|
`change_owners` reachs out to pluggable functionality to find out which `change-types` can be applied to which changes with a set of approvers. Currently, the only module to provide such `ChangeTypeContext` is `self_service_roles` which looks for explicitly bound `change-types` and files in the context of a `Role` with users and bots will can act as approvers.
|
|
21
21
|
|
|
@@ -23,6 +23,7 @@ from reconcile.change_owners.changes import (
|
|
|
23
23
|
)
|
|
24
24
|
from reconcile.change_owners.decision import (
|
|
25
25
|
ChangeDecision,
|
|
26
|
+
ChangeResponsibles,
|
|
26
27
|
DecisionCommand,
|
|
27
28
|
apply_decisions_to_changes,
|
|
28
29
|
get_approver_decisions_from_mr_comments,
|
|
@@ -115,6 +116,77 @@ def manage_conditional_label(
|
|
|
115
116
|
return set(new_labels)
|
|
116
117
|
|
|
117
118
|
|
|
119
|
+
def build_status_message(
|
|
120
|
+
self_serviceable: bool,
|
|
121
|
+
authoritative: bool,
|
|
122
|
+
change_admitted: bool,
|
|
123
|
+
approver_reachability: set[str],
|
|
124
|
+
supported_commands: list[str],
|
|
125
|
+
) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Build a user-friendly status message based on the MR state.
|
|
128
|
+
"""
|
|
129
|
+
approver_section = _build_approver_contact_section(approver_reachability)
|
|
130
|
+
|
|
131
|
+
# Check if changes are not admitted (security gate - takes priority)
|
|
132
|
+
if not change_admitted:
|
|
133
|
+
return f"""## ⏸️ Approval Required
|
|
134
|
+
Your changes need `/ok-to-test` approval from a listed approver before review can begin.
|
|
135
|
+
|
|
136
|
+
{approver_section}"""
|
|
137
|
+
|
|
138
|
+
commands_text = (
|
|
139
|
+
f"**Available commands:** {' '.join(f'`{cmd}`' for cmd in supported_commands)}"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
code_warning = ""
|
|
143
|
+
if not authoritative:
|
|
144
|
+
code_warning = "⚠️ **Code changes outside of data and resources detected** - please review carefully\n\n"
|
|
145
|
+
|
|
146
|
+
if self_serviceable:
|
|
147
|
+
return f"""## ✅ Ready for Review
|
|
148
|
+
Get `/lgtm` approval from the listed approvers below.
|
|
149
|
+
|
|
150
|
+
{code_warning}{approver_section}
|
|
151
|
+
|
|
152
|
+
{commands_text}"""
|
|
153
|
+
|
|
154
|
+
return f"""## 🔍 AppSRE Review Required
|
|
155
|
+
**What happens next:**
|
|
156
|
+
* AppSRE will review via their [review queue](https://gitlab.cee.redhat.com/service/app-interface-output/-/blob/master/app-interface-review-queue.md)
|
|
157
|
+
* Please don't ping directly unless this is **urgent**
|
|
158
|
+
* See [etiquette guide](https://gitlab.cee.redhat.com/service/app-interface#app-interface-etiquette) for more info
|
|
159
|
+
|
|
160
|
+
{code_warning}{approver_section}
|
|
161
|
+
|
|
162
|
+
{commands_text}"""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _build_approver_contact_section(approver_reachability: set[str]) -> str:
|
|
166
|
+
"""Build the approver contact information section."""
|
|
167
|
+
if not approver_reachability:
|
|
168
|
+
return ""
|
|
169
|
+
|
|
170
|
+
return "**Reach out to approvers:**\n" + "\n".join([
|
|
171
|
+
f"* {ar}" for ar in approver_reachability
|
|
172
|
+
])
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _format_change_responsible(cr: ChangeResponsibles) -> str:
|
|
176
|
+
"""
|
|
177
|
+
Format a ChangeResponsibles object.
|
|
178
|
+
"""
|
|
179
|
+
usernames = [
|
|
180
|
+
f"@{a.org_username}"
|
|
181
|
+
if (a.tag_on_merge_requests or len(cr.approvers) == 1)
|
|
182
|
+
else a.org_username
|
|
183
|
+
for a in cr.approvers
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
usernames_text = " ".join(usernames)
|
|
187
|
+
return f"<details><summary>{cr.context}</summary>{usernames_text}</details>"
|
|
188
|
+
|
|
189
|
+
|
|
118
190
|
def write_coverage_report_to_mr(
|
|
119
191
|
self_serviceable: bool,
|
|
120
192
|
change_decisions: list[ChangeDecision],
|
|
@@ -135,14 +207,11 @@ def write_coverage_report_to_mr(
|
|
|
135
207
|
startswith=change_coverage_report_header,
|
|
136
208
|
)
|
|
137
209
|
|
|
138
|
-
#
|
|
210
|
+
# Build change coverage table
|
|
139
211
|
results = []
|
|
140
212
|
approver_reachability = set()
|
|
141
213
|
for d in change_decisions:
|
|
142
|
-
approvers = [
|
|
143
|
-
f"{cr.context} - {' '.join([f'@{a.org_username}' if (a.tag_on_merge_requests or len(cr.approvers) == 1) else a.org_username for a in cr.approvers])}"
|
|
144
|
-
for cr in d.change_responsibles
|
|
145
|
-
]
|
|
214
|
+
approvers = [_format_change_responsible(cr) for cr in d.change_responsibles]
|
|
146
215
|
if d.coverable_by_fragment_decisions:
|
|
147
216
|
approvers.append(
|
|
148
217
|
"automatically approved if all sub-properties are approved"
|
|
@@ -164,41 +233,33 @@ def write_coverage_report_to_mr(
|
|
|
164
233
|
item["status"] = "hold"
|
|
165
234
|
elif d.is_approved():
|
|
166
235
|
item["status"] = "approved"
|
|
167
|
-
item["approvers"] = approvers
|
|
236
|
+
item["approvers"] = "".join(approvers)
|
|
168
237
|
results.append(item)
|
|
238
|
+
|
|
169
239
|
coverage_report = format_table(
|
|
170
240
|
results, ["file", "change", "status", "approvers"], table_format="github"
|
|
171
241
|
)
|
|
172
242
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
182
|
-
if not authoritative:
|
|
183
|
-
self_serviceability_hint += "\n\nchanges outside of data and resources detected - <b>PAY EXTRA ATTENTION WHILE REVIEWING</b>\n\n"
|
|
184
|
-
|
|
185
|
-
if not change_admitted:
|
|
186
|
-
self_serviceability_hint += "\n\nchanges are not admitted. Please request `/good-to-test` from one of the approvers.\n\n"
|
|
187
|
-
|
|
188
|
-
approver_reachability_hint = "Reach out to approvers for reviews"
|
|
189
|
-
if approver_reachability:
|
|
190
|
-
approver_reachability_hint += " on\n" + "\n".join([
|
|
191
|
-
f"* {ar}" for ar in approver_reachability or []
|
|
192
|
-
])
|
|
193
|
-
gl.add_comment_to_merge_request(
|
|
194
|
-
merge_request,
|
|
195
|
-
f"{change_coverage_report_header}<br/>"
|
|
196
|
-
f"{self_serviceability_hint}\n"
|
|
197
|
-
f"{coverage_report}\n\n"
|
|
198
|
-
f"{approver_reachability_hint}\n\n"
|
|
199
|
-
+ f"Supported commands: {' '.join([f'`{d.value}`' for d in DecisionCommand])} ",
|
|
243
|
+
# Build user-friendly status message
|
|
244
|
+
supported_commands = [d.value for d in DecisionCommand]
|
|
245
|
+
status_message = build_status_message(
|
|
246
|
+
self_serviceable=self_serviceable,
|
|
247
|
+
authoritative=authoritative,
|
|
248
|
+
change_admitted=change_admitted,
|
|
249
|
+
approver_reachability=approver_reachability,
|
|
250
|
+
supported_commands=supported_commands,
|
|
200
251
|
)
|
|
201
252
|
|
|
253
|
+
# Create the full comment
|
|
254
|
+
full_comment = f"""{change_coverage_report_header}
|
|
255
|
+
|
|
256
|
+
{status_message}
|
|
257
|
+
|
|
258
|
+
## 📋 Change Summary
|
|
259
|
+
{coverage_report}"""
|
|
260
|
+
|
|
261
|
+
gl.add_comment_to_merge_request(merge_request, full_comment)
|
|
262
|
+
|
|
202
263
|
|
|
203
264
|
def write_coverage_report_to_stdout(change_decisions: list[ChangeDecision]) -> None:
|
|
204
265
|
results = []
|
|
@@ -261,22 +322,22 @@ def init_gitlab(gitlab_project_id: str) -> GitLabApi:
|
|
|
261
322
|
|
|
262
323
|
|
|
263
324
|
def is_coverage_admitted(
|
|
264
|
-
coverage: ChangeTypeContext, mr_author: str,
|
|
325
|
+
coverage: ChangeTypeContext, mr_author: str, ok_to_test_approvers: set[str]
|
|
265
326
|
) -> bool:
|
|
266
327
|
return any(
|
|
267
|
-
a.org_username == mr_author or a.org_username in
|
|
328
|
+
a.org_username == mr_author or a.org_username in ok_to_test_approvers
|
|
268
329
|
for a in coverage.approvers
|
|
269
330
|
)
|
|
270
331
|
|
|
271
332
|
|
|
272
333
|
def is_change_admitted(
|
|
273
|
-
changes: list[BundleFileChange], mr_author: str,
|
|
334
|
+
changes: list[BundleFileChange], mr_author: str, ok_to_test_approvers: set[str]
|
|
274
335
|
) -> bool:
|
|
275
336
|
# Checks if mr authors are allowed to do the changes in the merge request.
|
|
276
337
|
# If a change type is restrictive and the author is not an approver,
|
|
277
338
|
# this is not admitted.
|
|
278
339
|
# A change might be admitted if a user that has the restrictive change
|
|
279
|
-
# type is an approver or an approver adds an /
|
|
340
|
+
# type is an approver or an approver adds an /ok-to-test comment.
|
|
280
341
|
|
|
281
342
|
restrictive_coverages = [
|
|
282
343
|
c
|
|
@@ -290,7 +351,7 @@ def is_change_admitted(
|
|
|
290
351
|
change_types_approved = {
|
|
291
352
|
c.origin
|
|
292
353
|
for c in restrictive_coverages
|
|
293
|
-
if is_coverage_admitted(c, mr_author,
|
|
354
|
+
if is_coverage_admitted(c, mr_author, ok_to_test_approvers)
|
|
294
355
|
}
|
|
295
356
|
return change_types_to_approve == change_types_approved
|
|
296
357
|
|
|
@@ -381,17 +442,22 @@ def run(
|
|
|
381
442
|
merge_request = gl.get_merge_request(gitlab_merge_request_id)
|
|
382
443
|
|
|
383
444
|
comments = gl.get_merge_request_comments(merge_request)
|
|
384
|
-
|
|
385
|
-
c.username for c in comments if c.body.strip() == "/
|
|
445
|
+
ok_to_test_approvers = {
|
|
446
|
+
c.username for c in comments if c.body.strip() == "/ok-to-test"
|
|
386
447
|
}
|
|
387
448
|
|
|
388
449
|
change_admitted = is_change_admitted(
|
|
389
450
|
changes,
|
|
390
451
|
gl.get_merge_request_author_username(merge_request),
|
|
391
|
-
|
|
452
|
+
ok_to_test_approvers,
|
|
392
453
|
)
|
|
393
454
|
approver_decisions = get_approver_decisions_from_mr_comments(
|
|
394
|
-
gl.get_merge_request_comments(
|
|
455
|
+
gl.get_merge_request_comments(
|
|
456
|
+
merge_request,
|
|
457
|
+
include_description=True,
|
|
458
|
+
include_approvals=True,
|
|
459
|
+
approval_body=DecisionCommand.APPROVED.value,
|
|
460
|
+
)
|
|
395
461
|
)
|
|
396
462
|
change_decisions = apply_decisions_to_changes(
|
|
397
463
|
changes,
|
reconcile/cli.py
CHANGED
|
@@ -53,7 +53,7 @@ TERRAFORM_VERSION_REGEX = r"^Terraform\sv([\d]+\.[\d]+\.[\d]+)$"
|
|
|
53
53
|
OC_VERSIONS = ["4.19.0", "4.16.2"]
|
|
54
54
|
OC_VERSION_REGEX = r"^Client\sVersion:\s([\d]+\.[\d]+\.[\d]+)"
|
|
55
55
|
|
|
56
|
-
HELM_VERSIONS = ["3.
|
|
56
|
+
HELM_VERSIONS = ["3.19.2"]
|
|
57
57
|
HELM_VERSION_REGEX = r"^version.BuildInfo{Version:\"v([\d]+\.[\d]+\.[\d]+)\".*$"
|
|
58
58
|
|
|
59
59
|
|
|
@@ -448,9 +448,8 @@ class VaultSecretsReconciler(SecretsReconciler):
|
|
|
448
448
|
secret_path = self.secret_path(self.vault_path, spec)
|
|
449
449
|
try:
|
|
450
450
|
logging.debug("Reading Secret %s", secret_path)
|
|
451
|
-
data = self.secrets_reader.read_all({"path": secret_path})
|
|
452
|
-
spec.metadata[SECRET_UPDATED_AT] = data
|
|
453
|
-
del data[SECRET_UPDATED_AT]
|
|
451
|
+
data = self.secrets_reader.read_all({"path": secret_path}).copy()
|
|
452
|
+
spec.metadata[SECRET_UPDATED_AT] = data.pop(SECRET_UPDATED_AT)
|
|
454
453
|
spec.secret = data
|
|
455
454
|
except SecretNotFoundError:
|
|
456
455
|
logging.info("Error getting secret from vault, skipping. [%s]", secret_path)
|
|
@@ -97,6 +97,10 @@ query AWSAccountManagerAccounts {
|
|
|
97
97
|
...AWSAccountManaged
|
|
98
98
|
}
|
|
99
99
|
organizationAccountTags
|
|
100
|
+
# for account request email address verification
|
|
101
|
+
accountOwners {
|
|
102
|
+
email
|
|
103
|
+
}
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
"""
|
|
@@ -149,6 +153,10 @@ class AWSAccountRequestV1(ConfiguredBaseModel):
|
|
|
149
153
|
account_file_target_path: Optional[str] = Field(..., alias="accountFileTargetPath")
|
|
150
154
|
|
|
151
155
|
|
|
156
|
+
class AWSAccountV1_OwnerV1(ConfiguredBaseModel):
|
|
157
|
+
email: str = Field(..., alias="email")
|
|
158
|
+
|
|
159
|
+
|
|
152
160
|
class AWSAccountV1(AWSAccountManaged):
|
|
153
161
|
resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
|
|
154
162
|
automation_token: VaultSecret = Field(..., alias="automationToken")
|
|
@@ -157,6 +165,7 @@ class AWSAccountV1(AWSAccountManaged):
|
|
|
157
165
|
account_requests: Optional[list[AWSAccountRequestV1]] = Field(..., alias="account_requests")
|
|
158
166
|
organization_accounts: Optional[list[AWSAccountManaged]] = Field(..., alias="organization_accounts")
|
|
159
167
|
organization_account_tags: Optional[Json] = Field(..., alias="organizationAccountTags")
|
|
168
|
+
account_owners: list[AWSAccountV1_OwnerV1] = Field(..., alias="accountOwners")
|
|
160
169
|
|
|
161
170
|
|
|
162
171
|
class AWSAccountManagerAccountsQueryData(ConfiguredBaseModel):
|
|
@@ -113,6 +113,7 @@ query Clusters($name: String) {
|
|
|
113
113
|
managedGroups
|
|
114
114
|
managedClusterRoles
|
|
115
115
|
insecureSkipTLSVerify
|
|
116
|
+
allowedToBypassPublicPeeringRestriction
|
|
116
117
|
jumpHost {
|
|
117
118
|
...CommonJumphostFields
|
|
118
119
|
}
|
|
@@ -635,6 +636,7 @@ class ClusterV1(ConfiguredBaseModel):
|
|
|
635
636
|
managed_groups: Optional[list[str]] = Field(..., alias="managedGroups")
|
|
636
637
|
managed_cluster_roles: Optional[bool] = Field(..., alias="managedClusterRoles")
|
|
637
638
|
insecure_skip_tls_verify: Optional[bool] = Field(..., alias="insecureSkipTLSVerify")
|
|
639
|
+
allowed_to_bypass_public_peering_restriction: Optional[bool] = Field(..., alias="allowedToBypassPublicPeeringRestriction")
|
|
638
640
|
jump_host: Optional[CommonJumphostFields] = Field(..., alias="jumpHost")
|
|
639
641
|
auth: list[Union[ClusterAuthGithubOrgTeamV1, ClusterAuthGithubOrgV1, ClusterAuthV1]] = Field(..., alias="auth")
|
|
640
642
|
ocm: Optional[OpenShiftClusterManagerV1] = Field(..., alias="ocm")
|
|
@@ -372,6 +372,7 @@ query ExternalResourcesNamespaces {
|
|
|
372
372
|
identifier
|
|
373
373
|
defaults
|
|
374
374
|
es_identifier
|
|
375
|
+
policy
|
|
375
376
|
output_resource_name
|
|
376
377
|
annotations
|
|
377
378
|
tags
|
|
@@ -574,6 +575,7 @@ query ExternalResourcesNamespaces {
|
|
|
574
575
|
name
|
|
575
576
|
labels
|
|
576
577
|
servicePhase
|
|
578
|
+
costCenter
|
|
577
579
|
}
|
|
578
580
|
app {
|
|
579
581
|
path
|
|
@@ -933,6 +935,7 @@ class NamespaceTerraformResourceKinesisV1(NamespaceTerraformResourceAWSV1):
|
|
|
933
935
|
identifier: str = Field(..., alias="identifier")
|
|
934
936
|
defaults: str = Field(..., alias="defaults")
|
|
935
937
|
es_identifier: Optional[str] = Field(..., alias="es_identifier")
|
|
938
|
+
policy: Optional[str] = Field(..., alias="policy")
|
|
936
939
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
937
940
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
938
941
|
tags: Optional[str] = Field(..., alias="tags")
|
|
@@ -1167,13 +1170,14 @@ class NamespaceTerraformResourceMskV1(NamespaceTerraformResourceAWSV1):
|
|
|
1167
1170
|
|
|
1168
1171
|
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
|
1169
1172
|
provisioner: AWSAccountV1 = Field(..., alias="provisioner")
|
|
1170
|
-
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceRDSProxyV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceMskV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1,
|
|
1173
|
+
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceRDSProxyV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceMskV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
|
|
1171
1174
|
|
|
1172
1175
|
|
|
1173
1176
|
class EnvironmentV1(ConfiguredBaseModel):
|
|
1174
1177
|
name: str = Field(..., alias="name")
|
|
1175
1178
|
labels: str = Field(..., alias="labels")
|
|
1176
1179
|
service_phase: str = Field(..., alias="servicePhase")
|
|
1180
|
+
cost_center: Optional[str] = Field(..., alias="costCenter")
|
|
1177
1181
|
|
|
1178
1182
|
|
|
1179
1183
|
class AppV1(ConfiguredBaseModel):
|
|
@@ -28,6 +28,10 @@ class ConfiguredBaseModel(BaseModel):
|
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
class DisableClusterAutomationsV1(ConfiguredBaseModel):
|
|
32
|
+
integrations: Optional[list[str]] = Field(..., alias="integrations")
|
|
33
|
+
|
|
34
|
+
|
|
31
35
|
class DeletionApprovalV1(ConfiguredBaseModel):
|
|
32
36
|
q_type: str = Field(..., alias="type")
|
|
33
37
|
name: str = Field(..., alias="name")
|
|
@@ -39,6 +43,7 @@ class AWSAccountV1(ConfiguredBaseModel):
|
|
|
39
43
|
uid: str = Field(..., alias="uid")
|
|
40
44
|
terraform_username: Optional[str] = Field(..., alias="terraformUsername")
|
|
41
45
|
automation_token: VaultSecret = Field(..., alias="automationToken")
|
|
46
|
+
disable: Optional[DisableClusterAutomationsV1] = Field(..., alias="disable")
|
|
42
47
|
supported_deployment_regions: Optional[list[str]] = Field(..., alias="supportedDeploymentRegions")
|
|
43
48
|
resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
|
|
44
49
|
provider_version: str = Field(..., alias="providerVersion")
|
|
@@ -6489,6 +6489,18 @@
|
|
|
6489
6489
|
"isDeprecated": false,
|
|
6490
6490
|
"deprecationReason": null
|
|
6491
6491
|
},
|
|
6492
|
+
{
|
|
6493
|
+
"name": "allowedToBypassPublicPeeringRestriction",
|
|
6494
|
+
"description": null,
|
|
6495
|
+
"args": [],
|
|
6496
|
+
"type": {
|
|
6497
|
+
"kind": "SCALAR",
|
|
6498
|
+
"name": "Boolean",
|
|
6499
|
+
"ofType": null
|
|
6500
|
+
},
|
|
6501
|
+
"isDeprecated": false,
|
|
6502
|
+
"deprecationReason": null
|
|
6503
|
+
},
|
|
6492
6504
|
{
|
|
6493
6505
|
"name": "namespaces",
|
|
6494
6506
|
"description": null,
|
|
@@ -17217,6 +17229,18 @@
|
|
|
17217
17229
|
"isDeprecated": false,
|
|
17218
17230
|
"deprecationReason": null
|
|
17219
17231
|
},
|
|
17232
|
+
{
|
|
17233
|
+
"name": "costCenter",
|
|
17234
|
+
"description": null,
|
|
17235
|
+
"args": [],
|
|
17236
|
+
"type": {
|
|
17237
|
+
"kind": "SCALAR",
|
|
17238
|
+
"name": "String",
|
|
17239
|
+
"ofType": null
|
|
17240
|
+
},
|
|
17241
|
+
"isDeprecated": false,
|
|
17242
|
+
"deprecationReason": null
|
|
17243
|
+
},
|
|
17220
17244
|
{
|
|
17221
17245
|
"name": "namespaces",
|
|
17222
17246
|
"description": null,
|
|
@@ -41646,6 +41670,18 @@
|
|
|
41646
41670
|
"isDeprecated": false,
|
|
41647
41671
|
"deprecationReason": null
|
|
41648
41672
|
},
|
|
41673
|
+
{
|
|
41674
|
+
"name": "certificate_format",
|
|
41675
|
+
"description": null,
|
|
41676
|
+
"args": [],
|
|
41677
|
+
"type": {
|
|
41678
|
+
"kind": "SCALAR",
|
|
41679
|
+
"name": "String",
|
|
41680
|
+
"ofType": null
|
|
41681
|
+
},
|
|
41682
|
+
"isDeprecated": false,
|
|
41683
|
+
"deprecationReason": null
|
|
41684
|
+
},
|
|
41649
41685
|
{
|
|
41650
41686
|
"name": "annotations",
|
|
41651
41687
|
"description": null,
|
|
@@ -47657,6 +47693,18 @@
|
|
|
47657
47693
|
},
|
|
47658
47694
|
"isDeprecated": false,
|
|
47659
47695
|
"deprecationReason": null
|
|
47696
|
+
},
|
|
47697
|
+
{
|
|
47698
|
+
"name": "bucket_policy",
|
|
47699
|
+
"description": null,
|
|
47700
|
+
"args": [],
|
|
47701
|
+
"type": {
|
|
47702
|
+
"kind": "SCALAR",
|
|
47703
|
+
"name": "JSON",
|
|
47704
|
+
"ofType": null
|
|
47705
|
+
},
|
|
47706
|
+
"isDeprecated": false,
|
|
47707
|
+
"deprecationReason": null
|
|
47660
47708
|
}
|
|
47661
47709
|
],
|
|
47662
47710
|
"inputFields": null,
|
|
@@ -48266,6 +48314,18 @@
|
|
|
48266
48314
|
"isDeprecated": false,
|
|
48267
48315
|
"deprecationReason": null
|
|
48268
48316
|
},
|
|
48317
|
+
{
|
|
48318
|
+
"name": "policy",
|
|
48319
|
+
"description": null,
|
|
48320
|
+
"args": [],
|
|
48321
|
+
"type": {
|
|
48322
|
+
"kind": "SCALAR",
|
|
48323
|
+
"name": "JSON",
|
|
48324
|
+
"ofType": null
|
|
48325
|
+
},
|
|
48326
|
+
"isDeprecated": false,
|
|
48327
|
+
"deprecationReason": null
|
|
48328
|
+
},
|
|
48269
48329
|
{
|
|
48270
48330
|
"name": "output_resource_name",
|
|
48271
48331
|
"description": null,
|
|
@@ -39,4 +39,5 @@ class OpenshiftResourceRhcsCert(ConfiguredBaseModel):
|
|
|
39
39
|
service_account_name: str = Field(..., alias="service_account_name")
|
|
40
40
|
service_account_password: Union[VaultSecretV1_VaultSecretV1, VaultSecretV1] = Field(..., alias="service_account_password")
|
|
41
41
|
auto_renew_threshold_days: Optional[int] = Field(..., alias="auto_renew_threshold_days")
|
|
42
|
+
certificate_format: Optional[str] = Field(..., alias="certificate_format")
|
|
42
43
|
annotations: Optional[Json] = Field(..., alias="annotations")
|
|
@@ -243,6 +243,7 @@ query TerraformResourcesNamespaces {
|
|
|
243
243
|
defaults
|
|
244
244
|
output_resource_name
|
|
245
245
|
storage_class
|
|
246
|
+
bucket_policy
|
|
246
247
|
annotations
|
|
247
248
|
}
|
|
248
249
|
... on NamespaceTerraformResourceS3SQS_v1 {
|
|
@@ -299,6 +300,7 @@ query TerraformResourcesNamespaces {
|
|
|
299
300
|
identifier
|
|
300
301
|
defaults
|
|
301
302
|
es_identifier
|
|
303
|
+
policy
|
|
302
304
|
output_resource_name
|
|
303
305
|
annotations
|
|
304
306
|
}
|
|
@@ -519,6 +521,7 @@ query TerraformResourcesNamespaces {
|
|
|
519
521
|
environment {
|
|
520
522
|
name
|
|
521
523
|
servicePhase
|
|
524
|
+
costCenter
|
|
522
525
|
}
|
|
523
526
|
app {
|
|
524
527
|
name
|
|
@@ -774,6 +777,7 @@ class NamespaceTerraformResourceS3CloudFrontV1(NamespaceTerraformResourceAWSV1):
|
|
|
774
777
|
defaults: str = Field(..., alias="defaults")
|
|
775
778
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
776
779
|
storage_class: Optional[str] = Field(..., alias="storage_class")
|
|
780
|
+
bucket_policy: Optional[str] = Field(..., alias="bucket_policy")
|
|
777
781
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
778
782
|
|
|
779
783
|
|
|
@@ -836,6 +840,7 @@ class NamespaceTerraformResourceKinesisV1(NamespaceTerraformResourceAWSV1):
|
|
|
836
840
|
identifier: str = Field(..., alias="identifier")
|
|
837
841
|
defaults: str = Field(..., alias="defaults")
|
|
838
842
|
es_identifier: Optional[str] = Field(..., alias="es_identifier")
|
|
843
|
+
policy: Optional[str] = Field(..., alias="policy")
|
|
839
844
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
840
845
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
841
846
|
|
|
@@ -1100,12 +1105,13 @@ class NamespaceTerraformResourceMskV1(NamespaceTerraformResourceAWSV1):
|
|
|
1100
1105
|
|
|
1101
1106
|
|
|
1102
1107
|
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
|
1103
|
-
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceRDSProxyV1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceMskV1,
|
|
1108
|
+
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceRDSProxyV1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceMskV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
|
|
1104
1109
|
|
|
1105
1110
|
|
|
1106
1111
|
class EnvironmentV1(ConfiguredBaseModel):
|
|
1107
1112
|
name: str = Field(..., alias="name")
|
|
1108
1113
|
service_phase: str = Field(..., alias="servicePhase")
|
|
1114
|
+
cost_center: Optional[str] = Field(..., alias="costCenter")
|
|
1109
1115
|
|
|
1110
1116
|
|
|
1111
1117
|
class AppV1(ConfiguredBaseModel):
|
|
@@ -23,6 +23,7 @@ from reconcile.gql_definitions.vpc_peerings_validator.vpc_peerings_validator_pee
|
|
|
23
23
|
DEFINITION = """
|
|
24
24
|
fragment VpcPeeringsValidatorPeeredCluster on Cluster_v1 {
|
|
25
25
|
name
|
|
26
|
+
allowedToBypassPublicPeeringRestriction
|
|
26
27
|
network {
|
|
27
28
|
vpc
|
|
28
29
|
}
|
|
@@ -35,6 +36,7 @@ fragment VpcPeeringsValidatorPeeredCluster on Cluster_v1 {
|
|
|
35
36
|
query VpcPeeringsValidator {
|
|
36
37
|
clusters: clusters_v1 {
|
|
37
38
|
name
|
|
39
|
+
allowedToBypassPublicPeeringRestriction
|
|
38
40
|
network {
|
|
39
41
|
vpc
|
|
40
42
|
}
|
|
@@ -128,6 +130,7 @@ class ClusterPeeringV1(ConfiguredBaseModel):
|
|
|
128
130
|
|
|
129
131
|
class ClusterV1(ConfiguredBaseModel):
|
|
130
132
|
name: str = Field(..., alias="name")
|
|
133
|
+
allowed_to_bypass_public_peering_restriction: Optional[bool] = Field(..., alias="allowedToBypassPublicPeeringRestriction")
|
|
131
134
|
network: Optional[ClusterNetworkV1] = Field(..., alias="network")
|
|
132
135
|
spec: Optional[ClusterSpecV1] = Field(..., alias="spec")
|
|
133
136
|
internal: Optional[bool] = Field(..., alias="internal")
|
reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py
CHANGED
|
@@ -34,6 +34,7 @@ class ClusterSpecV1(ConfiguredBaseModel):
|
|
|
34
34
|
|
|
35
35
|
class VpcPeeringsValidatorPeeredCluster(ConfiguredBaseModel):
|
|
36
36
|
name: str = Field(..., alias="name")
|
|
37
|
+
allowed_to_bypass_public_peering_restriction: Optional[bool] = Field(..., alias="allowedToBypassPublicPeeringRestriction")
|
|
37
38
|
network: Optional[ClusterNetworkV1] = Field(..., alias="network")
|
|
38
39
|
spec: Optional[ClusterSpecV1] = Field(..., alias="spec")
|
|
39
40
|
internal: Optional[bool] = Field(..., alias="internal")
|
|
@@ -43,6 +43,7 @@ class DesiredState:
|
|
|
43
43
|
cluster: str
|
|
44
44
|
namespace: str
|
|
45
45
|
delete: bool
|
|
46
|
+
cluster_admin: bool
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
class NamespaceDuplicateError(Exception):
|
|
@@ -92,6 +93,7 @@ def build_desired_state(
|
|
|
92
93
|
cluster=namespace.cluster.name,
|
|
93
94
|
namespace=namespace.name,
|
|
94
95
|
delete=namespace.delete or False,
|
|
96
|
+
cluster_admin=namespace.cluster_admin or False,
|
|
95
97
|
)
|
|
96
98
|
for namespace in namespaces
|
|
97
99
|
]
|
|
@@ -104,7 +106,7 @@ def manage_namespace(
|
|
|
104
106
|
) -> None:
|
|
105
107
|
namespace = desired_state.namespace
|
|
106
108
|
|
|
107
|
-
oc = oc_map.get(desired_state.cluster)
|
|
109
|
+
oc = oc_map.get(desired_state.cluster, privileged=desired_state.cluster_admin)
|
|
108
110
|
if isinstance(oc, OCLogMsg):
|
|
109
111
|
logging.log(level=oc.log_level, msg=oc.message)
|
|
110
112
|
return
|
|
@@ -116,9 +118,6 @@ def manage_namespace(
|
|
|
116
118
|
|
|
117
119
|
action = Action.DELETE if desired_state.delete else Action.CREATE
|
|
118
120
|
|
|
119
|
-
if namespace.startswith("openshift-"):
|
|
120
|
-
raise ValueError(f'cannot {action} a project starting with "openshift-"')
|
|
121
|
-
|
|
122
121
|
logging.info([str(action), desired_state.cluster, namespace])
|
|
123
122
|
if not dry_run:
|
|
124
123
|
match action:
|