qontract-reconcile 0.10.2.dev159__py3-none-any.whl → 0.10.2.dev173__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 (35) hide show
  1. {qontract_reconcile-0.10.2.dev159.dist-info → qontract_reconcile-0.10.2.dev173.dist-info}/METADATA +2 -2
  2. {qontract_reconcile-0.10.2.dev159.dist-info → qontract_reconcile-0.10.2.dev173.dist-info}/RECORD +34 -24
  3. reconcile/acs_rbac.py +1 -0
  4. reconcile/aws_cloudwatch_log_retention/integration.py +39 -25
  5. reconcile/cli.py +4 -6
  6. reconcile/dashdotdb_slo.py +45 -156
  7. reconcile/gcp_image_mirror.py +252 -0
  8. reconcile/gitlab_housekeeping.py +1 -1
  9. reconcile/gql_definitions/aws_cloudwatch_log_retention/__init__.py +0 -0
  10. reconcile/gql_definitions/aws_cloudwatch_log_retention/aws_accounts.py +158 -0
  11. reconcile/gql_definitions/common/saas_files.py +49 -0
  12. reconcile/gql_definitions/dashdotdb_slo/slo_documents_query.py +15 -67
  13. reconcile/gql_definitions/fragments/container_image_mirror.py +33 -0
  14. reconcile/gql_definitions/fragments/saas_slo_document.py +82 -0
  15. reconcile/gql_definitions/gcp/__init__.py +0 -0
  16. reconcile/gql_definitions/gcp/gcp_docker_repos.py +128 -0
  17. reconcile/gql_definitions/gcp/gcp_projects.py +77 -0
  18. reconcile/gql_definitions/introspection.json +380 -230
  19. reconcile/quay_mirror.py +3 -42
  20. reconcile/quay_mirror_org.py +3 -2
  21. reconcile/slack_base.py +2 -2
  22. reconcile/typed_queries/aws_cloudwatch_log_retention/aws_accounts.py +12 -0
  23. reconcile/utils/dynatrace/client.py +0 -31
  24. reconcile/utils/quay_mirror.py +42 -0
  25. reconcile/utils/saasherder/interfaces.py +2 -0
  26. reconcile/utils/saasherder/saasherder.py +5 -0
  27. reconcile/utils/slack_api.py +3 -1
  28. reconcile/utils/slo_document_manager.py +278 -0
  29. reconcile/utils/terrascript_aws_client.py +57 -0
  30. tools/{sd_app_sre_alert_report.py → alert_report.py} +1 -1
  31. tools/cli_commands/erv2.py +61 -0
  32. tools/qontract_cli.py +15 -5
  33. reconcile/gcr_mirror.py +0 -278
  34. {qontract_reconcile-0.10.2.dev159.dist-info → qontract_reconcile-0.10.2.dev173.dist-info}/WHEEL +0 -0
  35. {qontract_reconcile-0.10.2.dev159.dist-info → qontract_reconcile-0.10.2.dev173.dist-info}/entry_points.txt +0 -0
reconcile/quay_mirror.py CHANGED
@@ -28,9 +28,9 @@ from reconcile.utils import (
28
28
  metrics,
29
29
  sharding,
30
30
  )
31
- from reconcile.utils.helpers import match_patterns
32
31
  from reconcile.utils.instrumented_wrappers import InstrumentedImage as Image
33
32
  from reconcile.utils.instrumented_wrappers import InstrumentedSkopeo as Skopeo
33
+ from reconcile.utils.quay_mirror import record_timestamp, sync_tag
34
34
  from reconcile.utils.secret_reader import SecretReader
35
35
 
36
36
  _LOG = logging.getLogger(__name__)
@@ -136,7 +136,7 @@ class QuayMirror:
136
136
  _LOG.error("skopeo command error message: '%s'", details)
137
137
 
138
138
  if self.is_compare_tags and not self.dry_run:
139
- self.record_timestamp(self.control_file_path)
139
+ record_timestamp(self.control_file_path)
140
140
 
