Skip to content

Commit 359e10e

Browse files
committed
Revert "Feat/use durable functions emulator image (aws#8708)"
This reverts commit 4e47788.
1 parent 57f68ad commit 359e10e

7 files changed

Lines changed: 238 additions & 371 deletions

File tree

samcli/commands/local/cli_common/durable_context.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,18 @@ class DurableContext:
1818
Automatically reuses existing running containers when possible.
1919
"""
2020

21-
def __init__(self, skip_pull_image=False):
21+
def __init__(self):
2222
"""
2323
Initialize the durable context.
24-
25-
Parameters
26-
----------
27-
skip_pull_image : bool
28-
If True, skip pulling the emulator container image
2924
"""
3025
self._emulator: Optional[DurableFunctionsEmulatorContainer] = None
3126
self._reused_container = False
32-
self._skip_pull_image = skip_pull_image
3327

3428
def __enter__(self) -> "DurableContext":
3529
"""
3630
Start the emulator container or attach to an already running one
3731
"""
38-
self._emulator = DurableFunctionsEmulatorContainer(skip_pull_image=self._skip_pull_image)
32+
self._emulator = DurableFunctionsEmulatorContainer()
3933
self._reused_container = self._emulator.start_or_attach()
4034
return self
4135

samcli/local/docker/durable_functions_emulator_container.py

Lines changed: 92 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import time
88
from http import HTTPStatus
99
from pathlib import Path
10+
from tempfile import NamedTemporaryFile
1011
from typing import Optional
1112

1213
import docker
@@ -15,7 +16,9 @@
1516

1617
from samcli.lib.build.utils import _get_host_architecture
1718
from samcli.lib.clients.lambda_client import DurableFunctionsClient
19+
from samcli.lib.utils.tar import create_tarball
1820
from samcli.local.docker.utils import (
21+
get_tar_filter_for_windows,
1922
get_validated_container_client,
2023
is_image_current,
2124
to_posix_path,
@@ -30,7 +33,8 @@ class DurableFunctionsEmulatorContainer:
3033
"""
3134

3235
_RAPID_SOURCE_PATH = Path(__file__).parent.joinpath("..", "rapid").resolve()
33-
_EMULATOR_IMAGE_PREFIX = "public.ecr.aws/durable-functions/aws-durable-execution-emulator"
36+
_EMULATOR_IMAGE = "public.ecr.aws/ubuntu/ubuntu:24.04"
37+
_EMULATOR_IMAGE_PREFIX = "samcli/durable-execution-emulator"
3438
_CONTAINER_NAME = "sam-durable-execution-emulator"
3539
_EMULATOR_DATA_DIR_NAME = ".durable-executions-local"
3640
_EMULATOR_DEFAULT_STORE_TYPE = "sqlite"
@@ -75,17 +79,11 @@ class DurableFunctionsEmulatorContainer:
7579
"""
7680
ENV_EMULATOR_PORT = "DURABLE_EXECUTIONS_EMULATOR_PORT"
7781

78-
"""
79-
Allow pinning to a specific emulator image tag/version
80-
"""
81-
ENV_EMULATOR_IMAGE_TAG = "DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG"
82-
83-
def __init__(self, container_client=None, existing_container=None, skip_pull_image=False):
82+
def __init__(self, container_client=None, existing_container=None):
8483
self._docker_client_param = container_client
8584
self._validated_docker_client: Optional[docker.DockerClient] = None
8685
self.container = existing_container
8786
self.lambda_client: Optional[DurableFunctionsClient] = None
88-
self._skip_pull_image = skip_pull_image
8987

9088
self.port = self._get_emulator_port()
9189

@@ -139,14 +137,6 @@ def _get_emulator_port(self):
139137
"""
140138
return self._get_port(self.ENV_EXTERNAL_EMULATOR_PORT, self.ENV_EMULATOR_PORT, self.EMULATOR_PORT)
141139

