qontract-reconcile 0.10.2.dev414__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 (55) hide show
  1. {qontract_reconcile-0.10.2.dev414.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/METADATA +2 -2
  2. {qontract_reconcile-0.10.2.dev414.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/RECORD +55 -53
  3. {qontract_reconcile-0.10.2.dev414.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/WHEEL +1 -1
  4. reconcile/aus/advanced_upgrade_service.py +3 -0
  5. reconcile/aus/aus_sts_gate_handler.py +59 -0
  6. reconcile/aus/base.py +115 -8
  7. reconcile/aus/models.py +2 -0
  8. reconcile/aus/ocm_addons_upgrade_scheduler_org.py +1 -0
  9. reconcile/aus/ocm_upgrade_scheduler.py +8 -1
  10. reconcile/aus/ocm_upgrade_scheduler_org.py +20 -5
  11. reconcile/aus/version_gate_approver.py +1 -16
  12. reconcile/aus/version_gates/sts_version_gate_handler.py +5 -72
  13. reconcile/automated_actions/config/integration.py +1 -1
  14. reconcile/aws_ecr_image_pull_secrets.py +1 -1
  15. reconcile/change_owners/change_owners.py +100 -34
  16. reconcile/cli.py +63 -5
  17. reconcile/external_resources/manager.py +7 -18
  18. reconcile/external_resources/model.py +8 -8
  19. reconcile/external_resources/secrets_sync.py +2 -3
  20. reconcile/external_resources/state.py +1 -34
  21. reconcile/gql_definitions/common/aws_vpc_requests.py +3 -0
  22. reconcile/gql_definitions/common/clusters.py +2 -0
  23. reconcile/gql_definitions/external_resources/external_resources_namespaces.py +3 -1
  24. reconcile/gql_definitions/fragments/aws_vpc_request.py +5 -0
  25. reconcile/gql_definitions/introspection.json +48 -0
  26. reconcile/gql_definitions/rhcs/certs.py +20 -74
  27. reconcile/gql_definitions/rhcs/openshift_resource_rhcs_cert.py +43 -0
  28. reconcile/gql_definitions/terraform_resources/terraform_resources_namespaces.py +5 -1
  29. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator.py +3 -0
  30. reconcile/gql_definitions/vpc_peerings_validator/vpc_peerings_validator_peered_cluster_fragment.py +1 -0
  31. reconcile/ocm_machine_pools.py +12 -6
  32. reconcile/openshift_base.py +60 -2
  33. reconcile/openshift_namespaces.py +3 -4
  34. reconcile/openshift_rhcs_certs.py +71 -34
  35. reconcile/rhidp/sso_client/base.py +15 -4
  36. reconcile/templates/rosa-classic-cluster-creation.sh.j2 +1 -1
  37. reconcile/templates/rosa-hcp-cluster-creation.sh.j2 +1 -1
  38. reconcile/terraform_vpc_resources/integration.py +10 -7
  39. reconcile/typed_queries/saas_files.py +9 -4
  40. reconcile/utils/binary.py +7 -12
  41. reconcile/utils/environ.py +5 -0
  42. reconcile/utils/gitlab_api.py +12 -0
  43. reconcile/utils/glitchtip/client.py +2 -2
  44. reconcile/utils/jjb_client.py +19 -3
  45. reconcile/utils/jobcontroller/controller.py +1 -1
  46. reconcile/utils/json.py +5 -1
  47. reconcile/utils/oc.py +144 -113
  48. reconcile/utils/rhcsv2_certs.py +87 -21
  49. reconcile/utils/rosa/session.py +16 -0
  50. reconcile/utils/saasherder/saasherder.py +20 -7
  51. reconcile/utils/terrascript_aws_client.py +140 -50
  52. reconcile/utils/vault.py +1 -1
  53. reconcile/vpc_peerings_validator.py +13 -0
  54. tools/cli_commands/erv2.py +1 -3
  55. {qontract_reconcile-0.10.2.dev414.dist-info → qontract_reconcile-0.10.2.dev456.dist-info}/entry_points.txt +0 -0
@@ -272,6 +272,7 @@ VARIABLE_KEYS = [
272
272
  "lifecycle",
273
273
  "max_session_duration",
274
274
  "secret_format",
275
+ "policy",
275
276
  ]
276
277
 
277
278
  EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
@@ -373,6 +374,10 @@ class aws_s3_bucket_logging(Resource):
373
374
  pass
374
375
 
375
376
 
377
+ class aws_kinesis_resource_policy(Resource):
378
+ pass
379
+
380
+
376
381
  class aws_cloudfront_log_delivery_canonical_user_id(Data):
377
382
  pass
378
383
 
@@ -2249,14 +2254,22 @@ class TerrascriptClient:
2249
2254
 
2250
2255
  return lifecycle_rules
2251
2256
 
2252
- def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2257
+ def _populate_tf_resource_s3_bucket(
2258
+ self,
2259
+ spec: ExternalResourceSpec,
2260
+ common_values: dict[str, Any],
2261
+ ) -> tuple[aws_s3_bucket, list[TFResource]]:
2262
+ """Create S3 bucket with configuration and notifications.
2263
+
2264
+ Creates aws_s3_bucket with versioning, encryption, lifecycle rules,
2265
+ CORS, logging, and replication. Also creates aws_s3_bucket_notification
2266
+ for SQS/SNS event notifications if configured.
2267
+ """
2253
2268
  account = spec.provisioner_name
2254
2269
  identifier = spec.identifier
2255
- common_values = self.init_values(spec)
2256
2270
  output_prefix = spec.output_prefix
2257
2271
 
2258
2272
  tf_resources: list[TFResource] = []
2259
- self.init_common_outputs(tf_resources, spec)
2260
2273
 
2261
2274
  # s3 bucket
2262
2275
  # Terraform resource reference:
@@ -2433,8 +2446,7 @@ class TerrascriptClient:
2433
2446
  output_name = output_prefix + "__endpoint"
2434
2447
  tf_resources.append(Output(output_name, value=endpoint))
2435
2448
 
2436
- sqs_identifier = common_values.get("sqs_identifier", None)
2437
- if sqs_identifier is not None:
2449
+ if sqs_identifier := common_values.get("sqs_identifier"):
2438
2450
  sqs_values = {"name": sqs_identifier}
2439
2451
  sqs_provider = values.get("provider")
2440
2452
  if sqs_provider:
@@ -2453,11 +2465,9 @@ class TerrascriptClient:
2453
2465
  }
2454
2466
  ],
2455
2467
  }
