zenml-nightly 0.73.0.dev20250131__py3-none-any.whl → 0.73.0.dev20250201__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/integrations/azure/artifact_stores/azure_artifact_store.py +18 -9
- zenml/integrations/azure/service_connectors/azure_service_connector.py +146 -29
- {zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/METADATA +2 -1
- {zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/RECORD +8 -8
- {zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/entry_points.txt +0 -0
zenml/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.73.0.
|
1
|
+
0.73.0.dev20250201
|
@@ -64,7 +64,10 @@ class AzureArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
|
64
64
|
"""
|
65
65
|
connector = self.get_connector()
|
66
66
|
if connector:
|
67
|
-
from azure.identity import
|
67
|
+
from azure.identity import (
|
68
|
+
ClientSecretCredential,
|
69
|
+
DefaultAzureCredential,
|
70
|
+
)
|
68
71
|
from azure.storage.blob import BlobServiceClient
|
69
72
|
|
70
73
|
client = connector.connect()
|
@@ -77,18 +80,24 @@ class AzureArtifactStore(BaseArtifactStore, AuthenticationMixin):
|
|
77
80
|
)
|
78
81
|
# Get the credentials from the client
|
79
82
|
credentials = client.credential
|
80
|
-
|
83
|
+
|
84
|
+
if isinstance(credentials, ClientSecretCredential):
|
85
|
+
return AzureSecretSchema(
|
86
|
+
client_id=credentials._client_id,
|
87
|
+
client_secret=credentials._client_credential,
|
88
|
+
tenant_id=credentials._tenant_id,
|
89
|
+
account_name=client.account_name,
|
90
|
+
)
|
91
|
+
|
92
|
+
elif isinstance(credentials, DefaultAzureCredential):
|
93
|
+
return AzureSecretSchema(account_name=client.account_name)
|
94
|
+
|
95
|
+
else:
|
81
96
|
raise RuntimeError(
|
82
97
|
"The Azure Artifact Store connector can only be used "
|
83
98
|
"with a service connector that is configured with "
|
84
|
-
"Azure service principal credentials
|
99
|
+
"Azure service principal credentials or implicit authentication"
|
85
100
|
)
|
86
|
-
return AzureSecretSchema(
|
87
|
-
client_id=credentials._client_id,
|
88
|
-
client_secret=credentials._client_credential,
|
89
|
-
tenant_id=credentials._tenant_id,
|
90
|
-
account_name=client.account_name,
|
91
|
-
)
|
92
101
|
|
93
102
|
secret = self.get_typed_authentication_secret(
|
94
103
|
expected_schema_type=AzureSecretSchema
|
@@ -14,12 +14,14 @@
|
|
14
14
|
"""Azure Service Connector."""
|
15
15
|
|
16
16
|
import datetime
|
17
|
+
import json
|
17
18
|
import logging
|
18
19
|
import re
|
19
20
|
import subprocess
|
20
21
|
from typing import Any, Dict, List, Optional, Tuple
|
21
22
|
from uuid import UUID
|
22
23
|
|
24
|
+
import requests
|
23
25
|
import yaml
|
24
26
|
from azure.core.credentials import AccessToken, TokenCredential
|
25
27
|
from azure.core.exceptions import AzureError
|
@@ -69,6 +71,7 @@ logger = get_logger(__name__)
|
|
69
71
|
AZURE_MANAGEMENT_TOKEN_SCOPE = "https://management.azure.com/.default"
|
70
72
|
AZURE_SESSION_TOKEN_DEFAULT_EXPIRATION_TIME = 60 * 60 # 1 hour
|
71
73
|
AZURE_SESSION_EXPIRATION_BUFFER = 15 # 15 minutes
|
74
|
+
AZURE_ACR_OAUTH_SCOPE = "repository:*:*"
|
72
75
|
|
73
76
|
|
74
77
|
class AzureBaseConfig(AuthenticationConfig):
|
@@ -362,8 +365,8 @@ neither a storage account nor a resource group is configured in the connector,
|
|
362
365
|
all blob storage containers in all accessible storage accounts will be
|
363
366
|
accessible.
|
364
367
|
|
365
|
-
The only Azure authentication
|
366
|
-
|
368
|
+
The only Azure authentication methods that work with Azure blob storage resources are the implicit
|
369
|
+
authentication and the service principal authentication method.
|
367
370
|
""",
|
368
371
|
auth_methods=AzureAuthenticationMethods.values(),
|
369
372
|
# Request a blob container to be configured in the
|
@@ -435,12 +438,10 @@ following formats:
|
|
435
438
|
If a resource group is configured in the connector, only ACR registries in that
|
436
439
|
resource group will be accessible.
|
437
440
|
|
438
|
-
If an authentication method other than the Azure service principal is used
|
439
|
-
|
440
|
-
|
441
|
-
[documentation on the admin account](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication#admin-account
|
442
|
-
)
|
443
|
-
for more information.
|
441
|
+
If an authentication method other than the Azure service principal is used, Entra ID authentication is used.
|
442
|
+
This requires the configured identity to have the `AcrPush` role to be configured.
|
443
|
+
If this fails, admin account authentication is tried. For this the admin account must be enabled for the registry.
|
444
|
+
See the official Azure[documentation on the admin account](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication#admin-account) for more information.
|
444
445
|
""",
|
445
446
|
auth_methods=AzureAuthenticationMethods.values(),
|
446
447
|
supports_instances=True,
|
@@ -1718,10 +1719,14 @@ class AzureServiceConnector(ServiceConnector):
|
|
1718
1719
|
resource_type=resource_type,
|
1719
1720
|
resource_id=resource_id,
|
1720
1721
|
)
|
1721
|
-
|
1722
|
+
|
1723
|
+
resource_group: Optional[str]
|
1724
|
+
registry_name: str
|
1725
|
+
cluster_name: str
|
1722
1726
|
|
1723
1727
|
if resource_type == DOCKER_REGISTRY_RESOURCE_TYPE:
|
1724
1728
|
registry_name = self._parse_acr_resource_id(resource_id)
|
1729
|
+
registry_domain = f"{registry_name}.azurecr.io"
|
1725
1730
|
|
1726
1731
|
# If a service principal is used for authentication, the client ID
|
1727
1732
|
# and client secret can be used to authenticate to the registry, if
|
@@ -1733,13 +1738,12 @@ class AzureServiceConnector(ServiceConnector):
|
|
1733
1738
|
):
|
1734
1739
|
assert isinstance(self.config, AzureServicePrincipalConfig)
|
1735
1740
|
username = str(self.config.client_id)
|
1736
|
-
password = self.config.client_secret
|
1741
|
+
password = str(self.config.client_secret)
|
1737
1742
|
|
1738
|
-
# Without a service principal,
|
1739
|
-
#
|
1740
|
-
# disabled by default.
|
1743
|
+
# Without a service principal, we try to use the AzureDefaultCredentials to authenticate against the ACR.
|
1744
|
+
# If this fails, we try to use the admin account.
|
1745
|
+
# This has to be enabled for the registry, but this is not recommended and disabled by default.
|
1741
1746
|
# https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication#admin-account
|
1742
|
-
|
1743
1747
|
else:
|
1744
1748
|
registries = self._list_acr_registries(
|
1745
1749
|
credential,
|
@@ -1748,22 +1752,35 @@ class AzureServiceConnector(ServiceConnector):
|
|
1748
1752
|
registry_name, resource_group = registries.popitem()
|
1749
1753
|
|
1750
1754
|
try:
|
1751
|
-
|
1752
|
-
|
1755
|
+
username = "00000000-0000-0000-0000-000000000000"
|
1756
|
+
password = _ACRTokenExchangeClient(
|
1757
|
+
credential
|
1758
|
+
).get_acr_access_token(
|
1759
|
+
registry_domain, AZURE_ACR_OAUTH_SCOPE
|
1753
1760
|
)
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1761
|
+
except AuthorizationException:
|
1762
|
+
logger.warning(
|
1763
|
+
"Falling back to admin credentials for ACR authentication. Be sure to assign AcrPush role to the configured identity."
|
1757
1764
|
)
|
1758
|
-
|
1759
|
-
|
1760
|
-
|
1761
|
-
|
1762
|
-
|
1763
|
-
|
1764
|
-
|
1765
|
-
|
1766
|
-
|
1765
|
+
try:
|
1766
|
+
client = ContainerRegistryManagementClient(
|
1767
|
+
credential, subscription_id
|
1768
|
+
)
|
1769
|
+
|
1770
|
+
registry_credentials = (
|
1771
|
+
client.registries.list_credentials(
|
1772
|
+
resource_group, registry_name
|
1773
|
+
)
|
1774
|
+
)
|
1775
|
+
username = registry_credentials.username
|
1776
|
+
password = registry_credentials.passwords[0].value
|
1777
|
+
except AzureError as e:
|
1778
|
+
raise AuthorizationException(
|
1779
|
+
f"failed to list admin credentials for Azure Container "
|
1780
|
+
f"Registry '{registry_name}' in resource group "
|
1781
|
+
f"'{resource_group}'. Make sure the registry is "
|
1782
|
+
f"configured with an admin account: {e}"
|
1783
|
+
) from e
|
1767
1784
|
|
1768
1785
|
# Create a client-side Docker connector instance with the temporary
|
1769
1786
|
# Docker credentials
|
@@ -1775,7 +1792,7 @@ class AzureServiceConnector(ServiceConnector):
|
|
1775
1792
|
config=DockerConfiguration(
|
1776
1793
|
username=username,
|
1777
1794
|
password=password,
|
1778
|
-
registry=
|
1795
|
+
registry=registry_domain,
|
1779
1796
|
),
|
1780
1797
|
expires_at=expires_at,
|
1781
1798
|
)
|
@@ -1847,3 +1864,103 @@ class AzureServiceConnector(ServiceConnector):
|
|
1847
1864
|
)
|
1848
1865
|
|
1849
1866
|
raise ValueError(f"Unsupported resource type: {resource_type}")
|
1867
|
+
|
1868
|
+
|
1869
|
+
class _ACRTokenExchangeClient:
|
1870
|
+
def __init__(self, credential: TokenCredential):
|
1871
|
+
self._credential = credential
|
1872
|
+
|
1873
|
+
def _get_aad_access_token(self) -> str:
|
1874
|
+
aad_access_token: str = self._credential.get_token(
|
1875
|
+
AZURE_MANAGEMENT_TOKEN_SCOPE
|
1876
|
+
).token
|
1877
|
+
return aad_access_token
|
1878
|
+
|
1879
|
+
# https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#authenticating-docker-with-an-acr-refresh-token
|
1880
|
+
def get_acr_refresh_token(self, acr_url: str) -> str:
|
1881
|
+
try:
|
1882
|
+
aad_access_token = self._get_aad_access_token()
|
1883
|
+
|
1884
|
+
headers = {
|
1885
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
1886
|
+
}
|
1887
|
+
|
1888
|
+
data = {
|
1889
|
+
"grant_type": "access_token",
|
1890
|
+
"service": acr_url,
|
1891
|
+
"access_token": aad_access_token,
|
1892
|
+
}
|
1893
|
+
|
1894
|
+
response = requests.post(
|
1895
|
+
f"https://{acr_url}/oauth2/exchange",
|
1896
|
+
headers=headers,
|
1897
|
+
data=data,
|
1898
|
+
timeout=5,
|
1899
|
+
)
|
1900
|
+
|
1901
|
+
if response.status_code != 200:
|
1902
|
+
raise AuthorizationException(
|
1903
|
+
f"failed to get refresh token for Azure Container "
|
1904
|
+
f"Registry '{acr_url}' in resource group. "
|
1905
|
+
f"Be sure to assign AcrPush to the configured principal. "
|
1906
|
+
f"The token exchange returned status {response.status_code} "
|
1907
|
+
f"with body '{response.content.decode()}'"
|
1908
|
+
)
|
1909
|
+
|
1910
|
+
acr_refresh_token_response = json.loads(response.content)
|
1911
|
+
acr_refresh_token: str = acr_refresh_token_response[
|
1912
|
+
"refresh_token"
|
1913
|
+
]
|
1914
|
+
return acr_refresh_token
|
1915
|
+
|
1916
|
+
except (AzureError, requests.exceptions.RequestException) as e:
|
1917
|
+
raise AuthorizationException(
|
1918
|
+
f"failed to get refresh token for Azure Container "
|
1919
|
+
f"Registry '{acr_url}' in resource group. "
|
1920
|
+
f"Make sure the implicit authentication identity "
|
1921
|
+
f"has access to the configured registry: {e}"
|
1922
|
+
) from e
|
1923
|
+
|
1924
|
+
# https://github.com/Azure/acr/blob/main/docs/AAD-OAuth.md#calling-post-oauth2token-to-get-an-acr-access-token
|
1925
|
+
def get_acr_access_token(self, acr_url: str, scope: str) -> str:
|
1926
|
+
acr_refresh_token = self.get_acr_refresh_token(acr_url)
|
1927
|
+
|
1928
|
+
headers = {
|
1929
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
1930
|
+
}
|
1931
|
+
|
1932
|
+
data = {
|
1933
|
+
"grant_type": "refresh_token",
|
1934
|
+
"service": acr_url,
|
1935
|
+
"scope": scope,
|
1936
|
+
"refresh_token": acr_refresh_token,
|
1937
|
+
}
|
1938
|
+
|
1939
|
+
try:
|
1940
|
+
response = requests.post(
|
1941
|
+
f"https://{acr_url}/oauth2/token",
|
1942
|
+
headers=headers,
|
1943
|
+
data=data,
|
1944
|
+
timeout=5,
|
1945
|
+
)
|
1946
|
+
|
1947
|
+
if response.status_code != 200:
|
1948
|
+
raise AuthorizationException(
|
1949
|
+
f"failed to get access token for Azure Container "
|
1950
|
+
f"Registry '{acr_url}' in resource group. "
|
1951
|
+
f"Be sure to assign AcrPush to the configured principal. "
|
1952
|
+
f"The token exchange returned status {response.status_code} "
|
1953
|
+
f"with body '{response.content.decode()}'"
|
1954
|
+
)
|
1955
|
+
|
1956
|
+
acr_access_token_response = json.loads(response.content)
|
1957
|
+
acr_access_token: str = acr_access_token_response["access_token"]
|
1958
|
+
return acr_access_token
|
1959
|
+
|
1960
|
+
except (AzureError, requests.exceptions.RequestException) as e:
|
1961
|
+
raise AuthorizationException(
|
1962
|
+
f"failed to get access token for Azure Container "
|
1963
|
+
f"Registry '{acr_url}' in resource group. "
|
1964
|
+
f"Make sure the implicit authentication identity "
|
1965
|
+
f"has access to the configured registry: {e}"
|
1966
|
+
) from e
|
{zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: zenml-nightly
|
3
|
-
Version: 0.73.0.
|
3
|
+
Version: 0.73.0.dev20250201
|
4
4
|
Summary: ZenML: Write production-ready ML code.
|
5
5
|
License: Apache-2.0
|
6
6
|
Keywords: machine learning,production,pipeline,mlops,devops
|
@@ -104,6 +104,7 @@ Requires-Dist: python-dateutil (>=2.8.1,<3.0.0)
|
|
104
104
|
Requires-Dist: python-multipart (>=0.0.9,<0.1.0) ; extra == "server"
|
105
105
|
Requires-Dist: pyyaml (>=6.0.1)
|
106
106
|
Requires-Dist: pyyaml-include (<2.0) ; extra == "templates"
|
107
|
+
Requires-Dist: requests (>=2.27.11,<3.0.0) ; extra == "connectors-azure"
|
107
108
|
Requires-Dist: rich[jupyter] (>=12.0.0)
|
108
109
|
Requires-Dist: ruff (>=0.1.7) ; extra == "templates" or extra == "dev"
|
109
110
|
Requires-Dist: s3fs (>=2022.11.0) ; extra == "s3fs"
|
{zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/RECORD
RENAMED
@@ -1,5 +1,5 @@
|
|
1
1
|
zenml/README.md,sha256=827dekbOWAs1BpW7VF1a4d7EbwPbjwccX-2zdXBENZo,1777
|
2
|
-
zenml/VERSION,sha256=
|
2
|
+
zenml/VERSION,sha256=cT39b7vWwaaT2iOZDbZ7n1WpXJzY1cAQW-F6qehm6kE,19
|
3
3
|
zenml/__init__.py,sha256=SkMObQA41ajqdZqGErN00S1Vf3KAxpLvbZ-OBy5uYoo,2130
|
4
4
|
zenml/actions/__init__.py,sha256=mrt6wPo73iKRxK754_NqsGyJ3buW7RnVeIGXr1xEw8Y,681
|
5
5
|
zenml/actions/base_action.py,sha256=UcaHev6BTuLDwuswnyaPjdA8AgUqB5xPZ-lRtuvf2FU,25553
|
@@ -155,7 +155,7 @@ zenml/integrations/aws/step_operators/sagemaker_step_operator.py,sha256=-y1zqfID
|
|
155
155
|
zenml/integrations/aws/step_operators/sagemaker_step_operator_entrypoint_config.py,sha256=2cXroe6-bXyHVkO5yPnZagNpqx6MrMDcvuvNr8J2j-A,1581
|
156
156
|
zenml/integrations/azure/__init__.py,sha256=99cefcfBlEXl5lYCVUzWN0uvYn_TFGpBsEATvn1d3c4,2938
|
157
157
|
zenml/integrations/azure/artifact_stores/__init__.py,sha256=dlIwbpgjE0Hy4rhMbelNJHVKm4t8tj_hRu9mQ_cEIAg,820
|
158
|
-
zenml/integrations/azure/artifact_stores/azure_artifact_store.py,sha256=
|
158
|
+
zenml/integrations/azure/artifact_stores/azure_artifact_store.py,sha256=wgXoZFdK1tT4XneOJTOr3uYd5e6dS5ruyiNo8xldCDk,12236
|
159
159
|
zenml/integrations/azure/azureml_utils.py,sha256=kl_ld7jgWQgVBmagO46ADvfIKvBK3qaGboZom2C5UIA,7561
|
160
160
|
zenml/integrations/azure/flavors/__init__.py,sha256=CRdRbAnd28j2SFETYz-6PlWwhyTzr3dMwW9-LQL2G7w,1411
|
161
161
|
zenml/integrations/azure/flavors/azure_artifact_store_flavor.py,sha256=b3gwcqsY1sYI8MevIyz5pu5THo5aZHgmz8MCqiOfQ7c,3589
|
@@ -166,7 +166,7 @@ zenml/integrations/azure/orchestrators/__init__.py,sha256=q4rBPIJHcuUr6dLUBdrTkQ
|
|
166
166
|
zenml/integrations/azure/orchestrators/azureml_orchestrator.py,sha256=mkbjg9j-s1e2G191Q0dy4LKOeVeXV4pYr1qTuxIbMFQ,19912
|
167
167
|
zenml/integrations/azure/orchestrators/azureml_orchestrator_entrypoint_config.py,sha256=lacOyorsHa-HuD_kxN9M6BUiZDvs5jL9AnJWwrFCtp4,3104
|
168
168
|
zenml/integrations/azure/service_connectors/__init__.py,sha256=yMz6bTCtIZqZwfEM6h7-PSWsd_DB8l0OD9z_bdzomZw,793
|
169
|
-
zenml/integrations/azure/service_connectors/azure_service_connector.py,sha256=
|
169
|
+
zenml/integrations/azure/service_connectors/azure_service_connector.py,sha256=8kAnH6NK9J6SfJPPxlmxIc2oH73YtDI3qElgKOZSRuI,81330
|
170
170
|
zenml/integrations/azure/step_operators/__init__.py,sha256=fV4_nAO0cH53x6_-F8-CbDEZwb_Vv64oq1r0-vtigEU,819
|
171
171
|
zenml/integrations/azure/step_operators/azureml_step_operator.py,sha256=cmDLcxVd4lw_C2hz-_z50i9tcGgGbNtrBeL5FBQ3HTE,7394
|
172
172
|
zenml/integrations/bentoml/__init__.py,sha256=q7Vo8DL4j5N3p2Gu0_vio5VUUlW8wQ6N-ZcunK_ibiY,1876
|
@@ -1293,8 +1293,8 @@ zenml/zen_stores/secrets_stores/sql_secrets_store.py,sha256=nEO0bAPlULBLxLVk-UTR
|
|
1293
1293
|
zenml/zen_stores/sql_zen_store.py,sha256=GEBQDPhm52-YyxLBJcebNviwtr-VK_dnaHrg21fzJOw,417086
|
1294
1294
|
zenml/zen_stores/template_utils.py,sha256=EKYBgmDLTS_PSMWaIO5yvHPLiQvMqHcsAe6NUCrv-i4,9068
|
1295
1295
|
zenml/zen_stores/zen_store_interface.py,sha256=vf2gKBWfUUPtcGZC35oQB6pPNVzWVyQC8nWxVLjfrxM,92692
|
1296
|
-
zenml_nightly-0.73.0.
|
1297
|
-
zenml_nightly-0.73.0.
|
1298
|
-
zenml_nightly-0.73.0.
|
1299
|
-
zenml_nightly-0.73.0.
|
1300
|
-
zenml_nightly-0.73.0.
|
1296
|
+
zenml_nightly-0.73.0.dev20250201.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
|
1297
|
+
zenml_nightly-0.73.0.dev20250201.dist-info/METADATA,sha256=x5kwq8JoXQFnocvMyIGWJHVE7YfX6ZXpDI3MrVq53YA,21428
|
1298
|
+
zenml_nightly-0.73.0.dev20250201.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
1299
|
+
zenml_nightly-0.73.0.dev20250201.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
|
1300
|
+
zenml_nightly-0.73.0.dev20250201.dist-info/RECORD,,
|
{zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/LICENSE
RENAMED
File without changes
|
{zenml_nightly-0.73.0.dev20250131.dist-info → zenml_nightly-0.73.0.dev20250201.dist-info}/WHEEL
RENAMED
File without changes
|
File without changes
|