qontract-reconcile 0.10.1rc975__py3-none-any.whl → 0.10.1rc977__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.
Files changed (110) hide show
  1. {qontract_reconcile-0.10.1rc975.dist-info → qontract_reconcile-0.10.1rc977.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.1rc975.dist-info → qontract_reconcile-0.10.1rc977.dist-info}/RECORD +110 -110
  3. reconcile/aus/healthchecks.py +1 -1
  4. reconcile/aws_account_manager/integration.py +23 -21
  5. reconcile/aws_account_manager/reconciler.py +1 -1
  6. reconcile/aws_saml_idp/integration.py +5 -5
  7. reconcile/aws_saml_roles/integration.py +5 -5
  8. reconcile/aws_version_sync/utils.py +3 -3
  9. reconcile/cna/state.py +2 -2
  10. reconcile/database_access_manager.py +2 -5
  11. reconcile/external_resources/manager.py +3 -3
  12. reconcile/external_resources/model.py +1 -1
  13. reconcile/external_resources/secrets_sync.py +2 -2
  14. reconcile/external_resources/state.py +1 -1
  15. reconcile/gcr_mirror.py +2 -6
  16. reconcile/jira_permissions_validator.py +4 -4
  17. reconcile/ldap_groups/integration.py +4 -7
  18. reconcile/ocm_internal_notifications/integration.py +2 -2
  19. reconcile/openshift_base.py +14 -14
  20. reconcile/openshift_cluster_bots.py +1 -1
  21. reconcile/openshift_clusterrolebindings.py +9 -10
  22. reconcile/openshift_namespace_labels.py +2 -2
  23. reconcile/openshift_namespaces.py +1 -1
  24. reconcile/openshift_resources_base.py +9 -9
  25. reconcile/openshift_rolebindings.py +8 -11
  26. reconcile/openshift_saas_deploy_trigger_base.py +8 -5
  27. reconcile/oum/base.py +1 -1
  28. reconcile/quay_mirror.py +3 -10
  29. reconcile/queries.py +1 -1
  30. reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py +2 -2
  31. reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py +1 -1
  32. reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py +0 -1
  33. reconcile/skupper_network/integration.py +3 -1
  34. reconcile/skupper_network/site_controller.py +8 -8
  35. reconcile/slack_usergroups.py +10 -10
  36. reconcile/status_board.py +1 -1
  37. reconcile/statuspage/status.py +1 -3
  38. reconcile/terraform_cloudflare_dns.py +2 -3
  39. reconcile/terraform_cloudflare_users.py +2 -3
  40. reconcile/terraform_repo.py +5 -3
  41. reconcile/terraform_resources.py +16 -16
  42. reconcile/terraform_tgw_attachments.py +6 -6
  43. reconcile/terraform_vpc_peerings.py +8 -8
  44. reconcile/terraform_vpc_resources/integration.py +1 -1
  45. reconcile/test/test_aws_cloudwatch_log_retention.py +2 -5
  46. reconcile/test/test_github_org.py +18 -16
  47. reconcile/test/test_github_repo_invites.py +10 -10
  48. reconcile/test/test_integrations_manager.py +11 -11
  49. reconcile/test/test_ocm_additional_routers.py +6 -6
  50. reconcile/test/test_ocm_clusters.py +1 -0
  51. reconcile/test/test_ocm_update_recommended_version.py +2 -2
  52. reconcile/test/test_openshift_serviceaccount_tokens.py +5 -5
  53. reconcile/test/test_openshift_tekton_resources.py +3 -3
  54. reconcile/test/test_saasherder.py +48 -48
  55. reconcile/test/test_sql_query.py +1 -1
  56. reconcile/test/test_terraform_cloudflare_resources.py +14 -14
  57. reconcile/test/test_terraform_cloudflare_users.py +4 -4
  58. reconcile/test/test_terraform_resources.py +32 -32
  59. reconcile/test/test_terraform_tgw_attachments.py +5 -5
  60. reconcile/typed_queries/saas_files.py +1 -1
  61. reconcile/unleash_feature_toggles/integration.py +2 -2
  62. reconcile/utils/aggregated_list.py +1 -1
  63. reconcile/utils/aws_api.py +1 -1
  64. reconcile/utils/aws_api_typed/iam.py +4 -2
  65. reconcile/utils/aws_api_typed/service_quotas.py +2 -2
  66. reconcile/utils/binary.py +1 -1
  67. reconcile/utils/clusterhealth/telemeter.py +5 -3
  68. reconcile/utils/config.py +4 -2
  69. reconcile/utils/expiration.py +1 -1
  70. reconcile/utils/external_resources.py +4 -7
  71. reconcile/utils/git.py +1 -3
  72. reconcile/utils/gitlab_api.py +1 -4
  73. reconcile/utils/glitchtip/client.py +7 -2
  74. reconcile/utils/gql.py +9 -8
  75. reconcile/utils/helm.py +1 -1
  76. reconcile/utils/imap_client.py +3 -1
  77. reconcile/utils/internal_groups/client.py +1 -1
  78. reconcile/utils/jinja2/utils.py +4 -4
  79. reconcile/utils/jobcontroller/models.py +2 -2
  80. reconcile/utils/jump_host.py +1 -1
  81. reconcile/utils/ldap_client.py +1 -1
  82. reconcile/utils/merge_request_manager/merge_request_manager.py +1 -4
  83. reconcile/utils/models.py +2 -5
  84. reconcile/utils/oc.py +18 -28
  85. reconcile/utils/ocm/ocm.py +3 -1
  86. reconcile/utils/ocm/products.py +2 -2
  87. reconcile/utils/ocm/search_filters.py +4 -11
  88. reconcile/utils/ocm_base_client.py +1 -4
  89. reconcile/utils/openshift_resource.py +12 -19
  90. reconcile/utils/quay_api.py +1 -3
  91. reconcile/utils/repo_owners.py +2 -8
  92. reconcile/utils/runtime/runner.py +1 -1
  93. reconcile/utils/saasherder/saasherder.py +4 -4
  94. reconcile/utils/secret_reader.py +2 -2
  95. reconcile/utils/slack_api.py +1 -1
  96. reconcile/utils/sqs_gateway.py +1 -1
  97. reconcile/utils/state.py +4 -4
  98. reconcile/utils/terraform/config_client.py +1 -1
  99. reconcile/utils/terraform_client.py +2 -2
  100. reconcile/utils/terrascript_aws_client.py +13 -23
  101. reconcile/utils/three_way_diff_strategy.py +3 -9
  102. reconcile/utils/unleash/client.py +2 -2
  103. reconcile/utils/vault.py +11 -14
  104. reconcile/utils/vcs.py +1 -1
  105. reconcile/vault_replication.py +1 -1
  106. tools/app_interface_reporter.py +2 -3
  107. tools/qontract_cli.py +1 -1
  108. {qontract_reconcile-0.10.1rc975.dist-info → qontract_reconcile-0.10.1rc977.dist-info}/WHEEL +0 -0
  109. {qontract_reconcile-0.10.1rc975.dist-info → qontract_reconcile-0.10.1rc977.dist-info}/entry_points.txt +0 -0
  110. {qontract_reconcile-0.10.1rc975.dist-info → qontract_reconcile-0.10.1rc977.dist-info}/top_level.txt +0 -0
