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
@@ -39,7 +39,7 @@ from reconcile.utils.oc import (
39
39
  OCLogMsg,
40
40
  PrimaryClusterIPCanNotBeUnsetError,
41
41
  RequestEntityTooLargeError,
42
- StatefulSetUpdateForbidden,
42
+ StatefulSetUpdateForbiddenError,
43
43
  StatusCodeError,
44
44
  UnsupportedMediaTypeError,
45
45
  )
@@ -52,13 +52,19 @@ from reconcile.utils.three_way_diff_strategy import three_way_diff_using_hash
52
52
 
53
53
  ACTION_APPLIED = "applied"
54
54
  ACTION_DELETED = "deleted"
55
+ AUTH_METHOD_USER_KEY = {
56
+ "github-org": "github_username",
57
+ "github-org-team": "github_username",
58
+ "oidc": "org_username",
59
+ "rhidp": "org_username",
60
+ }
55
61
 
56
62
 
57
63
  class ValidationError(Exception):
58
64
  pass
59
65
 
60
66
 
61
- class ValidationErrorJobFailed(Exception):
67
+ class ValidationErrorJobFailedError(Exception):
62
68
  pass
63
69
 
64
70
 
@@ -498,7 +504,7 @@ def apply(
498
504
 
499
505
  oc.delete(namespace=namespace, kind=resource_type, name=resource.name)
500
506
  oc.apply(namespace=namespace, resource=annotated)
501
- except StatefulSetUpdateForbidden:
507
+ except StatefulSetUpdateForbiddenError:
502
508
  if resource_type != "StatefulSet":
503
509
  raise
504
510
 
@@ -1228,7 +1234,7 @@ def validate_realized_data(actions: Iterable[dict[str, str]], oc_map: ClusterMap
1228
1234
  for c in conditions:
1229
1235
  if c.get("type") == "Failed":
1230
1236
  msg = f"{name}: {c.get('reason')}"
1231
- raise ValidationErrorJobFailed(msg)
1237
+ raise ValidationErrorJobFailedError(msg)
1232
1238
  raise ValidationError(name)
1233
1239
  elif kind == "ClowdApp":
1234
1240
  deployments = status.get("deployments")
@@ -1256,7 +1262,7 @@ def validate_realized_data(actions: Iterable[dict[str, str]], oc_map: ClusterMap
1256
1262
  if job_state == "Failed":
1257
1263
  failed_jobs.append(job_name)
1258
1264
  if failed_jobs:
1259
- raise ValidationErrorJobFailed(
1265
+ raise ValidationErrorJobFailedError(
1260
1266
  f"CJI {name} failed jobs: {failed_jobs}"
1261
1267
  )
1262
1268
  else:
@@ -1413,12 +1419,7 @@ def determine_user_keys_for_access(
1413
1419
  enforced_user_keys: list[str] | None = None,
1414
1420
  ) -> list[str]:
1415
1421
  """Return user keys based on enabled cluster authentication methods."""
1416
- AUTH_METHOD_USER_KEY = {
1417
- "github-org": "github_username",
1418
- "github-org-team": "github_username",
1419
- "oidc": "org_username",
1420
- "rhidp": "org_username",
1421
- }
1422
+
1422
1423
  user_keys: list[str] = []
1423
1424
 
1424
1425
  if enforced_user_keys:
@@ -111,7 +111,7 @@ def sa_secret_name(sa: str) -> str:
111
111
  return f"{sa}-token"
112
112
 
113
113
 
114
- class TokenNotReadyException(Exception):
114
+ class TokenNotReadyError(Exception):
115
115
  pass
116
116
 
117
117
 
@@ -120,7 +120,7 @@ class TokenNotReadyException(Exception):
120
120
  def retrieve_token(kubeconfig: str, namespace: str, sa: str) -> str:
121
121
  secret = oc(kubeconfig, namespace, ["get", "secret", sa_secret_name(sa)])
122
122
  if not secret or "token" not in secret.get("data", {}):
123
- raise TokenNotReadyException()
123
+ raise TokenNotReadyError()
124
124
  b64_token = secret["data"]["token"]
125
125
  return base64.b64decode(b64_token).decode()
126
126
 
@@ -66,8 +66,8 @@ from reconcile.utils.secret_reader import SecretReader, SecretReaderBase
66
66
  from reconcile.utils.semver_helper import make_semver
67
67
  from reconcile.utils.sharding import is_in_shard
68
68
  from reconcile.utils.vault import (
69
- SecretVersionIsNone,
70
- SecretVersionNotFound,
69
+ SecretVersionIsNoneError,
70
+ SecretVersionNotFoundError,
71
71
  )
72
72
 
73
73
  # +-----------------------+-------------------------+-------------+
@@ -602,7 +602,7 @@ def fetch_openshift_resource(
602
602
  alertmanager_config_key=alertmanager_config_key,
603
603
  settings=settings,
604
604
  )
605
- except (SecretVersionNotFound, SecretVersionIsNone) as e:
605
+ except (SecretVersionNotFoundError, SecretVersionIsNoneError) as e:
606
606
  raise FetchSecretError(e) from None
607
607
  elif provider == "route":
608
608
  path = resource["resource"]["path"]
@@ -35,7 +35,7 @@ from reconcile.utils.rhcsv2_certs import RhcsV2Cert, generate_cert
35
35
  from reconcile.utils.runtime.integration import DesiredStateShardConfig
36
36
  from reconcile.utils.secret_reader import create_secret_reader
37
37
  from reconcile.utils.semver_helper import make_semver
38
- from reconcile.utils.vault import SecretNotFound, VaultClient
38
+ from reconcile.utils.vault import SecretNotFoundError, VaultClient
39
39
 
40
40
  QONTRACT_INTEGRATION = "openshift-rhcs-certs"
41
41
  QONTRACT_INTEGRATION_VERSION = make_semver(1, 9, 3)
@@ -124,7 +124,7 @@ def get_vault_cert_secret(
124
124
  vault_cert_secret = vault.read_all({ # type: ignore[attr-defined]
125
125
  "path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}"
126
126
  })
127
- except SecretNotFound:
127
+ except SecretNotFoundError:
128
128
  logging.info(
129
129
  f"No existing cert found for cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}''"
130
130
  )
@@ -345,4 +345,4 @@ def run(
345
345
  if image_auth.auth_server:
346
346
  json_file = os.path.join(io_dir, "dockerconfigjson")
347
347
  with open(json_file, "w", encoding="locale") as f:
348
- f.write(json.dumps(image_auth.getDockerConfigJson(), indent=2))
348
+ f.write(json.dumps(image_auth.get_docker_config_json(), indent=2))
@@ -18,9 +18,9 @@ from reconcile.utils import (
18
18
  from reconcile.utils.aggregated_list import (
19
19
  AggregatedDiffRunner,
20
20
  AggregatedList,
21
- RunnerException,
21
+ RunnerError,
22
22
  )
23
- from reconcile.utils.quay_api import QuayTeamNotFoundException
23
+ from reconcile.utils.quay_api import QuayTeamNotFoundError
24
24
 
25
25
  QONTRACT_INTEGRATION = "quay-membership"
26
26
 
@@ -63,7 +63,7 @@ def fetch_current_state(quay_api_store):
63
63
  for team in teams:
64
64
  try:
65
65
  members = quay_api.list_team_members(team)
66
- except QuayTeamNotFoundException:
66
+ except QuayTeamNotFoundError:
67
67
  logging.warning(
68
68
  "Attempted to list members for team %s in "
69
69
  "org %s/%s, but it doesn't exist",
@@ -149,7 +149,7 @@ class RunnerAction:
149
149
  # Ensure all quay org/teams are declared as dependencies in a
150
150
  # `/dependencies/quay-org-1.yml` datafile.
151
151
  if team not in self.quay_api_store[org]["teams"]:
152
- raise RunnerException(
152
+ raise RunnerError(
153
153
  f"Quay team {team} is not defined as a "
154
154
  f"managedTeam in the {org} org."
155
155
  )
reconcile/rhidp/common.py CHANGED
@@ -61,8 +61,9 @@ class ClusterAuth(BaseModel):
61
61
  status: str
62
62
 
63
63
  @root_validator
64
- def name_no_spaces( # pylint: disable=no-self-argument
65
- cls, values: MutableMapping[str, Any]
64
+ def name_no_spaces(
65
+ cls, # noqa: N805
66
+ values: MutableMapping[str, Any],
66
67
  ) -> MutableMapping[str, Any]:
67
68
  values["name"] = values["name"].replace(" ", "-")
68
69
  return values
@@ -65,10 +65,13 @@ HANDLERS = [STREAM_HANDLER]
65
65
  # Messages to the log file
66
66
  if LOG_FILE is not None:
67
67
  FILE_HANDLER = logging.FileHandler(LOG_FILE)
68
- logFileFormat = "%(message)s"
69
- if PREFIX_LOG_LEVEL == "true":
70
- logFileFormat = "[%(levelname)s] %(message)s"
71
- FILE_HANDLER.setFormatter(logging.Formatter(fmt=logFileFormat))
68
+ FILE_HANDLER.setFormatter(
69
+ logging.Formatter(
70
+ fmt="[%(levelname)s] %(message)s"
71
+ if PREFIX_LOG_LEVEL == "true"
72
+ else "%(message)s"
73
+ )
74
+ )
72
75
  HANDLERS.append(FILE_HANDLER) # type: ignore
73
76
 
74
77
  # Setting up the root logger
@@ -0,0 +1,95 @@
1
+ from typing import Self
2
+
3
+ from reconcile.openshift_saas_deploy import (
4
+ QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
5
+ )
6
+ from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION
7
+ from reconcile.saas_auto_promotions_manager.utils.saas_files_inventory import (
8
+ SaasFilesInventory,
9
+ )
10
+ from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
11
+ from reconcile.typed_queries.app_interface_vault_settings import (
12
+ get_app_interface_vault_settings,
13
+ )
14
+ from reconcile.typed_queries.github_orgs import get_github_orgs
15
+ from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
16
+ from reconcile.typed_queries.saas_files import get_saas_files
17
+ from reconcile.utils.promotion_state import PromotionState
18
+ from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
19
+ from reconcile.utils.state import State, init_state
20
+ from reconcile.utils.unleash import get_feature_toggle_state
21
+ from reconcile.utils.vcs import VCS
22
+
23
+
24
+ class Dependencies:
25
+ """
26
+ Dependencies class to hold all the external dependencies (API clients, state, etc.) for SAPM.
27
+ Dependency inversion simplifies setting up tests and centralizes dependency management.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ secret_reader: SecretReaderBase,
33
+ deployment_state: PromotionState,
34
+ vcs: VCS,
35
+ saas_file_inventory: SaasFilesInventory,
36
+ saas_deploy_state: State,
37
+ sapm_state: State,
38
+ ):
39
+ self.secret_reader = secret_reader
40
+ self.deployment_state = deployment_state
41
+ self.vcs = vcs
42
+ self.saas_file_inventory = saas_file_inventory
43
+ self.saas_deploy_state = saas_deploy_state
44
+ self.sapm_state = sapm_state
45
+
46
+ @classmethod
47
+ def create(
48
+ cls,
49
+ dry_run: bool,
50
+ thread_pool_size: int,
51
+ env_name: str | None = None,
52
+ app_name: str | None = None,
53
+ ) -> Self:
54
+ vault_settings = get_app_interface_vault_settings()
55
+ allow_deleting_mrs = get_feature_toggle_state(
56
+ integration_name=f"{QONTRACT_INTEGRATION}-allow-deleting-mrs",
57
+ default=False,
58
+ )
59
+ allow_opening_mrs = get_feature_toggle_state(
60
+ integration_name=f"{QONTRACT_INTEGRATION}-allow-opening-mrs",
61
+ default=False,
62
+ )
63
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
64
+ vcs = VCS(
65
+ secret_reader=secret_reader,
66
+ github_orgs=get_github_orgs(),
67
+ gitlab_instances=get_gitlab_instances(),
68
+ app_interface_repo_url=get_app_interface_repo_url(),
69
+ dry_run=dry_run,
70
+ allow_deleting_mrs=allow_deleting_mrs,
71
+ allow_opening_mrs=allow_opening_mrs,
72
+ )
73
+ saas_files = get_saas_files(env_name=env_name, app_name=app_name)
74
+ saas_inventory = SaasFilesInventory(
75
+ saas_files=saas_files,
76
+ secret_reader=secret_reader,
77
+ thread_pool_size=thread_pool_size,
78
+ )
79
+ saas_deploy_state = init_state(
80
+ integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
81
+ )
82
+ deployment_state = PromotionState(
83
+ state=saas_deploy_state,
84
+ )
85
+ sapm_state = init_state(
86
+ integration=QONTRACT_INTEGRATION, secret_reader=secret_reader
87
+ )
88
+ return cls(
89
+ secret_reader=secret_reader,
90
+ deployment_state=deployment_state,
91
+ vcs=vcs,
92
+ saas_file_inventory=saas_inventory,
93
+ saas_deploy_state=saas_deploy_state,
94
+ sapm_state=sapm_state,
95
+ )
@@ -1,13 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections.abc import Callable
2
4
 
3
5
  from sretoolbox.utils import threaded
4
6
 
5
- from reconcile.openshift_saas_deploy import (
6
- QONTRACT_INTEGRATION as OPENSHIFT_SAAS_DEPLOY,
7
- )
8
- from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import (
9
- Batcher,
10
- )
7
+ from reconcile.saas_auto_promotions_manager.dependencies import Dependencies
8
+ from reconcile.saas_auto_promotions_manager.merge_request_manager.batcher import Batcher
11
9
  from reconcile.saas_auto_promotions_manager.merge_request_manager.merge_request_manager_v2 import (
12
10
  MergeRequestManagerV2,
13
11
  )
@@ -21,189 +19,111 @@ from reconcile.saas_auto_promotions_manager.meta import QONTRACT_INTEGRATION
21
19
  from reconcile.saas_auto_promotions_manager.publisher import Publisher
22
20
  from reconcile.saas_auto_promotions_manager.s3_exporter import S3Exporter
23
21
  from reconcile.saas_auto_promotions_manager.subscriber import Subscriber
24
- from reconcile.saas_auto_promotions_manager.utils.saas_files_inventory import (
25
- SaasFilesInventory,
26
- )
27
- from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
28
- from reconcile.typed_queries.app_interface_vault_settings import (
29
- get_app_interface_vault_settings,
30
- )
31
- from reconcile.typed_queries.github_orgs import get_github_orgs
32
- from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
33
- from reconcile.typed_queries.saas_files import get_saas_files
34
22
  from reconcile.utils.defer import defer
35
- from reconcile.utils.promotion_state import PromotionState
36
- from reconcile.utils.secret_reader import create_secret_reader
37
- from reconcile.utils.state import State, init_state
38
- from reconcile.utils.unleash import get_feature_toggle_state
39
- from reconcile.utils.vcs import VCS
23
+ from reconcile.utils.runtime.integration import (
24
+ PydanticRunParams,
25
+ QontractReconcileIntegration,
26
+ )
40
27
 
41
28
 
42
- class SaasAutoPromotionsManager:
43
- def __init__(
44
- self,
45
- deployment_state: PromotionState,
46
- vcs: VCS,
47
- saas_file_inventory: SaasFilesInventory,
48
- merge_request_manager_v2: MergeRequestManagerV2,
49
- s3_exporter: S3Exporter,
50
- thread_pool_size: int,
29
+ class SaasAutoPromotionsManagerParams(PydanticRunParams):
30
+ thread_pool_size: int
31
+ env_name: str | None
32
+ app_name: str | None
33
+
34
+
35
+ class SaasAutoPromotionsManager(
36
+ QontractReconcileIntegration[SaasAutoPromotionsManagerParams]
37
+ ):
38
+ @classmethod
39
+ def create(
40
+ cls,
51
41
  dry_run: bool,
42
+ thread_pool_size: int,
43
+ env_name: str | None = None,
44
+ app_name: str | None = None,
45
+ ) -> SaasAutoPromotionsManager:
46
+ dependencies = Dependencies.create(
47
+ dry_run=dry_run,
48
+ thread_pool_size=thread_pool_size,
49
+ env_name=env_name,
50
+ app_name=app_name,
51
+ )
52
+ params = SaasAutoPromotionsManagerParams(
53
+ thread_pool_size=thread_pool_size,
54
+ env_name=env_name,
55
+ app_name=app_name,
56
+ )
57
+ return cls(dependencies=dependencies, params=params)
58
+
59
+ def __init__(
60
+ self, dependencies: Dependencies, params: SaasAutoPromotionsManagerParams
52
61
  ):
53
- self._deployment_state = deployment_state
54
- self._vcs = vcs
55
- self._saas_file_inventory = saas_file_inventory
56
- self._merge_request_manager_v2 = merge_request_manager_v2
57
- self._s3_exporter = s3_exporter
58
- self._thread_pool_size = thread_pool_size
59
- self._dry_run = dry_run
62
+ super().__init__(params)
63
+ self._dependencies = dependencies
60
64
 
61
65
  def _fetch_publisher_state(
62
66
  self,
63
67
  publisher: Publisher,
64
68
  ) -> None:
65
69
  publisher.fetch_commit_shas_and_deployment_info(
66
- vcs=self._vcs,
67
- deployment_state=self._deployment_state,
70
+ vcs=self._dependencies.vcs,
71
+ deployment_state=self._dependencies.deployment_state,
68
72
  )
69
73
 
70
- def _fetch_publisher_real_world_states(self) -> None:
74
+ def _fetch_publisher_real_world_states(self, thread_pool_size: int) -> None:
71
75
  threaded.run(
72
76
  self._fetch_publisher_state,
73
- self._saas_file_inventory.publishers,
74
- thread_pool_size=self._thread_pool_size,
77
+ self._dependencies.saas_file_inventory.publishers,
78
+ thread_pool_size=thread_pool_size,
75
79
  )
76
80
 
77
81
  def _compute_desired_subscriber_states(self) -> None:
78
- for subscriber in self._saas_file_inventory.subscribers:
82
+ for subscriber in self._dependencies.saas_file_inventory.subscribers:
79
83
  subscriber.compute_desired_state()
80
84
 
81
85
  def _get_subscribers_with_diff(self) -> list[Subscriber]:
82
- return [s for s in self._saas_file_inventory.subscribers if s.has_diff()]
86
+ return [
87
+ s
88
+ for s in self._dependencies.saas_file_inventory.subscribers
89
+ if s.has_diff()
90
+ ]
83
91
 
84
- def reconcile(self) -> None:
85
- self._deployment_state.cache_commit_shas_from_s3()
86
- self._fetch_publisher_real_world_states()
92
+ def reconcile(self, thread_pool_size: int, dry_run: bool) -> None:
93
+ self._dependencies.deployment_state.cache_commit_shas_from_s3()
94
+ self._fetch_publisher_real_world_states(thread_pool_size)
87
95
  self._compute_desired_subscriber_states()
88
96
  subscribers_with_diff = self._get_subscribers_with_diff()
89
- self._merge_request_manager_v2.reconcile(subscribers=subscribers_with_diff)
90
- self._s3_exporter.export_publisher_data(
91
- publishers=self._saas_file_inventory.publishers
97
+
98
+ mr_parser = MRParser(vcs=self._dependencies.vcs)
99
+ merge_request_manager_v2 = MergeRequestManagerV2(
100
+ vcs=self._dependencies.vcs,
101
+ reconciler=Batcher(),
102
+ mr_parser=mr_parser,
103
+ renderer=Renderer(),
104
+ )
105
+ merge_request_manager_v2.reconcile(subscribers=subscribers_with_diff)
106
+
107
+ s3_exporter = S3Exporter(
108
+ state=self._dependencies.sapm_state,
109
+ dry_run=dry_run,
110
+ )
111
+ s3_exporter.export_publisher_data(
112
+ publishers=self._dependencies.saas_file_inventory.publishers
92
113
  )
93
114
 
115
+ @property
116
+ def name(self) -> str:
117
+ return QONTRACT_INTEGRATION
94
118
 
95
- def init_external_dependencies(
96
- dry_run: bool,
97
- thread_pool_size: int,
98
- env_name: str | None = None,
99
- app_name: str | None = None,
100
- ) -> tuple[
101
- PromotionState,
102
- VCS,
103
- SaasFilesInventory,
104
- MergeRequestManagerV2,
105
- S3Exporter,
106
- State,
107
- State,
108
- ]:
109
- """
110
- Lets initialize everything that involves calls to external dependencies:
111
- - VCS -> Gitlab / Github queries
112
- - SaaSFileInventory -> qontract-server GQL query
113
- - DeploymentState -> S3 queries
114
- - MergeRequestManager -> Managing SAPM MRs with app-interface
115
- """
116
- vault_settings = get_app_interface_vault_settings()
117
- allow_deleting_mrs = get_feature_toggle_state(
118
- integration_name=f"{QONTRACT_INTEGRATION}-allow-deleting-mrs",
119
- default=False,
120
- )
121
- allow_opening_mrs = get_feature_toggle_state(
122
- integration_name=f"{QONTRACT_INTEGRATION}-allow-opening-mrs",
123
- default=False,
124
- )
125
- secret_reader = create_secret_reader(use_vault=vault_settings.vault)
126
- vcs = VCS(
127
- secret_reader=secret_reader,
128
- github_orgs=get_github_orgs(),
129
- gitlab_instances=get_gitlab_instances(),
130
- app_interface_repo_url=get_app_interface_repo_url(),
131
- dry_run=dry_run,
132
- allow_deleting_mrs=allow_deleting_mrs,
133
- allow_opening_mrs=allow_opening_mrs,
134
- )
135
- mr_parser = MRParser(vcs=vcs)
136
- merge_request_manager_v2 = MergeRequestManagerV2(
137
- vcs=vcs,
138
- reconciler=Batcher(),
139
- mr_parser=mr_parser,
140
- renderer=Renderer(),
141
- )
142
- saas_files = get_saas_files(env_name=env_name, app_name=app_name)
143
- saas_inventory = SaasFilesInventory(
144
- saas_files=saas_files,
145
- secret_reader=secret_reader,
146
- thread_pool_size=thread_pool_size,
147
- )
148
- saas_deploy_state = init_state(
149
- integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
150
- )
151
- deployment_state = PromotionState(
152
- state=saas_deploy_state,
153
- )
154
- sapm_state = init_state(
155
- integration=QONTRACT_INTEGRATION, secret_reader=secret_reader
156
- )
157
- s3_exporter = S3Exporter(
158
- state=sapm_state,
159
- dry_run=dry_run,
160
- )
161
- return (
162
- deployment_state,
163
- vcs,
164
- saas_inventory,
165
- merge_request_manager_v2,
166
- s3_exporter,
167
- saas_deploy_state,
168
- sapm_state,
169
- )
170
-
171
-
172
- @defer
173
- def run(
174
- dry_run: bool,
175
- thread_pool_size: int,
176
- env_name: str | None = None,
177
- app_name: str | None = None,
178
- defer: Callable | None = None,
179
- ) -> None:
180
- (
181
- deployment_state,
182
- vcs,
183
- saas_inventory,
184
- merge_request_manager_v2,
185
- s3_exporter,
186
- saas_deploy_state,
187
- sapm_state,
188
- ) = init_external_dependencies(
189
- dry_run=dry_run,
190
- env_name=env_name,
191
- app_name=app_name,
192
- thread_pool_size=thread_pool_size,
193
- )
194
- if defer:
195
- defer(vcs.cleanup)
196
- defer(saas_deploy_state.cleanup)
197
- defer(sapm_state.cleanup)
198
-
199
- integration = SaasAutoPromotionsManager(
200
- deployment_state=deployment_state,
201
- vcs=vcs,
202
- saas_file_inventory=saas_inventory,
203
- merge_request_manager_v2=merge_request_manager_v2,
204
- s3_exporter=s3_exporter,
205
- thread_pool_size=thread_pool_size,
206
- dry_run=dry_run,
207
- )
208
-
209
- integration.reconcile()
119
+ @defer
120
+ def run(
121
+ self,
122
+ dry_run: bool,
123
+ defer: Callable | None = None,
124
+ ) -> None:
125
+ if defer:
126
+ defer(self._dependencies.vcs.cleanup)
127
+ defer(self._dependencies.saas_deploy_state.cleanup)
128
+ defer(self._dependencies.sapm_state.cleanup)
129
+ self.reconcile(thread_pool_size=self.params.thread_pool_size, dry_run=dry_run)
@@ -41,7 +41,7 @@ SITE_CONTROLLER_LABELS = {
41
41
  CONFIG_NAME = "skupper-site"
42
42
 
43
43
 
44
- class SkupperNetworkExcpetion(Exception):
44
+ class SkupperNetworkError(Exception):
45
45
  """Base exception for Skupper Network integration."""
46
46
 
47
47
 
@@ -55,7 +55,7 @@ def load_site_controller_template(
55
55
  resource["content"], undefined=jinja2.StrictUndefined
56
56
  ).render(variables)
57
57
  except jinja2.exceptions.UndefinedError as e:
58
- raise SkupperNetworkExcpetion(
58
+ raise SkupperNetworkError(
59
59
  f"Failed to render template {path}: {e.message}"
60
60
  ) from None
61
61
  return yaml.safe_load(body)
@@ -113,7 +113,7 @@ def compile_skupper_sites(
113
113
  # we don't support skupper installations with just one site
114
114
  for site in network_sites:
115
115
  if site.is_island(network_sites):
116
- raise SkupperNetworkExcpetion(
116
+ raise SkupperNetworkError(
117
117
  f"{site}: Site is not connected to any other skupper site in the network."
118
118
  )
119
119
 
@@ -52,7 +52,7 @@ from reconcile.utils.extended_early_exit import (
52
52
  from reconcile.utils.github_api import GithubRepositoryApi
53
53
  from reconcile.utils.gitlab_api import GitLabApi
54
54
  from reconcile.utils.pagerduty_api import (
55
- PagerDutyApiException,
55
+ PagerDutyApiError,
56
56
  PagerDutyMap,
57
57
  get_pagerduty_map,
58
58
  )
@@ -64,7 +64,7 @@ from reconcile.utils.secret_reader import (
64
64
  from reconcile.utils.slack_api import (
65
65
  SlackApi,
66
66
  SlackApiError,
67
- UsergroupNotFoundException,
67
+ UsergroupNotFoundError,
68
68
  )
69
69
  from reconcile.utils.vcs import VCS
70
70
 
@@ -204,7 +204,7 @@ def get_current_state(
204
204
  continue
205
205
  try:
206
206
  users, channels, description = spec.slack.describe_usergroup(ug)
207
- except UsergroupNotFoundException:
207
+ except UsergroupNotFoundError:
208
208
  continue
209
209
  current_state.setdefault(workspace, {})[ug] = State(
210
210
  workspace=workspace,
@@ -249,7 +249,7 @@ def get_usernames_from_pagerduty(
249
249
  pd = pagerduty_map.get(pagerduty.instance.name)
250
250
  try:
251
251
  pagerduty_names = pd.get_pagerduty_users(pd_resource_type, pd_resource_id)
252
- except PagerDutyApiException as e:
252
+ except PagerDutyApiError as e:
253
253
  logging.error(
254
254
  f"[{usergroup}] PagerDuty API error: {e} "
255
255
  "(hint: check that pagerduty schedule_id/escalation_policy_id is correct)"
reconcile/status_board.py CHANGED
@@ -97,7 +97,7 @@ class Product(AbstractStatusBoard):
97
97
  def update(self, ocm: OCMBaseClient) -> None:
98
98
  err_msg = "Called update on StatusBoardHandler that doesn't have update method"
99
99
  logging.error(err_msg)
100
- raise UpdateNotSupported(err_msg)
100
+ raise UpdateNotSupportedError(err_msg)
101
101
 
102
102
  def delete(self, ocm: OCMBaseClient) -> None:
103
103
  if not self.id:
@@ -133,7 +133,7 @@ class Application(AbstractStatusBoard):
133
133
  def update(self, ocm: OCMBaseClient) -> None:
134
134
  err_msg = "Called update on StatusBoardHandler that doesn't have update method"
135
135
  logging.error(err_msg)
136
- raise UpdateNotSupported(err_msg)
136
+ raise UpdateNotSupportedError(err_msg)
137
137
 
138
138
  def delete(self, ocm: OCMBaseClient) -> None:
139
139
  if not self.id:
@@ -219,7 +219,7 @@ Application.update_forward_refs()
219
219
  Service.update_forward_refs()
220
220
 
221
221
 
222
- class UpdateNotSupported(Exception):
222
+ class UpdateNotSupportedError(Exception):
223
223
  pass
224
224
 
225
225