141
141
  @classmethod
142
142
  def process_repos_query(
@@ -203,40 +203,6 @@ class QuayMirror:
203
203
  })
204
204
  return summary
205
205
 
206
- @staticmethod
207
- def sync_tag(
208
- tags: Iterable[str] | None,
209
- tags_exclude: Iterable[str] | None,
210
- candidate: str,
211
- ) -> bool:
212
- """
213
- Determine if the candidate tag should sync, tags_exclude check take precedence.
214
- :param tags: regex patterns to filter, match means to sync, None means no filter
215
- :param tags_exclude: regex patterns to filter, match means not to sync, None means no filter
216
- :param candidate: tag to check
217
- :return: bool, True means to sync, False means not to sync
218
- """
219
- if not tags and not tags_exclude:
220
- return True
221
-
222
- if not tags:
223
- # only tags_exclude provided
224
- assert tags_exclude # mypy can't infer not None
225
- return not match_patterns(tags_exclude, candidate)
226
-
227
- if not tags_exclude:
228
- # only tags provided
229
- return match_patterns(tags, candidate)
230
-
231
- # both tags and tags_exclude provided
232
- return not match_patterns(
233
- tags_exclude,
234
- candidate,
235
- ) and match_patterns(
236
- tags,
237
- candidate,
238
- )
239
-
240
206
  def process_sync_tasks(self):
241
207
  if self.is_compare_tags:
242
208
  _LOG.warning("Making a compare-tags run. This is a slow operation.")
@@ -282,7 +248,7 @@ class QuayMirror:
282
248
  tags_exclude = item["mirror"].get("tagsExclude")
283
249
 
284
250
  for tag in image_mirror:
285
- if not self.sync_tag(
251
+ if not sync_tag(
286
252
  tags=tags, tags_exclude=tags_exclude, candidate=tag
287
253
  ):
288
254
  continue
@@ -390,11 +356,6 @@ class QuayMirror:
390
356
  next_compare_tags = last_compare_tags + interval
391
357
  return time.time() >= next_compare_tags
392
358
 
393
- @staticmethod
394
- def record_timestamp(path) -> None:
395
- with open(path, "w", encoding="locale") as file_object:
396
- file_object.write(str(time.time()))
397
-
398
359
  def _get_push_creds(self):
399
360
  result = self.gqlapi.query(self.QUAY_ORG_CATALOG_QUERY)
400
361
 
@@ -18,6 +18,7 @@ from sretoolbox.container.skopeo import SkopeoCmdError
18
18
 
19
19
  from reconcile.quay_base import get_quay_api_store
20
20
  from reconcile.quay_mirror import QuayMirror
21
+ from reconcile.utils.quay_mirror import record_timestamp, sync_tag
21
22
 
22
23
  _LOG = logging.getLogger(__name__)
23
24
 
@@ -80,7 +81,7 @@ class QuayMirrorOrg:
80
81
  _LOG.error("skopeo command error message: '%s'", details)
81
82
 
82
83
  if self.is_compare_tags and not self.dry_run:
83
- QuayMirror.record_timestamp(self.control_file_path)
84
+ record_timestamp(self.control_file_path)
84
85
 
85
86
  def process_org_mirrors(self, summary):
86
87
  """adds new keys to the summary dict with information about mirrored
@@ -183,7 +184,7 @@ class QuayMirrorOrg:
183
184
  upstream = image_mirror[tag]
184
185
  downstream = image[tag]
185
186
 
186
- if not QuayMirror.sync_tag(
187
+ if not sync_tag(
187
188
  tags=tags, tags_exclude=tags_exclude, candidate=tag
188
189
  ):
189
190
  _LOG.debug(
reconcile/slack_base.py CHANGED
@@ -14,12 +14,12 @@ from reconcile.utils.slack_api import (
14
14
 
15
15
 
16
16
  def slackapi_from_queries(
17
- integration_name: str, init_usergroups: bool = True
17
+ integration_name: str, init_usergroups: bool = True, channel: str | None = None
18
18
  ) -> SlackApi:
19
19
  secret_reader = SecretReader(queries.get_secret_reader_settings())
20
20
  slack_workspace = {"workspace": queries.get_slack_workspace()}
21
21
  return slackapi_from_slack_workspace(
22
- slack_workspace, secret_reader, integration_name, init_usergroups
22
+ slack_workspace, secret_reader, integration_name, init_usergroups, channel
23
23
  )
24
24
 
25
25
 
@@ -0,0 +1,12 @@
1
+ from reconcile.gql_definitions.aws_cloudwatch_log_retention.aws_accounts import (
2
+ AWSAccountV1,
3
+ query,
4
+ )
5
+ from reconcile.utils.gql import GqlApi
6
+
7
+
8
+ def get_aws_accounts(
9
+ gql_api: GqlApi,
10
+ ) -> list[AWSAccountV1]:
11
+ data = query(query_func=gql_api.query)
12
+ return data.accounts or []
@@ -1,8 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Iterable
4
- from datetime import datetime
5
- from unittest.mock import patch
6
4
 
7
5
  from dynatrace import Dynatrace
8
6
  from dynatrace.environment_v2.tokens_api import ApiTokenUpdate
@@ -35,32 +33,11 @@ class DynatraceAPIToken(BaseModel):
35
33
  scopes: list[str]
36
34
 
37
35
 
38
- # TODO: Remove once APPSRE-11428 is resolved #######
39
- ISO_8601 = "%Y-%m-%dT%H:%M:%S.%fZ"
40
- FIXED_ISO_8601 = "%Y-%m-%dT%H:%M:%SZ"
41
-
42
-
43
- def custom_iso8601_to_datetime(timestamp: str | None) -> datetime | None:
44
- if isinstance(timestamp, str):
45
- try:
46
- return datetime.strptime(timestamp, ISO_8601)
47
- except ValueError:
48
- return datetime.strptime(timestamp, FIXED_ISO_8601)
49
- return timestamp
50
-
51
-
52
- ################################################
53
-
54
-
55
36
  class DynatraceClient:
56
37
  def __init__(self, environment_url: str, api: Dynatrace) -> None:
57
38
  self._environment_url = environment_url
58
39
  self._api = api
59
40
 
60
- @patch(
61
- "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
62
- custom_iso8601_to_datetime,
63
- )
64
41
  def create_api_token(
65
42
  self, name: str, scopes: Iterable[str]
66
43
  ) -> DynatraceAPITokenCreated:
@@ -72,10 +49,6 @@ class DynatraceClient:
72
49
  ) from e
73
50
  return DynatraceAPITokenCreated(token=token.token, id=token.id)
74
51
 
75
- @patch(
76
- "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
77
- custom_iso8601_to_datetime,
78
- )
79
52
  def get_token_ids_map_for_name_prefix(self, prefix: str) -> dict[str, str]:
80
53
  try:
81
54
  dt_tokens = self._api.tokens.list()
@@ -87,10 +60,6 @@ class DynatraceClient:
87
60
  token.id: token.name for token in dt_tokens if token.name.startswith(prefix)
88
61
  }
89
62
 
90
- @patch(
91
- "dynatrace.environment_v2.tokens_api.iso8601_to_datetime",
92
- custom_iso8601_to_datetime,
93
- )
94
63
  def get_token_by_id(self, token_id: str) -> DynatraceAPIToken:
95
64
  try:
96
65
  token = self._api.tokens.get(token_id=token_id)
@@ -0,0 +1,42 @@
1
+ import time
2
+ from collections.abc import Iterable
3
+
4
+ from reconcile.utils.helpers import match_patterns
5
+
6
+
7
+ def record_timestamp(path: str) -> None:
8
+ with open(path, "w", encoding="locale") as file_object:
9
+ file_object.write(str(time.time()))
10
+
11
+
12
+ def sync_tag(
13
+ tags: Iterable[str] | None,
14
+ tags_exclude: Iterable[str] | None,
15
+ candidate: str,
16
+ ) -> bool:
17
+ """
18
+ Determine if the candidate tag should sync, tags_exclude check take precedence.
19
+ :param tags: regex patterns to filter, match means to sync, None means no filter
20
+ :param tags_exclude: regex patterns to filter, match means not to sync, None means no filter
21
+ :param candidate: tag to check
22
+ :return: bool, True means to sync, False means not to sync
23
+ """
24
+ if tags:
25
+ if tags_exclude:
26
+ # both tags and tags_exclude provided
27
+ return not match_patterns(
28
+ tags_exclude,
29
+ candidate,
30
+ ) and match_patterns(
31
+ tags,
32
+ candidate,
33
+ )
34
+ else:
35
+ # only tags provided
36
+ return match_patterns(tags, candidate)
37
+ elif tags_exclude:
38
+ # only tags_exclude provided
39
+ return not match_patterns(tags_exclude, candidate)
40
+ else:
41
+ # neither tags nor tags_exclude provided
42
+ return True
@@ -367,6 +367,8 @@ class ManagedResourceName(Protocol):
367
367
  resource: str
368
368
  resource_names: list[str]
369
369
 
370
+ def dict(self) -> dict[str, str]: ...
371
+
370
372
 
371
373
  class SaasFile(HasParameters, HasSecretParameters, Protocol):
372
374
  path: str
@@ -1784,6 +1784,11 @@ class SaasHerder: # pylint: disable=too-many-public-methods
1784
1784
  desired_target_config["saas_file_managed_resource_types"] = (
1785
1785
  saas_file.managed_resource_types
1786
1786
  )
1787
+ if saas_file.managed_resource_names:
1788
+ desired_target_config["saas_file_managed_resource_names"] = [
1789
+ m.dict() for m in saas_file.managed_resource_names
1790
+ ]
1791
+
1787
1792
  desired_target_config["url"] = rt.url
1788
1793
  desired_target_config["path"] = rt.path
1789
1794
  # before the GQL classes are introduced, the parameters attribute
@@ -491,6 +491,8 @@ class SlackApi:
491
491
  from_timestamp to to_timestamp ignoring threads"""
492
492
  if not self.channel:
493
493
  raise ValueError("Expecting self.channel to be set")
494
+ channels_found = self.get_channels_by_names(self.channel)
495
+ [channel_id] = [k for k in channels_found if channels_found[k] == self.channel]
494
496
 
495
497
  cursor = ""
496
498
  responses = []
@@ -499,7 +501,7 @@ class SlackApi:
499
501
  slack_request.labels("conversations.history", "GET").inc()
500
502
 
501
503
  response = self._sc.conversations_history(
502
- cursor=cursor, channel=self.channel, **self.chat_kwargs
504
+ cursor=cursor, channel=channel_id, **self.chat_kwargs
503
505
  )
504
506
 
505
507
  for r in response["messages"]:
@@ -0,0 +1,278 @@
1
+ import itertools
2
+ import logging
3
+ from dataclasses import dataclass
4
+ from math import isnan
5
+ from typing import Any, Self
6
+
7
+ import jinja2
8
+ import requests
9
+ from sretoolbox.utils import threaded
10
+
11
+ from reconcile.gql_definitions.fragments.saas_slo_document import (
12
+ SLODocument,
13
+ SLODocumentSLOV1,
14
+ SLOExternalPrometheusAccessV1,
15
+ SLONamespacesV1,
16
+ )
17
+ from reconcile.utils.rest_api_base import ApiBase, BearerTokenAuth
18
+ from reconcile.utils.secret_reader import SecretReaderBase
19
+
20
+ PROM_QUERY_URL = "api/v1/query"
21
+
22
+ DEFAULT_READ_TIMEOUT = 30
23
+ DEFAULT_RETRIES = 3
24
+ DEFAULT_THREAD_POOL_SIZE = 10
25
+
26
+
27
+ class EmptySLOResult(Exception):
28
+ pass
29
+
30
+
31
+ class EmptySLOValue(Exception):
32
+ pass
33
+
34
+
35
+ class InvalidSLOValue(Exception):
36
+ pass
37
+
38
+
39
+ @dataclass
40
+ class SLODetails:
41
+ namespace_name: str
42
+ slo_document_name: str
43
+ cluster_name: str
44
+ slo: SLODocumentSLOV1
45
+ service_name: str
46
+ current_slo_value: float
47
+
48
+
49
+ @dataclass
50
+ class NamespaceSLODocument:
51
+ name: str
52
+ namespace: SLONamespacesV1
53
+ slos: list[SLODocumentSLOV1] | None
54
+
55
+ def get_host_url(self) -> str:
56
+ return (
57
+ self.namespace.prometheus_access.url
58
+ if self.namespace.prometheus_access
59
+ else self.namespace.namespace.cluster.prometheus_url
60
+ )
61
+
62
+
63
+ class PrometheusClient(ApiBase):
64
+ def get_current_slo_value(
65
+ self,
66
+ slo: SLODocumentSLOV1,
67
+ slo_document_name: str,
68
+ namespace_name: str,
69
+ service_name: str,
70
+ cluster_name: str,
71
+ ) -> SLODetails | None:
72
+ """
73
+ Retrieve the current SLO value from Prometheus for provided SLO configuration.
74
+ Returns an SLODetails instance if successful, or None on error.
75
+ """
76
+ template = jinja2.Template(slo.expr)
77
+ prom_query = template.render({"window": slo.slo_parameters.window})
78
+ try:
79
+ current_slo_response = self._get(
80
+ url=PROM_QUERY_URL, params={"query": (prom_query)}
81
+ )
82
+ current_slo_value = self._extract_current_slo_value(
83
+ data=current_slo_response
84
+ )
85
+ return SLODetails(
86
+ namespace_name=namespace_name,
87
+ slo=slo,
88
+ slo_document_name=slo_document_name,
89
+ current_slo_value=current_slo_value,
90
+ cluster_name=cluster_name,
91
+ service_name=service_name,
92
+ )
93
+ except requests.exceptions.ConnectionError:
94
+ logging.error(
95
+ f"Connection error getting current value for SLO: {slo.name} of document: {slo_document_name} for namespace: {namespace_name}"
96
+ )
97
+ raise
98
+ except Exception as e:
99
+ logging.error(
100
+ f"Unexpected error getting current value for SLO: {slo.name} of document: {slo_document_name} for namespace: {namespace_name} details: {e}"
101
+ )
102
+ return None
103
+
104
+ def _extract_current_slo_value(self, data: dict[str, Any]) -> float:
105
+ result = data["data"]["result"]
106
+ if not result:
107
+ raise EmptySLOResult("prometheus returned empty result")
108
+ slo_value = result[0]["value"]
109
+ if not slo_value:
110
+ raise EmptySLOValue("prometheus returned empty SLO value")
111
+ slo_value = float(slo_value[1])
112
+ if isnan(slo_value):
113
+ raise InvalidSLOValue("slo value should be a number")
114
+ return slo_value
115
+
116
+
117
+ class PrometheusClientMap:
118
+ """
119
+ A mapping from Prometheus URLs to PrometheusClient instances.
120
+ """
121
+
122
+ def __init__(
123
+ self,
124
+ secret_reader: SecretReaderBase,
125
+ namespace_slo_documents: list[NamespaceSLODocument],
126
+ read_timeout: int = DEFAULT_READ_TIMEOUT,
127
+ max_retries: int = DEFAULT_RETRIES,
128
+ ):
129
+ self.secret_reader = secret_reader
130
+ self.read_timeout = read_timeout
131
+ self.max_retries = max_retries
132
+ self.pc_map: dict[str, PrometheusClient] = self._build_pc_map(
133
+ namespace_slo_documents
134
+ )
135
+
136
+ def __enter__(self) -> Self:
137
+ return self
138
+
139
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
140
+ self.cleanup()
141
+
142
+ def get_prometheus_client(self, prom_url: str) -> PrometheusClient:
143
+ return self.pc_map[prom_url]
144
+
145
+ def _build_pc_map(
146
+ self, namespace_slo_documents: list[NamespaceSLODocument]
147
+ ) -> dict[str, PrometheusClient]:
148
+ pc_map: dict[str, PrometheusClient] = {}
149
+ for doc in namespace_slo_documents:
150
+ key = doc.get_host_url()
151
+ if key not in pc_map:
152
+ prom_client = self.build_prom_client_from_namespace(doc.namespace)
153
+ pc_map[key] = prom_client
154
+ return pc_map
155
+
156
+ def cleanup(self) -> None:
157
+ for prom_client in self.pc_map.values():
158
+ prom_client.cleanup()
159
+
160
+ def build_auth_for_prometheus_access(
161
+ self, prometheus_access: SLOExternalPrometheusAccessV1
162
+ ) -> requests.auth.HTTPBasicAuth | None:
163
+ """
164
+ Build authentication for Prometheus endpoint referred in prometheusAccess section.
165
+ """
166
+ if prometheus_access.username and prometheus_access.password:
167
+ username = self.secret_reader.read_secret(prometheus_access.username)
168
+ password = self.secret_reader.read_secret(prometheus_access.password)
169
+ return requests.auth.HTTPBasicAuth(username, password)
170
+ return None
171
+
172
+ def build_prom_client_from_namespace(
173
+ self, namespace: SLONamespacesV1
174
+ ) -> PrometheusClient:
175
+ auth: requests.auth.HTTPBasicAuth | BearerTokenAuth | None
176
+ if namespace.prometheus_access:
177
+ prom_url = namespace.prometheus_access.url
178
+ auth = self.build_auth_for_prometheus_access(namespace.prometheus_access)
179
+ return PrometheusClient(
180
+ host=prom_url,
181
+ read_timeout=self.read_timeout,
182
+ max_retries=self.max_retries,
183
+ auth=auth,
184
+ )
185
+ if not namespace.namespace.cluster.automation_token:
186
+ raise Exception(
187
+ f"cluster {namespace.namespace.cluster.name} does not have automation token set"
188
+ )
189
+ auth = BearerTokenAuth(
190
+ self.secret_reader.read_secret(namespace.namespace.cluster.automation_token)
191
+ )
192
+ return PrometheusClient(
193
+ host=namespace.namespace.cluster.prometheus_url,
194
+ read_timeout=self.read_timeout,
195
+ max_retries=self.max_retries,
196
+ auth=auth,
197
+ )
198
+
199
+
200
+ class SLODocumentManager:
201
+ """
202
+ Manages SLO document including authentication, querying, and SLO value extraction.
203
+ """
204
+
205
+ def __init__(
206
+ self,
207
+ slo_documents: list[SLODocument],
208
+ secret_reader: SecretReaderBase,
209
+ thread_pool_size: int = DEFAULT_THREAD_POOL_SIZE,
210
+ read_timeout: int = DEFAULT_READ_TIMEOUT,
211
+ max_retries: int = DEFAULT_RETRIES,
212
+ ):
213
+ self.namespace_slo_documents = self._build_namespace_slo_documents(
214
+ slo_documents
215
+ )
216
+ self.thread_pool_size = thread_pool_size
217
+ self.secret_reader = secret_reader
218
+ self.max_retries = max_retries
219
+ self.read_timeout = read_timeout
220
+
221
+ @staticmethod
222
+ def _build_namespace_slo_documents(
223
+ slo_documents: list[SLODocument],
224
+ ) -> list[NamespaceSLODocument]:
225
+ return [
226
+ NamespaceSLODocument(
227
+ name=slo_document.name,
228
+ namespace=namespace,
229
+ slos=slo_document.slos,
230
+ )
231
+ for slo_document in slo_documents
232
+ for namespace in slo_document.namespaces
233
+ ]
234
+
235
+ def get_current_slo_list(self) -> list[SLODetails | None]:
236
+ with PrometheusClientMap(
237
+ secret_reader=self.secret_reader,
238
+ namespace_slo_documents=self.namespace_slo_documents,
239
+ read_timeout=self.read_timeout,
240
+ max_retries=self.max_retries,
241
+ ) as pc_map:
242
+ current_slo_list_iterable = threaded.run(
243
+ func=self._get_current_slo_details_list,
244
+ pc_map=pc_map,
245
+ iterable=self.namespace_slo_documents,
246
+ thread_pool_size=self.thread_pool_size,
247
+ )
248
+ return list(itertools.chain.from_iterable(current_slo_list_iterable))
249
+
250
+ def get_breached_slos(self) -> list[SLODetails]:
251
+ current_slo_details_list = self.get_current_slo_list()
252
+ missing_slos = [slo for slo in current_slo_details_list if not slo]
253
+ if missing_slos:
254
+ raise RuntimeError("slo validation failed due to retrival errors")
255
+ return [
256
+ slo
257
+ for slo in current_slo_details_list
258
+ if slo and slo.current_slo_value < slo.slo.slo_target
259
+ ]
260
+
261
+ @staticmethod
262
+ def _get_current_slo_details_list(
263
+ slo_document: NamespaceSLODocument,
264
+ pc_map: PrometheusClientMap,
265
+ ) -> list[SLODetails | None]:
266
+ key = slo_document.get_host_url()
267
+ prom_client = pc_map.get_prometheus_client(key)
268
+ slo_details_list: list[SLODetails | None] = [
269
+ prom_client.get_current_slo_value(
270
+ slo=slo,
271
+ slo_document_name=slo_document.name,
272
+ namespace_name=slo_document.namespace.namespace.name,
273
+ service_name=slo_document.namespace.namespace.app.name,
274
+ cluster_name=slo_document.namespace.namespace.cluster.name,
275
+ )
276
+ for slo in slo_document.slos or []
277
+ ]
278
+ return slo_details_list
@@ -6138,6 +6138,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6138
6138
  callback_urls=[f"{bucket_url}/token.html"],
6139
6139
  depends_on=["aws_cognito_resource_server.userpool_gateway_resource_server"],
6140
6140
  **pool_client_args,
6141
+ token_validity_units={
6142
+ "access_token": "minutes",
6143
+ "id_token": "minutes",
6144
+ "refresh_token": "days",
6145
+ },
6141
6146
  )
6142
6147
  tf_resources.append(cognito_user_pool_client)
6143
6148
 
@@ -6149,9 +6154,15 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6149
6154
  user_pool_id=f"${{{cognito_user_pool_resource.id}}}",
6150
6155
  callback_urls=insights_callback_urls,
6151
6156
  **pool_client_args,
6157
+ token_validity_units={
6158
+ "access_token": "minutes",
6159
+ "id_token": "minutes",
6160
+ "refresh_token": "days",
6161
+ },
6152
6162
  )
6153
6163
  tf_resources.append(insights_cognito_user_pool_client)
6154
6164
 
6165
+ # todo: these scopes should be defined in an external resource file
6155
6166
  # POOL RESOURCE SERVER
6156
6167
  cognito_resource_server_resource = aws_cognito_resource_server(
6157
6168
  "userpool_service_resource_server",
@@ -6215,6 +6226,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6215
6226
  allowed_oauth_scopes=["ocm/AccountManagement"],
6216
6227
  depends_on=["aws_cognito_resource_server.userpool_service_resource_server"],
6217
6228
  **pool_client_service_account_common_args,
6229
+ token_validity_units={
6230
+ "access_token": "minutes",
6231
+ "id_token": "minutes",
6232
+ "refresh_token": "days",
6233
+ },
6218
6234
  )
6219
6235
  tf_resources.append(ams_service_account_pool_client_resource)
6220
6236
 
@@ -6226,6 +6242,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6226
6242
  allowed_oauth_scopes=["ocm/ClusterService"],
6227
6243
  depends_on=["aws_cognito_resource_server.userpool_service_resource_server"],
6228
6244
  **pool_client_service_account_common_args,
6245
+ token_validity_units={
6246
+ "access_token": "minutes",
6247
+ "id_token": "minutes",
6248
+ "refresh_token": "days",
6249
+ },
6229
6250
  )
6230
6251
  tf_resources.append(cs_service_account_pool_client_resource)
6231
6252
 
@@ -6237,6 +6258,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6237
6258
  allowed_oauth_scopes=["ocm/ServiceLogService"],
6238
6259
  depends_on=["aws_cognito_resource_server.userpool_service_resource_server"],
6239
6260
  **pool_client_service_account_common_args,
6261
+ token_validity_units={
6262
+ "access_token": "minutes",
6263
+ "id_token": "minutes",
6264
+ "refresh_token": "days",
6265
+ },
6240
6266
  )
6241
6267
  tf_resources.append(osl_service_account_pool_client_resource)
6242
6268
 
@@ -6251,6 +6277,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6251
6277
  "aws_cognito_resource_server.userpool_service_resource_server"
6252
6278
  ],
6253
6279
  **pool_client_service_account_common_args,
6280
+ token_validity_units={
6281
+ "access_token": "minutes",
6282
+ "id_token": "minutes",
6283
+ "refresh_token": "days",
6284
+ },
6254
6285
  )
6255
6286
  )
6256
6287
  tf_resources.append(backplane_cli_service_account_pool_client_resource)
@@ -6266,6 +6297,11 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6266
6297
  "aws_cognito_resource_server.userpool_service_resource_server"
6267
6298
  ],
6268
6299
  **pool_client_service_account_common_args,
6300
+ token_validity_units={
6301
+ "access_token": "minutes",
6302
+ "id_token": "minutes",
6303
+ "refresh_token": "days",
6304
+ },
6269
6305
  )
6270
6306
  )
6271
6307
  tf_resources.append(backplane_api_service_account_pool_client_resource)
@@ -6278,9 +6314,30 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
6278
6314
  allowed_oauth_scopes=["ocm/InsightsServiceAccount"],
6279
6315
  depends_on=["aws_cognito_resource_server.userpool_service_resource_server"],
6280
6316
  **pool_client_service_account_common_args,
6317
+ token_validity_units={
6318
+ "access_token": "minutes",
6319
+ "id_token": "minutes",
6320
+ "refresh_token": "days",
6321
+ },
6281
6322
  )
6282
6323
  tf_resources.append(insights_service_account_pool_client_resource)
6283
6324
 
6325
+ # OSD FLEET MANAGER
6326
+ osdfm_service_account_pool_client_resource = aws_cognito_user_pool_client(
6327
+ "osdfm_service_account",
6328
+ name=f"ocm-{identifier}-osdfm-service-account",
6329
+ user_pool_id=f"${{{cognito_user_pool_resource.id}}}",
6330
+ allowed_oauth_scopes=["ocm/OSDFleetManagerService"],
6331
+ depends_on=["aws_cognito_resource_server.userpool_service_resource_server"],
6332
+ **pool_client_service_account_common_args,
6333
+ token_validity_units={
6334
+ "access_token": "minutes",
6335
+ "id_token": "minutes",
6336
+ "refresh_token": "days",
6337
+ },
6338
+ )
6339
+ tf_resources.append(osdfm_service_account_pool_client_resource)
6340
+
6284
6341
  # USER POOL COMPLETE
6285
6342
 
6286
6343
  # rosa-authenticator-vpce provider OR pre-created vpce resources are required for this
@@ -3,7 +3,7 @@ import re
3
3
  from collections import defaultdict
4
4
  from dataclasses import dataclass
5
5
 
6
- QONTRACT_INTEGRATION = "sd-app-sre-alert-report"
6
+ QONTRACT_INTEGRATION = "alert-report"
7
7
 
8
8
 
9
9
  @dataclass