@@ -1487,39 +1487,39 @@ def test_render_templated_parameters(
1487
1487
  assert saas_file.resource_templates[0].targets[0].secret_parameters == [
1488
1488
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1489
1489
  name="no-template",
1490
- secret=dict(
1491
- path="path/to/secret",
1492
- field="secret_key",
1493
- version=1,
1494
- format=None,
1495
- ),
1490
+ secret={
1491
+ "path": "path/to/secret",
1492
+ "field": "secret_key",
1493
+ "version": 1,
1494
+ "format": None,
1495
+ },
1496
1496
  ),
1497
1497
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1498
1498
  name="ignore-go-template",
1499
- secret=dict(
1500
- path="path/{{ .GO_PARAM }}/secret",
1501
- field="{{ .GO_PARAM }}-secret_key",
1502
- version=1,
1503
- format=None,
1504
- ),
1499
+ secret={
1500
+ "path": "path/{{ .GO_PARAM }}/secret",
1501
+ "field": "{{ .GO_PARAM }}-secret_key",
1502
+ "version": 1,
1503
+ "format": None,
1504
+ },
1505
1505
  ),
1506
1506
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1507
1507
  name="template-param-1",
1508
- secret=dict(
1509
- path="path/appsres03ue1/test-namespace/secret",
1510
- field="secret_key",
1511
- version=1,
1512
- format=None,
1513
- ),
1508
+ secret={
1509
+ "path": "path/appsres03ue1/test-namespace/secret",
1510
+ "field": "secret_key",
1511
+ "version": 1,
1512
+ "format": None,
1513
+ },
1514
1514
  ),