2456
- filter_prefix = common_values.get("filter_prefix", None)
2457
- if filter_prefix is not None:
2468
+ if filter_prefix := common_values.get("filter_prefix"):
2458
2469
  notification_values["queue"][0]["filter_prefix"] = filter_prefix
2459
- filter_suffix = common_values.get("filter_suffix", None)
2460
- if filter_suffix is not None:
2470
+ if filter_suffix := common_values.get("filter_suffix"):
2461
2471
  notification_values["queue"][0]["filter_suffix"] = filter_suffix
2462
2472
 
2463
2473
  notification_tf_resource = aws_s3_bucket_notification(
@@ -2537,21 +2547,48 @@ class TerrascriptClient:
2537
2547
  )
2538
2548
  tf_resources.append(notification_tf_resource)
2539
2549
 
2540
- bucket_policy = common_values.get("bucket_policy")
2541
- if bucket_policy:
2542
- values = {
2543
- "bucket": identifier,
2544
- "policy": bucket_policy,
2545
- "depends_on": self.get_dependencies([bucket_tf_resource]),
2546
- }
2547
- if self._multiregion_account(account):
2548
- values["provider"] = "aws." + region
2549
- bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
2550
- tf_resources.append(bucket_policy_tf_resource)
2550
+ return bucket_tf_resource, tf_resources
2551
2551
 
2552
- # iam resources
2553
- # Terraform resource reference:
2554
- # https://www.terraform.io/docs/providers/aws/r/iam_access_key.html
2552
+ def _populate_tf_resource_s3_bucket_policy(
2553
+ self,
2554
+ spec: ExternalResourceSpec,
2555
+ bucket_tf_resource: aws_s3_bucket,
2556
+ policy: str,
2557
+ common_values: dict[str, Any],
2558
+ ) -> list[TFResource]:
2559
+ """Create S3 bucket policy resource.
2560
+
2561
+ Creates aws_s3_bucket_policy with the provided policy document.
2562
+ """
2563
+ account = spec.provisioner_name
2564
+ identifier = spec.identifier
2565
+ region = common_values.get("region") or self.default_regions.get(account)
2566
+ assert region # make mypy happy
2567
+
2568
+ values: dict[str, Any] = {
2569
+ "bucket": identifier,
2570
+ "policy": policy,
2571
+ "depends_on": self.get_dependencies([bucket_tf_resource]),
2572
+ }
2573
+ if self._multiregion_account(account):
2574
+ values["provider"] = "aws." + region
2575
+ bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values)
2576
+ return [bucket_policy_tf_resource]
2577
+
2578
+ def _populate_tf_resource_s3_iam(
2579
+ self,
2580
+ spec: ExternalResourceSpec,
2581
+ bucket_tf_resource: aws_s3_bucket,
2582
+ common_values: dict[str, Any],
2583
+ ) -> list[TFResource]:
2584
+ """Create IAM resources for S3 bucket access.
2585
+
2586
+ Creates aws_iam_user, aws_iam_access_key, aws_iam_policy,
2587
+ and aws_iam_user_policy_attachment for bucket access.
2588
+ """
2589
+ identifier = spec.identifier
2590
+ output_prefix = spec.output_prefix
2591
+ tf_resources: list[TFResource] = []
2555
2592
 
