dstack 0.18.44__py3-none-any.whl → 0.19.0rc1__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 (267) hide show
  1. dstack/_internal/cli/commands/gateway.py +15 -3
  2. dstack/_internal/cli/commands/logs.py +0 -22
  3. dstack/_internal/cli/commands/stats.py +8 -17
  4. dstack/_internal/cli/main.py +1 -5
  5. dstack/_internal/cli/services/configurators/fleet.py +4 -39
  6. dstack/_internal/cli/services/configurators/run.py +22 -21
  7. dstack/_internal/cli/services/profile.py +34 -83
  8. dstack/_internal/cli/utils/gateway.py +1 -1
  9. dstack/_internal/core/backends/__init__.py +56 -39
  10. dstack/_internal/core/backends/aws/__init__.py +0 -25
  11. dstack/_internal/core/backends/aws/auth.py +1 -10
  12. dstack/_internal/core/backends/aws/backend.py +26 -0
  13. dstack/_internal/core/backends/aws/compute.py +20 -45
  14. dstack/_internal/{server/services/backends/configurators/aws.py → core/backends/aws/configurator.py} +46 -85
  15. dstack/_internal/core/backends/aws/models.py +135 -0
  16. dstack/_internal/core/backends/aws/resources.py +1 -1
  17. dstack/_internal/core/backends/azure/__init__.py +0 -20
  18. dstack/_internal/core/backends/azure/auth.py +2 -11
  19. dstack/_internal/core/backends/azure/backend.py +21 -0
  20. dstack/_internal/core/backends/azure/compute.py +13 -27
  21. dstack/_internal/{server/services/backends/configurators/azure.py → core/backends/azure/configurator.py} +141 -210
  22. dstack/_internal/core/backends/azure/models.py +89 -0
  23. dstack/_internal/core/backends/base/__init__.py +0 -12
  24. dstack/_internal/core/backends/base/backend.py +18 -0
  25. dstack/_internal/core/backends/base/compute.py +153 -33
  26. dstack/_internal/core/backends/base/configurator.py +105 -0
  27. dstack/_internal/core/backends/base/models.py +14 -0
  28. dstack/_internal/core/backends/configurators.py +138 -0
  29. dstack/_internal/core/backends/cudo/__init__.py +0 -15
  30. dstack/_internal/core/backends/cudo/backend.py +16 -0
  31. dstack/_internal/core/backends/cudo/compute.py +8 -26
  32. dstack/_internal/core/backends/cudo/configurator.py +72 -0
  33. dstack/_internal/core/backends/cudo/models.py +37 -0
  34. dstack/_internal/core/backends/datacrunch/__init__.py +0 -15
  35. dstack/_internal/core/backends/datacrunch/backend.py +16 -0
  36. dstack/_internal/core/backends/datacrunch/compute.py +8 -25
  37. dstack/_internal/core/backends/datacrunch/configurator.py +66 -0
  38. dstack/_internal/core/backends/datacrunch/models.py +38 -0
  39. dstack/_internal/core/{models/backends/dstack.py → backends/dstack/models.py} +7 -7
  40. dstack/_internal/core/backends/gcp/__init__.py +0 -16
  41. dstack/_internal/core/backends/gcp/auth.py +2 -11
  42. dstack/_internal/core/backends/gcp/backend.py +17 -0
  43. dstack/_internal/core/backends/gcp/compute.py +13 -43
  44. dstack/_internal/{server/services/backends/configurators/gcp.py → core/backends/gcp/configurator.py} +46 -103
  45. dstack/_internal/core/backends/gcp/models.py +125 -0
  46. dstack/_internal/core/backends/kubernetes/__init__.py +0 -15
  47. dstack/_internal/core/backends/kubernetes/backend.py +16 -0
  48. dstack/_internal/core/backends/kubernetes/compute.py +16 -5
  49. dstack/_internal/core/backends/kubernetes/configurator.py +55 -0
  50. dstack/_internal/core/backends/kubernetes/models.py +72 -0
  51. dstack/_internal/core/backends/lambdalabs/__init__.py +0 -16
  52. dstack/_internal/core/backends/lambdalabs/backend.py +17 -0
  53. dstack/_internal/core/backends/lambdalabs/compute.py +7 -28
  54. dstack/_internal/core/backends/lambdalabs/configurator.py +82 -0
  55. dstack/_internal/core/backends/lambdalabs/models.py +37 -0
  56. dstack/_internal/core/backends/local/__init__.py +0 -13
  57. dstack/_internal/core/backends/local/backend.py +14 -0
  58. dstack/_internal/core/backends/local/compute.py +16 -2
  59. dstack/_internal/core/backends/models.py +128 -0
  60. dstack/_internal/core/backends/oci/__init__.py +0 -15
  61. dstack/_internal/core/backends/oci/auth.py +1 -5
  62. dstack/_internal/core/backends/oci/backend.py +16 -0
  63. dstack/_internal/core/backends/oci/compute.py +9 -23
  64. dstack/_internal/{server/services/backends/configurators/oci.py → core/backends/oci/configurator.py} +40 -85
  65. dstack/_internal/core/{models/backends/oci.py → backends/oci/models.py} +24 -25
  66. dstack/_internal/core/backends/oci/region.py +1 -1
  67. dstack/_internal/core/backends/runpod/__init__.py +0 -15
  68. dstack/_internal/core/backends/runpod/backend.py +16 -0
  69. dstack/_internal/core/backends/runpod/compute.py +7 -3
  70. dstack/_internal/core/backends/runpod/configurator.py +59 -0
  71. dstack/_internal/core/backends/runpod/models.py +54 -0
  72. dstack/_internal/core/backends/template/__init__.py +0 -0
  73. dstack/_internal/core/backends/tensordock/__init__.py +0 -15
  74. dstack/_internal/core/backends/tensordock/backend.py +16 -0
  75. dstack/_internal/core/backends/tensordock/compute.py +8 -27
  76. dstack/_internal/core/backends/tensordock/configurator.py +68 -0
  77. dstack/_internal/core/backends/tensordock/models.py +38 -0
  78. dstack/_internal/core/backends/vastai/__init__.py +0 -15
  79. dstack/_internal/core/backends/vastai/backend.py +16 -0
  80. dstack/_internal/core/backends/vastai/compute.py +2 -2
  81. dstack/_internal/core/backends/vastai/configurator.py +66 -0
  82. dstack/_internal/core/backends/vastai/models.py +37 -0
  83. dstack/_internal/core/backends/vultr/__init__.py +0 -15
  84. dstack/_internal/core/backends/vultr/backend.py +16 -0
  85. dstack/_internal/core/backends/vultr/compute.py +10 -24
  86. dstack/_internal/core/backends/vultr/configurator.py +64 -0
  87. dstack/_internal/core/backends/vultr/models.py +34 -0
  88. dstack/_internal/core/models/backends/__init__.py +0 -184
  89. dstack/_internal/core/models/backends/base.py +0 -19
  90. dstack/_internal/core/models/configurations.py +20 -15
  91. dstack/_internal/core/models/envs.py +4 -3
  92. dstack/_internal/core/models/fleets.py +17 -22
  93. dstack/_internal/core/models/gateways.py +3 -3
  94. dstack/_internal/core/models/instances.py +24 -0
  95. dstack/_internal/core/models/profiles.py +41 -46
  96. dstack/_internal/core/models/projects.py +1 -1
  97. dstack/_internal/core/models/repos/base.py +0 -5
  98. dstack/_internal/core/models/repos/local.py +3 -3
  99. dstack/_internal/core/models/repos/remote.py +26 -12
  100. dstack/_internal/core/models/repos/virtual.py +1 -1
  101. dstack/_internal/core/models/resources.py +45 -76
  102. dstack/_internal/core/models/runs.py +17 -19
  103. dstack/_internal/core/models/volumes.py +1 -3
  104. dstack/_internal/core/services/profiles.py +7 -16
  105. dstack/_internal/core/services/repos.py +0 -4
  106. dstack/_internal/server/app.py +0 -3
  107. dstack/_internal/server/background/tasks/process_gateways.py +4 -8
  108. dstack/_internal/server/background/tasks/process_instances.py +14 -9
  109. dstack/_internal/server/background/tasks/process_metrics.py +1 -1
  110. dstack/_internal/server/background/tasks/process_placement_groups.py +4 -1
  111. dstack/_internal/server/background/tasks/process_prometheus_metrics.py +1 -1
  112. dstack/_internal/server/background/tasks/process_running_jobs.py +14 -5
  113. dstack/_internal/server/background/tasks/process_submitted_jobs.py +16 -37
  114. dstack/_internal/server/background/tasks/process_volumes.py +5 -2
  115. dstack/_internal/server/migrations/versions/7bc2586e8b9e_make_instancemodel_pool_id_optional.py +36 -0
  116. dstack/_internal/server/migrations/versions/bc8ca4a505c6_store_backendtype_as_string.py +171 -0
  117. dstack/_internal/server/models.py +48 -9
  118. dstack/_internal/server/routers/backends.py +14 -23
  119. dstack/_internal/server/routers/instances.py +3 -4
  120. dstack/_internal/server/routers/metrics.py +10 -8
  121. dstack/_internal/server/routers/prometheus.py +1 -1
  122. dstack/_internal/server/routers/repos.py +1 -2
  123. dstack/_internal/server/routers/runs.py +13 -59
  124. dstack/_internal/server/schemas/gateways.py +14 -23
  125. dstack/_internal/server/schemas/projects.py +7 -2
  126. dstack/_internal/server/schemas/repos.py +2 -38
  127. dstack/_internal/server/schemas/runner.py +1 -0
  128. dstack/_internal/server/schemas/runs.py +1 -24
  129. dstack/_internal/server/services/backends/__init__.py +85 -158
  130. dstack/_internal/server/services/config.py +52 -576
  131. dstack/_internal/server/services/fleets.py +8 -103
  132. dstack/_internal/server/services/gateways/__init__.py +12 -4
  133. dstack/_internal/server/services/{pools.py → instances.py} +22 -329
  134. dstack/_internal/server/services/jobs/__init__.py +9 -6
  135. dstack/_internal/server/services/jobs/configurators/base.py +16 -0
  136. dstack/_internal/server/services/jobs/configurators/dev.py +9 -1
  137. dstack/_internal/server/services/jobs/configurators/extensions/cursor.py +42 -0
  138. dstack/_internal/server/services/metrics.py +39 -13
  139. dstack/_internal/server/services/offers.py +1 -1
  140. dstack/_internal/server/services/projects.py +23 -14
  141. dstack/_internal/server/services/prometheus.py +176 -18
  142. dstack/_internal/server/services/runs.py +24 -16
  143. dstack/_internal/server/services/volumes.py +8 -4
  144. dstack/_internal/server/statics/index.html +1 -1
  145. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js → main-4fd5a4770eff59325ee3.js} +7 -7
  146. dstack/_internal/server/statics/{main-4eb116b97819badd1e2c.js.map → main-4fd5a4770eff59325ee3.js.map} +1 -1
  147. dstack/_internal/server/testing/common.py +58 -32
  148. dstack/_internal/utils/json_schema.py +6 -0
  149. dstack/_internal/utils/ssh.py +2 -1
  150. dstack/api/__init__.py +4 -0
  151. dstack/api/_public/__init__.py +16 -20
  152. dstack/api/_public/backends.py +1 -1
  153. dstack/api/_public/repos.py +36 -36
  154. dstack/api/_public/runs.py +167 -83
  155. dstack/api/server/__init__.py +11 -13
  156. dstack/api/server/_backends.py +12 -16
  157. dstack/api/server/_fleets.py +15 -57
  158. dstack/api/server/_gateways.py +3 -14
  159. dstack/api/server/_repos.py +1 -4
  160. dstack/api/server/_runs.py +21 -100
  161. dstack/api/server/_volumes.py +10 -5
  162. dstack/version.py +1 -1
  163. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/METADATA +1 -1
  164. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/RECORD +218 -204
  165. tests/_internal/cli/services/configurators/test_profile.py +6 -6
  166. tests/_internal/core/backends/aws/test_configurator.py +35 -0
  167. tests/_internal/core/backends/aws/test_resources.py +1 -1
  168. tests/_internal/core/backends/azure/test_configurator.py +61 -0
  169. tests/_internal/core/backends/cudo/__init__.py +0 -0
  170. tests/_internal/core/backends/cudo/test_configurator.py +37 -0
  171. tests/_internal/core/backends/datacrunch/__init__.py +0 -0
  172. tests/_internal/core/backends/datacrunch/test_configurator.py +17 -0
  173. tests/_internal/core/backends/gcp/test_configurator.py +42 -0
  174. tests/_internal/core/backends/kubernetes/test_configurator.py +43 -0
  175. tests/_internal/core/backends/lambdalabs/__init__.py +0 -0
  176. tests/_internal/core/backends/lambdalabs/test_configurator.py +38 -0
  177. tests/_internal/core/backends/oci/test_configurator.py +55 -0
  178. tests/_internal/core/backends/runpod/__init__.py +0 -0
  179. tests/_internal/core/backends/runpod/test_configurator.py +33 -0
  180. tests/_internal/core/backends/tensordock/__init__.py +0 -0
  181. tests/_internal/core/backends/tensordock/test_configurator.py +38 -0
  182. tests/_internal/core/backends/vastai/__init__.py +0 -0
  183. tests/_internal/core/backends/vastai/test_configurator.py +33 -0
  184. tests/_internal/core/backends/vultr/__init__.py +0 -0
  185. tests/_internal/core/backends/vultr/test_configurator.py +33 -0
  186. tests/_internal/server/background/tasks/test_process_gateways.py +4 -0
  187. tests/_internal/server/background/tasks/test_process_instances.py +49 -48
  188. tests/_internal/server/background/tasks/test_process_metrics.py +0 -3
  189. tests/_internal/server/background/tasks/test_process_placement_groups.py +2 -0
  190. tests/_internal/server/background/tasks/test_process_prometheus_metrics.py +0 -3
  191. tests/_internal/server/background/tasks/test_process_running_jobs.py +0 -21
  192. tests/_internal/server/background/tasks/test_process_runs.py +8 -22
  193. tests/_internal/server/background/tasks/test_process_submitted_jobs.py +3 -40
  194. tests/_internal/server/background/tasks/test_process_submitted_volumes.py +2 -0
  195. tests/_internal/server/background/tasks/test_process_terminating_jobs.py +10 -15
  196. tests/_internal/server/routers/test_backends.py +6 -764
  197. tests/_internal/server/routers/test_fleets.py +0 -26
  198. tests/_internal/server/routers/test_gateways.py +27 -3
  199. tests/_internal/server/routers/test_instances.py +0 -10
  200. tests/_internal/server/routers/test_metrics.py +27 -0
  201. tests/_internal/server/routers/test_projects.py +56 -0
  202. tests/_internal/server/routers/test_prometheus.py +116 -27
  203. tests/_internal/server/routers/test_repos.py +0 -15
  204. tests/_internal/server/routers/test_runs.py +4 -219
  205. tests/_internal/server/routers/test_volumes.py +2 -3
  206. tests/_internal/server/services/backends/__init__.py +0 -0
  207. tests/_internal/server/services/jobs/configurators/test_task.py +35 -0
  208. tests/_internal/server/services/test_config.py +7 -4
  209. tests/_internal/server/services/test_fleets.py +1 -4
  210. tests/_internal/server/services/{test_pools.py → test_instances.py} +11 -49
  211. tests/_internal/server/services/test_metrics.py +9 -5
  212. tests/_internal/server/services/test_repos.py +1 -14
  213. tests/_internal/server/services/test_runs.py +0 -4
  214. dstack/_internal/cli/commands/pool.py +0 -581
  215. dstack/_internal/cli/commands/run.py +0 -75
  216. dstack/_internal/core/backends/aws/config.py +0 -18
  217. dstack/_internal/core/backends/azure/config.py +0 -12
  218. dstack/_internal/core/backends/base/config.py +0 -5
  219. dstack/_internal/core/backends/cudo/config.py +0 -9
  220. dstack/_internal/core/backends/datacrunch/config.py +0 -9
  221. dstack/_internal/core/backends/gcp/config.py +0 -22
  222. dstack/_internal/core/backends/kubernetes/config.py +0 -6
  223. dstack/_internal/core/backends/lambdalabs/config.py +0 -9
  224. dstack/_internal/core/backends/nebius/__init__.py +0 -15
  225. dstack/_internal/core/backends/nebius/api_client.py +0 -319
  226. dstack/_internal/core/backends/nebius/compute.py +0 -220
  227. dstack/_internal/core/backends/nebius/config.py +0 -6
  228. dstack/_internal/core/backends/nebius/types.py +0 -37
  229. dstack/_internal/core/backends/oci/config.py +0 -6
  230. dstack/_internal/core/backends/runpod/config.py +0 -17
  231. dstack/_internal/core/backends/tensordock/config.py +0 -9
  232. dstack/_internal/core/backends/vastai/config.py +0 -6
  233. dstack/_internal/core/backends/vultr/config.py +0 -9
  234. dstack/_internal/core/models/backends/aws.py +0 -86
  235. dstack/_internal/core/models/backends/azure.py +0 -68
  236. dstack/_internal/core/models/backends/cudo.py +0 -43
  237. dstack/_internal/core/models/backends/datacrunch.py +0 -44
  238. dstack/_internal/core/models/backends/gcp.py +0 -67
  239. dstack/_internal/core/models/backends/kubernetes.py +0 -40
  240. dstack/_internal/core/models/backends/lambdalabs.py +0 -43
  241. dstack/_internal/core/models/backends/nebius.py +0 -54
  242. dstack/_internal/core/models/backends/runpod.py +0 -42
  243. dstack/_internal/core/models/backends/tensordock.py +0 -44
  244. dstack/_internal/core/models/backends/vastai.py +0 -43
  245. dstack/_internal/core/models/backends/vultr.py +0 -40
  246. dstack/_internal/core/models/pools.py +0 -43
  247. dstack/_internal/server/routers/pools.py +0 -142
  248. dstack/_internal/server/schemas/pools.py +0 -38
  249. dstack/_internal/server/services/backends/configurators/base.py +0 -72
  250. dstack/_internal/server/services/backends/configurators/cudo.py +0 -87
  251. dstack/_internal/server/services/backends/configurators/datacrunch.py +0 -79
  252. dstack/_internal/server/services/backends/configurators/kubernetes.py +0 -63
  253. dstack/_internal/server/services/backends/configurators/lambdalabs.py +0 -98
  254. dstack/_internal/server/services/backends/configurators/nebius.py +0 -85
  255. dstack/_internal/server/services/backends/configurators/runpod.py +0 -67
  256. dstack/_internal/server/services/backends/configurators/tensordock.py +0 -82
  257. dstack/_internal/server/services/backends/configurators/vastai.py +0 -80
  258. dstack/_internal/server/services/backends/configurators/vultr.py +0 -80
  259. dstack/api/_public/pools.py +0 -41
  260. dstack/api/_public/resources.py +0 -105
  261. dstack/api/server/_pools.py +0 -63
  262. tests/_internal/server/routers/test_pools.py +0 -612
  263. /dstack/_internal/{server/services/backends/configurators → core/backends/dstack}/__init__.py +0 -0
  264. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/LICENSE.md +0 -0
  265. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/WHEEL +0 -0
  266. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/entry_points.txt +0 -0
  267. {dstack-0.18.44.dist-info → dstack-0.19.0rc1.dist-info}/top_level.txt +0 -0
@@ -19,39 +19,34 @@ from azure.mgmt.network.models import (
19
19
  )
20
20
  from azure.mgmt.resource.resources.models import ResourceGroup
21
21
 
22
- from dstack._internal.core.backends.azure import AzureBackend, auth, compute, resources
22
+ from dstack._internal.core.backends.azure import auth, compute, resources
23
23
  from dstack._internal.core.backends.azure import utils as azure_utils
24
- from dstack._internal.core.backends.azure.config import AzureConfig
25
- from dstack._internal.core.errors import (
26
- BackendAuthError,
27
- BackendError,
28
- ServerClientError,
29
- )
30
- from dstack._internal.core.models.backends.azure import (
31
- AnyAzureConfigInfo,
24
+ from dstack._internal.core.backends.azure.backend import AzureBackend
25
+ from dstack._internal.core.backends.azure.models import (
26
+ AnyAzureBackendConfig,
27
+ AzureBackendConfig,
28
+ AzureBackendConfigWithCreds,
32
29
  AzureClientCreds,
33
- AzureConfigInfo,
34
- AzureConfigInfoWithCreds,
35
- AzureConfigInfoWithCredsPartial,
36
- AzureConfigValues,
30
+ AzureConfig,
37
31
  AzureCreds,
38
32
  AzureDefaultCreds,
39
33
  AzureStoredConfig,
40
34
  )
41
- from dstack._internal.core.models.backends.base import (
42
- BackendType,
43
- ConfigElement,
44
- ConfigElementValue,
45
- ConfigMultiElement,
46
- )
47
- from dstack._internal.core.models.common import is_core_model_instance
48
- from dstack._internal.server import settings
49
- from dstack._internal.server.models import BackendModel, DecryptedString, ProjectModel
50
- from dstack._internal.server.services.backends.configurators.base import (
35
+ from dstack._internal.core.backends.base.configurator import (
51
36
  TAGS_MAX_NUM,
37
+ BackendRecord,
52
38
  Configurator,
53
39
  raise_invalid_credentials_error,
54
40
  )
41
+ from dstack._internal.core.errors import (
42
+ BackendAuthError,
43
+ BackendError,
44
+ ServerClientError,
45
+ )
46
+ from dstack._internal.core.models.backends.base import (
47
+ BackendType,
48
+ )
49
+ from dstack._internal.core.models.common import is_core_model_instance
55
50
 
56
51
  LOCATIONS = [
57
52
  ("(US) Central US", "centralus"),
@@ -77,47 +72,16 @@ MAIN_LOCATION = "eastus"
77
72
 
78
73
 
79
74
  class AzureConfigurator(Configurator):
80
- TYPE: BackendType = BackendType.AZURE
81
-
82
- def get_default_configs(self) -> List[AzureConfigInfoWithCreds]:
83
- if not auth.default_creds_available():
84
- return []
85
- try:
86
- credential, _ = auth.authenticate(AzureDefaultCreds())
87
- except BackendAuthError:
88
- return []
89
- tenant_id_element = self._get_tenant_id_element(credential=credential)
90
- tenant_ids = [v.value for v in tenant_id_element.values]
91
- subscription_id_element = self._get_subscription_id_element(credential=credential)
92
- subscription_ids = [v.value for v in subscription_id_element.values]
93
- configs = []
94
- for tenant_id in tenant_ids:
95
- for subscription_id in subscription_ids:
96
- config = AzureConfigInfoWithCreds(
97
- tenant_id=tenant_id,
98
- subscription_id=subscription_id,
99
- locations=DEFAULT_LOCATIONS,
100
- creds=AzureDefaultCreds(),
101
- )
102
- configs.append(config)
103
- return configs
75
+ TYPE = BackendType.AZURE
76
+ BACKEND_CLASS = AzureBackend
104
77
 
105
- def get_config_values(self, config: AzureConfigInfoWithCredsPartial) -> AzureConfigValues:
106
- config_values = AzureConfigValues()
107
- config_values.default_creds = (
108
- settings.DEFAULT_CREDS_ENABLED and auth.default_creds_available()
109
- )
110
- if config.creds is None:
111
- return config_values
112
- if (
113
- is_core_model_instance(config.creds, AzureDefaultCreds)
114
- and not settings.DEFAULT_CREDS_ENABLED
115
- ):
78
+ def validate_config(self, config: AzureBackendConfigWithCreds, default_creds_enabled: bool):
79
+ if is_core_model_instance(config.creds, AzureDefaultCreds) and not default_creds_enabled:
116
80
  raise_invalid_credentials_error(fields=[["creds"]])
117
81
  if is_core_model_instance(config.creds, AzureClientCreds):
118
82
  self._set_client_creds_tenant_id(config.creds, config.tenant_id)
119
83
  try:
120
- credential, creds_tenant_id = auth.authenticate(config.creds)
84
+ credential, _ = auth.authenticate(config.creds)
121
85
  except BackendAuthError:
122
86
  if is_core_model_instance(config.creds, AzureClientCreds):
123
87
  raise_invalid_credentials_error(
@@ -129,29 +93,18 @@ class AzureConfigurator(Configurator):
129
93
  )
130
94
  else:
131
95
  raise_invalid_credentials_error(fields=[["creds"]])
132
- config_values.tenant_id = self._get_tenant_id_element(
133
- credential=credential,
134
- selected=config.tenant_id or creds_tenant_id,
135
- )
136
- if config_values.tenant_id.selected is None:
137
- return config_values
138
- config_values.subscription_id = self._get_subscription_id_element(
139
- credential=credential,
140
- selected=config.subscription_id,
141
- )
142
- if config_values.subscription_id.selected is None:
143
- return config_values
144
- config_values.locations = self._get_locations_element(
145
- selected=config.locations or DEFAULT_LOCATIONS
146
- )
147
- self._check_config(config=config, credential=credential)
148
- return config_values
96
+ self._check_config_tenant_id(config=config, credential=credential)
97
+ self._check_config_subscription_id(config=config, credential=credential)
98
+ self._check_config_locations(config)
99
+ self._check_config_tags(config)
100
+ self._check_config_resource_group(config=config, credential=credential)
101
+ self._check_config_vpc(config=config, credential=credential)
149
102
 
150
103
  def create_backend(
151
- self, project: ProjectModel, config: AzureConfigInfoWithCreds
152
- ) -> BackendModel:
153
- if config.locations is None:
154
- config.locations = DEFAULT_LOCATIONS
104
+ self, project_name: str, config: AzureBackendConfigWithCreds
105
+ ) -> BackendRecord:
106
+ if config.regions is None:
107
+ config.regions = DEFAULT_LOCATIONS
155
108
  if is_core_model_instance(config.creds, AzureClientCreds):
156
109
  self._set_client_creds_tenant_id(config.creds, config.tenant_id)
157
110
  credential, _ = auth.authenticate(config.creds)
@@ -160,173 +113,85 @@ class AzureConfigurator(Configurator):
160
113
  credential=credential,
161
114
  subscription_id=config.subscription_id,
162
115
  location=MAIN_LOCATION,
163
- project_name=project.name,
116
+ project_name=project_name,
164
117
  )
165
118
  self._create_network_resources(
166
119
  credential=credential,
167
120
  subscription_id=config.subscription_id,
168
121
  resource_group=config.resource_group,
169
- locations=config.locations,
122
+ locations=config.regions,
170
123
  create_default_network=config.vpc_ids is None,
171
124
  )
172
- return BackendModel(
173
- project_id=project.id,
174
- type=self.TYPE.value,
125
+ return BackendRecord(
175
126
  config=AzureStoredConfig(
176
- **AzureConfigInfo.__response__.parse_obj(config).dict(),
127
+ **AzureBackendConfig.__response__.parse_obj(config).dict()
177
128
  ).json(),
178
- auth=DecryptedString(plaintext=AzureCreds.parse_obj(config.creds).__root__.json()),
129
+ auth=AzureCreds.parse_obj(config.creds).__root__.json(),
179
130
  )
180
131
 
181
- def get_config_info(self, model: BackendModel, include_creds: bool) -> AnyAzureConfigInfo:
182
- config = self._get_backend_config(model)
132
+ def get_backend_config(
133
+ self, record: BackendRecord, include_creds: bool
134
+ ) -> AnyAzureBackendConfig:
135
+ config = self._get_config(record)
183
136
  if include_creds:
184
- return AzureConfigInfoWithCreds.__response__.parse_obj(config)
185
- return AzureConfigInfo.__response__.parse_obj(config)
137
+ return AzureBackendConfigWithCreds.__response__.parse_obj(config)
138
+ return AzureBackendConfig.__response__.parse_obj(config)
186
139
 
187
- def get_backend(self, model: BackendModel) -> AzureBackend:
188
- config = self._get_backend_config(model)
140
+ def get_backend(self, record: BackendRecord) -> AzureBackend:
141
+ config = self._get_config(record)
189
142
  return AzureBackend(config=config)
190
143
 
191
- def _get_backend_config(self, model: BackendModel) -> AzureConfig:
144
+ def _get_config(self, record: BackendRecord) -> AzureConfig:
145
+ config_dict = json.loads(record.config)
146
+ regions = config_dict.pop("regions", None)
147
+ if regions is None:
148
+ # Legacy config stores regions as locations
149
+ regions = config_dict.pop("locations")
192
150
  return AzureConfig.__response__(
193
- **json.loads(model.config),
194
- creds=AzureCreds.parse_raw(model.auth.get_plaintext_or_error()).__root__,
151
+ **config_dict,
152
+ regions=regions,
153
+ creds=AzureCreds.parse_raw(record.auth).__root__,
195
154
  )
196
155
 
197
- def _set_client_creds_tenant_id(
198
- self,
199
- creds: AzureClientCreds,
200
- tenant_id: Optional[str],
156
+ def _check_config_tenant_id(
157
+ self, config: AzureBackendConfigWithCreds, credential: auth.AzureCredential
201
158
  ):
202
- if creds.tenant_id is not None:
203
- return
204
- if tenant_id is None:
205
- raise_invalid_credentials_error(
206
- fields=[
207
- ["creds", "tenant_id"],
208
- ["tenant_id"],
209
- ]
210
- )
211
- creds.tenant_id = tenant_id
212
-
213
- def _get_tenant_id_element(
214
- self,
215
- credential: auth.AzureCredential,
216
- selected: Optional[str] = None,
217
- ) -> ConfigElement:
218
159
  subscription_client = subscription_mgmt.SubscriptionClient(credential=credential)
219
- element = ConfigElement(selected=selected)
220
160
  tenant_ids = []
221
161
  for tenant in subscription_client.tenants.list():
222
162
  tenant_ids.append(tenant.tenant_id)
223
- element.values.append(
224
- ConfigElementValue(value=tenant.tenant_id, label=tenant.tenant_id)
225
- )
226
- if selected is not None and selected not in tenant_ids:
163
+ if config.tenant_id not in tenant_ids:
227
164
  raise ServerClientError(
228
165
  "Invalid tenant_id",
229
166
  fields=[["tenant_id"]],
230
167
  )
231
- if len(tenant_ids) == 1:
232
- element.selected = tenant_ids[0]
233
- return element
234
168
 
235
- def _get_subscription_id_element(
236
- self,
237
- credential: auth.AzureCredential,
238
- selected: Optional[str] = None,
239
- ) -> ConfigElement:
169
+ def _check_config_subscription_id(
170
+ self, config: AzureBackendConfigWithCreds, credential: auth.AzureCredential
171
+ ):
240
172
  subscription_client = subscription_mgmt.SubscriptionClient(credential=credential)
241
- element = ConfigElement(selected=selected)
242
173
  subscription_ids = []
243
174
  for subscription in subscription_client.subscriptions.list():
244
175
  subscription_ids.append(subscription.subscription_id)
245
- element.values.append(
246
- ConfigElementValue(
247
- value=subscription.subscription_id,
248
- label=f"{subscription.display_name} ({subscription.subscription_id})",
249
- )
250
- )
251
- if selected is not None and selected not in subscription_ids:
176
+ if config.subscription_id not in subscription_ids:
252
177
  raise ServerClientError(
253
178
  "Invalid subscription_id",
254
179
  fields=[["subscription_id"]],
255
180
  )
256
- if len(subscription_ids) == 1:
257
- element.selected = subscription_ids[0]
258
181
  if len(subscription_ids) == 0:
259
182
  # Credentials without granted roles don't see any subscriptions
260
183
  raise ServerClientError(
261
184
  msg="No Azure subscriptions found for provided credentials. Ensure the account has enough permissions.",
262
185
  )
263
- return element
264
-
265
- def _get_locations_element(self, selected: List[str]) -> ConfigMultiElement:
266
- element = ConfigMultiElement()
267
- for loc in LOCATION_VALUES:
268
- element.values.append(ConfigElementValue(value=loc, label=loc))
269
- element.selected = selected
270
- return element
271
-
272
- def _create_resource_group(
273
- self,
274
- credential: auth.AzureCredential,
275
- subscription_id: str,
276
- location: str,
277
- project_name: str,
278
- ) -> str:
279
- resource_manager = ResourceManager(
280
- credential=credential,
281
- subscription_id=subscription_id,
282
- )
283
- return resource_manager.create_resource_group(
284
- name=_get_resource_group_name(project_name),
285
- location=location,
286
- )
287
-
288
- def _create_network_resources(
289
- self,
290
- credential: auth.AzureCredential,
291
- subscription_id: str,
292
- resource_group: str,
293
- locations: List[str],
294
- create_default_network: bool,
295
- ):
296
- def func(location: str):
297
- network_manager = NetworkManager(
298
- credential=credential, subscription_id=subscription_id
299
- )
300
- if create_default_network:
301
- network_manager.create_virtual_network(
302
- resource_group=resource_group,
303
- location=location,
304
- name=azure_utils.get_default_network_name(resource_group, location),
305
- subnet_name=azure_utils.get_default_subnet_name(resource_group, location),
306
- )
307
- network_manager.create_network_security_group(
308
- resource_group=resource_group,
309
- location=location,
310
- name=azure_utils.get_default_network_security_group_name(resource_group, location),
311
- )
312
- network_manager.create_gateway_network_security_group(
313
- resource_group=resource_group,
314
- location=location,
315
- name=azure_utils.get_gateway_network_security_group_name(resource_group, location),
316
- )
317
-
318
- with ThreadPoolExecutor(max_workers=8) as executor:
319
- for location in locations:
320
- executor.submit(func, location)
321
186
 
322
- def _check_config(
323
- self, config: AzureConfigInfoWithCredsPartial, credential: auth.AzureCredential
324
- ):
325
- self._check_tags_config(config)
326
- self._check_resource_group(config=config, credential=credential)
327
- self._check_vpc_config(config=config, credential=credential)
187
+ def _check_config_locations(self, config: AzureBackendConfigWithCreds):
188
+ if config.regions is None:
189
+ return
190
+ for location in config.regions:
191
+ if location not in LOCATION_VALUES:
192
+ raise ServerClientError(f"Unknown Azure location {location}")
328
193
 
329
- def _check_tags_config(self, config: AzureConfigInfoWithCredsPartial):
194
+ def _check_config_tags(self, config: AzureBackendConfigWithCreds):
330
195
  if not config.tags:
331
196
  return
332
197
  if len(config.tags) > TAGS_MAX_NUM:
@@ -338,8 +203,8 @@ class AzureConfigurator(Configurator):
338
203
  except BackendError as e:
339
204
  raise ServerClientError(e.args[0])
340
205
 
341
- def _check_resource_group(
342
- self, config: AzureConfigInfoWithCredsPartial, credential: auth.AzureCredential
206
+ def _check_config_resource_group(
207
+ self, config: AzureBackendConfigWithCreds, credential: auth.AzureCredential
343
208
  ):
344
209
  if config.resource_group is None:
345
210
  return
@@ -350,22 +215,22 @@ class AzureConfigurator(Configurator):
350
215
  if not resource_manager.resource_group_exists(config.resource_group):
351
216
  raise ServerClientError(f"Resource group {config.resource_group} not found")
352
217
 
353
- def _check_vpc_config(
354
- self, config: AzureConfigInfoWithCredsPartial, credential: auth.AzureCredential
218
+ def _check_config_vpc(
219
+ self, config: AzureBackendConfigWithCreds, credential: auth.AzureCredential
355
220
  ):
356
221
  if config.subscription_id is None:
357
222
  return None
358
223
  allocate_public_ip = config.public_ips if config.public_ips is not None else True
359
224
  if config.public_ips is False and config.vpc_ids is None:
360
225
  raise ServerClientError(msg="`vpc_ids` must be specified if `public_ips: false`.")
361
- locations = config.locations
226
+ locations = config.regions
362
227
  if locations is None:
363
228
  locations = DEFAULT_LOCATIONS
364
229
  if config.vpc_ids is not None:
365
230
  vpc_ids_locations = list(config.vpc_ids.keys())
366
231
  not_configured_locations = [loc for loc in locations if loc not in vpc_ids_locations]
367
232
  if len(not_configured_locations) > 0:
368
- if config.locations is None:
233
+ if config.regions is None:
369
234
  raise ServerClientError(
370
235
  f"`vpc_ids` not configured for regions {not_configured_locations}. "
371
236
  "Configure `vpc_ids` for all regions or specify `regions`."
@@ -396,6 +261,72 @@ class AzureConfigurator(Configurator):
396
261
  except BackendError as e:
397
262
  raise ServerClientError(e.args[0])
398
263
 
264
+ def _set_client_creds_tenant_id(
265
+ self,
266
+ creds: AzureClientCreds,
267
+ tenant_id: Optional[str],
268
+ ):
269
+ if creds.tenant_id is not None:
270
+ return
271
+ if tenant_id is None:
272
+ raise_invalid_credentials_error(
273
+ fields=[
274
+ ["creds", "tenant_id"],
275
+ ["tenant_id"],
276
+ ]
277
+ )
278
+ creds.tenant_id = tenant_id
279
+
280
+ def _create_resource_group(
281
+ self,
282
+ credential: auth.AzureCredential,
283
+ subscription_id: str,
284
+ location: str,
285
+ project_name: str,
286
+ ) -> str:
287
+ resource_manager = ResourceManager(
288
+ credential=credential,
289
+ subscription_id=subscription_id,
290
+ )
291
+ return resource_manager.create_resource_group(
292
+ name=_get_resource_group_name(project_name),
293
+ location=location,
294
+ )
295
+
296
+ def _create_network_resources(
297
+ self,
298
+ credential: auth.AzureCredential,
299
+ subscription_id: str,
300
+ resource_group: str,
301
+ locations: List[str],
302
+ create_default_network: bool,
303
+ ):
304
+ def func(location: str):
305
+ network_manager = NetworkManager(
306
+ credential=credential, subscription_id=subscription_id
307
+ )
308
+ if create_default_network:
309
+ network_manager.create_virtual_network(
310
+ resource_group=resource_group,
311
+ location=location,
312
+ name=azure_utils.get_default_network_name(resource_group, location),
313
+ subnet_name=azure_utils.get_default_subnet_name(resource_group, location),
314
+ )
315
+ network_manager.create_network_security_group(
316
+ resource_group=resource_group,
317
+ location=location,
318
+ name=azure_utils.get_default_network_security_group_name(resource_group, location),
319
+ )
320
+ network_manager.create_gateway_network_security_group(
321
+ resource_group=resource_group,
322
+ location=location,
323
+ name=azure_utils.get_gateway_network_security_group_name(resource_group, location),
324
+ )
325
+
326
+ with ThreadPoolExecutor(max_workers=8) as executor:
327
+ for location in locations:
328
+ executor.submit(func, location)
329
+
399
330
 
400
331
  def _get_resource_group_name(project_name: str) -> str:
401
332
  return f"dstack-{project_name}"
@@ -0,0 +1,89 @@
1
+ from typing import Annotated, Dict, List, Literal, Optional, Union
2
+
3
+ from pydantic import Field
4
+
5
+ from dstack._internal.core.models.common import CoreModel
6
+
7
+
8
+ class AzureClientCreds(CoreModel):
9
+ type: Annotated[Literal["client"], Field(description="The type of credentials")] = "client"
10
+ client_id: Annotated[str, Field(description="The client ID")]
11
+ client_secret: Annotated[str, Field(description="The client secret")]
12
+ # if tenant_id is missing, it will be populated from config info
13
+ tenant_id: Optional[str]
14
+
15
+
16
+ class AzureDefaultCreds(CoreModel):
17
+ type: Annotated[Literal["default"], Field(description="The type of credentials")] = "default"
18
+
19
+
20
+ AnyAzureCreds = Union[AzureClientCreds, AzureDefaultCreds]
21
+
22
+
23
+ class AzureCreds(CoreModel):
24
+ __root__: AnyAzureCreds = Field(..., discriminator="type")
25
+
26
+
27
+ class AzureBackendConfig(CoreModel):
28
+ type: Annotated[Literal["azure"], Field(description="The type of the backend")] = "azure"
29
+ tenant_id: Annotated[str, Field(description="The tenant ID")]
30
+ subscription_id: Annotated[str, Field(description="The subscription ID")]
31
+ resource_group: Annotated[
32
+ Optional[str],
33
+ Field(
34
+ description=(
35
+ "The resource group for resources created by `dstack`."
36
+ " If not specified, `dstack` will create a new resource group"
37
+ )
38
+ ),
39
+ ] = None
40
+ regions: Annotated[
41
+ Optional[List[str]],
42
+ Field(description="The list of Azure regions (locations). Omit to use all regions"),
43
+ ] = None
44
+ vpc_ids: Annotated[
45
+ Optional[Dict[str, str]],
46
+ Field(
47
+ description=(
48
+ "The mapping from configured Azure locations to network IDs."
49
+ " A network ID must have a format `networkResourceGroup/networkName`"
50
+ " If not specified, `dstack` will create a new network for every configured region"
51
+ )
52
+ ),
53
+ ] = None
54
+ public_ips: Annotated[
55
+ Optional[bool],
56
+ Field(
57
+ description=(
58
+ "A flag to enable/disable public IP assigning on instances."
59
+ " `public_ips: false` requires `vpc_ids` that specifies custom networks with outbound internet connectivity"
60
+ " provided by NAT Gateway or other mechanism."
61
+ " Defaults to `true`"
62
+ )
63
+ ),
64
+ ] = None
65
+ tags: Annotated[
66
+ Optional[Dict[str, str]],
67
+ Field(description="The tags that will be assigned to resources created by `dstack`"),
68
+ ] = None
69
+
70
+
71
+ class AzureBackendConfigWithCreds(AzureBackendConfig):
72
+ creds: AnyAzureCreds = Field(..., description="The credentials", discriminator="type")
73
+
74
+
75
+ AnyAzureBackendConfig = Union[AzureBackendConfig, AzureBackendConfigWithCreds]
76
+
77
+
78
+ class AzureStoredConfig(AzureBackendConfig):
79
+ resource_group: str = ""
80
+
81
+
82
+ class AzureConfig(AzureStoredConfig):
83
+ creds: AnyAzureCreds
84
+
85
+ @property
86
+ def allocate_public_ips(self) -> bool:
87
+ if self.public_ips is not None:
88
+ return self.public_ips
89
+ return True
@@ -1,12 +0,0 @@
1
- from abc import ABC, abstractmethod
2
-
3
- from dstack._internal.core.backends.base.compute import Compute
4
- from dstack._internal.core.models.backends.base import BackendType
5
-
6
-
7
- class Backend(ABC):
8
- TYPE: BackendType
9
-
10
- @abstractmethod
11
- def compute(self) -> Compute:
12
- pass
@@ -0,0 +1,18 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import ClassVar
3
+
4
+ from dstack._internal.core.backends.base.compute import Compute
5
+ from dstack._internal.core.models.backends.base import BackendType
6
+
7
+
8
+ class Backend(ABC):
9
+ TYPE: ClassVar[BackendType]
10
+ # `COMPUTE_CLASS` is used to introspect compute features without initializing it.
11
+ COMPUTE_CLASS: ClassVar[type[Compute]]
12
+
13
+ @abstractmethod
14
+ def compute(self) -> Compute:
15
+ """
16
+ Returns Compute instance.
17
+ """
18
+ pass