1515
1515
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1516
1516
  name="template-param-2",
1517
- secret=dict(
1518
- path="path/appsres03ue1/test-namespace/secret",
1519
- field="App-SRE-stage-secret_key",
1520
- version=1,
1521
- format=None,
1522
- ),
1517
+ secret={
1518
+ "path": "path/appsres03ue1/test-namespace/secret",
1519
+ "field": "App-SRE-stage-secret_key",
1520
+ "version": 1,
1521
+ "format": None,
1522
+ },
1523
1523
  ),
1524
1524
  ]
1525
1525
 
@@ -1549,38 +1549,38 @@ def test_render_templated_parameters_in_init(
1549
1549
  assert saas_file.resource_templates[0].targets[0].secret_parameters == [
1550
1550
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1551
1551
  name="no-template",
1552
- secret=dict(
1553
- path="path/to/secret",
1554
- field="secret_key",
1555
- version=1,
1556
- format=None,
1557
- ),
1552
+ secret={
1553
+ "path": "path/to/secret",
1554
+ "field": "secret_key",
1555
+ "version": 1,
1556
+ "format": None,
1557
+ },
1558
1558
  ),
1559
1559
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1560
1560
  name="ignore-go-template",
1561
- secret=dict(
1562
- path="path/{{ .GO_PARAM }}/secret",
1563
- field="{{ .GO_PARAM }}-secret_key",
1564
- version=1,
1565
- format=None,
1566
- ),
1561
+ secret={
1562
+ "path": "path/{{ .GO_PARAM }}/secret",
1563
+ "field": "{{ .GO_PARAM }}-secret_key",
1564
+ "version": 1,
1565
+ "format": None,
1566
+ },
1567
1567
  ),
1568
1568
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1569
1569
  name="template-param-1",
1570
- secret=dict(
1571
- path="path/appsres03ue1/test-namespace/secret",
1572
- field="secret_key",
1573
- version=1,
1574
- format=None,
1575
- ),
1570
+ secret={
1571
+ "path": "path/appsres03ue1/test-namespace/secret",
1572
+ "field": "secret_key",
1573
+ "version": 1,
1574
+ "format": None,
1575
+ },
1576
1576
  ),
1577
1577
  SaasResourceTemplateTargetV2_SaasSecretParametersV1(
1578
1578
  name="template-param-2",
1579
- secret=dict(
1580
- path="path/appsres03ue1/test-namespace/secret",
1581
- field="App-SRE-stage-secret_key",
1582
- version=1,
1583
- format=None,
1584
- ),
1579
+ secret={
1580
+ "path": "path/appsres03ue1/test-namespace/secret",
1581
+ "field": "App-SRE-stage-secret_key",
1582
+ "version": 1,
1583
+ "format": None,
1584
+ },
1585
1585
  ),
1586
1586
  ]