2556
2593
  # iam user for bucket
2557
2594
  values = {
@@ -2609,6 +2646,32 @@ class TerrascriptClient:
2609
2646
  )
2610
2647
  tf_resources.append(tf_user_policy_attachment)
2611
2648
 
2649
+ return tf_resources
2650
+
2651
+ def populate_tf_resource_s3(self, spec: ExternalResourceSpec) -> aws_s3_bucket:
2652
+ account = spec.provisioner_name
2653
+ common_values = self.init_values(spec)
2654
+
2655
+ tf_resources: list[TFResource] = []
2656
+ self.init_common_outputs(tf_resources, spec)
2657
+
2658
+ bucket_tf_resource, bucket_resources = self._populate_tf_resource_s3_bucket(
2659
+ spec, common_values
2660
+ )
2661
+ tf_resources.extend(bucket_resources)
2662
+
2663
+ bucket_policy = common_values.get("bucket_policy")
2664
+ if bucket_policy:
2665
+ tf_resources.extend(
2666
+ self._populate_tf_resource_s3_bucket_policy(
2667
+ spec, bucket_tf_resource, bucket_policy, common_values
2668
+ )
2669
+ )
2670
+
2671
+ tf_resources.extend(
2672
+ self._populate_tf_resource_s3_iam(spec, bucket_tf_resource, common_values)
2673
+ )
2674
+
2612
2675
  self.add_resources(account, tf_resources)
2613
2676
 
2614
2677
  return bucket_tf_resource
@@ -3383,42 +3446,53 @@ class TerrascriptClient:
3383
3446
  common_values = self.init_values(spec)
3384
3447
  output_prefix = spec.output_prefix
3385
3448
 
3386
- bucket_tf_resource = self.populate_tf_resource_s3(spec)
3387
-
3388
3449
  tf_resources: list[TFResource] = []
3450
+ self.init_common_outputs(tf_resources, spec)
3451
+
3452
+ bucket_tf_resource, bucket_resources = self._populate_tf_resource_s3_bucket(
3453
+ spec, common_values
3454
+ )
3455
+ tf_resources.extend(bucket_resources)
3456
+
3457
+ tf_resources.extend(
3458
+ self._populate_tf_resource_s3_iam(spec, bucket_tf_resource, common_values)
3459
+ )
3389
3460
 
3390
3461
  # cloudfront origin access identity
3391
3462
  values = {"comment": f"{identifier}-cf-identity"}
3392
3463
  cf_oai_tf_resource = aws_cloudfront_origin_access_identity(identifier, **values)
3393
3464
  tf_resources.append(cf_oai_tf_resource)
3394
3465
 
3395
- # bucket policy for cloudfront
3396
- values_policy: dict[str, Any] = {"bucket": identifier}
3397
- policy = {
3398
- "Version": "2012-10-17",
3399
- "Statement": [
3400
- {
3401
- "Sid": "Grant access to CloudFront Origin Identity",
3402
- "Effect": "Allow",
3403
- "Principal": {"AWS": "${" + cf_oai_tf_resource.iam_arn + "}"},
3404
- "Action": "s3:GetObject",
3405
- "Resource": [
3406
- f"arn:aws:s3:::{identifier}/{enable_dir}/*"
3407
- for enable_dir in common_values.get(
3408
- "get_object_enable_dirs", []
3409
- )
3410
- ],
3411
- }
3466
+ # bucket policy for cloudfront - merge custom policy with CloudFront access statement
3467
+ cf_statement = {
3468
+ "Sid": "Grant access to CloudFront Origin Identity",
3469
+ "Effect": "Allow",
3470
+ "Principal": {"AWS": "${" + cf_oai_tf_resource.iam_arn + "}"},
3471
+ "Action": "s3:GetObject",
3472
+ "Resource": [
3473
+ f"arn:aws:s3:::{identifier}/{enable_dir}/*"
3474
+ for enable_dir in common_values.get("get_object_enable_dirs", [])
3412
3475
  ],
3413
3476
  }
3414
- values_policy["policy"] = json_dumps(policy)
3415
- values_policy["depends_on"] = self.get_dependencies([bucket_tf_resource])
3416
- region = common_values.get("region") or self.default_regions.get(account)
3417
- assert region # make mypy happy
3418
- if self._multiregion_account(account):
3419
- values_policy["provider"] = "aws." + region
3420
- bucket_policy_tf_resource = aws_s3_bucket_policy(identifier, **values_policy)
3421
- tf_resources.append(bucket_policy_tf_resource)
3477
+
3478
+ custom_bucket_policy = common_values.get("bucket_policy")
3479
+ if custom_bucket_policy:
3480
+ # if the user specifies a custom bucket policy then we merge their statements with the cloudfront origin identity policy
3481
+ if isinstance(custom_bucket_policy, str):
3482
+ custom_bucket_policy = json.loads(custom_bucket_policy)
3483
+ custom_bucket_policy.setdefault("Statement", []).append(cf_statement)
3484
+ policy = custom_bucket_policy
3485
+ else:
3486
+ policy = {
3487
+ "Version": "2012-10-17",
3488
+ "Statement": [cf_statement],
3489
+ }
3490
+
3491
+ tf_resources.extend(
3492
+ self._populate_tf_resource_s3_bucket_policy(
3493
+ spec, bucket_tf_resource, json_dumps(policy), common_values
3494
+ )
3495
+ )
3422
3496
 
3423
3497
  distribution_config = common_values.get("distribution_config", {})
3424
3498
  # aws_s3_bucket_acl
@@ -4019,6 +4093,22 @@ class TerrascriptClient:
4019
4093
  kinesis_tf_resource = aws_kinesis_stream(identifier, **kinesis_values)
4020
4094
  tf_resources.append(kinesis_tf_resource)
4021
4095
 
4096
+ # kinesis resource policy (optional)
4097
+ # Terraform resource reference:
4098
+ # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kinesis_resource_policy
4099
+ if policy := common_values.get("policy"):
4100
+ policy_identifier = f"{identifier}-policy"
4101
+ policy_values: dict[str, Any] = {
4102
+ "resource_arn": "${" + kinesis_tf_resource.arn + "}",
4103
+ "policy": policy,
4104
+ }
4105
+ if provider:
4106
+ policy_values["provider"] = provider
4107
+ kinesis_policy_tf_resource = aws_kinesis_resource_policy(
4108
+ policy_identifier, **policy_values
4109
+ )
4110
+ tf_resources.append(kinesis_policy_tf_resource)
4111
+
4022
4112
  es_identifier = common_values.get("es_identifier", None)
4023
4113
  if es_identifier:
4024
4114
  es_resource = self._find_resource_spec(
reconcile/utils/vault.py CHANGED
@@ -200,7 +200,7 @@ class VaultClient:
200
200
  a v2 KV engine)
201
201
  """
202
202
  secret_path = secret["path"]
203
- secret_version = secret.get("version", SECRET_VERSION_LATEST)
203
+ secret_version = secret.get("version") or SECRET_VERSION_LATEST
204
204
 
205
205
  kv_version = self._get_mount_version_by_secret_path(secret_path)
206
206
 
@@ -159,6 +159,19 @@ def validate_no_public_to_public_peerings(
159
159
  if peer.internal or (peer.spec and peer.spec.private):
160
160
  continue
161
161
 
162
+ # If both sides are allowed to override this check, then we can
163
+ # allow the peering.
164
+ if (
165
+ cluster.allowed_to_bypass_public_peering_restriction
166
+ and peer.allowed_to_bypass_public_peering_restriction
167
+ ):
168
+ logging.debug(
169
+ f"{cluster.name} and {peer.name} are both allowed to skip \
170
+ the check 'no peering with public clusters' check, so their \
171
+ peering is allowed"
172
+ )
173
+ continue
174
+
162
175
  valid = False
163
176
  pair = {cluster.name, peer.name}
164
177
  if pair in found_pairs:
@@ -133,9 +133,7 @@ class Erv2Cli:
133
133
 
134
134
  @property
135
135
  def input_data(self) -> str:
136
- return self._resource.model_dump_json(
137
- exclude={"data": {FLAG_RESOURCE_MANAGED_BY_ERV2}}
138
- )
136
+ return self._resource.export(exclude={"data": {FLAG_RESOURCE_MANAGED_BY_ERV2}})
139
137
 
140
138
  @property
141
139
  def image(self) -> str: