qontract-reconcile 0.10.2.dev208__py3-none-any.whl → 0.10.2.dev213__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev208
3
+ Version: 0.10.2.dev213
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -71,7 +71,7 @@ reconcile/openshift_network_policies.py,sha256=p81ShFK1WSEGiWHVURopDpg8YvtA3RE3O
71
71
  reconcile/openshift_prometheus_rules.py,sha256=onowXab248zmHH8SbYDTc1W1bl7JiqRFU1xdTkZyLFg,1332
72
72
  reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
73
73
  reconcile/openshift_resources.py,sha256=I2nO_C37mG3rfyGrd4cGwN3mVseVGuTAHAyhFzLyqF4,1518
74
- reconcile/openshift_resources_base.py,sha256=3HudPdM7EE0HNWUn1eu0O20Ij25fqGisaDBMVvTk1fk,41768
74
+ reconcile/openshift_resources_base.py,sha256=fogdGOnAk-8xK7z9UeLs1rT2IwDs6Q7jSx_iOVAWDLk,42316
75
75
  reconcile/openshift_rhcs_certs.py,sha256=lP0GwKMRl8YBzxrwdbBOxrPqIPYNmu2KkZPGzWKyRVU,9859
76
76
  reconcile/openshift_rolebindings.py,sha256=9mlJ2FjWUoH-rsjtasreA_hV-K5Z_YR00qR_RR60OZM,6555
77
77
  reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
@@ -767,9 +767,9 @@ reconcile/utils/runtime/meta.py,sha256=dWdKS9eHVuowFkTK4lgXJ723vS1y9giOMzePUKnHn
767
767
  reconcile/utils/runtime/runner.py,sha256=I30KRrX1UQbHc_Ir1cIZX3OfNSdoHKdnDSPAEB69Ilk,7944
768
768
  reconcile/utils/runtime/sharding.py,sha256=r0ieUtNed7NvknSw6qQrCkKpVXE1shuHGnfFcnpA_k4,16142
769
769
  reconcile/utils/saasherder/__init__.py,sha256=3U8plqMAPRE1kjwZ5YnIsYsggTf4_gS7flRUEuXVBAs,343
770
- reconcile/utils/saasherder/interfaces.py,sha256=NEYQspYfyWQhBeJyNCqSFbixi1A4wRVGB7FeNM5BDCk,9141
771
- reconcile/utils/saasherder/models.py,sha256=JaOz_DEtudJZhiDe90kaBlJkppFufn81V92oK9PHYx0,10208
772
- reconcile/utils/saasherder/saasherder.py,sha256=uQovEpcnZXpJlG_tBUY0X1v5obYMH55vc5cSvi3eZjU,86862
770
+ reconcile/utils/saasherder/interfaces.py,sha256=nbGVLiIXJvOtd5ZfKsP3bfrFbMpdQ02D0cTTM9rrED0,9286
771
+ reconcile/utils/saasherder/models.py,sha256=MSKaC65_bXSxKvhCibRH5K1DNppLPbw5w7_6VrjCCFU,11018
772
+ reconcile/utils/saasherder/saasherder.py,sha256=W9nmQyULr4Jx9VAMwFyhULbKo5WRP9nSieOnpO5UxKQ,90224
773
773
  reconcile/utils/terraform/__init__.py,sha256=zNbiyTWo35AT1sFTElL2j_AA0jJ_yWE_bfFn-nD2xik,250
774
774
  reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79evfV_8,164
775
775
  reconcile/utils/terraform/config_client.py,sha256=gRL1rQ0AqvShei_rcGqC3HDYGskOFKE1nPrJyJE9yno,4676
@@ -815,7 +815,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
815
815
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
816
816
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
817
817
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
818
- qontract_reconcile-0.10.2.dev208.dist-info/METADATA,sha256=aHM0xspqvI1keKkT4Wm4ydHb1j9QL-QknaweeU7D6L0,24555
819
- qontract_reconcile-0.10.2.dev208.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
820
- qontract_reconcile-0.10.2.dev208.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
821
- qontract_reconcile-0.10.2.dev208.dist-info/RECORD,,
818
+ qontract_reconcile-0.10.2.dev213.dist-info/METADATA,sha256=KF0B_KMRSL7FeDdMZr1P7osgPw5qyZm10v6Pe90T4oA,24555
819
+ qontract_reconcile-0.10.2.dev213.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
820
+ qontract_reconcile-0.10.2.dev213.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
821
+ qontract_reconcile-0.10.2.dev213.dist-info/RECORD,,
@@ -667,6 +667,15 @@ def fetch_current_state(
667
667
  openshift_resource = OR(
668
668
  item, QONTRACT_INTEGRATION, QONTRACT_INTEGRATION_VERSION
669
669
  )
670
+ labels = openshift_resource.body.get("metadata", {}).get("labels", {})
671
+ # Skip resources managed by ArgoCD
672
+ # This uses the Kubernetes recommended label 'app.kubernetes.io/part-of=argocd'
673
+ # https://argo-cd.readthedocs.io/en/stable/user-guide/resource_tracking/
674
+ if labels.get("app.kubernetes.io/part-of") == "argocd":
675
+ _locked_info_log(
676
+ f"Skipping {openshift_resource.kind} {openshift_resource.name} in current state because it is managed by ArgoCD"
677
+ )
678
+ continue
670
679
  ri.add_current(
671
680
  cluster,
672
681
  namespace,
@@ -698,7 +707,6 @@ def fetch_desired_state(
698
707
  msg = f"[{cluster}/{namespace}] {e!s}"
699
708
  _locked_error_log(msg)
700
709
  return
701
-
702
710
  # add to inventory
703
711
  try:
704
712
  ri.add_desired_resource(
@@ -7,6 +7,7 @@ from typing import (
7
7
  runtime_checkable,
8
8
  )
9
9
 
10
+ from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
10
11
  from reconcile.utils import oc_connection_parameters
11
12
  from reconcile.utils.secret_reader import HasSecret
12
13
 
@@ -326,6 +327,9 @@ class SaasResourceTemplateTarget(HasParameters, HasSecretParameters, Protocol):
326
327
  @property
327
328
  def images(self) -> Sequence[SaasResourceTemplateTargetImage] | None: ...
328
329
 
330
+ @property
331
+ def slos(self) -> list[SLODocument] | None: ...
332
+
329
333
  def uid(
330
334
  self, parent_saas_file_name: str, parent_resource_template_name: str
331
335
  ) -> str: ...
@@ -12,6 +12,9 @@ from pydantic import (
12
12
  Field,
13
13
  )
14
14
 
15
+ from reconcile.gql_definitions.fragments.saas_slo_document import (
16
+ SLODocument,
17
+ )
15
18
  from reconcile.utils.oc_connection_parameters import Cluster
16
19
  from reconcile.utils.saasherder.interfaces import (
17
20
  HasParameters,
@@ -66,8 +69,18 @@ class TriggerSpecBase:
66
69
  raise NotImplementedError("implement this function in inheriting classes")
67
70
 
68
71
 
72
+ @dataclass(frozen=True)
73
+ class SLOKey:
74
+ slo_document_name: str
75
+ namespace_name: str
76
+ cluster_name: str
77
+
78
+
69
79
  @dataclass
70
80
  class TriggerSpecConfig(TriggerSpecBase):
81
+ resource_template_url: str
82
+ target_ref: str
83
+ slos: list[SLODocument] | None = None
71
84
  target_name: str | None = None
72
85
  reason: str | None = None
73
86
 
@@ -81,6 +94,19 @@ class TriggerSpecConfig(TriggerSpecBase):
81
94
  key += f"/{self.target_name}"
82
95
  return key
83
96
 
97
+ def extract_slo_keys(self) -> list[SLOKey]:
98
+ return [
99
+ SLOKey(
100
+ slo_document_name=slo_document.name,
101
+ namespace_name=namespace.namespace.name,
102
+ cluster_name=namespace.namespace.cluster.name,
103
+ )
104
+ for slo_document in self.slos or []
105
+ for namespace in slo_document.namespaces
106
+ if namespace.namespace.name == self.namespace_name
107
+ and namespace.namespace.cluster.name == self.cluster_name
108
+ ]
109
+
84
110
 
85
111
  @dataclass
86
112
  class TriggerSpecMovingCommit(TriggerSpecBase):
@@ -71,6 +71,7 @@ from reconcile.utils.saasherder.models import (
71
71
  ImageAuth,
72
72
  Namespace,
73
73
  Promotion,
74
+ SLOKey,
74
75
  TargetSpec,
75
76
  TriggerSpecConfig,
76
77
  TriggerSpecContainerImage,
@@ -81,6 +82,7 @@ from reconcile.utils.saasherder.models import (
81
82
  UpstreamJob,
82
83
  )
83
84
  from reconcile.utils.secret_reader import SecretReaderBase
85
+ from reconcile.utils.slo_document_manager import SLODetails, SLODocumentManager
84
86
  from reconcile.utils.state import State
85
87
  from reconcile.utils.vcs import VCS
86
88
 
@@ -1040,19 +1042,19 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1040
1042
  return channel_map
1041
1043
 
1042
1044
  def _collect_blocked_versions(self) -> dict[str, set[str]]:
1043
- blocked_versions: dict[str, set[str]] = {}
1045
+ blocked_versions: dict[str, set[str]] = defaultdict(set[str])
1044
1046
  for saas_file in self.saas_files:
1045
1047
  for cc in saas_file.app.code_components or []:
1046
1048
  for v in cc.blocked_versions or []:
1047
- blocked_versions.setdefault(cc.url, set()).add(v)
1049
+ blocked_versions[cc.url].add(v)
1048
1050
  return blocked_versions
1049
1051
 
1050
1052
  def _collect_hotfix_versions(self) -> dict[str, set[str]]:
1051
- hotfix_versions: dict[str, set[str]] = {}
1053
+ hotfix_versions: dict[str, set[str]] = defaultdict(set[str])
1052
1054
  for saas_file in self.saas_files:
1053
1055
  for cc in saas_file.app.code_components or []:
1054
1056
  for v in cc.hotfix_versions or []:
1055
- hotfix_versions.setdefault(cc.url, set()).add(v)
1057
+ hotfix_versions[cc.url].add(v)
1056
1058
  return hotfix_versions
1057
1059
 
1058
1060
  @staticmethod
@@ -1273,6 +1275,8 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1273
1275
  cluster_name=target.namespace.cluster.name,
1274
1276
  namespace_name=target.namespace.name,
1275
1277
  target_name=target.name,
1278
+ resource_template_url=rt.url,
1279
+ target_ref=target.ref,
1276
1280
  state_content=None,
1277
1281
  ).state_key
1278
1282
  digest = SaasHerder.get_target_config_hash(
@@ -1691,7 +1695,82 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1691
1695
  results = threaded.run(
1692
1696
  self.get_configs_diff_saas_file, self.saas_files, self.thread_pool_size
1693
1697
  )
1694
- return list(itertools.chain.from_iterable(results))
1698
+ trigger_config_spec_list = list(itertools.chain.from_iterable(results))
1699
+ return self.filter_slo_breached_triggers(trigger_config_spec_list)
1700
+
1701
+ def filter_slo_breached_triggers(
1702
+ self, trigger_config_spec_list: list[TriggerSpecConfig]
1703
+ ) -> list[TriggerSpecConfig]:
1704
+ trigger_config_specs_to_validate: list[TriggerSpecConfig] = [
1705
+ trigger_config_spec
1706
+ for trigger_config_spec in trigger_config_spec_list
1707
+ if trigger_config_spec.slos
1708
+ and trigger_config_spec.target_ref
1709
+ not in self.hotfix_versions[trigger_config_spec.resource_template_url]
1710
+ ]
1711
+ if not trigger_config_specs_to_validate:
1712
+ return trigger_config_spec_list
1713
+
1714
+ slo_documents = [
1715
+ slo
1716
+ for trigger_spec in trigger_config_specs_to_validate
1717
+ for slo in trigger_spec.slos or []
1718
+ ]
1719
+ slo_document_manager = SLODocumentManager(
1720
+ slo_documents=slo_documents,
1721
+ thread_pool_size=self.thread_pool_size,
1722
+ secret_reader=self.secret_reader,
1723
+ )
1724
+ breached_slos = slo_document_manager.get_breached_slos()
1725
+ if not breached_slos:
1726
+ return trigger_config_spec_list
1727
+
1728
+ breached_slos_map = self.make_breached_slos_map(breached_slos)
1729
+ valid_trigger_config_specs = [
1730
+ trigger_config_spec
1731
+ for trigger_config_spec in trigger_config_spec_list
1732
+ if not self.has_breached_slos(trigger_config_spec, breached_slos_map)
1733
+ ]
1734
+ return valid_trigger_config_specs
1735
+
1736
+ @staticmethod
1737
+ def make_breached_slos_map(
1738
+ breached_slos: list[SLODetails],
1739
+ ) -> dict[SLOKey, list[SLODetails]]:
1740
+ breached_slos_map: dict[SLOKey, list[SLODetails]] = defaultdict(
1741
+ list[SLODetails]
1742
+ )
1743
+ for breached_slo in breached_slos:
1744
+ breached_slos_map[
1745
+ SLOKey(
1746
+ slo_document_name=breached_slo.slo_document_name,
1747
+ namespace_name=breached_slo.namespace_name,
1748
+ cluster_name=breached_slo.cluster_name,
1749
+ )
1750
+ ].append(breached_slo)
1751
+ return breached_slos_map
1752
+
1753
+ @staticmethod
1754
+ def has_breached_slos(
1755
+ trigger_spec: TriggerSpecConfig,
1756
+ breached_slo_map: dict[SLOKey, list[SLODetails]],
1757
+ ) -> bool:
1758
+ matching_slo_keys = [
1759
+ slo_key
1760
+ for slo_key in trigger_spec.extract_slo_keys()
1761
+ if slo_key in breached_slo_map
1762
+ ]
1763
+ if not matching_slo_keys:
1764
+ return False
1765
+ logging.info(
1766
+ f"Skipping target from saas file {trigger_spec.saas_file_name} due to following breached SLOs."
1767
+ )
1768
+ for matching_key in matching_slo_keys:
1769
+ for breached_slo in breached_slo_map[matching_key]:
1770
+ logging.info(
1771
+ f"SLO: {breached_slo.slo.name} of document {breached_slo.slo_document_name} is breached. Current value: {breached_slo.current_slo_value} Expected: {breached_slo.slo.slo_target}"
1772
+ )
1773
+ return True
1695
1774
 
1696
1775
  @staticmethod
1697
1776
  def remove_none_values(d: dict[Any, Any] | None) -> dict[Any, Any]:
@@ -1725,7 +1804,6 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1725
1804
  dtc = SaasHerder.remove_none_values(trigger_spec.state_content)
1726
1805
  if ctc == dtc:
1727
1806
  continue
1728
-
1729
1807
  if self.include_trigger_trace:
1730
1808
  trigger_spec.reason = f"{self.repo_url}/commit/{RunningState().commit}"
1731
1809
  # For now we count every saas config change as an auto-promotion
@@ -1820,6 +1898,9 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1820
1898
  namespace_name=target.namespace.name,
1821
1899
  target_name=target.name,
1822
1900
  state_content=serializable_target_config,
1901
+ resource_template_url=rt.url,
1902
+ target_ref=target.ref,
1903
+ slos=target.slos or None,
1823
1904
  )
1824
1905
  configs[trigger_spec.state_key] = trigger_spec
1825
1906