142-
def _get_emulator_image_tag(self):
143-
"""Get the emulator image tag from environment variable or use default."""
144-
return os.environ.get(self.ENV_EMULATOR_IMAGE_TAG, "latest")
145-
146-
def _get_emulator_image(self):
147-
"""Get the full emulator image name with tag."""
148-
return f"{self._EMULATOR_IMAGE_PREFIX}:{self._get_emulator_image_tag()}"
149-
150140
def _get_emulator_store_type(self):
151141
"""Get the store type from environment variable or use default."""
152142
store_type = os.environ.get(self.ENV_STORE_TYPE, self._EMULATOR_DEFAULT_STORE_TYPE)
@@ -182,7 +172,15 @@ def _get_emulator_environment(self):
182172
Get the environment variables for the emulator container.
183173
"""
184174
return {
185-
"DURABLE_EXECUTION_TIME_SCALE": self._get_emulator_time_scale(),
175+
"HOST": "0.0.0.0",
176+
"PORT": str(self.port),
177+
"LOG_LEVEL": "DEBUG",
178+
# The emulator needs to have credential variables set, or else it will fail to create boto clients.
179+
"AWS_ACCESS_KEY_ID": "foo",
180+
"AWS_SECRET_ACCESS_KEY": "bar",
181+
"AWS_DEFAULT_REGION": "us-east-1",
182+
"EXECUTION_STORE_TYPE": self._get_emulator_store_type(),
183+
"EXECUTION_TIME_SCALE": self._get_emulator_time_scale(),
186184
}
187185

188186
@property
@@ -200,35 +198,87 @@ def _get_emulator_binary_name(self):
200198
arch = _get_host_architecture()
201199
return f"aws-durable-execution-emulator-{arch}"
202200

201+
def _generate_emulator_dockerfile(self, emulator_binary_name: str) -> str:
202+
"""Generate Dockerfile content for emulator image."""
203+
return (
204+
f"FROM {self._EMULATOR_IMAGE}\n"
205+
f"COPY {emulator_binary_name} /usr/local/bin/{emulator_binary_name}\n"
206+
f"RUN chmod +x /usr/local/bin/{emulator_binary_name}\n"
207+
)
208+
209+
def _get_emulator_image_tag(self, emulator_binary_name: str) -> str:
210+
"""Get the Docker image tag for the emulator."""
211+
return f"{self._EMULATOR_IMAGE_PREFIX}:{emulator_binary_name}"
212+
213+
def _build_emulator_image(self):
214+
"""Build Docker image with emulator binary."""
215+
emulator_binary_name = self._get_emulator_binary_name()
216+
binary_path = self._RAPID_SOURCE_PATH / emulator_binary_name
217+
218+
if not binary_path.exists():
219+
raise RuntimeError(f"Durable Functions Emulator binary not found at {binary_path}")
220+
221+
image_tag = self._get_emulator_image_tag(emulator_binary_name)
222+
223+
# Check if image already exists
224+
try:
225+
self._docker_client.images.get(image_tag)
226+
LOG.debug(f"Emulator image {image_tag} already exists")
227+
return image_tag
228+
except docker.errors.ImageNotFound:
229+
LOG.debug(f"Building emulator image {image_tag}")
230+
231+
# Generate Dockerfile content
232+
dockerfile_content = self._generate_emulator_dockerfile(emulator_binary_name)
233+
234+
# Write Dockerfile to temp location and build image.
235+
# Use delete=False because on Windows, NamedTemporaryFile keeps the file
236+
# locked while open, preventing tarfile.add() from reading it.
237+
dockerfile = NamedTemporaryFile(mode="w", suffix="_Dockerfile", delete=False)
238+
try:
239+
dockerfile.write(dockerfile_content)
240+
dockerfile.flush()
241+
dockerfile.close()
242+
243+
# Prepare tar paths for build context
244+
tar_paths = {
245+
dockerfile.name: "Dockerfile",
246+
str(binary_path): emulator_binary_name,
247+
}
248+
249+
# Use shared tar filter for Windows compatibility
250+
tar_filter = get_tar_filter_for_windows()
251+
252+
# Build image using create_tarball utility
253+
with create_tarball(tar_paths, tar_filter=tar_filter, dereference=True) as tarballfile:
254+
try:
255+
self._docker_client.images.build(fileobj=tarballfile, custom_context=True, tag=image_tag, rm=True)
256+
LOG.info(f"Built emulator image {image_tag}")
257+
return image_tag
258+
except Exception as e:
259+
raise ClickException(f"Failed to build emulator image: {e}")
260+
finally:
261+
os.unlink(dockerfile.name)
262+
203263
def _pull_image_if_needed(self):
204-
local_image_exists = False
205264
"""Pull the emulator image if it doesn't exist locally or is out of date."""
206265
try:
207-
self._docker_client.images.get(self._get_emulator_image())
208-
local_image_exists = True
209-
LOG.debug(f"Emulator image {self._get_emulator_image()} exists locally")
210-
if is_image_current(self._docker_client, self._get_emulator_image()):
266+
self._docker_client.images.get(self._EMULATOR_IMAGE)
267+
LOG.debug(f"Emulator image {self._EMULATOR_IMAGE} exists locally")
268+
269+
if is_image_current(self._docker_client, self._EMULATOR_IMAGE):
211270
LOG.debug("Local emulator image is up-to-date")
212271
return
213272

