qontract-reconcile 0.10.2.dev256__py3-none-any.whl → 0.10.2.dev258__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 (96) hide show
  1. {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/METADATA +1 -1
  2. {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/RECORD +96 -95
  3. reconcile/aus/advanced_upgrade_service.py +1 -1
  4. reconcile/aus/base.py +2 -2
  5. reconcile/aus/version_gates/sts_version_gate_handler.py +2 -2
  6. reconcile/aws_account_manager/reconciler.py +22 -20
  7. reconcile/aws_iam_keys.py +5 -5
  8. reconcile/aws_iam_password_reset.py +5 -5
  9. reconcile/aws_saml_roles/integration.py +5 -5
  10. reconcile/aws_version_sync/integration.py +4 -3
  11. reconcile/cli.py +16 -12
  12. reconcile/closedbox_endpoint_monitoring_base.py +1 -0
  13. reconcile/database_access_manager.py +4 -4
  14. reconcile/dynatrace_token_provider/integration.py +2 -2
  15. reconcile/external_resources/manager.py +2 -2
  16. reconcile/external_resources/model.py +1 -1
  17. reconcile/external_resources/secrets_sync.py +2 -2
  18. reconcile/gabi_authorized_users.py +3 -3
  19. reconcile/github_org.py +2 -2
  20. reconcile/gitlab_housekeeping.py +1 -1
  21. reconcile/gitlab_mr_sqs_consumer.py +1 -1
  22. reconcile/glitchtip/integration.py +2 -2
  23. reconcile/jenkins_worker_fleets.py +5 -5
  24. reconcile/ldap_groups/integration.py +3 -3
  25. reconcile/ocm_clusters.py +2 -2
  26. reconcile/ocm_internal_notifications/integration.py +2 -2
  27. reconcile/ocm_labels/integration.py +3 -2
  28. reconcile/openshift_base.py +12 -11
  29. reconcile/openshift_cluster_bots.py +2 -2
  30. reconcile/openshift_resources_base.py +3 -3
  31. reconcile/openshift_rhcs_certs.py +2 -2
  32. reconcile/openshift_saas_deploy.py +1 -1
  33. reconcile/quay_membership.py +4 -4
  34. reconcile/rhidp/common.py +3 -2
  35. reconcile/run_integration.py +7 -4
  36. reconcile/saas_auto_promotions_manager/dependencies.py +95 -0
  37. reconcile/saas_auto_promotions_manager/integration.py +85 -165
  38. reconcile/skupper_network/integration.py +3 -3
  39. reconcile/slack_usergroups.py +4 -4
  40. reconcile/status_board.py +3 -3
  41. reconcile/terraform_cloudflare_dns.py +5 -5
  42. reconcile/terraform_cloudflare_users.py +15 -17
  43. reconcile/terraform_resources.py +6 -6
  44. reconcile/terraform_vpc_peerings.py +9 -9
  45. reconcile/unleash_feature_toggles/integration.py +1 -1
  46. reconcile/utils/aggregated_list.py +2 -2
  47. reconcile/utils/aws_api_typed/iam.py +2 -2
  48. reconcile/utils/aws_api_typed/organization.py +4 -4
  49. reconcile/utils/aws_api_typed/service_quotas.py +4 -4
  50. reconcile/utils/aws_api_typed/support.py +9 -9
  51. reconcile/utils/aws_helper.py +1 -1
  52. reconcile/utils/config.py +8 -4
  53. reconcile/utils/deadmanssnitch_api.py +2 -4
  54. reconcile/utils/glitchtip/models.py +18 -12
  55. reconcile/utils/gql.py +4 -4
  56. reconcile/utils/internal_groups/client.py +2 -2
  57. reconcile/utils/jinja2/utils.py +7 -3
  58. reconcile/utils/jjb_client.py +2 -2
  59. reconcile/utils/models.py +2 -1
  60. reconcile/utils/mr/__init__.py +3 -3
  61. reconcile/utils/mr/app_interface_reporter.py +2 -2
  62. reconcile/utils/mr/aws_access.py +5 -2
  63. reconcile/utils/mr/base.py +3 -3
  64. reconcile/utils/mr/user_maintenance.py +1 -1
  65. reconcile/utils/oc.py +11 -11
  66. reconcile/utils/oc_connection_parameters.py +4 -4
  67. reconcile/utils/ocm/base.py +3 -3
  68. reconcile/utils/ocm/products.py +8 -8
  69. reconcile/utils/ocm/search_filters.py +2 -2
  70. reconcile/utils/openshift_resource.py +21 -18
  71. reconcile/utils/pagerduty_api.py +5 -5
  72. reconcile/utils/quay_api.py +2 -2
  73. reconcile/utils/rosa/rosa_cli.py +1 -1
  74. reconcile/utils/rosa/session.py +2 -2
  75. reconcile/utils/runtime/desired_state_diff.py +7 -7
  76. reconcile/utils/saasherder/interfaces.py +1 -0
  77. reconcile/utils/saasherder/models.py +1 -1
  78. reconcile/utils/saasherder/saasherder.py +1 -1
  79. reconcile/utils/secret_reader.py +20 -20
  80. reconcile/utils/slack_api.py +5 -5
  81. reconcile/utils/slo_document_manager.py +6 -6
  82. reconcile/utils/state.py +8 -8
  83. reconcile/utils/terraform_client.py +3 -3
  84. reconcile/utils/terrascript/cloudflare_client.py +2 -2
  85. reconcile/utils/terrascript/cloudflare_resources.py +1 -0
  86. reconcile/utils/terrascript_aws_client.py +12 -11
  87. reconcile/utils/vault.py +22 -22
  88. reconcile/vault_replication.py +15 -15
  89. tools/cli_commands/erv2.py +3 -2
  90. tools/cli_commands/gpg_encrypt.py +9 -9
  91. tools/cli_commands/systems_and_tools.py +1 -1
  92. tools/qontract_cli.py +13 -14
  93. tools/saas_promotion_state/saas_promotion_state.py +4 -4
  94. tools/template_validation.py +5 -5
  95. {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/WHEEL +0 -0
  96. {qontract_reconcile-0.10.2.dev256.dist-info → qontract_reconcile-0.10.2.dev258.dist-info}/entry_points.txt +0 -0
@@ -44,8 +44,8 @@ from reconcile.utils.terraform_client import TerraformClient
44
44
  from reconcile.utils.terrascript.cloudflare_client import (
45
45
  DEFAULT_PROVIDER_RPS,
46
46
  DNSZoneShardingStrategy,
47
- IntegrationUndefined,
48
- InvalidTerraformState,
47
+ IntegrationUndefinedError,
48
+ InvalidTerraformStateError,
49
49
  TerrascriptCloudflareClientFactory,
50
50
  )
51
51
  from reconcile.utils.terrascript.models import (
@@ -312,9 +312,9 @@ def build_cloudflare_terraform_config_collection(
312
312
  integrations = tf_state.integrations
313
313
 
314
314
  if not bucket:
315
- raise InvalidTerraformState("Terraform state must have bucket defined")
315
+ raise InvalidTerraformStateError("Terraform state must have bucket defined")
316
316
  if not region:
317
- raise InvalidTerraformState("Terraform state must have region defined")
317
+ raise InvalidTerraformStateError("Terraform state must have region defined")
318
318
 
319
319
  integration = None
320
320
  for i in integrations:
@@ -323,7 +323,7 @@ def build_cloudflare_terraform_config_collection(
323
323
  break
324
324
 
325
325
  if not integration:
326
- raise IntegrationUndefined(
326
+ raise IntegrationUndefinedError(
327
327
  f"Must declare integration name under Terraform state in {zone.account.terraform_state_account.name} AWS account for {cf_account.name} Cloudflare account in app-interface"
328
328
  )
329
329
 
@@ -35,15 +35,15 @@ from reconcile.utils.terraform.config_client import (
35
35
  TerraformConfigClientCollection,
36
36
  )
37
37
  from reconcile.utils.terraform_client import (
38
- TerraformApplyFailed,
38
+ TerraformApplyFailedError,
39
39
  TerraformClient,
40
- TerraformDeletionDetected,
41
- TerraformPlanFailed,
40
+ TerraformDeletionDetectedError,
41
+ TerraformPlanFailedError,
42
42
  )
43
43
  from reconcile.utils.terrascript.cloudflare_client import (
44
44
  AccountShardingStrategy,
45
- IntegrationUndefined,
46
- InvalidTerraformState,
45
+ IntegrationUndefinedError,
46
+ InvalidTerraformStateError,
47
47
  TerrascriptCloudflareClientFactory,
48
48
  )
49
49
  from reconcile.utils.terrascript.models import (
@@ -153,9 +153,6 @@ class TerraformCloudflareUsers(
153
153
  ]
154
154
 
155
155
  self._run_terraform(
156
- QONTRACT_INTEGRATION,
157
- QONTRACT_INTEGRATION_VERSION,
158
- QONTRACT_TF_PREFIX,
159
156
  dry_run,
160
157
  enable_deletion,
161
158
  thread_pool_size,
@@ -165,9 +162,6 @@ class TerraformCloudflareUsers(
165
162
 
166
163
  def _run_terraform(
167
164
  self,
168
- QONTRACT_INTEGRATION: str,
169
- QONTRACT_INTEGRATION_VERSION: str,
170
- QONTRACT_TF_PREFIX: str,
171
165
  dry_run: bool,
172
166
  enable_deletion: bool,
173
167
  thread_pool_size: int,
@@ -186,11 +180,11 @@ class TerraformCloudflareUsers(
186
180
  try:
187
181
  disabled_deletions_detected, err = tf.plan(enable_deletion)
188
182
  if err:
189
- raise TerraformPlanFailed(
183
+ raise TerraformPlanFailedError(
190
184
  f"Failed to run terraform plan for integration {QONTRACT_INTEGRATION}"
191
185
  )
192
186
  if disabled_deletions_detected:
193
- raise TerraformDeletionDetected(
187
+ raise TerraformDeletionDetectedError(
194
188
  "Deletions detected but they are disabled"
195
189
  )
196
190
 
@@ -199,7 +193,7 @@ class TerraformCloudflareUsers(
199
193
 
200
194
  err = tf.apply()
201
195
  if err:
202
- raise TerraformApplyFailed(
196
+ raise TerraformApplyFailedError(
203
197
  f"Failed to run terraform apply for integration {QONTRACT_INTEGRATION}"
204
198
  )
205
199
  finally:
@@ -235,9 +229,13 @@ class TerraformCloudflareUsers(
235
229
  integrations = tf_state.integrations
236
230
 
237
231
  if not bucket:
238
- raise InvalidTerraformState("Terraform state must have bucket defined")
232
+ raise InvalidTerraformStateError(
233
+ "Terraform state must have bucket defined"
234
+ )
239
235
  if not region:
240
- raise InvalidTerraformState("Terraform state must have region defined")
236
+ raise InvalidTerraformStateError(
237
+ "Terraform state must have region defined"
238
+ )
241
239
 
242
240
  integration = None
243
241
  for i in integrations:
@@ -246,7 +244,7 @@ class TerraformCloudflareUsers(
246
244
  break
247
245
 
248
246
  if not integration:
249
- raise IntegrationUndefined(
247
+ raise IntegrationUndefinedError(
250
248
  "Must declare integration name under Terraform state in app-interface"
251
249
  )
252
250
 
@@ -200,20 +200,20 @@ def get_aws_accounts(
200
200
  if exclude_accounts and not dry_run:
201
201
  message = "--exclude-accounts is only supported in dry-run mode"
202
202
  logging.error(message)
203
- raise ExcludeAccountsAndDryRunException(message)
203
+ raise ExcludeAccountsAndDryRunError(message)
204
204
 
205
205
  if (exclude_accounts and include_accounts) and any(
206
206
  a in exclude_accounts for a in include_accounts
207
207
  ):
208
208
  message = "Using --exclude-accounts and --account-name with the same account is not allowed"
209
209
  logging.error(message)
210
- raise ExcludeAccountsAndAccountNameException(message)
210
+ raise ExcludeAccountsAndAccountNameError(message)
211
211
 
212
212
  # If we are not running in dry run we don't want to run with more than one account
213
213
  if include_accounts and len(include_accounts) > 1 and not dry_run:
214
214
  message = "Running with multiple accounts is only supported in dry-run mode"
215
215
  logging.error(message)
216
- raise MultipleAccountNamesInDryRunException(message)
216
+ raise MultipleAccountNamesInDryRunError(message)
217
217
 
218
218
  accounts = queries.get_aws_accounts(terraform_state=True)
219
219
 
@@ -345,15 +345,15 @@ def populate_desired_state(
345
345
  )
346
346
 
347
347
 
348
- class ExcludeAccountsAndDryRunException(Exception):
348
+ class ExcludeAccountsAndDryRunError(Exception):
349
349
  pass
350
350
 
351
351
 
352
- class ExcludeAccountsAndAccountNameException(Exception):
352
+ class ExcludeAccountsAndAccountNameError(Exception):
353
353
  pass
354
354
 
355
355
 
356
- class MultipleAccountNamesInDryRunException(Exception):
356
+ class MultipleAccountNamesInDryRunError(Exception):
357
357
  pass
358
358
 
359
359
 
@@ -28,7 +28,7 @@ QONTRACT_INTEGRATION = "terraform_vpc_peerings"
28
28
  QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
29
29
 
30
30
 
31
- class BadTerraformPeeringState(Exception):
31
+ class BadTerraformPeeringStateError(Exception):
32
32
  pass
33
33
 
34
34
 
@@ -122,7 +122,7 @@ def aws_assume_roles_for_cluster_vpc_peering(
122
122
  # accepters peering connection
123
123
  infra_account = accepter_connection["awsInfrastructureManagementAccount"]
124
124
  if infra_account and infra_account["name"] not in allowed_accounts:
125
- raise BadTerraformPeeringState(
125
+ raise BadTerraformPeeringStateError(
126
126
  "[account_not_allowed] "
127
127
  f"account {infra_account['name']} used on the peering accepter of "
128
128
  f"cluster {accepter_cluster['name']} is not listed as a "
@@ -135,7 +135,7 @@ def aws_assume_roles_for_cluster_vpc_peering(
135
135
  infra_account = _get_default_management_account(accepter_cluster)
136
136
 
137
137
  if not infra_account:
138
- raise BadTerraformPeeringState(
138
+ raise BadTerraformPeeringStateError(
139
139
  f"[no_account_available] unable to find infra account "
140
140
  f"for {accepter_cluster['name']} to manage the VPC peering "
141
141
  f"with {requester_cluster['name']}"
@@ -147,7 +147,7 @@ def aws_assume_roles_for_cluster_vpc_peering(
147
147
  infra_account, requester_cluster, ocm, requester_connection.get("assumeRole")
148
148
  )
149
149
  if req_aws is None:
150
- raise BadTerraformPeeringState(
150
+ raise BadTerraformPeeringStateError(
151
151
  f"[assume_role_not_found] unable to find assume role "
152
152
  f"on cluster-vpc-requester for account {infra_account['name']} and "
153
153
  f"cluster {requester_cluster['name']} "
@@ -156,7 +156,7 @@ def aws_assume_roles_for_cluster_vpc_peering(
156
156
  infra_account, accepter_cluster, ocm, accepter_connection.get("assumeRole")
157
157
  )
158
158
  if acc_aws is None:
159
- raise BadTerraformPeeringState(
159
+ raise BadTerraformPeeringStateError(
160
160
  f"[assume_role_not_found] unable to find assume role "
161
161
  f"on cluster-vpc-accepter for account {infra_account['name']} and "
162
162
  f"cluster {accepter_cluster['name']} "
@@ -192,7 +192,7 @@ def build_desired_state_single_cluster(
192
192
  cluster_info, peer_cluster, "cluster-vpc-accepter"
193
193
  )
194
194
  if not peer_info:
195
- raise BadTerraformPeeringState(
195
+ raise BadTerraformPeeringStateError(
196
196
  "[no_matching_peering] could not find a matching peering "
197
197
  f"connection for cluster {cluster_name}, connection "
198
198
  f"{peer_connection_name}"
@@ -297,7 +297,7 @@ def build_desired_state_all_clusters(
297
297
  cluster_info, ocm, awsapi, account_filter
298
298
  )
299
299
  desired_state.extend(items)
300
- except (KeyError, BadTerraformPeeringState, aws_api.MissingARNError):
300
+ except (KeyError, BadTerraformPeeringStateError, aws_api.MissingARNError):
301
301
  logging.exception(f"Failed to get desired state for {cluster}")
302
302
  error = True
303
303
 
@@ -421,7 +421,7 @@ def build_desired_state_vpc_mesh(
421
421
  cluster_info, ocm, awsapi, account_filter
422
422
  )
423
423
  desired_state.extend(items)
424
- except (KeyError, BadTerraformPeeringState, aws_api.MissingARNError):
424
+ except (KeyError, BadTerraformPeeringStateError, aws_api.MissingARNError):
425
425
  logging.exception(f"Unable to create VPC mesh for cluster {cluster}")
426
426
  error = True
427
427
 
@@ -554,7 +554,7 @@ def build_desired_state_vpc(
554
554
  cluster_info, ocm, awsapi, account_filter
555
555
  )
556
556
  desired_state.extend(items)
557
- except (KeyError, BadTerraformPeeringState, aws_api.MissingARNError):
557
+ except (KeyError, BadTerraformPeeringStateError, aws_api.MissingARNError):
558
558
  logging.exception(f"Unable to process {cluster_info['name']}")
559
559
  error = True
560
560
 
@@ -44,7 +44,7 @@ def feature_toggle_equal(c: FeatureToggle, d: FeatureToggleUnleashV1) -> bool:
44
44
  )
45
45
 
46
46
 
47
- class UnleashFeatureToggleException(Exception):
47
+ class UnleashFeatureToggleError(Exception):
48
48
  """Raised when a feature toggle is manually created."""
49
49
 
50
50
 
@@ -7,7 +7,7 @@ Action = Callable[[Any, list[Any]], bool]
7
7
  Cond = Callable[[Any], bool]
8
8
 
9
9
 
10
- class RunnerException(Exception):
10
+ class RunnerError(Exception):
11
11
  pass
12
12
 
13
13
 
@@ -88,7 +88,7 @@ class AggregatedList:
88
88
  def dump(self) -> list[AggregatedItem]:
89
89
  return list(self._dict.values())
90
90
 
91
- def toJSON(self) -> str:
91
+ def to_json(self) -> str:
92
92
  return json.dumps(self.dump(), indent=4)
93
93
 
94
94
  @staticmethod
@@ -20,7 +20,7 @@ class AWSUser(BaseModel):
20
20
  path: str = Field(..., alias="Path")
21
21
 
22
22
 
23
- class AWSEntityAlreadyExistsException(Exception):
23
+ class AWSEntityAlreadyExistsError(Exception):
24
24
  """Raised when the user already exists in IAM."""
25
25
 
26
26
 
@@ -41,7 +41,7 @@ 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(
44
+ raise AWSEntityAlreadyExistsError(
45
45
  f"User {user_name} already exists"
46
46
  ) from None
47
47
 
@@ -61,11 +61,11 @@ class AWSAccount(BaseModel):
61
61
  state: str = Field(..., alias="Status")
62
62
 
63
63
 
64
- class AWSAccountCreationException(Exception):
64
+ class AWSAccountCreationError(Exception):
65
65
  """Exception raised when account creation failed."""
66
66
 
67
67
 
68
- class AWSAccountNotFoundException(Exception):
68
+ class AWSAccountNotFoundError(Exception):
69
69
  """Exception raised when the account cannot be found in the specified OU."""
70
70
 
71
71
 
@@ -102,7 +102,7 @@ class AWSApiOrganizations:
102
102
  )
103
103
  status = AWSAccountStatus(**resp["CreateAccountStatus"])
104
104
  if status.state == "FAILED":
105
- raise AWSAccountCreationException(
105
+ raise AWSAccountCreationError(
106
106
  f"Account creation failed: {status.failure_reason}"
107
107
  )
108
108
  return status
@@ -122,7 +122,7 @@ class AWSApiOrganizations:
122
122
  for p in resp.get("Parents", []):
123
123
  if p["Type"] in {"ORGANIZATIONAL_UNIT", "ROOT"}:
124
124
  return p["Id"]
125
- raise AWSAccountNotFoundException(f"Account {uid} not found!")
125
+ raise AWSAccountNotFoundError(f"Account {uid} not found!")
126
126
 
127
127
  def move_account(self, uid: str, destination_parent_id: str) -> None:
128
128
  """Move an account to a different organizational unit."""
@@ -31,11 +31,11 @@ class AWSQuota(BaseModel):
31
31
  return str(self)
32
32
 
33
33
 
34
- class AWSNoSuchResourceException(Exception):
34
+ class AWSNoSuchResourceError(Exception):
35
35
  """Raised when a resource is not found in a service quotas API call."""
36
36
 
37
37
 
38
- class AWSResourceAlreadyExistsException(Exception):
38
+ class AWSResourceAlreadyExistsError(Exception):
39
39
  """Raised when quota increase request already exists."""
40
40
 
41
41
 
@@ -62,7 +62,7 @@ class AWSApiServiceQuotas:
62
62
  )
63
63
  return AWSRequestedServiceQuotaChange(**req["RequestedQuota"])
64
64
  except self.client.exceptions.ResourceAlreadyExistsException:
65
- raise AWSResourceAlreadyExistsException(
65
+ raise AWSResourceAlreadyExistsError(
66
66
  f"Service quota increase request {service_code=}, {quota_code=} already exists."
67
67
  ) from None
68
68
 
@@ -74,6 +74,6 @@ class AWSApiServiceQuotas:
74
74
  )
75
75
  return AWSQuota(**quota["Quota"])
76
76
  except self.client.exceptions.NoSuchResourceException:
77
- raise AWSNoSuchResourceException(
77
+ raise AWSNoSuchResourceError(
78
78
  f"Service quota {service_code=}, {quota_code=} not found."
79
79
  ) from None
@@ -15,7 +15,7 @@ class AWSCase(BaseModel):
15
15
  status: str
16
16
 
17
17
 
18
- class SUPPORT_PLAN(Enum):
18
+ class SupportPlan(Enum):
19
19
  BASIC = "basic"
20
20
  DEVELOPER = "developer"
21
21
  BUSINESS = "business"
@@ -53,27 +53,27 @@ class AWSApiSupport:
53
53
  case = self.client.describe_cases(caseIdList=[case_id])["cases"][0]
54
54
  return AWSCase(**case)
55
55
 
56
- def get_support_level(self) -> SUPPORT_PLAN:
56
+ def get_support_level(self) -> SupportPlan:
57
57
  """Return the support level of the account."""
58
58
 
59
59
  try:
60
60
  response = self.client.describe_severity_levels(language="en")
61
61
  except self.client.exceptions.ClientError as err:
62
62
  if err.response["Error"]["Code"] == "SubscriptionRequiredException":
63
- return SUPPORT_PLAN.BASIC
63
+ return SupportPlan.BASIC
64
64
  raise err
65
65
 
66
66
  severity_levels = {
67
67
  level["code"].lower() for level in response["severityLevels"]
68
68
  }
69
69
  if "critical" in severity_levels:
70
- return SUPPORT_PLAN.ENTERPRISE
70
+ return SupportPlan.ENTERPRISE
71
71
  if "urgent" in severity_levels:
72
- return SUPPORT_PLAN.BUSINESS
72
+ return SupportPlan.BUSINESS
73
73
  if "high" in severity_levels:
74
- return SUPPORT_PLAN.BUSINESS
74
+ return SupportPlan.BUSINESS
75
75
  if "normal" in severity_levels:
76
- return SUPPORT_PLAN.DEVELOPER
76
+ return SupportPlan.DEVELOPER
77
77
  if "low" in severity_levels:
78
- return SUPPORT_PLAN.DEVELOPER
79
- return SUPPORT_PLAN.BASIC
78
+ return SupportPlan.DEVELOPER
79
+ return SupportPlan.BASIC
@@ -27,7 +27,7 @@ def get_account_uid_from_arn(arn: str) -> str:
27
27
 
28
28
  def get_role_name_from_arn(arn: str) -> str:
29
29
  # arn:aws:iam::12345:role/role-1 --> role-1
30
- return arn.split("/")[-1]
30
+ return arn.split("/")[-1] # noqa: PLC0207
31
31
 
32
32
 
33
33
  def is_aws_managed_resource(arn: str) -> bool:
reconcile/utils/config.py CHANGED
@@ -6,11 +6,11 @@ import toml
6
6
  _config: dict = {}
7
7
 
8
8
 
9
- class ConfigNotFound(Exception):
9
+ class ConfigNotFoundError(Exception):
10
10
  pass
11
11
 
12
12
 
13
- class SecretNotFound(Exception):
13
+ class SecretNotFoundError(Exception):
14
14
  pass
15
15
 
16
16
 
@@ -38,7 +38,9 @@ def read(secret: Mapping[str, Any]) -> str:
38
38
  config = config[t]
39
39
  return config[field]
40
40
  except Exception as e:
41
- raise SecretNotFound(f"key not found in config file {path}: {e!s}") from None
41
+ raise SecretNotFoundError(
42
+ f"key not found in config file {path}: {e!s}"
43
+ ) from None
42
44
 
43
45
 
44
46
  def read_all(secret: Mapping[str, Any]) -> dict:
@@ -50,4 +52,6 @@ def read_all(secret: Mapping[str, Any]) -> dict:
50
52
  config = config[t]
51
53
  return config
52
54
  except Exception as e:
53
- raise SecretNotFound(f"secret {path} not found in config file: {e!s}") from None
55
+ raise SecretNotFoundError(
56
+ f"secret {path} not found in config file: {e!s}"
57
+ ) from None
@@ -11,7 +11,7 @@ BASE_URL = "https://api.deadmanssnitch.com/v1/snitches"
11
11
  REQUEST_TIMEOUT = 60
12
12
 
13
13
 
14
- class DeadManssnitchException(Exception):
14
+ class DeadManssnitchError(Exception):
15
15
  pass
16
16
 
17
17
 
@@ -61,9 +61,7 @@ class DeadMansSnitchApi:
61
61
 
62
62
  def create_snitch(self, payload: dict) -> Snitch:
63
63
  if payload.get("name") is None or payload.get("interval") is None:
64
- raise DeadManssnitchException(
65
- "Invalid payload,name and interval are mandatory"
66
- )
64
+ raise DeadManssnitchError("Invalid payload,name and interval are mandatory")
67
65
  headers = {"Content-Type": "application/json"}
68
66
  logging.debug("Creating new snitch with name:: %s ", payload["name"])
69
67
  response = self.session.post(
@@ -48,8 +48,9 @@ class Team(BaseModel):
48
48
  users: list[User] = []
49
49
 
50
50
  @root_validator(pre=True)
51
- def name_xor_slug_must_be_set( # pylint: disable=no-self-argument
52
- cls, values: MutableMapping[str, Any]
51
+ def name_xor_slug_must_be_set(
52
+ cls, # noqa: N805
53
+ values: MutableMapping[str, Any],
53
54
  ) -> MutableMapping[str, Any]:
54
55
  assert ("name" in values or "slug" in values) and not (
55
56
  "name" in values and "slug" in values
@@ -57,8 +58,9 @@ class Team(BaseModel):
57
58
  return values
58
59
 
59
60
  @root_validator
60
- def slugify( # pylint: disable=no-self-argument
61
- cls, values: MutableMapping[str, Any]
61
+ def slugify(
62
+ cls, # noqa: N805
63
+ values: MutableMapping[str, Any],
62
64
  ) -> MutableMapping[str, Any]:
63
65
  values["slug"] = values.get("slug") or slugify(values.get("name", ""))
64
66
  values["name"] = slugify(values.get("name", "")) or values.get("slug")
@@ -96,8 +98,9 @@ class ProjectAlertRecipient(BaseModel):
96
98
  use_enum_values = True
97
99
 
98
100
  @validator("recipient_type")
99
- def recipient_type_enforce_enum_type( # pylint: disable=no-self-argument
100
- cls, v: str | RecipientType
101
+ def recipient_type_enforce_enum_type(
102
+ cls, # noqa: N805
103
+ v: str | RecipientType,
101
104
  ) -> RecipientType:
102
105
  if isinstance(v, RecipientType):
103
106
  return v
@@ -126,8 +129,9 @@ class ProjectAlert(BaseModel):
126
129
  allow_population_by_field_name = True
127
130
 
128
131
  @root_validator
129
- def empty_name( # pylint: disable=no-self-argument
130
- cls, values: MutableMapping[str, Any]
132
+ def empty_name(
133
+ cls, # noqa: N805
134
+ values: MutableMapping[str, Any],
131
135
  ) -> MutableMapping[str, Any]:
132
136
  # name is an empty string if the alert was created manually because it can't be set via UI
133
137
  # use the pk instead.
@@ -159,8 +163,9 @@ class Project(BaseModel):
159
163
  allow_population_by_field_name = True
160
164
 
161
165
  @root_validator
162
- def slugify( # pylint: disable=no-self-argument
163
- cls, values: MutableMapping[str, Any]
166
+ def slugify(
167
+ cls, # noqa: N805
168
+ values: MutableMapping[str, Any],
164
169
  ) -> MutableMapping[str, Any]:
165
170
  values["slug"] = values.get("slug") or slugify(values["name"])
166
171
  return values
@@ -202,8 +207,9 @@ class Organization(BaseModel):
202
207
  users: list[User] = []
203
208
 
204
209
  @root_validator
205
- def slugify( # pylint: disable=no-self-argument
206
- cls, values: MutableMapping[str, Any]
210
+ def slugify(
211
+ cls, # noqa: N805
212
+ values: MutableMapping[str, Any],
207
213
  ) -> MutableMapping[str, Any]:
208
214
  values["slug"] = values.get("slug") or slugify(values["name"])
209
215
  return values
reconcile/utils/gql.py CHANGED
@@ -53,7 +53,7 @@ class GqlApiError(Exception):
53
53
  pass
54
54
 
55
55
 
56
- class GqlApiIntegrationNotFound(Exception):
56
+ class GqlApiIntegrationNotFoundError(Exception):
57
57
  def __init__(self, integration: str):
58
58
  msg = f"""
59
59
  Integration not found: {integration}
@@ -64,7 +64,7 @@ class GqlApiIntegrationNotFound(Exception):
64
64
  super().__init__(textwrap.dedent(msg).strip())
65
65
 
66
66
 
67
- class GqlApiErrorForbiddenSchema(Exception):
67
+ class GqlApiErrorForbiddenSchemaError(Exception):
68
68
  def __init__(self, schemas: list):
69
69
  msg = f"""
70
70
  Forbidden schemas: {schemas}
@@ -115,7 +115,7 @@ class GqlApi:
115
115
  break
116
116
 
117
117
  if validate_schemas and not self._valid_schemas:
118
- raise GqlApiIntegrationNotFound(int_name)
118
+ raise GqlApiIntegrationNotFoundError(int_name)
119
119
 
120
120
  def _init_gql_client(self) -> Client:
121
121
  req_headers = None
@@ -170,7 +170,7 @@ class GqlApi:
170
170
  schema for schema in query_schemas if schema not in self._valid_schemas
171
171
  ]
172
172
  if forbidden_schemas:
173
- raise GqlApiErrorForbiddenSchema(forbidden_schemas)
173
+ raise GqlApiErrorForbiddenSchemaError(forbidden_schemas)
174
174
 
175
175
  # This is to appease mypy. This exception won't be thrown as this condition
176
176
  # is already handled above with AssertionError
@@ -17,7 +17,7 @@ from reconcile.utils.internal_groups.models import Group
17
17
  REQUEST_TIMEOUT = 30
18
18
 
19
19
 
20
- class NotFound(Exception):
20
+ class NotFoundError(Exception):
21
21
  """Not found exception."""
22
22
 
23
23
 
@@ -48,7 +48,7 @@ class InternalGroupsApi:
48
48
  resp.raise_for_status()
49
49
  except requests.exceptions.HTTPError as e:
50
50
  if e.response is not None and e.response.status_code == 404:
51
- raise NotFound(e.response.text) from e
51
+ raise NotFoundError(e.response.text) from e
52
52
  raise
53
53
 
54
54
  def __enter__(self) -> Self:
@@ -29,8 +29,12 @@ from reconcile.utils.jinja2.filters import (
29
29
  urlunescape,
30
30
  yaml_to_dict,
31
31
  )
32
- from reconcile.utils.secret_reader import SecretNotFound, SecretReader, SecretReaderBase
33
- from reconcile.utils.vault import SecretFieldNotFound
32
+ from reconcile.utils.secret_reader import (
33
+ SecretNotFoundError,
34
+ SecretReader,
35
+ SecretReaderBase,
36
+ )
37
+ from reconcile.utils.vault import SecretFieldNotFoundError
34
38
 
35
39
 
36
40
  class Jinja2TemplateError(Exception):
@@ -209,7 +213,7 @@ def lookup_secret(
209
213
  if not secret_reader:
210
214
  secret_reader = SecretReader(settings)
211
215
  return secret_reader.read(secret)
212
- except (SecretNotFound, SecretFieldNotFound) as e:
216
+ except (SecretNotFoundError, SecretFieldNotFoundError) as e:
213
217
  if allow_not_found:
214
218
  return None
215
219
  raise FetchSecretError(e) from None
@@ -7,7 +7,7 @@ import re
7
7
  import shutil
8
8
  import subprocess
9
9
  import tempfile
10
- import xml.etree.ElementTree as et
10
+ import xml.etree.ElementTree as ET
11
11
  from os import path
12
12
  from subprocess import (
13
13
  PIPE,
@@ -192,7 +192,7 @@ class JJB: # pylint: disable=too-many-public-methods
192
192
  name = "/".join(items)
193
193
  raise ValueError(f"Invalid job name contains '/' in {instance}: {name}")
194
194
  item = items[0]
195
- item_type = et.parse(f).getroot().tag
195
+ item_type = ET.parse(f).getroot().tag
196
196
  item_type = item_type.replace("hudson.model.ListView", "view")
197
197
  item_type = item_type.replace("project", "job")
198
198
  logging.info([action, item_type, instance, item])
reconcile/utils/models.py CHANGED
@@ -1,3 +1,4 @@
1
+ from collections import UserList
1
2
  from collections.abc import (
2
3
  Callable,
3
4
  Generator,
@@ -81,7 +82,7 @@ def data_default_none(
81
82
  return data
82
83
 
83
84
 
84
- class CSV(list[str]):
85
+ class CSV(UserList[str]):
85
86
  """
86
87
  A pydantic custom type that converts a CSV into a list of strings. It
87
88
  also supports basic validation of length constraints.