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.

Files changed (41) hide show
  1. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/METADATA +2 -2
  2. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/RECORD +41 -40
  3. {qontract_reconcile-0.10.2.dev427.dist-info → qontract_reconcile-0.10.2.dev465.dist-info}/WHEEL +1 -1
  4. reconcile/aus/aus_sts_gate_handler.py +59 -0
  5. reconcile/aus/base.py +9 -8
  6. reconcile/aus/version_gate_approver.py +1 -16
  7. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -125
  8. reconcile/aws_account_manager/integration.py +13 -1
  9. reconcile/aws_account_manager/utils.py +1 -1
  10. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  11. reconcile/change_owners/README.md +1 -1
  12. reconcile/change_owners/change_owners.py +108 -42
  13. reconcile/change_owners/decision.py +1 -1
  14. reconcile/cli.py +1 -1
  15. reconcile/external_resources/secrets_sync.py +2 -3
  16. reconcile/gql_definitions/aws_account_manager/aws_accounts.py +9 -0
  17. reconcile/gql_definitions/common/aws_vpc_requests.py +3 -0
  18. reconcile/gql_definitions/common/clusters.py +2 -0
  19. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +5 -1
  20. reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -0
  21. reconcile/gql_definitions/introspection.json +60 -0
  22. reconcile/gql_definitions/rhcs/certs.py +1 -0
  23. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +1 -0
  24. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +7 -1
  25. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +3 -0
  26. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +1 -0
  27. reconcile/openshift_namespaces.py +3 -4
  28. reconcile/openshift_rhcs_certs.py +51 -12
  29. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  30. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  31. reconcile/terraform_vpc_resources/integration.py +10 -7
  32. reconcile/typed_queries/saas_files.py +9 -4
  33. reconcile/utils/environ.py +5 -0
  34. reconcile/utils/external_resource_spec.py +2 -0
  35. reconcile/utils/gitlab_api.py +12 -0
  36. reconcile/utils/jjb_client.py +19 -3
  37. reconcile/utils/oc.py +8 -2
  38. reconcile/utils/rhcsv2_certs.py +87 -21
  39. reconcile/utils/terrascript_aws_client.py +140 -50
  40. reconcile/vpc_peerings_validator.py +13 -0
  41. {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 `/good-to-test` command to override this restriction.
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
- # 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 = []
@@ -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, good_to_test_approvers: set[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 good_to_test_approvers
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, good_to_test_approvers: set[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 /good-to-test comment.
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, good_to_test_approvers)
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
- good_to_test_approvers = {
385
- c.username for c in comments if c.body.strip() == "/good-to-test"
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
- good_to_test_approvers,
452
+ ok_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,
@@ -20,7 +20,7 @@ class DecisionCommand(Enum):
20
20
  CANCEL_APPROVED = "/lgtm cancel"
21
21
  HOLD = "/hold"
22
22
  CANCEL_HOLD = "/hold cancel"
23
- GOOD_TO_TEST = "/good-to-test"
23
+ OK_TO_TEST = "/ok-to-test"
24
24
 
25
25
 
26
26
  @dataclass
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)
@@ -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):
@@ -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
@@ -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, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
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,
@@ -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
  }
@@ -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, NamespaceTerraformResourceS3CloudFrontV1, NamespaceTerraformResourceElasticSearchV1, NamespaceTerraformResourceACMV1, NamespaceTerraformResourceKinesisV1, NamespaceTerraformResourceSecretsManagerV1, NamespaceTerraformResourceRoute53ZoneV1, NamespaceTerraformResourceSQSV1, NamespaceTerraformResourceDynamoDBV1, NamespaceTerraformResourceECRV1, NamespaceTerraformResourceS3CloudFrontPublicKeyV1, NamespaceTerraformResourceSecretsManagerServiceAccountV1, NamespaceTerraformResourceAWSV1]] = Field(..., alias="resources")
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")
@@ -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: