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.
Files changed (71) hide show
  1. dagster_cloud_cli/__init__.py +5 -0
  2. dagster_cloud_cli/commands/__init__.py +0 -0
  3. dagster_cloud_cli/commands/branch_deployment/__init__.py +154 -0
  4. dagster_cloud_cli/commands/ci/__init__.py +1207 -0
  5. dagster_cloud_cli/commands/ci/checks.py +135 -0
  6. dagster_cloud_cli/commands/ci/report.py +49 -0
  7. dagster_cloud_cli/commands/ci/state.py +129 -0
  8. dagster_cloud_cli/commands/ci/utils.py +22 -0
  9. dagster_cloud_cli/commands/config.py +250 -0
  10. dagster_cloud_cli/commands/deployment/__init__.py +108 -0
  11. dagster_cloud_cli/commands/deployment/alert_policies/__init__.py +0 -0
  12. dagster_cloud_cli/commands/deployment/alert_policies/commands.py +52 -0
  13. dagster_cloud_cli/commands/integration/__init__.py +6 -0
  14. dagster_cloud_cli/commands/integration/atlan/__init__.py +94 -0
  15. dagster_cloud_cli/commands/job/__init__.py +117 -0
  16. dagster_cloud_cli/commands/metrics.py +114 -0
  17. dagster_cloud_cli/commands/organization/__init__.py +134 -0
  18. dagster_cloud_cli/commands/organization/saml/__init__.py +0 -0
  19. dagster_cloud_cli/commands/organization/saml/commands.py +85 -0
  20. dagster_cloud_cli/commands/run/__init__.py +27 -0
  21. dagster_cloud_cli/commands/serverless/Dockerfile +38 -0
  22. dagster_cloud_cli/commands/serverless/Dockerfile.dagster-cloud-cli +20 -0
  23. dagster_cloud_cli/commands/serverless/__init__.py +560 -0
  24. dagster_cloud_cli/commands/workspace/__init__.py +358 -0
  25. dagster_cloud_cli/config/__init__.py +6 -0
  26. dagster_cloud_cli/config/jinja_template_loader.py +45 -0
  27. dagster_cloud_cli/config/list_merge_strategies.py +77 -0
  28. dagster_cloud_cli/config/loader.py +23 -0
  29. dagster_cloud_cli/config/merger.py +92 -0
  30. dagster_cloud_cli/config/models.py +76 -0
  31. dagster_cloud_cli/config_utils.py +506 -0
  32. dagster_cloud_cli/core/__init__.py +0 -0
  33. dagster_cloud_cli/core/agent_queue.py +3 -0
  34. dagster_cloud_cli/core/alert_types.py +26 -0
  35. dagster_cloud_cli/core/artifacts.py +192 -0
  36. dagster_cloud_cli/core/docker_runner.py +57 -0
  37. dagster_cloud_cli/core/errors.py +32 -0
  38. dagster_cloud_cli/core/graphql_client.py +420 -0
  39. dagster_cloud_cli/core/headers/__init__.py +0 -0
  40. dagster_cloud_cli/core/headers/auth.py +16 -0
  41. dagster_cloud_cli/core/headers/impl.py +31 -0
  42. dagster_cloud_cli/core/headers/versioning/__init__.py +0 -0
  43. dagster_cloud_cli/core/headers/versioning/constants.py +2 -0
  44. dagster_cloud_cli/core/pex_builder/__init__.py +8 -0
  45. dagster_cloud_cli/core/pex_builder/code_location.py +126 -0
  46. dagster_cloud_cli/core/pex_builder/deploy.py +255 -0
  47. dagster_cloud_cli/core/pex_builder/deps.py +495 -0
  48. dagster_cloud_cli/core/pex_builder/git_context.py +29 -0
  49. dagster_cloud_cli/core/pex_builder/github_context.py +178 -0
  50. dagster_cloud_cli/core/pex_builder/gitlab_context.py +30 -0
  51. dagster_cloud_cli/core/pex_builder/parse_workspace.py +39 -0
  52. dagster_cloud_cli/core/pex_builder/pex_registry.py +148 -0
  53. dagster_cloud_cli/core/pex_builder/platforms.py +6105 -0
  54. dagster_cloud_cli/core/pex_builder/selftest.py +14 -0
  55. dagster_cloud_cli/core/pex_builder/source.py +166 -0
  56. dagster_cloud_cli/core/pex_builder/util.py +165 -0
  57. dagster_cloud_cli/core/workspace.py +145 -0
  58. dagster_cloud_cli/docker_utils.py +98 -0
  59. dagster_cloud_cli/entrypoint.py +209 -0
  60. dagster_cloud_cli/gql.py +1025 -0
  61. dagster_cloud_cli/pex_utils.py +180 -0
  62. dagster_cloud_cli/py.typed +0 -0
  63. dagster_cloud_cli/types.py +78 -0
  64. dagster_cloud_cli/ui.py +101 -0
  65. dagster_cloud_cli/utils.py +107 -0
  66. dagster_cloud_cli/version.py +1 -0
  67. dagster_cloud_cli-1.12.16.dist-info/METADATA +31 -0
  68. dagster_cloud_cli-1.12.16.dist-info/RECORD +71 -0
  69. dagster_cloud_cli-1.12.16.dist-info/WHEEL +5 -0
  70. dagster_cloud_cli-1.12.16.dist-info/entry_points.txt +3 -0
  71. dagster_cloud_cli-1.12.16.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,16 @@
1
+ from enum import Enum
2
+
3
+ API_TOKEN_HEADER = "Dagster-Cloud-Api-Token"
4
+
5
+ DEPLOYMENT_NAME_HEADER = "Dagster-Cloud-Deployment"
6
+
7
+ DAGSTER_CLOUD_SCOPE_HEADER = "Dagster-Cloud-Scope"
8
+
9
+ AUTHORIZATION_HEADER = "Authorization"
10
+ BEARER_HEADER_SCHEME = "bearer"
11
+
12
+
13
+ class DagsterCloudInstanceScope(Enum):
14
+ ORGANIZATION = "organization"
15
+ DEPLOYMENT = "deployment"
16
+ UNSCOPED = "unscoped"
@@ -0,0 +1,31 @@
1
+ import platform
2
+
3
+ from dagster_cloud_cli.core.headers.auth import (
4
+ API_TOKEN_HEADER,
5
+ DAGSTER_CLOUD_SCOPE_HEADER,
6
+ DEPLOYMENT_NAME_HEADER,
7
+ DagsterCloudInstanceScope,
8
+ )
9
+ from dagster_cloud_cli.core.headers.versioning.constants import (
10
+ DAGSTER_CLOUD_VERSION_HEADER,
11
+ PYTHON_VERSION_HEADER,
12
+ )
13
+ from dagster_cloud_cli.version import __version__
14
+
15
+
16
+ def get_dagster_cloud_api_headers(
17
+ agent_token: str,
18
+ scope: DagsterCloudInstanceScope,
19
+ deployment_name: str | None = None,
20
+ additional_headers: dict[str, str] | None = None,
21
+ ) -> dict[str, str]:
22
+ return {
23
+ **{
24
+ API_TOKEN_HEADER: agent_token,
25
+ PYTHON_VERSION_HEADER: platform.python_version(),
26
+ DAGSTER_CLOUD_VERSION_HEADER: __version__,
27
+ DAGSTER_CLOUD_SCOPE_HEADER: scope.value,
28
+ },
29
+ **({DEPLOYMENT_NAME_HEADER: deployment_name} if deployment_name else {}),
30
+ **(additional_headers if additional_headers else {}),
31
+ }
File without changes
@@ -0,0 +1,2 @@
1
+ DAGSTER_CLOUD_VERSION_HEADER = "Dagster-Cloud-Version"
2
+ PYTHON_VERSION_HEADER = "Python-Version"
@@ -0,0 +1,8 @@
1
+ from dagster_cloud_cli.core.pex_builder import (
2
+ deploy as deploy,
3
+ deps as deps,
4
+ parse_workspace as parse_workspace,
5
+ pex_registry as pex_registry,
6
+ source as source,
7
+ util as util,
8
+ )
@@ -0,0 +1,126 @@
1
+ import logging
2
+ import re
3
+ import subprocess
4
+ import time
5
+
6
+ from dagster_cloud_cli import gql
7
+ from dagster_cloud_cli.core.pex_builder import github_context
8
+ from dagster_cloud_cli.core.pex_builder.github_context import get_git_commit_metadata
9
+ from dagster_cloud_cli.types import SnapshotBaseDeploymentCondition
10
+
11
+
12
+ def get_local_branch_name(project_dir: str) -> str:
13
+ return (
14
+ subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=project_dir)
15
+ .decode("utf-8")
16
+ .strip()
17
+ )
18
+
19
+
20
+ def get_local_repo_name(project_dir: str) -> str:
21
+ repo_url = (
22
+ subprocess.check_output(["git", "config", "--get", "remote.origin.url"], cwd=project_dir)
23
+ .decode("utf-8")
24
+ .strip()
25
+ )
26
+
27
+ # Handle both SSH URLs (git@github.com:org/repo.git) and https URLs (https://github.com/org/repo.git)
28
+ delimiters = r"[:/]"
29
+
30
+ split_repo_url = re.split(delimiters, repo_url)
31
+ # Extract just the org/repo part from the git URL
32
+ return split_repo_url[-2] + "/" + split_repo_url[-1].replace(".git", "")
33
+
34
+
35
+ def _get_local_commit_hash(project_dir: str) -> str:
36
+ return (
37
+ subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project_dir)
38
+ .decode("utf-8")
39
+ .strip()
40
+ )
41
+
42
+
43
+ def create_or_update_branch_deployment_from_local_git_context(
44
+ dagster_cloud_org_url: str,
45
+ dagster_cloud_api_token: str,
46
+ project_dir: str,
47
+ mark_closed: bool,
48
+ base_deployment_name: str | None,
49
+ snapshot_base_condition: SnapshotBaseDeploymentCondition | None,
50
+ ) -> str | None:
51
+ branch_name = get_local_branch_name(project_dir)
52
+
53
+ if not branch_name:
54
+ raise Exception("Could not determine branch name from local git context")
55
+
56
+ # Extract just the org/repo part from the git URL
57
+ repo_name = get_local_repo_name(project_dir)
58
+
59
+ if not repo_name:
60
+ raise Exception("Could not determine repo name from local git context")
61
+
62
+ commit_hash = _get_local_commit_hash(project_dir)
63
+
64
+ git_metadata = get_git_commit_metadata(project_dir)
65
+
66
+ try:
67
+ timestamp = float(git_metadata["timestamp"])
68
+ except Exception:
69
+ logging.warn("Could not determine commit timestamp from git metadata, using current time")
70
+ timestamp = time.time()
71
+
72
+ commit_msg = git_metadata["message"]
73
+ author_name = git_metadata["name"]
74
+ author_email = git_metadata["email"]
75
+
76
+ with gql.graphql_client_from_url(dagster_cloud_org_url, dagster_cloud_api_token) as client:
77
+ deployment_name = gql.create_or_update_branch_deployment(
78
+ client,
79
+ repo_name=repo_name,
80
+ branch_name=branch_name,
81
+ commit_hash=commit_hash,
82
+ timestamp=timestamp,
83
+ author_name=author_name,
84
+ author_email=author_email,
85
+ commit_message=commit_msg,
86
+ pull_request_status="CLOSED" if mark_closed else "OPEN",
87
+ base_deployment_name=base_deployment_name,
88
+ snapshot_base_condition=snapshot_base_condition,
89
+ )
90
+ return deployment_name
91
+
92
+
93
+ def create_or_update_branch_deployment_from_github_context(
94
+ dagster_cloud_org_url: str,
95
+ dagster_cloud_api_token: str,
96
+ github_event: github_context.GithubEvent,
97
+ mark_closed: bool,
98
+ base_deployment_name: str | None,
99
+ snapshot_base_condition: SnapshotBaseDeploymentCondition | None,
100
+ ) -> str | None:
101
+ """Return the branch deployment associated with the github PR."""
102
+ event = github_event
103
+ logging.debug("Read github event GithubEvent(%r)", event.__dict__)
104
+ if not event.branch_name:
105
+ return None
106
+ else:
107
+ url = f"{dagster_cloud_org_url}"
108
+ with gql.graphql_client_from_url(url, dagster_cloud_api_token) as client:
109
+ deployment_name = gql.create_or_update_branch_deployment(
110
+ client,
111
+ repo_name=event.repo_name,
112
+ branch_name=event.branch_name,
113
+ commit_hash=event.github_sha,
114
+ timestamp=event.timestamp,
115
+ branch_url=event.branch_url,
116
+ pull_request_url=event.pull_request_url,
117
+ pull_request_status="CLOSED" if mark_closed else event.pull_request_status,
118
+ pull_request_number=event.pull_request_id,
119
+ author_name=event.author_name,
120
+ author_email=event.author_email,
121
+ commit_message=event.commit_msg,
122
+ author_avatar_url=github_event.get_github_avatar_url(),
123
+ base_deployment_name=base_deployment_name,
124
+ snapshot_base_condition=snapshot_base_condition,
125
+ )
126
+ return deployment_name
@@ -0,0 +1,255 @@
1
+ # end-to-end workflow for rebuilding and publishing code locations
2
+
3
+ import logging
4
+ import os
5
+ import sys
6
+ from dataclasses import dataclass
7
+
8
+ import click
9
+ from packaging import version
10
+
11
+ from dagster_cloud_cli import gql, ui
12
+ from dagster_cloud_cli.core.pex_builder import (
13
+ deps,
14
+ github_context,
15
+ parse_workspace,
16
+ pex_registry,
17
+ source,
18
+ util,
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class LocationBuild:
24
+ """Inputs and outputs for each code location."""
25
+
26
+ location: parse_workspace.Location
27
+
28
+ # local dependencies are packaged with the source.pex along with the main code location
29
+ local_packages: deps.LocalPackages
30
+
31
+ # all other dependencies are packaged in the deps.pex
32
+ deps_requirements: deps.DepsRequirements
33
+
34
+ # One of deps_pex_path or published_deps_pex should be set
35
+ deps_pex_path: str | None = None # locally build deps.pex
36
+ published_deps_pex: str | None = None # already published deps.pex
37
+ # dagster_version should be always set for both cases, pre published and locally built deps
38
+ dagster_version: str | None = None
39
+ source_pex_path: str | None = None
40
+ pex_tag: str | None = None # composite tag used to identify the set of pex files
41
+
42
+ code_location_update_error: Exception | None = None
43
+
44
+
45
+ @dataclass
46
+ class DepsCacheTags:
47
+ deps_cache_from_tag: str | None
48
+ deps_cache_to_tag: str | None
49
+
50
+
51
+ def build_locations(
52
+ dagster_cloud_url: str,
53
+ dagster_cloud_api_token: str,
54
+ locations: list[parse_workspace.Location],
55
+ output_directory: str,
56
+ upload_pex: bool,
57
+ deps_cache_tags: DepsCacheTags,
58
+ python_version: version.Version,
59
+ build_method: deps.BuildMethod = deps.BuildMethod.DOCKER_FALLBACK,
60
+ ) -> list[LocationBuild]:
61
+ location_builds = []
62
+ for location in locations:
63
+ local_packages, deps_requirements = deps.get_deps_requirements(
64
+ location.directory,
65
+ python_version=python_version,
66
+ )
67
+ location_builds.append(
68
+ LocationBuild(
69
+ location=location,
70
+ local_packages=local_packages,
71
+ deps_requirements=deps_requirements,
72
+ )
73
+ )
74
+
75
+ # dedup requirements so each is only built once
76
+ builds_for_requirements_hash: dict[str, list[LocationBuild]] = {}
77
+ for location_build in location_builds:
78
+ requirements_hash = location_build.deps_requirements.hash
79
+ builds_for_requirements_hash.setdefault(requirements_hash, []).append(location_build)
80
+
81
+ # build each deps pex once and assign to all related builds
82
+ for requirements_hash in builds_for_requirements_hash:
83
+ builds = builds_for_requirements_hash[requirements_hash]
84
+ deps_requirements = builds[0].deps_requirements
85
+
86
+ # if a --deps-cache-from is specified, don't build deps.pex files if it is already published
87
+ if upload_pex and deps_cache_tags.deps_cache_from_tag:
88
+ published_deps_pex_info = pex_registry.get_cached_deps_details(
89
+ dagster_cloud_url,
90
+ dagster_cloud_api_token,
91
+ deps_requirements.hash,
92
+ deps_cache_tags.deps_cache_from_tag,
93
+ )
94
+ else:
95
+ published_deps_pex_info = None
96
+
97
+ if published_deps_pex_info:
98
+ published_deps_pex = published_deps_pex_info["deps_pex_name"]
99
+ ui.print(
100
+ f"Found published deps.pex {published_deps_pex} for requirements_hash {deps_requirements.hash}, cache_tag {deps_cache_tags.deps_cache_from_tag}, "
101
+ "skipping rebuild.",
102
+ )
103
+
104
+ for location_build in builds:
105
+ location_build.published_deps_pex = published_deps_pex
106
+ location_build.dagster_version = published_deps_pex_info["dagster_version"]
107
+ else:
108
+ ui.print(
109
+ f"No published deps.pex found for requirements_hash {deps_requirements.hash}, cache_tag {deps_cache_tags.deps_cache_from_tag}, will rebuild.",
110
+ )
111
+ try:
112
+ deps_pex_path, dagster_version = deps.build_deps_from_requirements(
113
+ deps_requirements,
114
+ output_directory,
115
+ build_method=build_method,
116
+ )
117
+ except deps.DepsBuildFailure as err:
118
+ logging.error("Failed to build dependencies: %s", err.stderr)
119
+ sys.exit(1)
120
+ for location_build in builds:
121
+ location_build.deps_pex_path = deps_pex_path
122
+ location_build.dagster_version = dagster_version
123
+
124
+ # build each source once
125
+ for location_build in location_builds:
126
+ location_build.source_pex_path = source.build_source_pex(
127
+ location_build.location.directory,
128
+ location_build.local_packages.local_package_paths,
129
+ output_directory,
130
+ python_version,
131
+ )
132
+
133
+ # compute pex tags
134
+ for location_build in location_builds:
135
+ deps_pex = (
136
+ location_build.deps_pex_path
137
+ if location_build.deps_pex_path
138
+ else location_build.published_deps_pex
139
+ )
140
+ if not deps_pex or not location_build.source_pex_path:
141
+ raise ValueError("No deps.pex or source.pex")
142
+
143
+ location_build.pex_tag = util.build_pex_tag([deps_pex, location_build.source_pex_path])
144
+ return location_builds
145
+
146
+
147
+ def get_user_specified_base_image_for(
148
+ dagster_cloud_url: str, dagster_cloud_api_token: str, location_build: LocationBuild
149
+ ) -> str | None:
150
+ # Full path to base image is supplied
151
+ base_image = os.getenv("SERVERLESS_BASE_IMAGE")
152
+ if base_image:
153
+ return base_image
154
+
155
+ # Tag suffix for base image is supplied - uses custom uploaded image
156
+ base_image_tag = os.getenv("SERVERLESS_BASE_IMAGE_TAG")
157
+ if base_image_tag:
158
+ # Point to user's registry info with this tag suffix
159
+ with gql.graphql_client_from_url(dagster_cloud_url, dagster_cloud_api_token) as client:
160
+ registry_info = gql.get_ecr_info(client)
161
+ return f"{registry_info['registry_url']}:{base_image_tag}"
162
+
163
+ return None
164
+
165
+
166
+ def notify(deployment_name: str | None, location_name: str, action: str):
167
+ if github_event:
168
+ github_context.update_pr_comment(
169
+ github_event,
170
+ action=action,
171
+ deployment_name=deployment_name,
172
+ location_name=location_name,
173
+ )
174
+
175
+
176
+ github_event: github_context.GithubEvent | None = None
177
+
178
+
179
+ def load_github_event(project_dir):
180
+ global github_event # noqa: PLW0603
181
+ github_event = github_context.get_github_event(project_dir)
182
+
183
+
184
+ @click.command()
185
+ @click.argument("dagster_cloud_file", type=click.Path(exists=True))
186
+ @click.argument("build_output_dir", type=click.Path(exists=False))
187
+ @click.option(
188
+ "--upload-pex",
189
+ is_flag=True,
190
+ show_default=True,
191
+ default=False,
192
+ help="Upload PEX files to registry.",
193
+ )
194
+ @click.option(
195
+ "--deps-cache-from",
196
+ type=str,
197
+ required=False,
198
+ help=(
199
+ "Try to reuse a pre-existing deps pex file. A deps pex file is reused if it was "
200
+ "built with a --deps-cache-to value that matches this flag value, AND the requirements.txt "
201
+ "and setup.py were identical."
202
+ ),
203
+ )
204
+ @click.option(
205
+ "--deps-cache-to",
206
+ type=str,
207
+ required=False,
208
+ help=(
209
+ "Allow reusing the generated deps pex and associate with the given tag. "
210
+ "See --deps-cache-from for how to reuse deps pex files."
211
+ ),
212
+ )
213
+ @click.option(
214
+ "--update-code-location",
215
+ is_flag=True,
216
+ show_default=True,
217
+ default=False,
218
+ help="Update code location to use new PEX files.",
219
+ )
220
+ @click.option(
221
+ "--code-location-details",
222
+ callback=util.parse_kv,
223
+ help=(
224
+ "Syntax: --code-location-details deployment=NAME,commit_hash=HASH. "
225
+ "When not provided, details are inferred from the github action environment."
226
+ ),
227
+ )
228
+ @util.python_version_option()
229
+ @click.option(
230
+ "--build-sdists/--no-build-sdists",
231
+ is_flag=True,
232
+ default=False,
233
+ help="Whether to build source only Python dependencies (sdists).",
234
+ )
235
+ def cli(
236
+ dagster_cloud_file,
237
+ build_output_dir,
238
+ upload_pex,
239
+ deps_cache_to,
240
+ deps_cache_from,
241
+ update_code_location,
242
+ code_location_details,
243
+ python_version,
244
+ build_sdists,
245
+ ):
246
+ logging.error(
247
+ "This entrypoint is obsolete. Please use `dagster-cloud serverless"
248
+ " deploy-python-executable`."
249
+ )
250
+ sys.exit(1)
251
+
252
+
253
+ if __name__ == "__main__":
254
+ logging.basicConfig(level=logging.INFO)
255
+ cli()