zenml-nightly 0.83.1.dev20250625__py3-none-any.whl → 0.83.1.dev20250627__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.
- zenml/VERSION +1 -1
- zenml/cli/base.py +3 -2
- zenml/cli/service_connectors.py +5 -12
- zenml/cli/stack.py +1 -5
- zenml/cli/utils.py +8 -52
- zenml/client.py +40 -42
- zenml/integrations/aws/container_registries/aws_container_registry.py +3 -1
- zenml/integrations/aws/flavors/sagemaker_orchestrator_flavor.py +1 -1
- zenml/integrations/databricks/orchestrators/databricks_orchestrator_entrypoint_config.py +8 -3
- zenml/integrations/integration.py +23 -58
- zenml/models/__init__.py +2 -0
- zenml/models/v2/core/pipeline_run.py +1 -0
- zenml/models/v2/core/service_connector.py +178 -108
- zenml/service_connectors/service_connector.py +11 -61
- zenml/service_connectors/service_connector_utils.py +4 -2
- zenml/stack/stack_component.py +1 -1
- zenml/utils/package_utils.py +111 -1
- zenml/zen_server/routers/service_connectors_endpoints.py +7 -22
- zenml/zen_stores/migrations/versions/5bb25e95849c_add_internal_secrets.py +62 -0
- zenml/zen_stores/rest_zen_store.py +57 -4
- zenml/zen_stores/schemas/pipeline_run_schemas.py +10 -10
- zenml/zen_stores/schemas/secret_schemas.py +5 -0
- zenml/zen_stores/schemas/service_connector_schemas.py +16 -14
- zenml/zen_stores/schemas/step_run_schemas.py +44 -14
- zenml/zen_stores/secrets_stores/service_connector_secrets_store.py +4 -1
- zenml/zen_stores/sql_zen_store.py +238 -122
- zenml/zen_stores/zen_store_interface.py +9 -1
- {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250627.dist-info}/METADATA +1 -1
- {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250627.dist-info}/RECORD +32 -32
- zenml/utils/integration_utils.py +0 -34
- {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250627.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250627.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.83.1.dev20250625.dist-info → zenml_nightly-0.83.1.dev20250627.dist-info}/entry_points.txt +0 -0
@@ -16,9 +16,15 @@
|
|
16
16
|
import json
|
17
17
|
from datetime import datetime
|
18
18
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
19
|
-
from uuid import UUID
|
20
19
|
|
21
|
-
from pydantic import
|
20
|
+
from pydantic import (
|
21
|
+
Field,
|
22
|
+
GetCoreSchemaHandler,
|
23
|
+
SecretStr,
|
24
|
+
ValidationError,
|
25
|
+
model_validator,
|
26
|
+
)
|
27
|
+
from pydantic_core import CoreSchema, core_schema
|
22
28
|
|
23
29
|
from zenml.constants import STR_FIELD_MAX_LENGTH
|
24
30
|
from zenml.logger import get_logger
|
@@ -38,6 +44,120 @@ from zenml.utils.secret_utils import PlainSerializedSecretStr
|
|
38
44
|
|
39
45
|
logger = get_logger(__name__)
|
40
46
|
|
47
|
+
# ------------------ Configuration Model ------------------
|
48
|
+
|
49
|
+
|
50
|
+
class ServiceConnectorConfiguration(Dict[str, Any]):
|
51
|
+
"""Model for service connector configuration."""
|
52
|
+
|
53
|
+
@classmethod
|
54
|
+
def from_dict(
|
55
|
+
cls, data: Dict[str, Any]
|
56
|
+
) -> "ServiceConnectorConfiguration":
|
57
|
+
"""Create a configuration model from a dictionary.
|
58
|
+
|
59
|
+
Args:
|
60
|
+
data: The dictionary to create the configuration model from.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
A configuration model.
|
64
|
+
"""
|
65
|
+
return cls(**data)
|
66
|
+
|
67
|
+
@property
|
68
|
+
def secrets(self) -> Dict[str, PlainSerializedSecretStr]:
|
69
|
+
"""Get the secrets from the configuration.
|
70
|
+
|
71
|
+
Returns:
|
72
|
+
A dictionary of secrets.
|
73
|
+
"""
|
74
|
+
return {k: v for k, v in self.items() if isinstance(v, SecretStr)}
|
75
|
+
|
76
|
+
@property
|
77
|
+
def plain_secrets(self) -> Dict[str, str]:
|
78
|
+
"""Get the plain secrets from the configuration.
|
79
|
+
|
80
|
+
Returns:
|
81
|
+
A dictionary of secrets.
|
82
|
+
"""
|
83
|
+
return {
|
84
|
+
k: v.get_secret_value()
|
85
|
+
for k, v in self.items()
|
86
|
+
if isinstance(v, SecretStr)
|
87
|
+
}
|
88
|
+
|
89
|
+
@property
|
90
|
+
def non_secrets(self) -> Dict[str, Any]:
|
91
|
+
"""Get the non-secrets from the configuration.
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
A dictionary of non-secrets.
|
95
|
+
"""
|
96
|
+
return {k: v for k, v in self.items() if not isinstance(v, SecretStr)}
|
97
|
+
|
98
|
+
@property
|
99
|
+
def plain(self) -> Dict[str, Any]:
|
100
|
+
"""Get the configuration with secrets unpacked.
|
101
|
+
|
102
|
+
Returns:
|
103
|
+
A dictionary of configuration with secrets unpacked.
|
104
|
+
"""
|
105
|
+
return {
|
106
|
+
k: v.get_secret_value() if isinstance(v, SecretStr) else v
|
107
|
+
for k, v in self.items()
|
108
|
+
}
|
109
|
+
|
110
|
+
def get_plain(self, key: str, default: Any = None) -> Any:
|
111
|
+
"""Get the plain value for the given key.
|
112
|
+
|
113
|
+
Args:
|
114
|
+
key: The key to get the value for.
|
115
|
+
default: The default value to return if the key is not found.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
The plain value for the given key.
|
119
|
+
"""
|
120
|
+
result = self.get(key, default)
|
121
|
+
if isinstance(result, SecretStr):
|
122
|
+
return result.get_secret_value()
|
123
|
+
return result
|
124
|
+
|
125
|
+
def add_secrets(self, secrets: Dict[str, str]) -> None:
|
126
|
+
"""Add the secrets to the configuration.
|
127
|
+
|
128
|
+
Args:
|
129
|
+
secrets: The secrets to add to the configuration.
|
130
|
+
"""
|
131
|
+
self.update({k: SecretStr(v) for k, v in secrets.items()})
|
132
|
+
|
133
|
+
@classmethod
|
134
|
+
def __get_pydantic_core_schema__(
|
135
|
+
cls, source_type: Any, handler: GetCoreSchemaHandler
|
136
|
+
) -> CoreSchema:
|
137
|
+
"""Additional method for pydantic to recognize it as a valid type.
|
138
|
+
|
139
|
+
Args:
|
140
|
+
source_type: the source type
|
141
|
+
handler: the handler
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
the schema for the custom type.
|
145
|
+
"""
|
146
|
+
return core_schema.no_info_after_validator_function(
|
147
|
+
cls,
|
148
|
+
handler(
|
149
|
+
core_schema.dict_schema(
|
150
|
+
keys_schema=core_schema.str_schema(),
|
151
|
+
values_schema=core_schema.any_schema(),
|
152
|
+
)
|
153
|
+
),
|
154
|
+
serialization=core_schema.plain_serializer_function_ser_schema(
|
155
|
+
lambda v: v.plain,
|
156
|
+
when_used="json",
|
157
|
+
),
|
158
|
+
)
|
159
|
+
|
160
|
+
|
41
161
|
# ------------------ Request Model ------------------
|
42
162
|
|
43
163
|
|
@@ -97,13 +217,9 @@ class ServiceConnectorRequest(UserScopedRequest):
|
|
97
217
|
"connectors and authentication methods that involve generating "
|
98
218
|
"temporary credentials from the ones configured in the connector.",
|
99
219
|
)
|
100
|
-
configuration:
|
101
|
-
default_factory=
|
102
|
-
title="The service connector configuration
|
103
|
-
)
|
104
|
-
secrets: Dict[str, Optional[PlainSerializedSecretStr]] = Field(
|
105
|
-
default_factory=dict,
|
106
|
-
title="The service connector secrets.",
|
220
|
+
configuration: ServiceConnectorConfiguration = Field(
|
221
|
+
default_factory=ServiceConnectorConfiguration,
|
222
|
+
title="The service connector configuration.",
|
107
223
|
)
|
108
224
|
labels: Dict[str, str] = Field(
|
109
225
|
default_factory=dict,
|
@@ -178,7 +294,6 @@ class ServiceConnectorRequest(UserScopedRequest):
|
|
178
294
|
resource_types: Optional[Union[str, List[str]]] = None,
|
179
295
|
resource_id: Optional[str] = None,
|
180
296
|
configuration: Optional[Dict[str, Any]] = None,
|
181
|
-
secrets: Optional[Dict[str, Optional[SecretStr]]] = None,
|
182
297
|
) -> None:
|
183
298
|
"""Validate and configure the resources that the connector can be used to access.
|
184
299
|
|
@@ -191,7 +306,6 @@ class ServiceConnectorRequest(UserScopedRequest):
|
|
191
306
|
resource_id: Uniquely identifies a specific resource instance that
|
192
307
|
the connector instance can be used to access.
|
193
308
|
configuration: The connector configuration.
|
194
|
-
secrets: The connector secrets.
|
195
309
|
"""
|
196
310
|
_validate_and_configure_resources(
|
197
311
|
connector=self,
|
@@ -199,7 +313,6 @@ class ServiceConnectorRequest(UserScopedRequest):
|
|
199
313
|
resource_types=resource_types,
|
200
314
|
resource_id=resource_id,
|
201
315
|
configuration=configuration,
|
202
|
-
secrets=secrets,
|
203
316
|
)
|
204
317
|
|
205
318
|
|
@@ -219,10 +332,9 @@ class ServiceConnectorUpdate(BaseUpdate):
|
|
219
332
|
|
220
333
|
In addition to the above exceptions, the following rules apply:
|
221
334
|
|
222
|
-
* the `configuration`
|
223
|
-
|
224
|
-
|
225
|
-
will replace the existing configuration and secrets values.
|
335
|
+
* the `configuration` field represents a full valid configuration update,
|
336
|
+
not just a partial update. If it is set (i.e. not None) in the update,
|
337
|
+
its values will replace the existing configuration values.
|
226
338
|
* the `labels` field is also a full labels update: if set (i.e. not
|
227
339
|
`None`), all existing labels are removed and replaced by the new labels
|
228
340
|
in the update.
|
@@ -289,12 +401,8 @@ class ServiceConnectorUpdate(BaseUpdate):
|
|
289
401
|
"configured in the connector.",
|
290
402
|
default=None,
|
291
403
|
)
|
292
|
-
configuration: Optional[
|
293
|
-
title="The service connector configuration
|
294
|
-
default=None,
|
295
|
-
)
|
296
|
-
secrets: Optional[Dict[str, Optional[PlainSerializedSecretStr]]] = Field(
|
297
|
-
title="The service connector secrets.",
|
404
|
+
configuration: Optional[ServiceConnectorConfiguration] = Field(
|
405
|
+
title="The service connector full configuration replacement.",
|
298
406
|
default=None,
|
299
407
|
)
|
300
408
|
labels: Optional[Dict[str, str]] = Field(
|
@@ -348,7 +456,6 @@ class ServiceConnectorUpdate(BaseUpdate):
|
|
348
456
|
resource_types: Optional[Union[str, List[str]]] = None,
|
349
457
|
resource_id: Optional[str] = None,
|
350
458
|
configuration: Optional[Dict[str, Any]] = None,
|
351
|
-
secrets: Optional[Dict[str, Optional[SecretStr]]] = None,
|
352
459
|
) -> None:
|
353
460
|
"""Validate and configure the resources that the connector can be used to access.
|
354
461
|
|
@@ -361,7 +468,6 @@ class ServiceConnectorUpdate(BaseUpdate):
|
|
361
468
|
resource_id: Uniquely identifies a specific resource instance that
|
362
469
|
the connector instance can be used to access.
|
363
470
|
configuration: The connector configuration.
|
364
|
-
secrets: The connector secrets.
|
365
471
|
"""
|
366
472
|
_validate_and_configure_resources(
|
367
473
|
connector=self,
|
@@ -369,7 +475,6 @@ class ServiceConnectorUpdate(BaseUpdate):
|
|
369
475
|
resource_types=resource_types,
|
370
476
|
resource_id=resource_id,
|
371
477
|
configuration=configuration,
|
372
|
-
secrets=secrets,
|
373
478
|
)
|
374
479
|
|
375
480
|
def convert_to_request(self) -> "ServiceConnectorRequest":
|
@@ -447,14 +552,9 @@ class ServiceConnectorResponseBody(UserScopedResponseBody):
|
|
447
552
|
class ServiceConnectorResponseMetadata(UserScopedResponseMetadata):
|
448
553
|
"""Response metadata for service connectors."""
|
449
554
|
|
450
|
-
configuration:
|
451
|
-
default_factory=
|
452
|
-
title="The service connector configuration
|
453
|
-
)
|
454
|
-
secret_id: Optional[UUID] = Field(
|
455
|
-
default=None,
|
456
|
-
title="The ID of the secret that contains the service connector "
|
457
|
-
"secret configuration values.",
|
555
|
+
configuration: ServiceConnectorConfiguration = Field(
|
556
|
+
default_factory=ServiceConnectorConfiguration,
|
557
|
+
title="The service connector configuration.",
|
458
558
|
)
|
459
559
|
expiration_seconds: Optional[int] = Field(
|
460
560
|
default=None,
|
@@ -463,10 +563,6 @@ class ServiceConnectorResponseMetadata(UserScopedResponseMetadata):
|
|
463
563
|
"connectors and authentication methods that involve generating "
|
464
564
|
"temporary credentials from the ones configured in the connector.",
|
465
565
|
)
|
466
|
-
secrets: Dict[str, Optional[PlainSerializedSecretStr]] = Field(
|
467
|
-
default_factory=dict,
|
468
|
-
title="The service connector secrets.",
|
469
|
-
)
|
470
566
|
labels: Dict[str, str] = Field(
|
471
567
|
default_factory=dict,
|
472
568
|
title="Service connector labels.",
|
@@ -604,19 +700,6 @@ class ServiceConnectorResponse(
|
|
604
700
|
"""
|
605
701
|
return not self.is_multi_type and not self.is_multi_instance
|
606
702
|
|
607
|
-
@property
|
608
|
-
def full_configuration(self) -> Dict[str, str]:
|
609
|
-
"""Get the full connector configuration, including secrets.
|
610
|
-
|
611
|
-
Returns:
|
612
|
-
The full connector configuration, including secrets.
|
613
|
-
"""
|
614
|
-
config = self.configuration.copy()
|
615
|
-
config.update(
|
616
|
-
{k: v.get_secret_value() for k, v in self.secrets.items() if v}
|
617
|
-
)
|
618
|
-
return config
|
619
|
-
|
620
703
|
def set_connector_type(
|
621
704
|
self, value: Union[str, "ServiceConnectorTypeModel"]
|
622
705
|
) -> None:
|
@@ -627,13 +710,22 @@ class ServiceConnectorResponse(
|
|
627
710
|
"""
|
628
711
|
self.get_body().connector_type = value
|
629
712
|
|
713
|
+
def validate_configuration(self) -> None:
|
714
|
+
"""Validate the configuration of the connector."""
|
715
|
+
if isinstance(self.connector_type, ServiceConnectorTypeModel):
|
716
|
+
self.validate_and_configure_resources(
|
717
|
+
connector_type=self.connector_type,
|
718
|
+
resource_types=self.resource_types,
|
719
|
+
resource_id=self.resource_id,
|
720
|
+
configuration=self.configuration,
|
721
|
+
)
|
722
|
+
|
630
723
|
def validate_and_configure_resources(
|
631
724
|
self,
|
632
725
|
connector_type: "ServiceConnectorTypeModel",
|
633
726
|
resource_types: Optional[Union[str, List[str]]] = None,
|
634
727
|
resource_id: Optional[str] = None,
|
635
728
|
configuration: Optional[Dict[str, Any]] = None,
|
636
|
-
secrets: Optional[Dict[str, Optional[SecretStr]]] = None,
|
637
729
|
) -> None:
|
638
730
|
"""Validate and configure the resources that the connector can be used to access.
|
639
731
|
|
@@ -646,7 +738,6 @@ class ServiceConnectorResponse(
|
|
646
738
|
resource_id: Uniquely identifies a specific resource instance that
|
647
739
|
the connector instance can be used to access.
|
648
740
|
configuration: The connector configuration.
|
649
|
-
secrets: The connector secrets.
|
650
741
|
"""
|
651
742
|
_validate_and_configure_resources(
|
652
743
|
connector=self,
|
@@ -654,7 +745,6 @@ class ServiceConnectorResponse(
|
|
654
745
|
resource_types=resource_types,
|
655
746
|
resource_id=resource_id,
|
656
747
|
configuration=configuration,
|
657
|
-
secrets=secrets,
|
658
748
|
)
|
659
749
|
|
660
750
|
# Body and metadata properties
|
@@ -731,7 +821,7 @@ class ServiceConnectorResponse(
|
|
731
821
|
return self.get_body().expires_skew_tolerance
|
732
822
|
|
733
823
|
@property
|
734
|
-
def configuration(self) ->
|
824
|
+
def configuration(self) -> ServiceConnectorConfiguration:
|
735
825
|
"""The `configuration` property.
|
736
826
|
|
737
827
|
Returns:
|
@@ -739,14 +829,20 @@ class ServiceConnectorResponse(
|
|
739
829
|
"""
|
740
830
|
return self.get_metadata().configuration
|
741
831
|
|
742
|
-
|
743
|
-
|
744
|
-
|
832
|
+
def remove_secrets(self) -> None:
|
833
|
+
"""Remove the secrets from the configuration."""
|
834
|
+
metadata = self.get_metadata()
|
835
|
+
metadata.configuration = ServiceConnectorConfiguration(
|
836
|
+
**metadata.configuration.non_secrets
|
837
|
+
)
|
745
838
|
|
746
|
-
|
747
|
-
|
839
|
+
def add_secrets(self, secrets: Dict[str, str]) -> None:
|
840
|
+
"""Add the secrets to the configuration.
|
841
|
+
|
842
|
+
Args:
|
843
|
+
secrets: The secrets to add to the configuration.
|
748
844
|
"""
|
749
|
-
|
845
|
+
self.get_metadata().configuration.add_secrets(secrets)
|
750
846
|
|
751
847
|
@property
|
752
848
|
def expiration_seconds(self) -> Optional[int]:
|
@@ -757,15 +853,6 @@ class ServiceConnectorResponse(
|
|
757
853
|
"""
|
758
854
|
return self.get_metadata().expiration_seconds
|
759
855
|
|
760
|
-
@property
|
761
|
-
def secrets(self) -> Dict[str, Optional[SecretStr]]:
|
762
|
-
"""The `secrets` property.
|
763
|
-
|
764
|
-
Returns:
|
765
|
-
the value of the property.
|
766
|
-
"""
|
767
|
-
return self.get_metadata().secrets
|
768
|
-
|
769
856
|
@property
|
770
857
|
def labels(self) -> Dict[str, str]:
|
771
858
|
"""The `labels` property.
|
@@ -826,12 +913,6 @@ class ServiceConnectorFilter(UserScopedFilter):
|
|
826
913
|
"value, the filter will match all service connectors that have that "
|
827
914
|
"label present, regardless of value.",
|
828
915
|
)
|
829
|
-
secret_id: Optional[Union[UUID, str]] = Field(
|
830
|
-
default=None,
|
831
|
-
title="Filter by the ID of the secret that contains the service "
|
832
|
-
"connector's credentials",
|
833
|
-
union_mode="left_to_right",
|
834
|
-
)
|
835
916
|
|
836
917
|
# Use this internally to configure and access the labels as a dictionary
|
837
918
|
labels: Optional[Dict[str, Optional[str]]] = Field(
|
@@ -877,7 +958,6 @@ def _validate_and_configure_resources(
|
|
877
958
|
resource_types: Optional[Union[str, List[str]]] = None,
|
878
959
|
resource_id: Optional[str] = None,
|
879
960
|
configuration: Optional[Dict[str, Any]] = None,
|
880
|
-
secrets: Optional[Dict[str, Optional[SecretStr]]] = None,
|
881
961
|
) -> None:
|
882
962
|
"""Validate and configure the resources that a connector can be used to access.
|
883
963
|
|
@@ -891,7 +971,6 @@ def _validate_and_configure_resources(
|
|
891
971
|
resource_id: Uniquely identifies a specific resource instance that
|
892
972
|
the connector instance can be used to access.
|
893
973
|
configuration: The connector configuration.
|
894
|
-
secrets: The connector secrets.
|
895
974
|
|
896
975
|
Raises:
|
897
976
|
ValueError: If the connector configuration is not valid.
|
@@ -970,16 +1049,14 @@ def _validate_and_configure_resources(
|
|
970
1049
|
)
|
971
1050
|
update_connector_body.supports_instances = False
|
972
1051
|
|
973
|
-
if configuration is None
|
974
|
-
# No configuration
|
1052
|
+
if configuration is None:
|
1053
|
+
# No configuration provided
|
975
1054
|
return
|
976
1055
|
|
977
|
-
update_connector_metadata.configuration =
|
978
|
-
update_connector_metadata.secrets = {}
|
1056
|
+
update_connector_metadata.configuration = ServiceConnectorConfiguration()
|
979
1057
|
|
980
|
-
# Validate and configure the connector configuration
|
1058
|
+
# Validate and configure the connector configuration
|
981
1059
|
configuration = configuration or {}
|
982
|
-
secrets = secrets or {}
|
983
1060
|
supported_attrs = []
|
984
1061
|
for attr_name, attr_schema in auth_method_spec.config_schema.get(
|
985
1062
|
"properties", {}
|
@@ -1009,7 +1086,10 @@ def _validate_and_configure_resources(
|
|
1009
1086
|
secret = attr_schema.get("format", "") == "password"
|
1010
1087
|
attr_type = attr_schema.get("type", "string")
|
1011
1088
|
|
1012
|
-
value = configuration.get(attr_name
|
1089
|
+
value = configuration.get(attr_name)
|
1090
|
+
if isinstance(value, SecretStr):
|
1091
|
+
value = value.get_secret_value()
|
1092
|
+
|
1013
1093
|
if required:
|
1014
1094
|
if value is None:
|
1015
1095
|
raise ValueError(
|
@@ -1019,19 +1099,21 @@ def _validate_and_configure_resources(
|
|
1019
1099
|
elif value is None:
|
1020
1100
|
continue
|
1021
1101
|
|
1022
|
-
# Split the configuration into secrets and non-secrets
|
1023
1102
|
if secret:
|
1024
|
-
if isinstance(value,
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
|
1103
|
+
if not isinstance(value, str):
|
1104
|
+
raise ValueError(
|
1105
|
+
f"connector configuration is not valid: attribute '{attr_name}' "
|
1106
|
+
"is a secret but is not a string"
|
1107
|
+
)
|
1108
|
+
value = SecretStr(value)
|
1109
|
+
|
1110
|
+
elif attr_type == "array" and isinstance(value, str):
|
1111
|
+
try:
|
1112
|
+
value = json.loads(value)
|
1113
|
+
except json.decoder.JSONDecodeError:
|
1114
|
+
value = value.split(",")
|
1115
|
+
|
1116
|
+
update_connector_metadata.configuration[attr_name] = value
|
1035
1117
|
|
1036
1118
|
# Warn about attributes that are not part of the configuration schema
|
1037
1119
|
for attr_name in set(list(configuration.keys())) - set(supported_attrs):
|
@@ -1040,15 +1122,3 @@ def _validate_and_configure_resources(
|
|
1040
1122
|
f"configuration {attr_name}. Supported attributes are: "
|
1041
1123
|
f"{supported_attrs}",
|
1042
1124
|
)
|
1043
|
-
# Warn about secrets that are not part of the configuration schema
|
1044
|
-
connector_secrets = (
|
1045
|
-
set(connector.secrets.keys())
|
1046
|
-
if connector.secrets is not None
|
1047
|
-
else set()
|
1048
|
-
)
|
1049
|
-
for attr_name in set(secrets.keys()) - connector_secrets:
|
1050
|
-
logger.warning(
|
1051
|
-
f"Ignoring unknown attribute in connector '{connector.name}' "
|
1052
|
-
f"configuration {attr_name}. Supported attributes are: "
|
1053
|
-
f"{supported_attrs}",
|
1054
|
-
)
|
@@ -31,12 +31,10 @@ from uuid import UUID
|
|
31
31
|
|
32
32
|
from pydantic import (
|
33
33
|
BaseModel,
|
34
|
-
SecretStr,
|
35
34
|
ValidationError,
|
36
35
|
)
|
37
36
|
from pydantic._internal._model_construction import ModelMetaclass
|
38
37
|
|
39
|
-
from zenml.client import Client
|
40
38
|
from zenml.constants import (
|
41
39
|
ENV_ZENML_ENABLE_IMPLICIT_AUTH_METHODS,
|
42
40
|
SERVICE_CONNECTOR_SKEW_TOLERANCE_SECONDS,
|
@@ -63,32 +61,6 @@ logger = get_logger(__name__)
|
|
63
61
|
class AuthenticationConfig(BaseModel):
|
64
62
|
"""Base authentication configuration."""
|
65
63
|
|
66
|
-
@property
|
67
|
-
def secret_values(self) -> Dict[str, SecretStr]:
|
68
|
-
"""Get the secret values as a dictionary.
|
69
|
-
|
70
|
-
Returns:
|
71
|
-
A dictionary of all secret values in the configuration.
|
72
|
-
"""
|
73
|
-
return {
|
74
|
-
k: v
|
75
|
-
for k, v in self.model_dump(exclude_none=True).items()
|
76
|
-
if isinstance(v, SecretStr)
|
77
|
-
}
|
78
|
-
|
79
|
-
@property
|
80
|
-
def non_secret_values(self) -> Dict[str, str]:
|
81
|
-
"""Get the non-secret values as a dictionary.
|
82
|
-
|
83
|
-
Returns:
|
84
|
-
A dictionary of all non-secret values in the configuration.
|
85
|
-
"""
|
86
|
-
return {
|
87
|
-
k: v
|
88
|
-
for k, v in self.model_dump(exclude_none=True).items()
|
89
|
-
if not isinstance(v, SecretStr)
|
90
|
-
}
|
91
|
-
|
92
64
|
@property
|
93
65
|
def all_values(self) -> Dict[str, Any]:
|
94
66
|
"""Get all values as a dictionary.
|
@@ -643,34 +615,7 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
643
615
|
) from e
|
644
616
|
|
645
617
|
# Unpack the authentication configuration
|
646
|
-
config = model.configuration.
|
647
|
-
if isinstance(model, ServiceConnectorResponse) and model.secret_id:
|
648
|
-
try:
|
649
|
-
secret = Client().get_secret(model.secret_id)
|
650
|
-
except KeyError as e:
|
651
|
-
raise ValueError(
|
652
|
-
f"could not fetch secret with ID '{model.secret_id}' "
|
653
|
-
f"referenced in the connector configuration: {e}"
|
654
|
-
) from e
|
655
|
-
|
656
|
-
if secret.has_missing_values:
|
657
|
-
raise ValueError(
|
658
|
-
f"secret with ID '{model.secret_id}' referenced in the "
|
659
|
-
"connector configuration has missing values. This can "
|
660
|
-
"happen for example if your user lacks the permissions "
|
661
|
-
"required to access the secret."
|
662
|
-
)
|
663
|
-
|
664
|
-
config.update(secret.secret_values)
|
665
|
-
|
666
|
-
if model.secrets:
|
667
|
-
config.update(
|
668
|
-
{
|
669
|
-
k: v.get_secret_value()
|
670
|
-
for k, v in model.secrets.items()
|
671
|
-
if v
|
672
|
-
}
|
673
|
-
)
|
618
|
+
config = model.configuration.plain
|
674
619
|
|
675
620
|
if method_spec.config_class is None:
|
676
621
|
raise ValueError(
|
@@ -683,8 +628,15 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
683
628
|
try:
|
684
629
|
auth_config = method_spec.config_class(**config)
|
685
630
|
except ValidationError as e:
|
631
|
+
hint = ""
|
632
|
+
if isinstance(model, ServiceConnectorResponse):
|
633
|
+
hint = (
|
634
|
+
"If the error is related to missing secret attributes, "
|
635
|
+
"you might be missing permissions to access the service "
|
636
|
+
"connector's secret values."
|
637
|
+
)
|
686
638
|
raise ValueError(
|
687
|
-
f"connector configuration is not valid: {e}"
|
639
|
+
f"connector configuration is not valid: {e}\n{hint}"
|
688
640
|
) from e
|
689
641
|
|
690
642
|
assert isinstance(auth_config, AuthenticationConfig)
|
@@ -748,8 +700,7 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
748
700
|
connector_type=spec,
|
749
701
|
resource_types=self.resource_type,
|
750
702
|
resource_id=self.resource_id,
|
751
|
-
configuration=self.config.
|
752
|
-
secrets=self.config.secret_values, # type: ignore[arg-type]
|
703
|
+
configuration=self.config.all_values,
|
753
704
|
)
|
754
705
|
|
755
706
|
return model
|
@@ -815,8 +766,7 @@ class ServiceConnector(BaseModel, metaclass=ServiceConnectorMeta):
|
|
815
766
|
connector_type=spec,
|
816
767
|
resource_types=self.resource_type,
|
817
768
|
resource_id=self.resource_id,
|
818
|
-
configuration=self.config.
|
819
|
-
secrets=self.config.secret_values, # type: ignore[arg-type]
|
769
|
+
configuration=self.config.all_values,
|
820
770
|
)
|
821
771
|
|
822
772
|
return model
|
@@ -20,6 +20,7 @@ from zenml.client import Client
|
|
20
20
|
from zenml.enums import StackComponentType
|
21
21
|
from zenml.models import (
|
22
22
|
ResourcesInfo,
|
23
|
+
ServiceConnectorConfiguration,
|
23
24
|
ServiceConnectorInfo,
|
24
25
|
ServiceConnectorRequest,
|
25
26
|
ServiceConnectorResourcesInfo,
|
@@ -187,8 +188,9 @@ def get_resources_options_from_resource_model_for_full_stack(
|
|
187
188
|
name="fake",
|
188
189
|
connector_type=connector_details.type,
|
189
190
|
auth_method=connector_details.auth_method,
|
190
|
-
configuration=
|
191
|
-
|
191
|
+
configuration=ServiceConnectorConfiguration(
|
192
|
+
**connector_details.configuration
|
193
|
+
),
|
192
194
|
labels={},
|
193
195
|
),
|
194
196
|
list_resources=True,
|
zenml/stack/stack_component.py
CHANGED
@@ -527,7 +527,7 @@ class StackComponent:
|
|
527
527
|
)
|
528
528
|
|
529
529
|
# Use the current config as a base
|
530
|
-
settings_dict = self.config.model_dump()
|
530
|
+
settings_dict = self.config.model_dump(exclude_unset=True)
|
531
531
|
|
532
532
|
if key in all_settings:
|
533
533
|
settings_dict.update(dict(all_settings[key]))
|