qontract-reconcile 0.10.2.dev179__py3-none-any.whl → 0.10.2.dev181__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.dev179
3
+ Version: 0.10.2.dev181
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
@@ -123,14 +123,14 @@ reconcile/vpc_peerings_validator.py,sha256=aESqrhm1tpkc2iqSL1UV5to_HjNgjRSffD0cr
123
123
  reconcile/aus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  reconcile/aus/advanced_upgrade_service.py,sha256=lt684trHbKvVDLwwuNVz3Wu_MnytFSbS_7MZTIITh9k,23969
125
125
  reconcile/aus/aus_label_source.py,sha256=o0S2f0qwcII_8nzhHZhRQ83gEZ1DrSXyO4xzSwLebuU,4382
126
- reconcile/aus/base.py,sha256=rx2OuShoFRP7O6Kov9rRjEkhCPpPavfDF81tieB6XFg,50747
126
+ reconcile/aus/base.py,sha256=Kicj39qfAlINywfk0Legu7RUcUxI4AuoBhA8vJ1gWag,51343
127
127
  reconcile/aus/cluster_version_data.py,sha256=VZWbUEIbrDKO-sroMpQtiWCTqDraTMd8tssKV0HyTQ0,7140
128
128
  reconcile/aus/healthchecks.py,sha256=jR9c-syh9impnkV0fd6XW3Bnk7iRN5zv8oCRYM-yIRY,2700
129
- reconcile/aus/metrics.py,sha256=nKT4m2zGT-QOMR0c-z-npVNKWsNMubzdffpU_f9n4II,3927
129
+ reconcile/aus/metrics.py,sha256=qh3-oWL8-Hbj1uXgAKonw7sVXGBlPpCyHtVYJusZ9n8,4271
130
130
  reconcile/aus/models.py,sha256=qLjWLDJe5PGXPPtJ5PI01IVEYaSGweu9dkAgf0ZM2hk,8297
131
131
  reconcile/aus/node_pool_spec.py,sha256=FkMggklG-4BgQwud2Swp2m3AAAKzZmeaXgohl9uwxZ8,1138
132
- reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=SHbFEEq9ETtkkuzBBaNuwSDze8fj6DWHeA_XNgOyTbo,10308
133
- reconcile/aus/ocm_upgrade_scheduler.py,sha256=2uPn13y3QGCHLoKwCc1Z7q9wQsoQf_F1HATMYUbl53s,3695
132
+ reconcile/aus/ocm_addons_upgrade_scheduler_org.py,sha256=PL8QdzWh8lhGneMwSbIYxOp002mJd5gHf_T0Q2cWQho,10350
133
+ reconcile/aus/ocm_upgrade_scheduler.py,sha256=WPIUUr3n3j-ZtyE3DgsPKzBJ3Dm9i1PSHBRepAf6CF4,3783
134
134
  reconcile/aus/ocm_upgrade_scheduler_org.py,sha256=QeZAQ1wdDPcwZ77b3Xmt4yBV60rWi3qd8h-vGwnwoCs,3948
135
135
  reconcile/aus/upgrades.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
136
  reconcile/aus/version_gate_approver.py,sha256=iZg7l-VgcgyVf7jV9mLZzXq3d22r8feAnl-1LDVUJR4,7358
@@ -482,11 +482,11 @@ reconcile/rhidp/sso_client/base.py,sha256=EfQ2ewcOKh5idg46UKAkY6z0m_nGQfvnQKffa2
482
482
  reconcile/rhidp/sso_client/integration.py,sha256=kA8g7c38ZBSdrRtyfEqy_WgSreD1PbwY7ZIN-3tZRPc,2221
483
483
  reconcile/rhidp/sso_client/metrics.py,sha256=Tq7tSOsqL3XdcPUdozxqzSPIodUeOV87UCTqpuuqqhw,1013
484
484
  reconcile/saas_auto_promotions_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
485
- reconcile/saas_auto_promotions_manager/integration.py,sha256=haXTpwi0rEQtN9-MadBzFvSNtbxw5QQuFpMQLg3bZlQ,6707
485
+ reconcile/saas_auto_promotions_manager/integration.py,sha256=PhngUNVQMdZ_7KlIOCokdYqQ2k-XdsvvfCb8BAdSiAA,6889
486
486
  reconcile/saas_auto_promotions_manager/meta.py,sha256=76Jp50r6Y_KyJoXFfSjrt5YrCtXyg_A4FXXxHYiS3TE,161
487
487
  reconcile/saas_auto_promotions_manager/publisher.py,sha256=5gphMxr2NUvyB7WDK4eAbgZeyeF30cZ3a2ZGrbFQgZk,2976
488
488
  reconcile/saas_auto_promotions_manager/s3_exporter.py,sha256=Y-r5R6viiAzglUHbYKItYSjT_axmLlPEJVmu_H6N170,2682
489
- reconcile/saas_auto_promotions_manager/subscriber.py,sha256=tZLcjPt64cIxiswlSGpEcAg3VTp0PHvnDqgFQ2pajVM,10044
489
+ reconcile/saas_auto_promotions_manager/subscriber.py,sha256=V8e2tz0s3A-pcUNb2UoREQm6-6ZzS4F3gJdGUBoa9WY,11281
490
490
  reconcile/saas_auto_promotions_manager/merge_request_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
491
491
  reconcile/saas_auto_promotions_manager/merge_request_manager/batcher.py,sha256=R2CRtjdOggY5lSqt-A5qz2ymqomx4opVeVV_oqBAW0A,7804
492
492
  reconcile/saas_auto_promotions_manager/merge_request_manager/desired_state.py,sha256=isY8frVsL3PlcdZmdZ4O0qyp76oczl4DUMX9uMArs5Y,1222
@@ -497,7 +497,7 @@ reconcile/saas_auto_promotions_manager/merge_request_manager/mr_parser.py,sha256
497
497
  reconcile/saas_auto_promotions_manager/merge_request_manager/open_merge_requests.py,sha256=-qGQOh6Jdp4lomNDij3zWVC0pl6uPHFWS5Woqcp5HQk,410
498
498
  reconcile/saas_auto_promotions_manager/merge_request_manager/renderer.py,sha256=EgImn9SpeThTNN5-P5lvz__bMKZNU4m2SfKpocRCy5w,7278
499
499
  reconcile/saas_auto_promotions_manager/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
500
- reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=LhK3OtHf-l_wm1SZCb3vZUrPpNZJRpM65WKY2HATXRY,8606
500
+ reconcile/saas_auto_promotions_manager/utils/saas_files_inventory.py,sha256=BPzSsuXy5ULKSo36v1bItC3rU-AEFfVZeWXZw1tXqLg,9674
501
501
  reconcile/skupper_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
502
502
  reconcile/skupper_network/integration.py,sha256=oZIVDBRcQPC-lWxNFiJhGbtCM7Yj7fjwAzYZ8JvVe3I,10789
503
503
  reconcile/skupper_network/models.py,sha256=HEwlVKsbmMaKaaBGvITIiSYNEVdjwXVhLaOJgLSZ2xQ,6604
@@ -569,7 +569,7 @@ reconcile/typed_queries/pagerduty_instances.py,sha256=zxCNxMak4iikryePaRi71lTADV
569
569
  reconcile/typed_queries/quay.py,sha256=3IMy9jjHF2f9t47EXZOQVA3p0nFkWFhaFhxhvib-71o,644
570
570
  reconcile/typed_queries/repos.py,sha256=8A93dKDt6igT4ClqMjt7YUTsoP4qh1Wnm0W3xsMgj48,824
571
571
  reconcile/typed_queries/reserved_networks.py,sha256=XY9y3amtIQT0n06O0Toubqr_UmylJ2ELAv9-BJCK890,345
572
- reconcile/typed_queries/saas_files.py,sha256=O2kd0nSFfMgnbXvSv9oMIdlBGZg7XlOU3y2CWg1W2DQ,14001
572
+ reconcile/typed_queries/saas_files.py,sha256=SOE36sWPBcuaRmEaNxXCQZMQdJiUZX8_A92o42XwHQA,14141
573
573
  reconcile/typed_queries/slack.py,sha256=r30lspctHloyygPn8_DVybxPwUWwiBpvBRRXiTVcQYk,251
574
574
  reconcile/typed_queries/slo_documents.py,sha256=YMdox_-lBRqrdxamPhdnUlRTY_Ro35ptsupq7OaynUQ,362
575
575
  reconcile/typed_queries/smtp.py,sha256=aSLglYa5bHKmlGwKkxq2RZqyMWuAf0a4S_mOuhDa084,542
@@ -807,7 +807,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
807
807
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
808
808
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
809
809
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
810
- qontract_reconcile-0.10.2.dev179.dist-info/METADATA,sha256=vGUEKBGnRX-V-Y5trKn107wjTWzK8k9k_cbQ92eYeLE,24627
811
- qontract_reconcile-0.10.2.dev179.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
812
- qontract_reconcile-0.10.2.dev179.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
813
- qontract_reconcile-0.10.2.dev179.dist-info/RECORD,,
810
+ qontract_reconcile-0.10.2.dev181.dist-info/METADATA,sha256=GFcwt6xmX1m6ffKhsTJe6HD5YUe_VT-8-OiGbWIN2cE,24627
811
+ qontract_reconcile-0.10.2.dev181.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
812
+ qontract_reconcile-0.10.2.dev181.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
813
+ qontract_reconcile-0.10.2.dev181.dist-info/RECORD,,
reconcile/aus/base.py CHANGED
@@ -35,6 +35,7 @@ from reconcile.aus.metrics import (
35
35
  UPGRADE_SCHEDULED_METRIC_VALUE,
36
36
  UPGRADE_STARTED_METRIC_VALUE,
37
37
  AUSClusterHealthStateGauge,
38
+ AUSClusterMissingVersionGateAgreementsGauge,
38
39
  AUSClusterUpgradePolicyInfoMetric,
39
40
  AUSOCMEnvironmentError,
40
41
  AUSOrganizationErrorRate,
@@ -1066,6 +1067,7 @@ def calculate_diff(
1066
1067
  ocm_api: OCMBaseClient,
1067
1068
  version_data: VersionData,
1068
1069
  addon_id: str = "",
1070
+ integration: str = "",
1069
1071
  ) -> list[UpgradePolicyHandler]:
1070
1072
  """Check available upgrades for each cluster in the desired state
1071
1073
  according to upgrade conditions
@@ -1169,13 +1171,25 @@ def calculate_diff(
1169
1171
  },
1170
1172
  )
1171
1173
  if gates_with_missing_agreements:
1172
- missing_gate_ids = [
1173
- gate.id for gate in gates_with_missing_agreements
1174
+ missing_gate_labels = [
1175
+ gate.label for gate in gates_with_missing_agreements
1174
1176
  ]
1175
1177
  logging.info(
1176
- f"[{spec.org.org_id}/{spec.org.name}/{spec.cluster.name}] found gates with missing agreements for {target_version_prefix} - {missing_gate_ids} "
1178
+ f"[{spec.org.org_id}/{spec.org.name}/{spec.cluster.name}] found gates with missing agreements for {target_version_prefix} - {missing_gate_labels} "
1177
1179
  "Skip creation of an upgrade policy until all of them have been acked by the version-gate-approver integration or a user."
1178
1180
  )
1181
+
1182
+ metrics.set_gauge(
1183
+ AUSClusterMissingVersionGateAgreementsGauge(
1184
+ integration=integration,
1185
+ ocm_env=spec.org.environment.name,
1186
+ org_id=spec.org.org_id,
1187
+ cluster_uuid=spec.cluster.id,
1188
+ version_prefix=target_version_prefix,
1189
+ ),
1190
+ len(gates_with_missing_agreements),
1191
+ )
1192
+
1179
1193
  continue
1180
1194
  diffs.append(
1181
1195
  UpgradePolicyHandler(
reconcile/aus/metrics.py CHANGED
@@ -56,6 +56,18 @@ class AUSClusterHealthStateGauge(AUSBaseMetric, GaugeMetric):
56
56
  return "aus_cluster_health_state"
57
57
 
58
58
 
59
+ class AUSClusterMissingVersionGateAgreementsGauge(AUSBaseMetric, GaugeMetric):
60
+ "The number of missing version gate agreements for a cluster and a target version prefix."
61
+
62
+ org_id: str
63
+ cluster_uuid: str
64
+ version_prefix: str
65
+
66
+ @classmethod
67
+ def name(cls) -> str:
68
+ return "aus_cluster_missing_version_gate_agreements"
69
+
70
+
59
71
  class AUSAddonVersionRemainingSoakDaysGauge(AUSClusterVersionRemainingSoakDaysGauge):
60
72
  "Remaining days a version needs to soak for an addon on a cluster"
61
73
 
@@ -250,6 +250,7 @@ def calculate_diff(
250
250
  ocm_api,
251
251
  version_data,
252
252
  addon_id,
253
+ integration=QONTRACT_INTEGRATION,
253
254
  )
254
255
  for current in addon_current_state:
255
256
  if addon_id == current.addon_id and (
@@ -76,7 +76,11 @@ class OCMClusterUpgradeSchedulerIntegration(
76
76
  )
77
77
 
78
78
  diffs = aus.calculate_diff(
79
- current_state, org_upgrade_spec, ocm_api, version_data
79
+ current_state,
80
+ org_upgrade_spec,
81
+ ocm_api,
82
+ version_data,
83
+ integration=self.name,
80
84
  )
81
85
  aus.act(dry_run, diffs, ocm_api)
82
86
 
@@ -94,6 +94,7 @@ class SaasAutoPromotionsManager:
94
94
 
95
95
  def init_external_dependencies(
96
96
  dry_run: bool,
97
+ thread_pool_size: int,
97
98
  env_name: str | None = None,
98
99
  app_name: str | None = None,
99
100
  ) -> tuple[
@@ -139,7 +140,11 @@ def init_external_dependencies(
139
140
  renderer=Renderer(),
140
141
  )
141
142
  saas_files = get_saas_files(env_name=env_name, app_name=app_name)
142
- saas_inventory = SaasFilesInventory(saas_files=saas_files)
143
+ saas_inventory = SaasFilesInventory(
144
+ saas_files=saas_files,
145
+ secret_reader=secret_reader,
146
+ thread_pool_size=thread_pool_size,
147
+ )
143
148
  saas_deploy_state = init_state(
144
149
  integration=OPENSHIFT_SAAS_DEPLOY, secret_reader=secret_reader
145
150
  )
@@ -181,7 +186,10 @@ def run(
181
186
  saas_deploy_state,
182
187
  sapm_state,
183
188
  ) = init_external_dependencies(
184
- dry_run=dry_run, env_name=env_name, app_name=app_name
189
+ dry_run=dry_run,
190
+ env_name=env_name,
191
+ app_name=app_name,
192
+ thread_pool_size=thread_pool_size,
185
193
  )
186
194
  if defer:
187
195
  defer(vcs.cleanup)
@@ -13,6 +13,7 @@ from reconcile.saas_auto_promotions_manager.publisher import (
13
13
  DeploymentInfo,
14
14
  Publisher,
15
15
  )
16
+ from reconcile.utils.slo_document_manager import SLODocumentManager
16
17
 
17
18
  CONTENT_HASH_LENGTH = 32
18
19
 
@@ -47,7 +48,9 @@ class Subscriber:
47
48
  uid: str,
48
49
  soak_days: int,
49
50
  blocked_versions: set[str],
51
+ hotfix_versions: set[str],
50
52
  schedule: str,
53
+ slo_document_manager: SLODocumentManager | None = None,
51
54
  ):
52
55
  self.saas_name = saas_name
53
56
  self.template_name = template_name
@@ -64,6 +67,8 @@ class Subscriber:
64
67
  self._content_hash = ""
65
68
  self._use_target_config_hash = use_target_config_hash
66
69
  self._blocked_versions = blocked_versions
70
+ self._hotfix_versions = hotfix_versions
71
+ self.slo_document_manager = slo_document_manager
67
72
 
68
73
  def has_diff(self) -> bool:
69
74
  current_hashes = {
@@ -192,6 +197,10 @@ class Subscriber:
192
197
  return
193
198
 
194
199
  desired_ref = next(iter(publisher_refs))
200
+ # validate slo gatekeeping
201
+ if self._has_breached_slos(desired_ref=desired_ref):
202
+ return
203
+
195
204
  if desired_ref in self._blocked_versions:
196
205
  logging.info(
197
206
  "Subscriber at path %s promotion stopped because of blocked ref: %s",
@@ -203,6 +212,25 @@ class Subscriber:
203
212
  # Passed all gates -> lets promote desired ref
204
213
  self.desired_ref = desired_ref
205
214
 
215
+ def _has_breached_slos(self, desired_ref: str) -> bool:
216
+ if self.slo_document_manager and desired_ref not in self._hotfix_versions:
217
+ breached_slos = self.slo_document_manager.get_breached_slos()
218
+ if breached_slos:
219
+ logging.info(
220
+ "Subscriber at path %s promotion stopped because following breached SLOs",
221
+ self.target_file_path,
222
+ )
223
+ for slo in breached_slos:
224
+ logging.info(
225
+ "SLO:%s of document %s is breached. Current value:%f Expected:%f ",
226
+ slo.slo.name,
227
+ slo.slo_document_name,
228
+ slo.current_slo_value,
229
+ slo.slo.slo_target,
230
+ )
231
+ return True
232
+ return False
233
+
206
234
  def _compute_desired_config_hashes(self) -> None:
207
235
  """
208
236
  Compute the desired config hashes for this subscriber.
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from collections import defaultdict
2
3
  from collections.abc import Iterable
3
4
 
4
5
  from reconcile.gql_definitions.common.saas_files import ParentSaasPromotionV1
@@ -8,7 +9,9 @@ from reconcile.saas_auto_promotions_manager.subscriber import (
8
9
  ConfigHash,
9
10
  Subscriber,
10
11
  )
11
- from reconcile.typed_queries.saas_files import SaasFile
12
+ from reconcile.typed_queries.saas_files import SaasFile, SaasResourceTemplateTarget
13
+ from reconcile.utils.secret_reader import SecretReaderBase
14
+ from reconcile.utils.slo_document_manager import SLODocumentManager
12
15
 
13
16
 
14
17
  class SaasFileInventoryError(Exception):
@@ -25,8 +28,15 @@ class SaasFilesInventory:
25
28
  This basically spans a directed graph, with subscribers as the root.
26
29
  """
27
30
 
28
- def __init__(self, saas_files: Iterable[SaasFile]):
31
+ def __init__(
32
+ self,
33
+ saas_files: Iterable[SaasFile],
34
+ secret_reader: SecretReaderBase,
35
+ thread_pool_size: int,
36
+ ):
29
37
  self._saas_files = saas_files
38
+ self.secret_reader = secret_reader
39
+ self.thread_pool_size = thread_pool_size
30
40
  self._channels_by_name: dict[str, Channel] = {}
31
41
  self.subscribers: list[Subscriber] = []
32
42
  self.publishers: list[Publisher] = []
@@ -86,10 +96,13 @@ class SaasFilesInventory:
86
96
 
87
97
  def _assemble_subscribers_with_auto_promotions(self) -> None:
88
98
  for saas_file in self._saas_files:
89
- blocked_versions: dict[str, set[str]] = {}
99
+ blocked_versions: dict[str, set[str]] = defaultdict(set[str])
100
+ hotfix_versions: dict[str, set[str]] = defaultdict(set[str])
90
101
  for code_component in saas_file.app.code_components or []:
91
102
  for version in code_component.blocked_versions or []:
92
- blocked_versions.setdefault(code_component.url, set()).add(version)
103
+ blocked_versions[code_component.url].add(version)
104
+ for hf_version in code_component.hotfix_versions or []:
105
+ hotfix_versions[code_component.url].add(hf_version)
93
106
  for resource_template in saas_file.resource_templates:
94
107
  for target in resource_template.targets:
95
108
  file_path = target.path or saas_file.path
@@ -101,6 +114,7 @@ class SaasFilesInventory:
101
114
  continue
102
115
  soak_days = target.promotion.soak_days or 0
103
116
  schedule = target.promotion.schedule or "* * * * *"
117
+
104
118
  subscriber = Subscriber(
105
119
  uid=target.uid(
106
120
  parent_saas_file_name=saas_file.name,
@@ -112,10 +126,10 @@ class SaasFilesInventory:
112
126
  ref=target.ref,
113
127
  target_namespace=target.namespace,
114
128
  soak_days=soak_days,
129
+ slo_document_manager=self._build_slo_document_manager(target),
115
130
  schedule=schedule,
116
- blocked_versions=blocked_versions.get(
117
- resource_template.url, set()
118
- ),
131
+ hotfix_versions=hotfix_versions[resource_template.url],
132
+ blocked_versions=blocked_versions[resource_template.url],
119
133
  use_target_config_hash=bool(
120
134
  target.promotion.redeploy_on_publisher_config_change
121
135
  ),
@@ -158,6 +172,17 @@ class SaasFilesInventory:
158
172
  self._channels_by_name[subscribe_channel]
159
173
  )
160
174
 
175
+ def _build_slo_document_manager(
176
+ self, target: SaasResourceTemplateTarget
177
+ ) -> SLODocumentManager | None:
178
+ if target.slos:
179
+ return SLODocumentManager(
180
+ slo_documents=target.slos,
181
+ secret_reader=self.secret_reader,
182
+ thread_pool_size=self.thread_pool_size,
183
+ )
184
+ return None
185
+
161
186
  def _remove_unsupported(self) -> None:
162
187
  """
163
188
  Lets remove subscribers from which we know we do not support them and log an error.
@@ -39,6 +39,7 @@ from reconcile.gql_definitions.common.saasherder_settings import AppInterfaceSet
39
39
  from reconcile.gql_definitions.common.saasherder_settings import (
40
40
  query as saasherder_settings_query,
41
41
  )
42
+ from reconcile.gql_definitions.fragments.saas_slo_document import SLODocument
42
43
  from reconcile.gql_definitions.fragments.saas_target_namespace import (
43
44
  SaasTargetNamespace,
44
45
  )
@@ -63,6 +64,7 @@ class SaasResourceTemplateTarget(ConfiguredBaseModel):
63
64
  secret_parameters: (
64
65
  list[SaasResourceTemplateTargetV2_SaasSecretParametersV1] | None
65
66
  ) = Field(..., alias="secretParameters")
67
+ slos: list[SLODocument] | None = Field(..., alias="slos")
66
68
  upstream: SaasResourceTemplateTargetUpstreamV1 | None = Field(..., alias="upstream")
67
69
  images: list[SaasResourceTemplateTargetImageV1] | None = Field(..., alias="images")
68
70
  disable: bool | None = Field(..., alias="disable")