214273
LOG.debug("Local image is out of date and will be updated to the latest version")
215274
except docker.errors.ImageNotFound:
216-
LOG.debug(f"Pulling emulator image {self._get_emulator_image()}...")
275+
LOG.debug(f"Pulling emulator image {self._EMULATOR_IMAGE}...")
217276

218277
try:
219-
if self._skip_pull_image and local_image_exists:
220-
LOG.debug("Skipping pulling new emulator image")
221-
return
222-
self._docker_client.images.pull(self._get_emulator_image())
223-
LOG.info(f"Successfully pulled image {self._get_emulator_image()}")
278+
self._docker_client.images.pull(self._EMULATOR_IMAGE)
279+
LOG.info(f"Successfully pulled image {self._EMULATOR_IMAGE}")
224280
except Exception as e:
225-
if local_image_exists:
226-
LOG.debug(
227-
f"Using existing local emulator image since we failed to pull emulator image "
228-
f"{self._get_emulator_image()}: {e}"
229-
)
230-
else:
231-
raise ClickException(f"Failed to pull emulator image {self._get_emulator_image()}: {e}")
281+
raise ClickException(f"Failed to pull emulator image {self._EMULATOR_IMAGE}: {e}")
232282

233283
def start(self):
234284
"""Start the emulator container."""
@@ -237,6 +287,8 @@ def start(self):
237287
LOG.info("Using external durable functions emulator, skipping container start")
238288
return
239289

290+
emulator_binary_name = self._get_emulator_binary_name()
291+
240292
"""
241293
Create persistent volume for execution data to be stored in.
242294
This will be at the current working directory. If a user is running `sam local invoke` in the same
@@ -249,27 +301,13 @@ def start(self):
249301
to_posix_path(emulator_data_dir): {"bind": "/tmp/.durable-executions-local", "mode": "rw"},
250302
}
251303

252-
self._pull_image_if_needed()
304+
# Build image with emulator binary
305+
image_tag = self._build_emulator_image()
253306

254307
LOG.debug(f"Creating container with name={self._container_name}, port={self.port}")
255308
self.container = self._docker_client.containers.create(
256-
image=self._get_emulator_image(),
257-
command=[
258-
"dex-local-runner",
259-
"start-server",
260-
"--host",
261-
"0.0.0.0",
262-
"--port",
263-
str(self.port),
264-
"--log-level",
265-
"DEBUG",
266-
"--lambda-endpoint",
267-
"http://host.docker.internal:3001",
268-
"--store-type",
269-
self._get_emulator_store_type(),
270-
"--store-path",
271-
"/tmp/.durable-executions-local/durable-executions.db", # this is the path within the container
272-
],
309+
image=image_tag,
310+
command=[f"/usr/local/bin/{emulator_binary_name}", "--host", "0.0.0.0", "--port", str(self.port)],
273311
name=self._container_name,
274312
ports={f"{self.port}/tcp": self.port},
275313
volumes=volumes,
@@ -420,14 +458,4 @@ def _wait_for_ready(self, timeout=30):
420458
except Exception:
421459
pass
422460

423-
raise RuntimeError(
424-
f"Durable Functions Emulator container failed to become ready within {timeout} seconds. "
425-
"You may set the DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG env variable to a specific image "
426-
"to ensure that you are using a compatible version. "
427-
f"Check https://${self._get_emulator_image().replace('public.ecr', 'gallery.ecr')}. "
428-
"and https://github.com/aws/aws-durable-execution-sdk-python-testing/releases "
429-
"for valid image tags. If the problems persist, you can try updating the SAM CLI version "
430-
" in case of incompatibility. "
431-
"You may check the emulator_data_dir for the durable-execution-emulator-{timestamp}.log file which "
432-
"contains the emulator logs. This may be useful for debugging."
433-
)
461+
raise RuntimeError(f"Durable Functions Emulator container failed to become ready within {timeout} seconds")

samcli/local/lambdafn/runtime.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -489,9 +489,7 @@ def get_or_create_emulator_container(self):
489489
DurableFunctionsEmulatorContainer: The singleton emulator container
490490
"""
491491
if self._durable_execution_emulator_container is None:
492-
self._durable_execution_emulator_container = DurableFunctionsEmulatorContainer(
493-
skip_pull_image=self._container_manager.skip_pull_image,
494-
)
492+
self._durable_execution_emulator_container = DurableFunctionsEmulatorContainer()
495493
self._durable_execution_emulator_container.start_or_attach()
496494
LOG.debug("Created and started durable functions emulator container")
497495
return self._durable_execution_emulator_container
27.6 MB
Binary file not shown.
27.5 MB
Binary file not shown.

0 commit comments

Comments
 (0)