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,30 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from dagster_cloud_cli.core.pex_builder import git_context
|
|
4
|
+
|
|
5
|
+
# Loads event details from within a gitlab pipeline
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GitlabEvent:
|
|
9
|
+
def __init__(self, project_dir: str):
|
|
10
|
+
# https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
|
|
11
|
+
self.commit_sha = os.environ["CI_COMMIT_SHA"]
|
|
12
|
+
self.branch_name = os.environ.get("CI_COMMIT_REF_NAME", "")
|
|
13
|
+
self.job_url = os.environ["CI_JOB_URL"]
|
|
14
|
+
self.project_name = os.environ["CI_PROJECT_NAME"]
|
|
15
|
+
self.project_url = os.environ["CI_PROJECT_URL"]
|
|
16
|
+
|
|
17
|
+
self.merge_request_iid: str | None = os.environ.get("CI_MERGE_REQUEST_IID")
|
|
18
|
+
|
|
19
|
+
self.branch_url = f"{self.project_url}/-/tree/{self.branch_name}"
|
|
20
|
+
|
|
21
|
+
if self.merge_request_iid:
|
|
22
|
+
self.merge_request_url = f"{self.project_url}/-/merge_requests/{self.merge_request_iid}"
|
|
23
|
+
else:
|
|
24
|
+
self.merge_request_url = None
|
|
25
|
+
|
|
26
|
+
self.git_metadata = git_context.get_git_commit_metadata(project_dir)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_gitlab_event(project_dir) -> GitlabEvent:
|
|
30
|
+
return GitlabEvent(project_dir)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from dagster_cloud_cli import ui
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Location:
|
|
11
|
+
name: str
|
|
12
|
+
directory: str
|
|
13
|
+
build_folder: str
|
|
14
|
+
location_file: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_locations(dagster_cloud_yaml_file) -> list[Location]:
|
|
18
|
+
"""Returns list of locations parsed from dagster_cloud.yaml."""
|
|
19
|
+
base_dir = os.path.abspath(os.path.dirname(dagster_cloud_yaml_file))
|
|
20
|
+
|
|
21
|
+
with open(dagster_cloud_yaml_file, encoding="utf-8") as yaml_file:
|
|
22
|
+
workspace_contents = yaml_file.read()
|
|
23
|
+
workspace_contents_yaml = yaml.safe_load(workspace_contents)
|
|
24
|
+
|
|
25
|
+
locations = []
|
|
26
|
+
for location in workspace_contents_yaml["locations"]:
|
|
27
|
+
location_dir = os.path.join(
|
|
28
|
+
base_dir, location.get("build", {"directory": "."}).get("directory")
|
|
29
|
+
)
|
|
30
|
+
locations.append(
|
|
31
|
+
Location(
|
|
32
|
+
name=location["location_name"],
|
|
33
|
+
directory=location_dir,
|
|
34
|
+
build_folder=location_dir,
|
|
35
|
+
location_file=os.path.abspath(dagster_cloud_yaml_file),
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
ui.print(f"Parsed {len(locations)} locations from {locations}")
|
|
39
|
+
return locations
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from tempfile import TemporaryDirectory
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from dagster_cloud_cli import gql, ui
|
|
10
|
+
|
|
11
|
+
GENERATE_PUT_URL_QUERY = """
|
|
12
|
+
mutation GenerateServerlessPexUrlMutation($filenames: [String!]!) {
|
|
13
|
+
generateServerlessPexUrl(filenames: $filenames, method:PUT) {
|
|
14
|
+
url
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
GENERATE_GET_URL_QUERY = """
|
|
20
|
+
mutation GenerateServerlessPexUrlMutation($filenames: [String!]!) {
|
|
21
|
+
generateServerlessPexUrl(filenames: $filenames, method:GET) {
|
|
22
|
+
url
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_s3_urls_for_put(
|
|
29
|
+
dagster_cloud_url: str, dagster_cloud_api_token: str, filenames: list[str]
|
|
30
|
+
) -> list[str] | None:
|
|
31
|
+
with gql.graphql_client_from_url(dagster_cloud_url, dagster_cloud_api_token) as client:
|
|
32
|
+
result = client.execute(
|
|
33
|
+
GENERATE_PUT_URL_QUERY,
|
|
34
|
+
variable_values={"filenames": filenames},
|
|
35
|
+
idempotent_mutation=True,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if result["data"]:
|
|
39
|
+
return [item["url"] for item in result["data"]["generateServerlessPexUrl"]]
|
|
40
|
+
else:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_s3_urls_for_get(
|
|
45
|
+
dagster_cloud_url: str, dagster_cloud_api_token: str, filenames: list[str]
|
|
46
|
+
) -> list[str] | None:
|
|
47
|
+
with gql.graphql_client_from_url(dagster_cloud_url, dagster_cloud_api_token) as client:
|
|
48
|
+
result = client.execute(
|
|
49
|
+
GENERATE_GET_URL_QUERY,
|
|
50
|
+
variable_values={"filenames": filenames},
|
|
51
|
+
idempotent_mutation=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if result["data"]:
|
|
55
|
+
return [item["url"] for item in result["data"]["generateServerlessPexUrl"]]
|
|
56
|
+
else:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def requirements_hash_filename(requirements_hash: str, cache_tag: str | None):
|
|
61
|
+
# encode cache_tag as a filesystem safe string
|
|
62
|
+
if cache_tag:
|
|
63
|
+
cache_tag_suffix = "-" + base64.urlsafe_b64encode(cache_tag.encode("utf-8")).decode("utf-8")
|
|
64
|
+
else:
|
|
65
|
+
cache_tag_suffix = ""
|
|
66
|
+
|
|
67
|
+
return f"requirements-{requirements_hash}{cache_tag_suffix}.txt"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_cached_deps_details(
|
|
71
|
+
dagster_cloud_url: str,
|
|
72
|
+
dagster_cloud_api_token: str,
|
|
73
|
+
requirements_hash: str,
|
|
74
|
+
cache_tag: str | None,
|
|
75
|
+
) -> dict[str, Any] | None:
|
|
76
|
+
"""Returns a metadata dict for the requirements_hash and cache_tag.
|
|
77
|
+
|
|
78
|
+
The dict contains:
|
|
79
|
+
'deps_pex_name': filename for the deps pex, eg 'deps-123234334.pex'
|
|
80
|
+
'dagster_version': dagster package version included in the deps, eg '1.0.14'
|
|
81
|
+
"""
|
|
82
|
+
urls = get_s3_urls_for_get(
|
|
83
|
+
dagster_cloud_url,
|
|
84
|
+
dagster_cloud_api_token,
|
|
85
|
+
[requirements_hash_filename(requirements_hash, cache_tag)],
|
|
86
|
+
)
|
|
87
|
+
if not urls:
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
url = urls[0]
|
|
91
|
+
if not url:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
result = requests.get(url)
|
|
95
|
+
if result.ok:
|
|
96
|
+
data = json.loads(result.content)
|
|
97
|
+
# Don't return partial information
|
|
98
|
+
if "deps_pex_name" in data and "dagster_version" in data:
|
|
99
|
+
return data
|
|
100
|
+
|
|
101
|
+
if result.status_code == 404:
|
|
102
|
+
ui.print("No dependencies in cache")
|
|
103
|
+
else:
|
|
104
|
+
ui.warn(f"Unable to read cached deps details {result}")
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def set_cached_deps_details(
|
|
109
|
+
dagster_cloud_url: str,
|
|
110
|
+
dagster_cloud_api_token: str,
|
|
111
|
+
requirements_hash: str,
|
|
112
|
+
cache_tag: str | None,
|
|
113
|
+
deps_pex_name: str,
|
|
114
|
+
dagster_version: str,
|
|
115
|
+
):
|
|
116
|
+
"""Saves the deps_pex_name and dagster_version into the requirements hash file."""
|
|
117
|
+
filename = requirements_hash_filename(requirements_hash, cache_tag)
|
|
118
|
+
content = json.dumps({"deps_pex_name": deps_pex_name, "dagster_version": dagster_version})
|
|
119
|
+
with TemporaryDirectory() as tmp_dir:
|
|
120
|
+
filepath = os.path.join(tmp_dir, filename)
|
|
121
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
122
|
+
f.write(content)
|
|
123
|
+
|
|
124
|
+
upload_files(dagster_cloud_url, dagster_cloud_api_token, [filepath])
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def upload_files(dagster_cloud_url: str, dagster_cloud_api_token: str, filepaths: list[str]):
|
|
128
|
+
filenames = [os.path.basename(filepath) for filepath in filepaths]
|
|
129
|
+
urls = get_s3_urls_for_put(dagster_cloud_url, dagster_cloud_api_token, filenames)
|
|
130
|
+
if not urls:
|
|
131
|
+
ui.error(f"Cannot upload files, did not get PUT urls for: {filenames}")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# we expect response list to be in the same order as the request
|
|
135
|
+
for _, filepath, url in zip(filenames, filepaths, urls):
|
|
136
|
+
if not url:
|
|
137
|
+
ui.print(f"No upload URL received for {filepath} - skipping")
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
ui.print(f"Uploading {filepath} ...")
|
|
141
|
+
with open(filepath, "rb") as f:
|
|
142
|
+
response = requests.put(url, data=f)
|
|
143
|
+
if response.ok:
|
|
144
|
+
ui.print(f"Upload successful: {filepath}")
|
|
145
|
+
else:
|
|
146
|
+
ui.error(f"Upload failed for {filepath}: {response}")
|
|
147
|
+
ui.error(f"Upload URL: {url}")
|
|
148
|
+
ui.error(f"{response.content}")
|