Skip to content

Commit d04e510

Browse files
Release 1.12.20 (tracks 8d1f1cc2d6d384f1121a04eae95e909c8edf7b70)
1 parent 57bc356 commit d04e510

12 files changed

Lines changed: 117 additions & 31 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.12.19"
1+
__version__ = "1.12.20"

dagster-cloud-examples/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
77
[project]
88
name = "dagster-cloud-examples"
99
requires-python = ">=3.9,<3.13"
10-
version = "1.12.19"
10+
version = "1.12.20"
1111
authors = [
1212
{ name = "Elementl", email = "hello@elementl.com" },
1313
]
@@ -17,7 +17,7 @@ classifiers = [
1717
"Operating System :: OS Independent",
1818
]
1919
dependencies = [
20-
"dagster-cloud==1.12.19",
20+
"dagster-cloud==1.12.20",
2121
]
2222

2323
[project.license]

dagster-cloud/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
def pytest_addoption(parser):
1+
import pytest
2+
3+
4+
def pytest_addoption(parser: pytest.Parser):
25
parser.addoption("--force-shard1", action="store_true", default=False, help="Enable sharding")
36

47

dagster-cloud/dagster_cloud/agent/dagster_cloud_agent.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def run_loop(
319319

320320
self._check_update_workspace(
321321
user_code_launcher,
322-
upload_all=user_code_launcher.upload_snapshots_on_startup,
322+
upload_outdated=user_code_launcher.upload_outdated_snapshots_on_startup,
323323
)
324324

325325
self._logger.info(
@@ -371,7 +371,7 @@ def run_loop(
371371
pass
372372

373373
try:
374-
self._check_update_workspace(user_code_launcher, upload_all=False)
374+
self._check_update_workspace(user_code_launcher, upload_outdated=False)
375375

376376
except Exception:
377377
self._logger.error(
@@ -428,7 +428,7 @@ def _check_for_long_running_reconcile(self, user_code_launcher):
428428
else:
429429
self._warned_about_long_in_progress_reconcile = False
430430

431-
def _check_update_workspace(self, user_code_launcher, upload_all):
431+
def _check_update_workspace(self, user_code_launcher, upload_outdated):
432432
curr_time = get_current_datetime()
433433

434434
if (
@@ -439,7 +439,7 @@ def _check_update_workspace(self, user_code_launcher, upload_all):
439439
return
440440

441441
self._last_workspace_check_time = curr_time
442-
self._query_for_workspace_updates(user_code_launcher, upload_all=upload_all)
442+
self._query_for_workspace_updates(user_code_launcher, upload_outdated=upload_outdated)
443443

444444
def _check_add_heartbeat(
445445
self,
@@ -723,7 +723,7 @@ def _get_locations_with_ttl_to_query(self, user_code_launcher) -> list[tuple[str
723723
def _query_for_workspace_updates(
724724
self,
725725
user_code_launcher: DagsterCloudUserCodeLauncher,
726-
upload_all: bool,
726+
upload_outdated: bool,
727727
):
728728
locations_with_ttl_to_query = self._get_locations_with_ttl_to_query(user_code_launcher)
729729

@@ -828,7 +828,7 @@ def _query_for_workspace_updates(
828828
deployment_map,
829829
control_plane_error_locations=tracked_error_locations,
830830
control_plane_outdated_locations=tracked_outdated_locations,
831-
upload_all=upload_all,
831+
upload_outdated=upload_outdated,
832832
)
833833

834834
# Tell run worker monitoring which deployments it should care about
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.12.19"
1+
__version__ = "1.12.20"

dagster-cloud/dagster_cloud/workspace/config_schema/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def process_workspace_config(workspace_config) -> dict[str, Any]:
161161
),
162162
"deployment_metadata": Permissive(),
163163
"service_metadata": Permissive(),
164+
"service_spec_config": Permissive(),
164165
}
165166
),
166167
is_required=False,

dagster-cloud/dagster_cloud/workspace/ecs/client.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import asyncio
2+
import datetime
23
import json
34
import logging
45
import os
56
import time
7+
import uuid
68

79
import boto3
810
import botocore
@@ -64,11 +66,19 @@ def __init__(
6466
launch_type: str = "FARGATE",
6567
show_debug_cluster_info: bool = True,
6668
assign_public_ip: bool | None = None,
69+
service_discovery_role_arn: str | None = None,
6770
):
6871
self.ecs = ecs_client if ecs_client else boto3.client("ecs", config=config)
6972
self.logs = boto3.client("logs", config=config)
70-
self.service_discovery = boto3.client("servicediscovery", config=config)
7173
self.tags_client = boto3.client("resourcegroupstaggingapi", config=config)
74+
75+
self._service_discovery_role_arn = service_discovery_role_arn
76+
if service_discovery_role_arn:
77+
self._sts = boto3.client("sts", config=config)
78+
self._sd_session_expires: datetime.datetime | None = None
79+
self._create_service_discovery_session()
80+
else:
81+
self._service_discovery = boto3.client("servicediscovery", config=config)
7282
self._use_legacy_tag_filtering = False
7383

7484
self.cluster_name = cluster_name.split("/")[-1]
@@ -87,6 +97,35 @@ def __init__(
8797
self._namespace: str | None = None
8898
self._assign_public_ip_override = assign_public_ip
8999

100+
def _create_service_discovery_session(self) -> None:
101+
"""Assume cross-account role and create a servicediscovery client with temporary credentials."""
102+
response = self._sts.assume_role(
103+
RoleArn=check.not_none(self._service_discovery_role_arn),
104+
RoleSessionName=f"dagster-ecs-sd-{uuid.uuid4()}",
105+
DurationSeconds=3600,
106+
)
107+
self._sd_session_expires = response["Credentials"]["Expiration"]
108+
session = boto3.Session(
109+
aws_access_key_id=response["Credentials"]["AccessKeyId"],
110+
aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
111+
aws_session_token=response["Credentials"]["SessionToken"],
112+
)
113+
self._service_discovery = session.client("servicediscovery", config=config)
114+
115+
def _refresh_service_discovery_session(self) -> None:
116+
"""Refresh the cross-account session if it's within 5 minutes of expiration."""
117+
if self._sd_session_expires is None:
118+
return
119+
now = datetime.datetime.now(self._sd_session_expires.tzinfo)
120+
if self._sd_session_expires <= now + datetime.timedelta(minutes=5):
121+
self._create_service_discovery_session()
122+
123+
@property
124+
def service_discovery(self):
125+
if self._service_discovery_role_arn:
126+
self._refresh_service_discovery_session()
127+
return self._service_discovery
128+
90129
@property
91130
def ec2(self):
92131
# Reconstruct the resource on each call because it's not threadsafe

dagster-cloud/dagster_cloud/workspace/ecs/launcher.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,14 @@ def __init__(
9393
server_task_definition_prefix: str = "server",
9494
run_task_definition_prefix: str = "run",
9595
assign_public_ip: bool | None = None,
96+
service_discovery_role_arn: str | None = None,
9697
**kwargs,
9798
):
9899
self.ecs = boto3.client("ecs")
99100
self.logs = boto3.client("logs")
100101
self.service_discovery = boto3.client("servicediscovery")
101102
self.secrets_manager = boto3.client("secretsmanager")
103+
self.service_discovery_role_arn = service_discovery_role_arn
102104

103105
self.cluster = cluster
104106
self.subnets = subnets
@@ -186,6 +188,7 @@ def __init__(
186188
grace_period=self._ecs_grace_period,
187189
launch_type=self.launch_type,
188190
assign_public_ip=assign_public_ip,
191+
service_discovery_role_arn=self.service_discovery_role_arn,
189192
)
190193
super().__init__(**kwargs)
191194

@@ -311,6 +314,17 @@ def config_type(cls):
311314
"determination may not be accurate. In this case, you can explicitly set this value to True or False."
312315
),
313316
),
317+
"service_discovery_role_arn": Field(
318+
StringSource,
319+
is_required=False,
320+
description=(
321+
"An optional IAM role ARN to assume when making AWS Cloud Map (service discovery) "
322+
"API calls. Use this when the Cloud Map namespace lives in a different AWS account "
323+
"than the ECS agent (e.g., centralized networking in AWS Organizations). The agent's "
324+
"task role must have sts:AssumeRole permission for this role, and the target role "
325+
"must trust the agent's task role."
326+
),
327+
),
314328
},
315329
SHARED_ECS_CONFIG,
316330
SHARED_USER_CODE_LAUNCHER_CONFIG,

dagster-cloud/dagster_cloud/workspace/kubernetes/launcher.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def __init__(
7777
inst_data=None,
7878
namespace=None,
7979
kubeconfig_file=None,
80+
k8s_api_ssl_ca_cert_file=None,
8081
pull_policy=None,
8182
env_config_maps=None,
8283
env_secrets=None,
@@ -147,6 +148,13 @@ def __init__(
147148
else:
148149
kubernetes.config.load_incluster_config()
149150

151+
# Override the SSL CA cert if a custom CA bundle is provided
152+
if k8s_api_ssl_ca_cert_file:
153+
# Get the active configuration and override the ssl_ca_cert
154+
config = kubernetes.client.Configuration.get_default_copy()
155+
config.ssl_ca_cert = k8s_api_ssl_ca_cert_file
156+
kubernetes.client.Configuration.set_default(config)
157+
150158
self._k8s_apps_api_client = k8s_apps_api_client
151159
self._k8s_core_api_client = k8s_core_api_client
152160

@@ -202,14 +210,15 @@ def __init__(
202210
run_k8s_config=self._run_k8s_config,
203211
fail_pod_on_run_failure=fail_pod_on_run_failure,
204212
kubeconfig_file=kubeconfig_file,
213+
k8s_api_ssl_ca_cert_file=k8s_api_ssl_ca_cert_file,
205214
load_incluster_config=not kubeconfig_file,
206215
security_context=self._security_context,
207216
# leave out the code server specific fields from only_allow_user_defined_k8s_config_fields
208217
only_allow_user_defined_k8s_config_fields=(
209218
{
210219
key: val
211220
for key, val in only_allow_user_defined_k8s_config_fields.items()
212-
if key not in {"service_metadata", "deployment_metadata"}
221+
if key not in {"service_metadata", "deployment_metadata", "service_spec_config"}
213222
}
214223
if only_allow_user_defined_k8s_config_fields
215224
else only_allow_user_defined_k8s_config_fields
@@ -244,6 +253,15 @@ def config_type(cls):
244253
"dagster_home": Field(StringSource, is_required=True),
245254
"instance_config_map": Field(StringSource, is_required=True),
246255
"kubeconfig_file": Field(StringSource, is_required=False),
256+
"k8s_api_ssl_ca_cert_file": Field(
257+
StringSource,
258+
is_required=False,
259+
description=(
260+
"Path to a custom CA bundle file for TLS verification when connecting to "
261+
"the Kubernetes API. Use this in enterprise environments with custom CA "
262+
"chains where the default service account CA cert is not sufficient."
263+
),
264+
),
247265
"deployment_startup_timeout": Field(
248266
IntSource,
249267
is_required=False,
@@ -302,6 +320,7 @@ def config_type(cls):
302320
"pod_template_spec_metadata": Permissive(),
303321
"deployment_metadata": Permissive(),
304322
"service_metadata": Permissive(),
323+
"service_spec_config": Permissive(),
305324
}
306325
),
307326
is_required=False,
@@ -331,6 +350,9 @@ def config_type(cls):
331350
"service_metadata": Field(
332351
Map(key_type=str, inner_type=bool), is_required=False
333352
),
353+
"service_spec_config": Field(
354+
Map(key_type=str, inner_type=bool), is_required=False
355+
),
334356
"namespace": Field(bool, is_required=False),
335357
}
336358
),

dagster-cloud/dagster_cloud/workspace/kubernetes/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ def construct_code_location_service(
8787
user_defined_k8s_config = container_context.server_k8s_config
8888
user_defined_service_metadata = copy.deepcopy(dict(user_defined_k8s_config.service_metadata))
8989
user_defined_service_labels = user_defined_service_metadata.pop("labels", {})
90+
user_defined_service_spec_config = copy.deepcopy(
91+
dict(user_defined_k8s_config.service_spec_config)
92+
)
9093

9194
return k8s_model_from_dict(
9295
client.V1Service,
@@ -104,6 +107,7 @@ def construct_code_location_service(
104107
},
105108
},
106109
"spec": {
110+
**user_defined_service_spec_config,
107111
"selector": {"user-deployment": service_name},
108112
"ports": [{"name": "grpc", "protocol": "TCP", "port": get_code_server_port()}],
109113
},

0 commit comments

Comments
 (0)