dagster-cloud-cli 1.12.16__py2.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.
- dagster_cloud_cli/__init__.py +5 -0
- dagster_cloud_cli/commands/__init__.py +0 -0
- dagster_cloud_cli/commands/branch_deployment/__init__.py +154 -0
- dagster_cloud_cli/commands/ci/__init__.py +1207 -0
- dagster_cloud_cli/commands/ci/checks.py +135 -0
- dagster_cloud_cli/commands/ci/report.py +49 -0
- dagster_cloud_cli/commands/ci/state.py +129 -0
- dagster_cloud_cli/commands/ci/utils.py +22 -0
- dagster_cloud_cli/commands/config.py +250 -0
- dagster_cloud_cli/commands/deployment/__init__.py +108 -0
- dagster_cloud_cli/commands/deployment/alert_policies/__init__.py +0 -0
- dagster_cloud_cli/commands/deployment/alert_policies/commands.py +52 -0
- dagster_cloud_cli/commands/integration/__init__.py +6 -0
- dagster_cloud_cli/commands/integration/atlan/__init__.py +94 -0
- dagster_cloud_cli/commands/job/__init__.py +117 -0
- dagster_cloud_cli/commands/metrics.py +114 -0
- dagster_cloud_cli/commands/organization/__init__.py +134 -0
- dagster_cloud_cli/commands/organization/saml/__init__.py +0 -0
- dagster_cloud_cli/commands/organization/saml/commands.py +85 -0
- dagster_cloud_cli/commands/run/__init__.py +27 -0
- dagster_cloud_cli/commands/serverless/Dockerfile +38 -0
- dagster_cloud_cli/commands/serverless/Dockerfile.dagster-cloud-cli +20 -0
- dagster_cloud_cli/commands/serverless/__init__.py +560 -0
- dagster_cloud_cli/commands/workspace/__init__.py +358 -0
- dagster_cloud_cli/config/__init__.py +6 -0
- dagster_cloud_cli/config/jinja_template_loader.py +45 -0
- dagster_cloud_cli/config/list_merge_strategies.py +77 -0
- dagster_cloud_cli/config/loader.py +23 -0
- dagster_cloud_cli/config/merger.py +92 -0
- dagster_cloud_cli/config/models.py +76 -0
- dagster_cloud_cli/config_utils.py +506 -0
- dagster_cloud_cli/core/__init__.py +0 -0
- dagster_cloud_cli/core/agent_queue.py +3 -0
- dagster_cloud_cli/core/alert_types.py +26 -0
- dagster_cloud_cli/core/artifacts.py +192 -0
- dagster_cloud_cli/core/docker_runner.py +57 -0
- dagster_cloud_cli/core/errors.py +32 -0
- dagster_cloud_cli/core/graphql_client.py +420 -0
- dagster_cloud_cli/core/headers/__init__.py +0 -0
- dagster_cloud_cli/core/headers/auth.py +16 -0
- dagster_cloud_cli/core/headers/impl.py +31 -0
- dagster_cloud_cli/core/headers/versioning/__init__.py +0 -0
- dagster_cloud_cli/core/headers/versioning/constants.py +2 -0
- dagster_cloud_cli/core/pex_builder/__init__.py +8 -0
- dagster_cloud_cli/core/pex_builder/code_location.py +126 -0
- dagster_cloud_cli/core/pex_builder/deploy.py +255 -0
- dagster_cloud_cli/core/pex_builder/deps.py +495 -0
- dagster_cloud_cli/core/pex_builder/git_context.py +29 -0
- dagster_cloud_cli/core/pex_builder/github_context.py +178 -0
- dagster_cloud_cli/core/pex_builder/gitlab_context.py +30 -0
- dagster_cloud_cli/core/pex_builder/parse_workspace.py +39 -0
- dagster_cloud_cli/core/pex_builder/pex_registry.py +148 -0
- dagster_cloud_cli/core/pex_builder/platforms.py +6105 -0
- dagster_cloud_cli/core/pex_builder/selftest.py +14 -0
- dagster_cloud_cli/core/pex_builder/source.py +166 -0
- dagster_cloud_cli/core/pex_builder/util.py +165 -0
- dagster_cloud_cli/core/workspace.py +145 -0
- dagster_cloud_cli/docker_utils.py +98 -0
- dagster_cloud_cli/entrypoint.py +209 -0
- dagster_cloud_cli/gql.py +1025 -0
- dagster_cloud_cli/pex_utils.py +180 -0
- dagster_cloud_cli/py.typed +0 -0
- dagster_cloud_cli/types.py +78 -0
- dagster_cloud_cli/ui.py +101 -0
- dagster_cloud_cli/utils.py +107 -0
- dagster_cloud_cli/version.py +1 -0
- dagster_cloud_cli-1.12.16.dist-info/METADATA +31 -0
- dagster_cloud_cli-1.12.16.dist-info/RECORD +71 -0
- dagster_cloud_cli-1.12.16.dist-info/WHEEL +5 -0
- dagster_cloud_cli-1.12.16.dist-info/entry_points.txt +3 -0
- dagster_cloud_cli-1.12.16.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# This docker image is used to build user code by `deploy-python-executable`.
|
|
2
|
+
# Using a Linux docker to build allows source-only depedencies to be built for Linux.
|
|
3
|
+
|
|
4
|
+
# Base image is injected by caller
|
|
5
|
+
|
|
6
|
+
ARG DAGSTER_CLOUD_DEPENDENCY
|
|
7
|
+
ARG PRE_INSTALL_CODE
|
|
8
|
+
ENV DAGSTER_CLOUD_CLI_IN_DOCKER true
|
|
9
|
+
|
|
10
|
+
RUN pip install pex dagster
|
|
11
|
+
|
|
12
|
+
# The following line gets uncommented when running in development
|
|
13
|
+
# dev: COPY dagster-cloud-cli /dagster-cloud-cli
|
|
14
|
+
|
|
15
|
+
RUN pip install "${DAGSTER_CLOUD_DEPENDENCY}"
|
|
16
|
+
|
|
17
|
+
# the target directory is mounted when this image is run
|
|
18
|
+
RUN ln -s /root/.dagster_cloud_cli /dot_dagster_cloud_cli
|
|
19
|
+
|
|
20
|
+
ENTRYPOINT [ "dagster-cloud" ]
|
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dagster_shared import seven
|
|
8
|
+
from typer import Argument, Option, Typer
|
|
9
|
+
|
|
10
|
+
from dagster_cloud_cli import docker_utils, gql, pex_utils, ui
|
|
11
|
+
from dagster_cloud_cli.commands import metrics
|
|
12
|
+
from dagster_cloud_cli.commands.workspace import wait_for_load
|
|
13
|
+
from dagster_cloud_cli.config_utils import (
|
|
14
|
+
DEPLOYMENT_CLI_OPTIONS,
|
|
15
|
+
dagster_cloud_options,
|
|
16
|
+
get_location_document,
|
|
17
|
+
)
|
|
18
|
+
from dagster_cloud_cli.core import pex_builder
|
|
19
|
+
from dagster_cloud_cli.types import CliEventTags, CliEventType
|
|
20
|
+
from dagster_cloud_cli.utils import DEFAULT_PYTHON_VERSION, add_options
|
|
21
|
+
|
|
22
|
+
app = Typer(help="Build and deploy your code to Dagster Cloud.")
|
|
23
|
+
|
|
24
|
+
_BUILD_OPTIONS = {
|
|
25
|
+
"source_directory": (
|
|
26
|
+
Path,
|
|
27
|
+
Option(
|
|
28
|
+
None,
|
|
29
|
+
"--source-directory",
|
|
30
|
+
"-d",
|
|
31
|
+
exists=False,
|
|
32
|
+
help="Source directory to build for the image.",
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
"base_image": (
|
|
36
|
+
str,
|
|
37
|
+
Option(None, "--base-image", exists=False),
|
|
38
|
+
),
|
|
39
|
+
"env": (
|
|
40
|
+
list[str],
|
|
41
|
+
Option(
|
|
42
|
+
[],
|
|
43
|
+
"--env",
|
|
44
|
+
exists=False,
|
|
45
|
+
help="Environment variable to be defined in image, in the form of `MY_ENV_VAR=hello`",
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
DEPLOY_DOCKER_OPTIONS = {
|
|
51
|
+
"image": (
|
|
52
|
+
str,
|
|
53
|
+
Option(
|
|
54
|
+
None,
|
|
55
|
+
"--image",
|
|
56
|
+
exists=False,
|
|
57
|
+
help="Override built Docker image tag. Should not be needed outside of debugging.",
|
|
58
|
+
hidden=True,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
"base_image": (
|
|
62
|
+
str,
|
|
63
|
+
Option(None, "--base-image", exists=False, help="Custom base image"),
|
|
64
|
+
),
|
|
65
|
+
"env": (
|
|
66
|
+
list[str],
|
|
67
|
+
Option(
|
|
68
|
+
[],
|
|
69
|
+
"--env",
|
|
70
|
+
exists=False,
|
|
71
|
+
help="Environment variable to be defined in image, in the form of `MY_ENV_VAR=hello`",
|
|
72
|
+
),
|
|
73
|
+
),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@app.command(name="build", short_help="Build image for Dagster Cloud code location.", hidden=True)
|
|
78
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
79
|
+
@add_options(_BUILD_OPTIONS)
|
|
80
|
+
@metrics.instrument(
|
|
81
|
+
CliEventType.BUILD,
|
|
82
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.docker],
|
|
83
|
+
)
|
|
84
|
+
def build_command(
|
|
85
|
+
api_token: str,
|
|
86
|
+
url: str,
|
|
87
|
+
location_load_timeout: int,
|
|
88
|
+
agent_heartbeat_timeout: int,
|
|
89
|
+
image: str = Argument(None, help="Image name."),
|
|
90
|
+
**kwargs,
|
|
91
|
+
):
|
|
92
|
+
"""Add or update the image for a code location in the workspace."""
|
|
93
|
+
source_directory = str(kwargs.get("source_directory"))
|
|
94
|
+
base_image = kwargs.get("base_image")
|
|
95
|
+
env_vars = kwargs.get("env", [])
|
|
96
|
+
docker_utils.verify_docker()
|
|
97
|
+
|
|
98
|
+
with gql.graphql_client_from_url(url, api_token) as client:
|
|
99
|
+
ecr_info = gql.get_ecr_info(client)
|
|
100
|
+
registry = ecr_info["registry_url"]
|
|
101
|
+
|
|
102
|
+
if base_image and not ecr_info.get("allow_custom_base"):
|
|
103
|
+
ui.warn("Custom base images are not enabled for this organization.")
|
|
104
|
+
base_image = None
|
|
105
|
+
|
|
106
|
+
retval = docker_utils.build_image(
|
|
107
|
+
source_directory, image, ecr_info, env_vars, base_image, use_editable_dagster=False
|
|
108
|
+
)
|
|
109
|
+
if retval == 0:
|
|
110
|
+
ui.print(f"Built image {registry}:{image}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@app.command(
|
|
114
|
+
name="upload",
|
|
115
|
+
short_help="Upload the built code location image to Dagster Cloud's image repository.",
|
|
116
|
+
hidden=True,
|
|
117
|
+
)
|
|
118
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
119
|
+
@metrics.instrument(
|
|
120
|
+
CliEventType.UPLOAD,
|
|
121
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.docker],
|
|
122
|
+
)
|
|
123
|
+
def upload_command(
|
|
124
|
+
api_token: str,
|
|
125
|
+
url: str,
|
|
126
|
+
location_load_timeout: int,
|
|
127
|
+
agent_heartbeat_timeout: int,
|
|
128
|
+
image: str = Argument(None, help="Image name."),
|
|
129
|
+
**kwargs,
|
|
130
|
+
):
|
|
131
|
+
"""Add or update the image for a code location in the workspace."""
|
|
132
|
+
docker_utils.verify_docker()
|
|
133
|
+
|
|
134
|
+
with gql.graphql_client_from_url(url, api_token) as client:
|
|
135
|
+
ecr_info = gql.get_ecr_info(client)
|
|
136
|
+
registry = ecr_info["registry_url"]
|
|
137
|
+
retval = docker_utils.upload_image(image, ecr_info)
|
|
138
|
+
if retval == 0:
|
|
139
|
+
ui.print(f"Pushed image {image} to {registry}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@app.command(
|
|
143
|
+
name="registry-info",
|
|
144
|
+
short_help="Get registry information and temporary creds for an image repository",
|
|
145
|
+
hidden=True,
|
|
146
|
+
)
|
|
147
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
148
|
+
def registry_info_command(
|
|
149
|
+
api_token: str,
|
|
150
|
+
url: str,
|
|
151
|
+
location_load_timeout: int,
|
|
152
|
+
agent_heartbeat_timeout: int,
|
|
153
|
+
**kwargs,
|
|
154
|
+
):
|
|
155
|
+
"""Add or update the image for a code location in the workspace. Used by GH action to
|
|
156
|
+
authenticate to the image registry.
|
|
157
|
+
"""
|
|
158
|
+
with gql.graphql_client_from_url(url, api_token) as client:
|
|
159
|
+
ecr_info = gql.get_ecr_info(client)
|
|
160
|
+
registry_url = ecr_info["registry_url"]
|
|
161
|
+
aws_region = ecr_info.get("aws_region", "us-west-2")
|
|
162
|
+
aws_token = ecr_info["aws_auth_token"]
|
|
163
|
+
custom_base_image_allowed = ecr_info["allow_custom_base"]
|
|
164
|
+
|
|
165
|
+
if not aws_token or not registry_url:
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
username, password = base64.b64decode(aws_token).decode("utf-8").split(":")
|
|
169
|
+
|
|
170
|
+
values = [
|
|
171
|
+
f"REGISTRY_URL={registry_url}",
|
|
172
|
+
f"AWS_DEFAULT_REGION={aws_region}",
|
|
173
|
+
f"AWS_ECR_USERNAME={username}",
|
|
174
|
+
f"AWS_ECR_PASSWORD={password}",
|
|
175
|
+
]
|
|
176
|
+
if custom_base_image_allowed:
|
|
177
|
+
values.append("CUSTOM_BASE_IMAGE_ALLOWED=1")
|
|
178
|
+
ui.print("\n".join(values) + "\n")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@app.command(name="deploy", short_help="Alias for 'deploy-docker'.", hidden=True)
|
|
182
|
+
@app.command(
|
|
183
|
+
name="deploy-docker",
|
|
184
|
+
short_help=(
|
|
185
|
+
"Add a code location from a local directory, deployed as a Docker image. "
|
|
186
|
+
"Also see 'deploy-python-executable' as an alternative deployment method."
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
190
|
+
@add_options(DEPLOY_DOCKER_OPTIONS)
|
|
191
|
+
@add_options(DEPLOYMENT_CLI_OPTIONS)
|
|
192
|
+
@metrics.instrument(
|
|
193
|
+
CliEventType.DEPLOY,
|
|
194
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.docker],
|
|
195
|
+
)
|
|
196
|
+
def deploy_command(
|
|
197
|
+
api_token: str,
|
|
198
|
+
url: str,
|
|
199
|
+
location_load_timeout: int,
|
|
200
|
+
agent_heartbeat_timeout: int,
|
|
201
|
+
deployment: str,
|
|
202
|
+
source_directory: Path = Argument(".", help="Source directory."),
|
|
203
|
+
**kwargs,
|
|
204
|
+
):
|
|
205
|
+
"""Add a code location from a local directory, deployed as a Docker image."""
|
|
206
|
+
location_name = kwargs.get("location_name")
|
|
207
|
+
if not location_name:
|
|
208
|
+
raise ui.error(
|
|
209
|
+
"No location name provided. You must specify the location name as an argument."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if not source_directory:
|
|
213
|
+
raise ui.error("No source directory provided.")
|
|
214
|
+
|
|
215
|
+
_check_source_directory(source_directory)
|
|
216
|
+
docker_utils.verify_docker()
|
|
217
|
+
|
|
218
|
+
env_vars = kwargs.get("env", [])
|
|
219
|
+
base_image = kwargs.get("base_image")
|
|
220
|
+
|
|
221
|
+
with gql.graphql_client_from_url(url, api_token, deployment_name=deployment) as client:
|
|
222
|
+
ecr_info = gql.get_ecr_info(client)
|
|
223
|
+
registry = ecr_info["registry_url"]
|
|
224
|
+
|
|
225
|
+
image_tag = kwargs.get("image") or docker_utils.default_image_tag(
|
|
226
|
+
deployment, location_name, kwargs.get("commit_hash")
|
|
227
|
+
)
|
|
228
|
+
retval = docker_utils.build_image(
|
|
229
|
+
source_directory, image_tag, ecr_info, env_vars, base_image, use_editable_dagster=False
|
|
230
|
+
)
|
|
231
|
+
if retval != 0:
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
retval = docker_utils.upload_image(image_tag, ecr_info)
|
|
235
|
+
if retval != 0:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
location_args = {**kwargs, "image": f"{registry}:{image_tag}"}
|
|
239
|
+
location_document = get_location_document(location_name, location_args)
|
|
240
|
+
gql.add_or_update_code_location(client, location_document)
|
|
241
|
+
|
|
242
|
+
wait_for_load(
|
|
243
|
+
client,
|
|
244
|
+
[location_name],
|
|
245
|
+
location_load_timeout=location_load_timeout,
|
|
246
|
+
agent_heartbeat_timeout=agent_heartbeat_timeout,
|
|
247
|
+
url=url,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
workspace_url = f"{url}/locations"
|
|
251
|
+
ui.print(
|
|
252
|
+
f"Added or updated location {location_name}. "
|
|
253
|
+
f"View the status of your workspace at {workspace_url}."
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@app.command(
|
|
258
|
+
name="upload-base-image",
|
|
259
|
+
short_help=(
|
|
260
|
+
"Upload a local Docker image to Dagster cloud, to use as a custom base image for"
|
|
261
|
+
" deploy-python-executable."
|
|
262
|
+
),
|
|
263
|
+
)
|
|
264
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
265
|
+
@metrics.instrument(
|
|
266
|
+
CliEventType.UPLOAD,
|
|
267
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.pex],
|
|
268
|
+
)
|
|
269
|
+
def upload_base_image_command(
|
|
270
|
+
api_token: str,
|
|
271
|
+
url: str,
|
|
272
|
+
local_image: str = Argument(..., help="Pre-built local image, eg. 'local-image:local-tag'"),
|
|
273
|
+
published_tag: str = Option(
|
|
274
|
+
None,
|
|
275
|
+
help=(
|
|
276
|
+
"Published tag used to identify this image in Dagster Cloud. "
|
|
277
|
+
"A tag is auto-generated if not provided."
|
|
278
|
+
),
|
|
279
|
+
),
|
|
280
|
+
):
|
|
281
|
+
if not published_tag:
|
|
282
|
+
published_tag = _generate_published_tag_for_image(local_image)
|
|
283
|
+
|
|
284
|
+
with gql.graphql_client_from_url(url, api_token) as client:
|
|
285
|
+
ecr_info = gql.get_ecr_info(client)
|
|
286
|
+
registry = ecr_info["registry_url"]
|
|
287
|
+
published_image = f"{registry}:{published_tag}"
|
|
288
|
+
|
|
289
|
+
# tag local image with new tag
|
|
290
|
+
cmd = [
|
|
291
|
+
"docker",
|
|
292
|
+
"tag",
|
|
293
|
+
local_image,
|
|
294
|
+
published_image,
|
|
295
|
+
]
|
|
296
|
+
try:
|
|
297
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
|
298
|
+
except subprocess.CalledProcessError as err:
|
|
299
|
+
raise ui.error(
|
|
300
|
+
f"Error tagging local image {local_image}: " + err.stderr.decode("utf-8")
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# upload tagged image
|
|
304
|
+
retval = docker_utils.upload_image(image=published_tag, registry_info=ecr_info)
|
|
305
|
+
if retval == 0:
|
|
306
|
+
ui.print(f"Pushed image {published_tag} to {registry}.")
|
|
307
|
+
ui.print(
|
|
308
|
+
"To use the uploaded image run: "
|
|
309
|
+
f"dagster-cloud serverless deploy-python-executable --base-image-tag={published_tag} [ARGS]"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _generate_published_tag_for_image(image: str):
|
|
314
|
+
image_id = subprocess.check_output(["docker", "inspect", image, "--format={{.Id}}"])
|
|
315
|
+
# The id is something like 'sha256:518ad2f92b078c63c60e89f0310f13f19d3a1c7ea9e1976d67d59fcb7040d0d6'
|
|
316
|
+
return image_id.decode("utf-8").replace(":", "_").strip()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@app.command(name="build-python-executable", short_help="Build a Python Executable", hidden=True)
|
|
320
|
+
@add_options(
|
|
321
|
+
{
|
|
322
|
+
"python_version": (
|
|
323
|
+
str,
|
|
324
|
+
Option(
|
|
325
|
+
DEFAULT_PYTHON_VERSION,
|
|
326
|
+
help="Target Python version as 'major.minor'",
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
"build_method": (
|
|
330
|
+
pex_builder.deps.BuildMethod,
|
|
331
|
+
Option("docker-fallback", help="Environment used to build the dependencies."),
|
|
332
|
+
),
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
336
|
+
@metrics.instrument(
|
|
337
|
+
CliEventType.BUILD,
|
|
338
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.pex],
|
|
339
|
+
)
|
|
340
|
+
def build_python_executable_command(
|
|
341
|
+
code_directory: str,
|
|
342
|
+
output_directory: str,
|
|
343
|
+
python_version: str,
|
|
344
|
+
build_method: pex_builder.deps.BuildMethod,
|
|
345
|
+
api_token: str,
|
|
346
|
+
url: str,
|
|
347
|
+
):
|
|
348
|
+
parsed_python_version = pex_builder.util.parse_python_version(python_version)
|
|
349
|
+
code_directory = os.path.abspath(code_directory)
|
|
350
|
+
output_directory = os.path.abspath(output_directory)
|
|
351
|
+
|
|
352
|
+
locations = [
|
|
353
|
+
pex_builder.parse_workspace.Location(
|
|
354
|
+
name="", # unused since we don't upload
|
|
355
|
+
directory=code_directory,
|
|
356
|
+
build_folder=output_directory,
|
|
357
|
+
location_file="", # unused
|
|
358
|
+
)
|
|
359
|
+
]
|
|
360
|
+
ui.print(
|
|
361
|
+
f"Going to build Python executable from {code_directory} for Python {parsed_python_version}"
|
|
362
|
+
)
|
|
363
|
+
builds = pex_builder.deploy.build_locations(
|
|
364
|
+
url,
|
|
365
|
+
api_token,
|
|
366
|
+
locations,
|
|
367
|
+
output_directory,
|
|
368
|
+
upload_pex=False,
|
|
369
|
+
deps_cache_tags=pex_builder.deploy.DepsCacheTags(
|
|
370
|
+
deps_cache_from_tag=None, deps_cache_to_tag=None
|
|
371
|
+
),
|
|
372
|
+
python_version=parsed_python_version,
|
|
373
|
+
build_method=build_method,
|
|
374
|
+
)
|
|
375
|
+
build = builds[0]
|
|
376
|
+
pex_utils.print_built_pex_details(build)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@app.command(
|
|
380
|
+
name="build-python-deps", short_help="Build dependencies for a Python Executable", hidden=True
|
|
381
|
+
)
|
|
382
|
+
def build_python_dependencies(
|
|
383
|
+
requirements_path: str,
|
|
384
|
+
output_pex_path: str,
|
|
385
|
+
pex_flags: str,
|
|
386
|
+
):
|
|
387
|
+
ui.print(f"Going to build Python dependencies pex from {requirements_path}")
|
|
388
|
+
if not os.path.isfile(requirements_path):
|
|
389
|
+
raise ui.error(f"No such file {requirements_path}")
|
|
390
|
+
try:
|
|
391
|
+
pex_builder.deps.build_deps_from_requirements_file(
|
|
392
|
+
requirements_path, output_pex_path, json.loads(pex_flags)
|
|
393
|
+
)
|
|
394
|
+
except pex_builder.deps.DepsBuildFailure as err:
|
|
395
|
+
errors = [
|
|
396
|
+
"Could not build dependencies for this project.",
|
|
397
|
+
f"Dependencies:\n {' '.join(open(requirements_path).readlines())}",
|
|
398
|
+
err.format_error(),
|
|
399
|
+
]
|
|
400
|
+
raise ui.error("\n".join(errors))
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
@app.command(
|
|
404
|
+
name="deploy-python-executable",
|
|
405
|
+
short_help=(
|
|
406
|
+
"[Fast Deploys] Add a code location from a local directory, deployed as a Python"
|
|
407
|
+
" executable. Also see 'deploy-docker' as an alternative deployment method."
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
@dagster_cloud_options(allow_empty=True, requires_url=True)
|
|
411
|
+
@add_options(pex_utils.DEPLOY_PEX_OPTIONS)
|
|
412
|
+
@add_options(DEPLOYMENT_CLI_OPTIONS)
|
|
413
|
+
@metrics.instrument(
|
|
414
|
+
CliEventType.DEPLOY,
|
|
415
|
+
tags=[CliEventTags.subcommand.dagster_cloud_serverless, CliEventTags.server_strategy.pex],
|
|
416
|
+
)
|
|
417
|
+
def deploy_python_executable_command(
|
|
418
|
+
api_token: str,
|
|
419
|
+
url: str,
|
|
420
|
+
deployment: str | None,
|
|
421
|
+
location_load_timeout: int,
|
|
422
|
+
agent_heartbeat_timeout: int,
|
|
423
|
+
build_method: pex_builder.deps.BuildMethod,
|
|
424
|
+
source_directory: Path = Argument(".", help="Source directory."),
|
|
425
|
+
**kwargs,
|
|
426
|
+
):
|
|
427
|
+
"""Add a code location from a local directory, deployed as a Python executable."""
|
|
428
|
+
if seven.IS_WINDOWS:
|
|
429
|
+
raise ui.error(
|
|
430
|
+
"Deploying Python executables is not supported on Windows. See 'deploy-docker' as an"
|
|
431
|
+
" alternative deployment method."
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
location_name = kwargs.get("location_name")
|
|
435
|
+
if not location_name:
|
|
436
|
+
raise ui.error(
|
|
437
|
+
"No location name provided. You must specify the location name as an argument."
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
if (
|
|
441
|
+
not kwargs.get("location_file")
|
|
442
|
+
and not kwargs.get("python_file")
|
|
443
|
+
and not kwargs.get("module_name")
|
|
444
|
+
and not kwargs.get("package_name")
|
|
445
|
+
):
|
|
446
|
+
raise ui.error(
|
|
447
|
+
"Must specify --location-file, --package-name, --module-name, or --python-file."
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if not source_directory:
|
|
451
|
+
raise ui.error("No source directory provided.")
|
|
452
|
+
source_directory = source_directory.absolute()
|
|
453
|
+
|
|
454
|
+
location_file = kwargs.get("location_file")
|
|
455
|
+
if location_file:
|
|
456
|
+
location_file = location_file.absolute()
|
|
457
|
+
kwargs["location_file"] = location_file
|
|
458
|
+
locations = pex_builder.parse_workspace.get_locations(location_file)
|
|
459
|
+
else:
|
|
460
|
+
locations = []
|
|
461
|
+
|
|
462
|
+
# Multi-location deployments using '*'
|
|
463
|
+
if location_name == "*":
|
|
464
|
+
if not locations:
|
|
465
|
+
raise ui.error(
|
|
466
|
+
"--location-name=* requires --location-file=path/to/dagster_cloud.yaml "
|
|
467
|
+
"containing code location metadata, but no locations were found."
|
|
468
|
+
)
|
|
469
|
+
# location specific args don't make sense for multi location deploys
|
|
470
|
+
single_locations_args = set(DEPLOYMENT_CLI_OPTIONS) - {
|
|
471
|
+
"location_name",
|
|
472
|
+
"location_file",
|
|
473
|
+
"commit_hash",
|
|
474
|
+
"git_url",
|
|
475
|
+
}
|
|
476
|
+
for arg in single_locations_args:
|
|
477
|
+
if kwargs.get(arg):
|
|
478
|
+
raise ui.error(
|
|
479
|
+
"--location-name=* reads metadata from --location-file and does not allow a"
|
|
480
|
+
f" flag for {arg}"
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
if location_file:
|
|
484
|
+
# find the location matching this name loaded from location_file
|
|
485
|
+
locations = [location for location in locations if location.name == location_name]
|
|
486
|
+
if not locations:
|
|
487
|
+
raise ui.error(f"Location {location_name} not found in {location_file}")
|
|
488
|
+
else:
|
|
489
|
+
# create a location using cmdline arguments only
|
|
490
|
+
locations = [
|
|
491
|
+
pex_builder.parse_workspace.Location(
|
|
492
|
+
name=location_name,
|
|
493
|
+
directory=str(source_directory),
|
|
494
|
+
build_folder=str(source_directory),
|
|
495
|
+
location_file="",
|
|
496
|
+
)
|
|
497
|
+
]
|
|
498
|
+
|
|
499
|
+
if len(locations) == 1:
|
|
500
|
+
ui.print(f"Going to deploy location {locations[0].name}")
|
|
501
|
+
else:
|
|
502
|
+
ui.print(
|
|
503
|
+
f"Going to deploy {len(locations)} locations:"
|
|
504
|
+
f" {', '.join(location.name for location in locations)}"
|
|
505
|
+
)
|
|
506
|
+
for location in locations:
|
|
507
|
+
_check_source_directory(location.directory)
|
|
508
|
+
|
|
509
|
+
location_documents = []
|
|
510
|
+
for location in locations:
|
|
511
|
+
location_kwargs = pex_utils.build_upload_pex(
|
|
512
|
+
url=url,
|
|
513
|
+
api_token=api_token,
|
|
514
|
+
location=location,
|
|
515
|
+
build_method=build_method,
|
|
516
|
+
kwargs=kwargs,
|
|
517
|
+
)
|
|
518
|
+
location_documents.append(get_location_document(location.name, location_kwargs))
|
|
519
|
+
|
|
520
|
+
with gql.graphql_client_from_url(url, api_token, deployment_name=deployment) as client:
|
|
521
|
+
for location_document in location_documents:
|
|
522
|
+
gql.add_or_update_code_location(client, location_document)
|
|
523
|
+
|
|
524
|
+
wait_for_load(
|
|
525
|
+
client,
|
|
526
|
+
[location.name for location in locations],
|
|
527
|
+
location_load_timeout=location_load_timeout,
|
|
528
|
+
agent_heartbeat_timeout=agent_heartbeat_timeout,
|
|
529
|
+
url=url,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
workspace_url = f"{url}/locations"
|
|
533
|
+
ui.print(
|
|
534
|
+
f"Added or updated locations: {', '.join([location.name for location in locations])}. "
|
|
535
|
+
f"View the status of your workspace at {workspace_url}."
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
SOURCE_INSTRUCTIONS = (
|
|
540
|
+
"You can specify the directory you want to deploy by using the `--source-directory` argument "
|
|
541
|
+
"(defaults to current directory)."
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _check_source_directory(source_directory):
|
|
546
|
+
contents = os.listdir(source_directory)
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
"setup.py" not in contents
|
|
550
|
+
and "requirements.txt" not in contents
|
|
551
|
+
and "pyproject.toml" not in contents
|
|
552
|
+
):
|
|
553
|
+
message = (
|
|
554
|
+
"Could not find a `setup.py`, `requirements.txt`, or `pyproject.toml` in the target directory. You must "
|
|
555
|
+
"specify your required Python dependencies (including the `dagster-cloud` package) "
|
|
556
|
+
"along with your source files to deploy to Dagster Cloud."
|
|
557
|
+
)
|
|
558
|
+
if source_directory == ".":
|
|
559
|
+
message = f"{message} {SOURCE_INSTRUCTIONS}"
|
|
560
|
+
raise ui.error(message)
|