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.

Files changed (35) hide show
  1. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/RECORD +35 -34
  3. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/WHEEL +1 -1
  4. reconcile/aus/aus_sts_gate_handler.py +59 -0
  5. reconcile/aus/base.py +9 -5
  6. reconcile/aus/version_gate_approver.py +1 -16
  7. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -125
  8. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  9. reconcile/change_owners/change_owners.py +100 -34
  10. reconcile/cli.py +1 -1
  11. reconcile/external_resources/secrets_sync.py +2 -3
  12. reconcile/gql_definitions/common/aws_vpc_requests.py +3 -0
  13. reconcile/gql_definitions/common/clusters.py +2 -0
  14. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +3 -1
  15. reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -0
  16. reconcile/gql_definitions/introspection.json +48 -0
  17. reconcile/gql_definitions/rhcs/certs.py +1 -0
  18. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +1 -0
  19. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +5 -1
  20. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +3 -0
  21. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +1 -0
  22. reconcile/openshift_namespaces.py +3 -4
  23. reconcile/openshift_rhcs_certs.py +51 -12
  24. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  25. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  26. reconcile/terraform_vpc_resources/integration.py +10 -7
  27. reconcile/typed_queries/saas_files.py +9 -4
  28. reconcile/utils/environ.py +5 -0
  29. reconcile/utils/gitlab_api.py +12 -0
  30. reconcile/utils/jjb_client.py +19 -3
  31. reconcile/utils/oc.py +8 -2
  32. reconcile/utils/rhcsv2_certs.py +87 -21
  33. reconcile/utils/terrascript_aws_client.py +140 -50
  34. reconcile/vpc_peerings_validator.py +13 -0
  35. {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
- # add new report comment
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
- self_serviceability_hint = "All changes require an `/lgtm` from a listed approver "
174
- if not self_serviceable:
175
- self_serviceability_hint += (
176
- "but <b>not all changes are self-serviceable and require AppSRE approval</b>."
177
- "The AppSRE Interrupt Catcher (IC) will review your Merge Request (MR) as it comes up in their "
178
- "<a href='https://gitlab.cee.redhat.com/service/app-interface-output/-/blob/master/app-interface-review-queue.md'>queue</a>, "
179
- "please do not ping them directly unless this is <b>urgent</b>."
180
- "\nPlease see https://gitlab.cee.redhat.com/service/app-interface#app-interface-etiquette for more information. Thank you :)"
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(merge_request, include_description=True)
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.11.1"]
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[SECRET_UPDATED_AT]
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)
@@ -48,6 +48,9 @@ fragment VPCRequest on VPCRequest_v1 {
48
48
  automationToken {
49
49
  ...VaultSecret
50
50
  }
51
+ disable {
52
+ integrations
53
+ }
51
54
  supportedDeploymentRegions
52
55
  resourcesDefaultRegion
53
56
  providerVersion
@@ -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, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
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,
@@ -45,6 +45,7 @@ fragment OpenshiftResourceRhcsCert on NamespaceOpenshiftResourceRhcsCert_v1 {
45
45
  }
46
46
  }
47
47
  auto_renew_threshold_days
48
+ certificate_format
48
49
  annotations
49
50
  }
50
51
 
@@ -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, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
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")
@@ -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 RhcsV2Cert, generate_cert
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, cert: Mapping[str, Any], annotations: Mapping[str, 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 = RhcsV2Cert(
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, cert_resource.service_account_name, sa_password, ca_cert_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 {{ cluster.machine_pools | length }} \
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 {{ cluster.machine_pools | length }} \
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
- accounts = [vpc.account for vpc in data]
66
-
67
- if account_name:
68
- accounts = [account for account in accounts if account.name == account_name]
69
-
70
- return accounts
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 vpc. Verify your input"
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: