qontract-reconcile 0.10.2.dev427__py3-none-any.whl → 0.10.2.dev456__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.dev456.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/RECORD +35 -34
- {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/WHEEL +1 -1
- reconcile/aus/aus_sts_gate_handler.py +59 -0
- reconcile/aus/base.py +9 -5
- reconcile/aus/version_gate_approver.py +1 -16
- reconcile/aus/version_gates/sts_version_gate_handler.py +5 -125
- reconcile/aws_ecr_image_pull_secrets.py +1 -1
- reconcile/change_owners/change_owners.py +100 -34
- reconcile/cli.py +1 -1
- reconcile/external_resources/secrets_sync.py +2 -3
- 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 +3 -1
- reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -0
- reconcile/gql_definitions/introspection.json +48 -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 +5 -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/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.dev456.dist-info}/entry_points.txt +0 -0
|
@@ -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 `/good-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 = []
|
|
@@ -391,7 +452,12 @@ def run(
|
|
|
391
452
|
good_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)
|
|
@@ -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
|
|
@@ -933,6 +934,7 @@ class NamespaceTerraformResourceKinesisV1(NamespaceTerraformResourceAWSV1):
|
|
|
933
934
|
identifier: str = Field(..., alias="identifier")
|
|
934
935
|
defaults: str = Field(..., alias="defaults")
|
|
935
936
|
es_identifier: Optional[str] = Field(..., alias="es_identifier")
|
|
937
|
+
policy: Optional[str] = Field(..., alias="policy")
|
|
936
938
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
937
939
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
938
940
|
tags: Optional[str] = Field(..., alias="tags")
|
|
@@ -1167,7 +1169,7 @@ class NamespaceTerraformResourceMskV1(NamespaceTerraformResourceAWSV1):
|
|
|
1167
1169
|
|
|
1168
1170
|
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
|
1169
1171
|
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,
|
|
1172
|
+
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
1173
|
|
|
1172
1174
|
|
|
1173
1175
|
class EnvironmentV1(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,
|
|
@@ -41646,6 +41658,18 @@
|
|
|
41646
41658
|
"isDeprecated": false,
|
|
41647
41659
|
"deprecationReason": null
|
|
41648
41660
|
},
|
|
41661
|
+
{
|
|
41662
|
+
"name": "certificate_format",
|
|
41663
|
+
"description": null,
|
|
41664
|
+
"args": [],
|
|
41665
|
+
"type": {
|
|
41666
|
+
"kind": "SCALAR",
|
|
41667
|
+
"name": "String",
|
|
41668
|
+
"ofType": null
|
|
41669
|
+
},
|
|
41670
|
+
"isDeprecated": false,
|
|
41671
|
+
"deprecationReason": null
|
|
41672
|
+
},
|
|
41649
41673
|
{
|
|
41650
41674
|
"name": "annotations",
|
|
41651
41675
|
"description": null,
|
|
@@ -47657,6 +47681,18 @@
|
|
|
47657
47681
|
},
|
|
47658
47682
|
"isDeprecated": false,
|
|
47659
47683
|
"deprecationReason": null
|
|
47684
|
+
},
|
|
47685
|
+
{
|
|
47686
|
+
"name": "bucket_policy",
|
|
47687
|
+
"description": null,
|
|
47688
|
+
"args": [],
|
|
47689
|
+
"type": {
|
|
47690
|
+
"kind": "SCALAR",
|
|
47691
|
+
"name": "JSON",
|
|
47692
|
+
"ofType": null
|
|
47693
|
+
},
|
|
47694
|
+
"isDeprecated": false,
|
|
47695
|
+
"deprecationReason": null
|
|
47660
47696
|
}
|
|
47661
47697
|
],
|
|
47662
47698
|
"inputFields": null,
|
|
@@ -48266,6 +48302,18 @@
|
|
|
48266
48302
|
"isDeprecated": false,
|
|
48267
48303
|
"deprecationReason": null
|
|
48268
48304
|
},
|
|
48305
|
+
{
|
|
48306
|
+
"name": "policy",
|
|
48307
|
+
"description": null,
|
|
48308
|
+
"args": [],
|
|
48309
|
+
"type": {
|
|
48310
|
+
"kind": "SCALAR",
|
|
48311
|
+
"name": "JSON",
|
|
48312
|
+
"ofType": null
|
|
48313
|
+
},
|
|
48314
|
+
"isDeprecated": false,
|
|
48315
|
+
"deprecationReason": null
|
|
48316
|
+
},
|
|
48269
48317
|
{
|
|
48270
48318
|
"name": "output_resource_name",
|
|
48271
48319
|
"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
|
}
|
|
@@ -774,6 +776,7 @@ class NamespaceTerraformResourceS3CloudFrontV1(NamespaceTerraformResourceAWSV1):
|
|
|
774
776
|
defaults: str = Field(..., alias="defaults")
|
|
775
777
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
776
778
|
storage_class: Optional[str] = Field(..., alias="storage_class")
|
|
779
|
+
bucket_policy: Optional[str] = Field(..., alias="bucket_policy")
|
|
777
780
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
778
781
|
|
|
779
782
|
|
|
@@ -836,6 +839,7 @@ class NamespaceTerraformResourceKinesisV1(NamespaceTerraformResourceAWSV1):
|
|
|
836
839
|
identifier: str = Field(..., alias="identifier")
|
|
837
840
|
defaults: str = Field(..., alias="defaults")
|
|
838
841
|
es_identifier: Optional[str] = Field(..., alias="es_identifier")
|
|
842
|
+
policy: Optional[str] = Field(..., alias="policy")
|
|
839
843
|
output_resource_name: Optional[str] = Field(..., alias="output_resource_name")
|
|
840
844
|
annotations: Optional[str] = Field(..., alias="annotations")
|
|
841
845
|
|
|
@@ -1100,7 +1104,7 @@ class NamespaceTerraformResourceMskV1(NamespaceTerraformResourceAWSV1):
|
|
|
1100
1104
|
|
|
1101
1105
|
|
|
1102
1106
|
class NamespaceTerraformProviderResourceAWSV1(NamespaceExternalResourceV1):
|
|
1103
|
-
resources: list[Union[NamespaceTerraformResourceRDSV1, NamespaceTerraformResourceALBV1, NamespaceTerraformResourceRosaAuthenticatorV1, NamespaceTerraformResourceRoleV1, NamespaceTerraformResourceS3V1, NamespaceTerraformResourceASGV1, NamespaceTerraformResourceRDSProxyV1, NamespaceTerraformResourceElastiCacheV1, NamespaceTerraformResourceSNSTopicV1, NamespaceTerraformResourceCloudWatchV1, NamespaceTerraformResourceServiceAccountV1, NamespaceTerraformResourceS3SQSV1, NamespaceTerraformResourceKMSV1, NamespaceTerraformResourceRosaAuthenticatorVPCEV1, NamespaceTerraformResourceMskV1,
|
|
1107
|
+
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
1108
|
|
|
1105
1109
|
|
|
1106
1110
|
class EnvironmentV1(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:
|
|
@@ -32,7 +32,12 @@ from reconcile.utils.openshift_resource import (
|
|
|
32
32
|
ResourceInventory,
|
|
33
33
|
base64_encode_secret_field_value,
|
|
34
34
|
)
|
|
35
|
-
from reconcile.utils.rhcsv2_certs import
|
|
35
|
+
from reconcile.utils.rhcsv2_certs import (
|
|
36
|
+
CertificateFormat,
|
|
37
|
+
RhcsV2CertPem,
|
|
38
|
+
RhcsV2CertPkcs12,
|
|
39
|
+
generate_cert,
|
|
40
|
+
)
|
|
36
41
|
from reconcile.utils.runtime.integration import DesiredStateShardConfig
|
|
37
42
|
from reconcile.utils.secret_reader import create_secret_reader
|
|
38
43
|
from reconcile.utils.semver_helper import make_semver
|
|
@@ -66,6 +71,31 @@ class OpenshiftRhcsCertExpiration(GaugeMetric):
|
|
|
66
71
|
return "qontract_reconcile_rhcs_cert_expiration_timestamp"
|
|
67
72
|
|
|
68
73
|
|
|
74
|
+
def _generate_placeholder_cert(
|
|
75
|
+
cert_format: CertificateFormat,
|
|
76
|
+
) -> RhcsV2CertPem | RhcsV2CertPkcs12:
|
|
77
|
+
match cert_format:
|
|
78
|
+
case CertificateFormat.PKCS12:
|
|
79
|
+
return RhcsV2CertPkcs12(
|
|
80
|
+
pkcs12_keystore="PLACEHOLDER_KEYSTORE",
|
|
81
|
+
pkcs12_truststore="PLACEHOLDER_TRUSTSTORE",
|
|
82
|
+
expiration_timestamp=int(time.time()),
|
|
83
|
+
)
|
|
84
|
+
case CertificateFormat.PEM:
|
|
85
|
+
return RhcsV2CertPem(
|
|
86
|
+
certificate="PLACEHOLDER_CERT",
|
|
87
|
+
private_key="PLACEHOLDER_PRIVATE_KEY",
|
|
88
|
+
ca_cert="PLACEHOLDER_CA_CERT",
|
|
89
|
+
expiration_timestamp=int(time.time()),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_certificate_format(
|
|
94
|
+
cert_resource: OpenshiftResourceRhcsCert,
|
|
95
|
+
) -> CertificateFormat:
|
|
96
|
+
return CertificateFormat(cert_resource.certificate_format or "PEM")
|
|
97
|
+
|
|
98
|
+
|
|
69
99
|
def get_namespaces_with_rhcs_certs(
|
|
70
100
|
query_func: Callable,
|
|
71
101
|
cluster_name: Iterable[str] | None = None,
|
|
@@ -84,14 +114,21 @@ def get_namespaces_with_rhcs_certs(
|
|
|
84
114
|
|
|
85
115
|
|
|
86
116
|
def construct_rhcs_cert_oc_secret(
|
|
87
|
-
secret_name: str,
|
|
117
|
+
secret_name: str,
|
|
118
|
+
cert: Mapping[str, Any],
|
|
119
|
+
annotations: Mapping[str, str],
|
|
120
|
+
certificate_format: CertificateFormat,
|
|
88
121
|
) -> OR:
|
|
89
122
|
body: dict[str, Any] = {
|
|
90
123
|
"apiVersion": "v1",
|
|
91
124
|
"kind": "Secret",
|
|
92
|
-
"type": "kubernetes.io/tls",
|
|
93
125
|
"metadata": {"name": secret_name, "annotations": annotations},
|
|
94
126
|
}
|
|
127
|
+
match certificate_format:
|
|
128
|
+
case CertificateFormat.PKCS12:
|
|
129
|
+
body["type"] = "Opaque"
|
|
130
|
+
case CertificateFormat.PEM:
|
|
131
|
+
body["type"] = "kubernetes.io/tls"
|
|
95
132
|
for k, v in cert.items():
|
|
96
133
|
v = base64_encode_secret_field_value(v)
|
|
97
134
|
body.setdefault("data", {})[k] = v
|
|
@@ -145,17 +182,18 @@ def generate_vault_cert_secret(
|
|
|
145
182
|
f"Creating cert with service account credentials for '{cert_resource.service_account_name}'. cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}'"
|
|
146
183
|
)
|
|
147
184
|
sa_password = vault.read(cert_resource.service_account_password.model_dump())
|
|
185
|
+
cert_format = get_certificate_format(cert_resource)
|
|
186
|
+
|
|
148
187
|
if dry_run:
|
|
149
|
-
rhcs_cert =
|
|
150
|
-
certificate="PLACEHOLDER_CERT",
|
|
151
|
-
private_key="PLACEHOLDER_PRIVATE_KEY",
|
|
152
|
-
ca_cert="PLACEHOLDER_CA_CERT",
|
|
153
|
-
expiration_timestamp=int(time.time()),
|
|
154
|
-
)
|
|
188
|
+
rhcs_cert = _generate_placeholder_cert(cert_format)
|
|
155
189
|
else:
|
|
156
190
|
try:
|
|
157
191
|
rhcs_cert = generate_cert(
|
|
158
|
-
issuer_url,
|
|
192
|
+
issuer_url=issuer_url,
|
|
193
|
+
uid=cert_resource.service_account_name,
|
|
194
|
+
pwd=sa_password,
|
|
195
|
+
ca_url=ca_cert_url,
|
|
196
|
+
cert_format=cert_format,
|
|
159
197
|
)
|
|
160
198
|
except ValueError as e:
|
|
161
199
|
raise Exception(
|
|
@@ -166,12 +204,12 @@ def generate_vault_cert_secret(
|
|
|
166
204
|
)
|
|
167
205
|
vault.write(
|
|
168
206
|
secret={
|
|
169
|
-
"data": rhcs_cert.model_dump(by_alias=True),
|
|
207
|
+
"data": rhcs_cert.model_dump(by_alias=True, exclude_none=True),
|
|
170
208
|
"path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}",
|
|
171
209
|
},
|
|
172
210
|
decode_base64=False,
|
|
173
211
|
)
|
|
174
|
-
return rhcs_cert.model_dump(by_alias=True)
|
|
212
|
+
return rhcs_cert.model_dump(by_alias=True, exclude_none=True)
|
|
175
213
|
|
|
176
214
|
|
|
177
215
|
def fetch_openshift_resource_for_cert_resource(
|
|
@@ -213,6 +251,7 @@ def fetch_openshift_resource_for_cert_resource(
|
|
|
213
251
|
secret_name=cert_resource.secret_name,
|
|
214
252
|
cert=vault_cert_secret,
|
|
215
253
|
annotations=cert_resource.annotations or {},
|
|
254
|
+
certificate_format=get_certificate_format(cert_resource),
|
|
216
255
|
)
|
|
217
256
|
|
|
218
257
|
|
|
@@ -47,7 +47,7 @@ rosa create cluster -y --cluster-name={{ cluster_name }} \
|
|
|
47
47
|
--service-cidr {{ cluster.network.service }} \
|
|
48
48
|
--pod-cidr {{ cluster.network.pod }} \
|
|
49
49
|
--host-prefix 23 \
|
|
50
|
-
--replicas
|
|
50
|
+
--replicas 3 \
|
|
51
51
|
--compute-machine-type {{ cluster.machine_pools[0].instance_type }} \
|
|
52
52
|
{% if cluster.spec.disable_user_workload_monitoring -%}
|
|
53
53
|
--disable-workload-monitoring \
|
|
@@ -47,7 +47,7 @@ rosa create cluster --cluster-name={{ cluster_name }} \
|
|
|
47
47
|
--service-cidr {{ cluster.network.service }} \
|
|
48
48
|
--pod-cidr {{ cluster.network.pod }} \
|
|
49
49
|
--host-prefix 23 \
|
|
50
|
-
--replicas
|
|
50
|
+
--replicas 3 \
|
|
51
51
|
--compute-machine-type {{ cluster.machine_pools[0].instance_type }} \
|
|
52
52
|
{% if cluster.spec.private -%}
|
|
53
53
|
--private \
|
|
@@ -24,6 +24,7 @@ from reconcile.typed_queries.external_resources import get_settings
|
|
|
24
24
|
from reconcile.typed_queries.github_orgs import get_github_orgs
|
|
25
25
|
from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
|
|
26
26
|
from reconcile.utils import gql
|
|
27
|
+
from reconcile.utils.disabled_integrations import integration_is_enabled
|
|
27
28
|
from reconcile.utils.runtime.integration import (
|
|
28
29
|
DesiredStateShardConfig,
|
|
29
30
|
PydanticRunParams,
|
|
@@ -62,12 +63,14 @@ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesPa
|
|
|
62
63
|
) -> list[AWSAccountV1]:
|
|
63
64
|
"""Return a list of accounts extracted from the provided VPCRequests.
|
|
64
65
|
If account_name is given returns the account object with that name."""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
return [
|
|
67
|
+
vpc.account
|
|
68
|
+
for vpc in data
|
|
69
|
+
if (
|
|
70
|
+
integration_is_enabled(self.name, vpc.account)
|
|
71
|
+
and (not account_name or vpc.account.name == account_name)
|
|
72
|
+
)
|
|
73
|
+
]
|
|
71
74
|
|
|
72
75
|
def _handle_outputs(
|
|
73
76
|
self, requests: Iterable[VPCRequest], outputs: Mapping[str, Any]
|
|
@@ -155,7 +158,7 @@ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesPa
|
|
|
155
158
|
if data:
|
|
156
159
|
accounts = self._filter_accounts(data, account_name)
|
|
157
160
|
if account_name and not accounts:
|
|
158
|
-
msg = f"The account {account_name} doesn't have any managed
|
|
161
|
+
msg = f"The account {account_name} doesn't have any managed vpcs or the {QONTRACT_INTEGRATION} integration is disabled for this account. Verify your input"
|
|
159
162
|
logging.debug(msg)
|
|
160
163
|
sys.exit(ExitCodes.SUCCESS)
|
|
161
164
|
else:
|