@@ -107,7 +107,7 @@ def setup_mocks(
107
107
  mocked_queries.get_app_interface_settings.return_value = {}
108
108
  mocked_queries.get_app_interface_sql_queries.return_value = [sql_query]
109
109
  mocked_state = create_autospec(State)
110
- mocked_state.ls.return_value = [f"/{k}" for k in state.keys()]
110
+ mocked_state.ls.return_value = [f"/{k}" for k in state]
111
111
  mocked_state.__getitem__.side_effect = lambda x: state[x]
112
112
  mocked_secret_reader = mocker.patch("reconcile.sql_query.SecretReader")
113
113
  mocked_secret_reader.return_value.read_all_secret.return_value = (
@@ -166,23 +166,23 @@ def mock_app_interface_vault_settings(mocker):
166
166
 
167
167
 
168
168
  def secret_reader_side_effect(*args):
169
- if {
169
+ if args[0] == {
170
170
  "path": "aws-account-path",
171
171
  "field": "token",
172
172
  "version": 1,
173
173
  "q_format": "plain",
174
- } == args[0]:
174
+ }:
175
175
  aws_acct_creds = {}
176
176
  aws_acct_creds["aws_access_key_id"] = "key_id"
177
177
  aws_acct_creds["aws_secret_access_key"] = "access_key"
178
178
  return aws_acct_creds
179
179
 
180
- if {
180
+ if args[0] == {
181
181
  "path": "cf-account-path",
182
182
  "field": "key",
183
183
  "version": 1,
184
184
  "q_format": "plain",
185
- } == args[0]:
185
+ }:
186
186
  cf_acct_creds = {}
187
187
  cf_acct_creds["api_token"] = "api_token"
188
188
  cf_acct_creds["account_id"] = "account_id"
@@ -289,8 +289,8 @@ def test_cloudflare_accounts_validation(
289
289
  with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
290
290
  integ.run(True, None, False, 10)
291
291
  assert sample.value.code == 0
292
- assert ["No Cloudflare accounts were detected, nothing to do."] == [
293
- rec.message for rec in caplog.records
292
+ assert [rec.message for rec in caplog.records] == [
293
+ "No Cloudflare accounts were detected, nothing to do."
294
294
  ]
295
295
 
296
296
 
@@ -313,8 +313,8 @@ def test_namespace_validation(
313
313
  with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
314
314
  integ.run(True, None, False, 10)
315
315
  assert sample.value.code == 0
316
- assert ["No namespaces were detected, nothing to do."] == [
317
- rec.message for rec in caplog.records
316
+ assert [rec.message for rec in caplog.records] == [
317
+ "No namespaces were detected, nothing to do."
318
318
  ]
319
319
 
320
320
 
@@ -356,27 +356,27 @@ def test_cloudflare_namespace_validation(
356
356
  with caplog.at_level(logging.INFO), pytest.raises(SystemExit) as sample:
357
357
  integ.run(True, None, False, 10)
358
358
  assert sample.value.code == 0
359
- assert ["No cloudflare namespaces were detected, nothing to do."] == [
360
- rec.message for rec in caplog.records
359
+ assert [rec.message for rec in caplog.records] == [
360
+ "No cloudflare namespaces were detected, nothing to do."
361
361
  ]
362
362
 
363
363
 
364
364
  def custom_ssl_secret_reader_side_effect(*args):
365
365
  """For use of secret_reader inside cloudflare client"""
366
- if {
366
+ if args[0] == {
367
367
  "path": "certificate/secret/cert/path",
368
368
  "field": "certificate.crt",
369
369
  "version": 1,
370
370
  "q_format": "plain",
371
- } == args[0]:
371
+ }:
372
372
  return "----- CERTIFICATE -----"
373
373
 
374
- if {
374
+ if args[0] == {
375
375
  "path": "certificate/secret/cert/path",
376
376
  "field": "certificate.key",
377
377
  "version": 1,
378
378
  "q_format": "plain",
379
- } == args[0]:
379
+ }:
380
380
  return "----- KEY -----"
381
381
 
382
382
 
@@ -426,23 +426,23 @@ def app_interface_settings_cloudflare_and_vault():
426
426
 
427
427
 
428
428
  def secret_reader_side_effect(*args):
429
- if {
429
+ if args[0] == {
430
430
  "path": "some-path",
431
431
  "field": "some-field",
432
432
  "version": None,
433
433
  "q_format": None,
434
- } == args[0]:
434
+ }:
435
435
  aws_acct_creds = {}
436
436
  aws_acct_creds["aws_access_key_id"] = "key_id"
437
437
  aws_acct_creds["aws_secret_access_key"] = "access_key"
438
438
  return aws_acct_creds
439
439
 
440
- if {
440
+ if args[0] == {
441
441
  "path": "creds",
442
442
  "field": "some-field",
443
443
  "version": None,
444
444
  "q_format": None,
445
- } == args[0]:
445
+ }:
446
446
  cf_acct_creds = {}
447
447
  cf_acct_creds["api_token"] = "api_token"
448
448
  cf_acct_creds["account_id"] = "account_id"
@@ -449,22 +449,22 @@ def test_terraform_resources_runner_dry_run(
449
449
 
450
450
  defer = MagicMock()
451
451
 
452
- runner_params = dict(
453
- accounts=[{"name": "a"}],
454
- account_names={"a"},
455
- tf_namespaces=[],
456
- tf=tf,
457
- ts=ts,
458
- secret_reader=secret_reader,
459
- dry_run=True,
460
- enable_deletion=False,
461
- thread_pool_size=10,
462
- internal=None,
463
- use_jump_host=True,
464
- light=False,
465
- vault_output_path="",
466
- defer=defer,
467
- )
452
+ runner_params = {
453
+ "accounts": [{"name": "a"}],
454
+ "account_names": {"a"},
455
+ "tf_namespaces": [],
456
+ "tf": tf,
457
+ "ts": ts,
458
+ "secret_reader": secret_reader,
459
+ "dry_run": True,
460
+ "enable_deletion": False,
461
+ "thread_pool_size": 10,
462
+ "internal": None,
463
+ "use_jump_host": True,
464
+ "light": False,
465
+ "vault_output_path": "",
466
+ "defer": defer,
467
+ }
468
468
 
469
469
  result = integ.runner(**runner_params)
470
470
 
@@ -494,22 +494,22 @@ def test_terraform_resources_runner_no_dry_run(
494
494
  mocked_ob = mocker.patch("reconcile.terraform_resources.ob")
495
495
  mocked_ob.realize_data.return_value = [{"action": "applied"}]
496
496
 
497
- runner_params = dict(
498
- accounts=[{"name": "a"}],
499
- account_names={"a"},
500
- tf_namespaces=[],
501
- tf=tf,
502
- ts=ts,
503
- secret_reader=secret_reader,
504
- dry_run=False,
505
- enable_deletion=False,
506
- thread_pool_size=10,
507
- internal=None,
508
- use_jump_host=True,
509
- light=False,
510
- vault_output_path="",
511
- defer=defer,
512
- )
497
+ runner_params = {
498
+ "accounts": [{"name": "a"}],
499
+ "account_names": {"a"},
500
+ "tf_namespaces": [],
501
+ "tf": tf,
502
+ "ts": ts,
503
+ "secret_reader": secret_reader,
504
+ "dry_run": False,
505
+ "enable_deletion": False,
506
+ "thread_pool_size": 10,
507
+ "internal": None,
508
+ "use_jump_host": True,
509
+ "light": False,
510
+ "vault_output_path": "",
511
+ "defer": defer,
512
+ }
513
513
 
514
514
  result = integ.runner(**runner_params)
515
515
 
@@ -1124,7 +1124,7 @@ def test_duplicate_tgw_connection_names(
1124
1124
  with pytest.raises(integ.ValidationError) as e:
1125
1125
  integ.run(True)
1126
1126
 
1127
- assert "duplicate tgw connection names found" == str(e.value)
1127
+ assert str(e.value) == "duplicate tgw connection names found"
1128
1128
 
1129
1129
 
1130
1130
  def test_missing_vpc_id(
@@ -1149,7 +1149,7 @@ def test_missing_vpc_id(
1149
1149
  with pytest.raises(RuntimeError) as e:
1150
1150
  integ.run(True)
1151
1151
 
1152
- assert "Could not find VPC ID for cluster" == str(e.value)
1152
+ assert str(e.value) == "Could not find VPC ID for cluster"
1153
1153
 
1154
1154
 
1155
1155
  def test_error_in_tf_plan(
@@ -1176,7 +1176,7 @@ def test_error_in_tf_plan(
1176
1176
  with pytest.raises(RuntimeError) as e:
1177
1177
  integ.run(True)
1178
1178
 
1179
- assert "Error running terraform plan" == str(e.value)
1179
+ assert str(e.value) == "Error running terraform plan"
1180
1180
 
1181
1181
 
1182
1182
  def test_disabled_deletions_detected_in_tf_plan(
@@ -1203,7 +1203,7 @@ def test_disabled_deletions_detected_in_tf_plan(
1203
1203
  with pytest.raises(RuntimeError) as e:
1204
1204
  integ.run(True)
1205
1205
 
1206
- assert "Disabled deletions detected running terraform plan" == str(e.value)
1206
+ assert str(e.value) == "Disabled deletions detected running terraform plan"
1207
1207
 
1208
1208
 
1209
1209
  def test_error_in_terraform_apply(
@@ -1230,7 +1230,7 @@ def test_error_in_terraform_apply(
1230
1230
  with pytest.raises(RuntimeError) as e:
1231
1231
  integ.run(False)
1232
1232
 
1233
- assert "Error running terraform apply" == str(e.value)
1233
+ assert str(e.value) == "Error running terraform apply"
1234
1234
 
1235
1235
 
1236
1236
  def test_early_exit_desired_state(
@@ -235,7 +235,7 @@ class SaasFileList:
235
235
  except JsonPathParserError as e:
236
236
  raise ParameterError(
237
237
  f"Invalid jsonpath expression in namespaceSelector '{selector}' :{e}"
238
- )
238
+ ) from None
239
239
 
240
240
  return self._matching_namespaces_cache[selector]
241
241
 
@@ -141,7 +141,7 @@ class UnleashTogglesIntegration(
141
141
  except KeyError:
142
142
  raise ValueError(
143
143
  f"[{instance.name}/{project_id}/{add.name}] Invalid feature toggle type '{add.unleash.q_type}', Possible values are: {', '.join(FeatureToggleType.__members__)}"
144
- )
144
+ ) from None
145
145
  if not dry_run:
146
146
  client.create_feature_toggle(
147
147
  project_id=project_id,
@@ -161,7 +161,7 @@ class UnleashTogglesIntegration(
161
161
  except KeyError:
162
162
  raise ValueError(
163
163
  f"[{instance.name}/{project_id}/{change.current.name}] Invalid feature toggle type '{change.desired.unleash.q_type}', Possible values are: {', '.join(FeatureToggleType.__members__)}"
164
- )
164
+ ) from None
165
165
  if not dry_run:
166
166
  client.update_feature_toggle(
167
167
  project_id=project_id,
@@ -92,7 +92,7 @@ class AggregatedDiffRunner:
92
92
  self.actions = []
93
93
 
94
94
  def register(self, on, action, cond=None):
95
- if on not in self.diff.keys():
95
+ if on not in self.diff:
96
96
  raise Exception(f"Unknown diff key for 'on': {on}")
97
97
  self.actions.append((on, action, cond))
98
98
 
@@ -818,7 +818,7 @@ class AWSApi: # pylint: disable=too-many-public-methods
818
818
  assume_role may be None for ROSA (CCS) clusters where we own the account
819
819
  """
820
820
  required_keys = ["name", "assume_region"]
821
- ok = all(elem in account.keys() for elem in required_keys)
821
+ ok = all(elem in account for elem in required_keys)
822
822
  if not ok:
823
823
  account_name = account.get("name")
824
824
  raise KeyError(f"[{account_name}] account is missing required keys")
@@ -41,7 +41,9 @@ class AWSApiIam:
41
41
  user = self.client.create_user(UserName=user_name)
42
42
  return AWSUser(**user["User"])
43
43
  except self.client.exceptions.EntityAlreadyExistsException:
44
- raise AWSEntityAlreadyExistsException(f"User {user_name} already exists")
44
+ raise AWSEntityAlreadyExistsException(
45
+ f"User {user_name} already exists"
46
+ ) from None
45
47
 
46
48
  def attach_user_policy(self, user_name: str, policy_arn: str) -> None:
47
49
  """Attach a policy to a user."""
@@ -62,4 +64,4 @@ class AWSApiIam:
62
64
  if self.get_account_alias() != account_alias:
63
65
  raise ValueError(
64
66
  "Account alias already exists for another AWS account. Choose another one!"
65
- )
67
+ ) from None
@@ -64,7 +64,7 @@ class AWSApiServiceQuotas:
64
64
  except self.client.exceptions.ResourceAlreadyExistsException:
65
65
  raise AWSResourceAlreadyExistsException(
66
66
  f"Service quota increase request {service_code=}, {quota_code=} already exists."
67
- )
67
+ ) from None
68
68
 
69
69
  def get_service_quota(self, service_code: str, quota_code: str) -> AWSQuota:
70
70
  """Return the current value of the service quota."""
@@ -76,4 +76,4 @@ class AWSApiServiceQuotas:
76
76
  except self.client.exceptions.NoSuchResourceException:
77
77
  raise AWSNoSuchResourceException(
78
78
  f"Service quota {service_code=}, {quota_code=} not found."
79
- )
79
+ ) from None
reconcile/utils/binary.py CHANGED
@@ -42,7 +42,7 @@ def binary_version(binary, version_args, search_regex, expected_versions):
42
42
  f"Could not execute binary '{binary}' "
43
43
  f"for binary version check: {e}"
44
44
  )
45
- raise Exception(msg)
45
+ raise Exception(msg) from e
46
46
 
47
47
  found = False
48
48
  match = None
@@ -1,4 +1,4 @@
1
- from functools import cache
1
+ from functools import lru_cache
2
2
 
3
3
  from reconcile.utils.clusterhealth.providerbase import (
4
4
  ClusterHealth,
@@ -13,9 +13,11 @@ TELEMETER_SOURCE = "telemeter"
13
13
  class TelemeterClusterHealthProvider(ClusterHealthProvider):
14
14
  def __init__(self, querier: PrometheusQuerier):
15
15
  self.querier = querier
16
+ self.cluster_health_for_org = lru_cache(maxsize=None)(
17
+ self._cluster_health_for_org
18
+ )
16
19
 
17
- @cache
18
- def cluster_health_for_org(self, org_id: str) -> dict[str, ClusterHealth]:
20
+ def _cluster_health_for_org(self, org_id: str) -> dict[str, ClusterHealth]:
19
21
  vectors_by_cluster = group_by(
20
22
  self.querier.instant_vector_query(telemeter_alert_query(org_id)),
21
23
  lambda v: v.mandatory_label("_id"),
reconcile/utils/config.py CHANGED
@@ -35,7 +35,7 @@ def read(secret):
35
35
  config = config[t]
36
36
  return config[field]
37
37
  except Exception as e:
38
- raise SecretNotFound(f"key not found in config file {path}: {str(e)}")
38
+ raise SecretNotFound(f"key not found in config file {path}: {str(e)}") from None
39
39
 
40
40
 
41
41
  def read_all(secret):
@@ -47,4 +47,6 @@ def read_all(secret):
47
47
  config = config[t]
48
48
  return config
49
49
  except Exception as e:
50
- raise SecretNotFound(f"secret {path} not found in config file: {str(e)}")
50
+ raise SecretNotFound(
51
+ f"secret {path} not found in config file: {str(e)}"
52
+ ) from None
@@ -39,6 +39,6 @@ def filter(roles: DictsOrRoles | None) -> DictsOrRoles:
39
39
  except ValueError:
40
40
  raise ValueError(
41
41
  f"{key} field is not formatted as YYYY-MM-DD, currently set as {expiration_date}"
42
- )
42
+ ) from None
43
43
 
44
44
  return cast(DictsOrRoles, filtered)
@@ -62,10 +62,7 @@ def get_provision_providers(namespace_info: Mapping[str, Any]) -> set[str]:
62
62
 
63
63
 
64
64
  def managed_external_resources(namespace_info: Mapping[str, Any]) -> bool:
65
- if namespace_info.get("managedExternalResources"):
66
- return True
67
-
68
- return False
65
+ return bool(namespace_info.get("managedExternalResources"))
69
66
 
70
67
 
71
68
  def get_inventory_count_combinations(
@@ -141,7 +138,7 @@ class ResourceValueResolver:
141
138
  resource = self._spec.resource
142
139
 
143
140
  keys_to_add = [
144
- key for key in self._spec.resource.keys() if key not in self._IGNORE_KEYS
141
+ key for key in self._spec.resource if key not in self._IGNORE_KEYS
145
142
  ]
146
143
 
147
144
  defaults_path = resource.get("defaults", None)
@@ -176,7 +173,7 @@ class ResourceValueResolver:
176
173
  values.pop("$schema", None)
177
174
  except anymarkup.AnyMarkupError:
178
175
  e_msg = "Could not parse data. Skipping resource: {}"
179
- raise FetchResourceError(e_msg.format(path))
176
+ raise FetchResourceError(e_msg.format(path)) from None
180
177
  return values
181
178
 
182
179
  @staticmethod
@@ -185,7 +182,7 @@ class ResourceValueResolver:
185
182
  try:
186
183
  raw_values = gqlapi.get_resource(path)
187
184
  except gql.GqlGetResourceError as e:
188
- raise FetchResourceError(str(e))
185
+ raise FetchResourceError(str(e)) from e
189
186
  return raw_values
190
187
 
191
188
  @staticmethod
reconcile/utils/git.py CHANGED
@@ -39,9 +39,7 @@ def has_uncommited_changes() -> bool:
39
39
  result = subprocess.run(
40
40
  cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True
41
41
  )
42
- if result.stdout:
43
- return True
44
- return False
42
+ return bool(result.stdout)
45
43
 
46
44
 
47
45
  def show_uncommited_changes() -> str:
@@ -239,10 +239,7 @@ class GitLabApi: # pylint: disable=too-many-public-methods
239
239
  def get_project_maintainers(
240
240
  self, repo_url: str | None = None, query: dict | None = None
241
241
  ) -> list[str] | None:
242
- if repo_url is None:
243
- project = self.project
244
- else:
245
- project = self.get_project(repo_url)
242
+ project = self.project if repo_url is None else self.get_project(repo_url)
246
243
  if project is None:
247
244
  return None
248
245
  if query:
@@ -181,7 +181,12 @@ class GlitchtipClient(ApiBase):
181
181
  return User(
182
182
  **self._post(
183
183
  f"/api/0/organizations/{organization_slug}/members/",
184
- data={"email": email, "role": role, "teams": []},
184
+ data={
185
+ "email": email,
186
+ "send_invite": False,
187
+ "orgRole": role,
188
+ "teamRoles": [],
189
+ },
185
190
  )
186
191
  )
187
192
 
@@ -194,7 +199,7 @@ class GlitchtipClient(ApiBase):
194
199
  return User(
195
200
  **self._put(
196
201
  f"/api/0/organizations/{organization_slug}/members/{pk}/",
197
- data={"role": role},
202
+ data={"orgRole": role},
198
203
  )
199
204
  )
200
205
 
reconcile/utils/gql.py CHANGED
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import logging
2
3
  import textwrap
3
4
  import threading
@@ -44,10 +45,8 @@ def capture_and_forget(error):
44
45
  :type error: Exception
45
46
  """
46
47
 
47
- try:
48
+ with contextlib.suppress(Exception):
48
49
  capture_exception(error)
49
- except Exception:
50
- pass
51
50
 
52
51
 
53
52
  class GqlApiError(Exception):
@@ -142,11 +141,13 @@ class GqlApi:
142
141
  gql(query), variables, get_execution_result=True
143
142
  ).formatted
144
143
  except requests.exceptions.ConnectionError as e:
145
- raise GqlApiError(f"Could not connect to GraphQL server ({e})")
144
+ raise GqlApiError(f"Could not connect to GraphQL server ({e})") from None
146
145
  except TransportQueryError as e:
147
- raise GqlApiError(f"`error` returned with GraphQL response {e}")
146
+ raise GqlApiError(f"`error` returned with GraphQL response {e}") from None
148
147
  except AssertionError:
149
- raise GqlApiError("`data` field missing from GraphQL response payload")
148
+ raise GqlApiError(
149
+ "`data` field missing from GraphQL response payload"
150
+ ) from None
150
151
  except Exception as e:
151
152
  raise GqlApiError("Unexpected error occurred") from e
152
153
 
@@ -187,7 +188,7 @@ class GqlApi:
187
188
  if q_result:
188
189
  templates = q_result["templates"]
189
190
  except GqlApiError:
190
- raise GqlGetResourceError(path, "Template not found.")
191
+ raise GqlGetResourceError(path, "Template not found.") from None
191
192
 
192
193
  if len(templates) != 1:
193
194
  raise GqlGetResourceError(path, "Expecting one and only one template.")
@@ -212,7 +213,7 @@ class GqlApi:
212
213
  "resources"
213
214
  ]
214
215
  except GqlApiError:
215
- raise GqlGetResourceError(path, "Resource not found.")
216
+ raise GqlGetResourceError(path, "Resource not found.") from None
216
217
 
217
218
  if len(resources) != 1:
218
219
  raise GqlGetResourceError(path, "Expecting one and only one resource.")