-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds warnings if automounts seem to be required #2667
Changes from 45 commits
304d09d
956c439
631fd54
a887cf1
0dbac39
0cf5340
261932b
0315a22
3a5fcae
d3d0a42
1f04e22
8e14192
3e99220
b74ac27
f2f92c1
859859e
35d1f0a
a57bf58
0573ece
e6da2fd
a2a7647
14db34f
6f7e792
68584cc
498c947
f5190b6
e39d6b2
b3dc36b
5c48fc5
0851be3
ce7e700
18f30a5
8d592a9
0236192
bf2b590
9284f1c
10fe5dc
72d5f23
e0b223e
a48bfbf
fb7db5b
b084a55
ec0ad9a
7384abf
2f962db
341abc4
c3ef2da
9766ec8
6d0c18a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,7 @@ | |
) | ||
from .gpu import GPU_T, parse_gpu_config | ||
from .image import _Image | ||
from .mount import _get_client_mount, _Mount, get_auto_mounts | ||
from .mount import _get_client_mount, _Mount, get_sys_modules_mounts | ||
from .network_file_system import _NetworkFileSystem, network_file_system_mount_protos | ||
from .output import _get_output_manager | ||
from .parallel_map import ( | ||
|
@@ -434,7 +434,7 @@ def _bind_method( | |
return fun | ||
|
||
@staticmethod | ||
def from_args( | ||
def from_local( | ||
info: FunctionInfo, | ||
app, | ||
image: _Image, | ||
|
@@ -501,18 +501,41 @@ def from_args( | |
if include_source_mode != IncludeSourceMode.INCLUDE_NOTHING: | ||
entrypoint_mounts = info.get_entrypoint_mount() | ||
else: | ||
entrypoint_mounts = [] | ||
entrypoint_mounts = {} | ||
|
||
all_mounts = [ | ||
_get_client_mount(), | ||
*explicit_mounts, | ||
*entrypoint_mounts, | ||
*entrypoint_mounts.values(), | ||
] | ||
|
||
if include_source_mode is IncludeSourceMode.INCLUDE_FIRST_PARTY: | ||
# TODO(elias): if using INCLUDE_FIRST_PARTY *and* mounts are added that haven't already been | ||
# added to the image via add_local_python_source | ||
all_mounts += get_auto_mounts() | ||
auto_mounts = get_sys_modules_mounts() | ||
# don't need to add entrypoint modules to automounts: | ||
for entrypoint_module in entrypoint_mounts: | ||
auto_mounts.pop(entrypoint_module, None) | ||
|
||
warn_missing_modules = set(auto_mounts.keys()) - image._added_python_source_set | ||
|
||
if warn_missing_modules: | ||
python_stringified_modules = ", ".join(f'"{mod}"' for mod in sorted(warn_missing_modules)) | ||
deprecation_warning( | ||
(2024, 1, 28), | ||
( | ||
"Automatic adding of local python source will be deprecated in the future.\n" | ||
f"Make sure you have explicitly added the source for the following modules to the image " | ||
f"used by `{info.function_name}`:\n" | ||
) | ||
+ ", ".join(sorted(warn_missing_modules)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: can we make this a bulleted list? It looks visually strange for a single module imo:
|
||
+ "\n\n" | ||
+ ( | ||
"This can be using `Image.add_local_python_source`, e.g.:\n" | ||
f"image_with_source = Image.debian_slim().add_local_python_source" | ||
f"({python_stringified_modules})" | ||
), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a line like "For more information, see https://modal.com/docs/guide/modal-1-0-migration"? When users start seeing all the new deprecation warnings I think it will be helpful to call their attention to the idea that this is "one last hurrah" to manage their annoyance :D. We can also add a more verbose explanation of what's going on and the various options for ameliorating it there. I can work on that. |
||
pending=True, | ||
) | ||
all_mounts += auto_mounts.values() | ||
else: | ||
# skip any mount introspection/logic inside containers, since the function | ||
# should already be hydrated | ||
|
@@ -561,7 +584,7 @@ def from_args( | |
for k, pf in build_functions: | ||
build_function = pf.raw_f | ||
snapshot_info = FunctionInfo(build_function, user_cls=info.user_cls) | ||
snapshot_function = _Function.from_args( | ||
snapshot_function = _Function.from_local( | ||
snapshot_info, | ||
app=None, | ||
image=image, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -402,12 +402,14 @@ class _Image(_Object, type_prefix="im"): | |
_deferred_mounts: Sequence[ | ||
_Mount | ||
] # added as mounts on any container referencing the Image, see `def _mount_layers` | ||
_added_python_source_set: frozenset[str] # used to warn about missing mounts during auto-mount deprecation | ||
_metadata: Optional[api_pb2.ImageMetadata] = None # set on hydration, private for now | ||
|
||
def _initialize_from_empty(self): | ||
self.inside_exceptions = [] | ||
self._serve_mounts = frozenset() | ||
self._deferred_mounts = () | ||
self._added_python_source_set = frozenset() | ||
self.force_build = False | ||
|
||
def _initialize_from_other(self, other: "_Image"): | ||
|
@@ -416,6 +418,7 @@ def _initialize_from_other(self, other: "_Image"): | |
self.force_build = other.force_build | ||
self._serve_mounts = other._serve_mounts | ||
self._deferred_mounts = other._deferred_mounts | ||
self._added_python_source_set = other._added_python_source_set | ||
|
||
def _hydrate_metadata(self, metadata: Optional[Message]): | ||
env_image_id = config.get("image_id") # set as an env var in containers | ||
|
@@ -657,9 +660,18 @@ async def _load(self: _Image, resolver: Resolver, existing_object_id: Optional[s | |
self._serve_mounts = frozenset(local_mounts) | ||
|
||
rep = f"Image({dockerfile_function})" | ||
obj = _Image._from_loader(_load, rep, deps=_deps) | ||
obj.force_build = force_build | ||
return obj | ||
img = _Image._from_loader(_load, rep, deps=_deps) | ||
img.force_build = force_build | ||
return img | ||
|
||
def _extend(self, **kwargs) -> "_Image": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we used to have this as a public (?) method, but removed/deprecated it. Now I needed a new private version of it to make it more ergonomic to transfer attributes from parent layers to children ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there some reason There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't seem to come up with one now, but I have a feeling there was something that made me go this route back in december 😬 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reverted it and managed to get it working now. Also found a bug and added a test for that in the process There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, seems not ideal to have the logic required to extend an Image distributed over two methods instead of just one. |
||
"""Internal use only - helper method to create a new Image with self as base | ||
|
||
Transfers required static attributes to the new layer as expected. | ||
""" | ||
img = _Image._from_args(base_images={"base": self}, **kwargs) | ||
img._added_python_source_set = self._added_python_source_set | ||
return img | ||
|
||
def copy_mount(self, mount: _Mount, remote_path: Union[str, Path] = ".") -> "_Image": | ||
""" | ||
|
@@ -686,8 +698,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
commands = ["FROM base", f"COPY . {remote_path}"] # copy everything from the supplied mount | ||
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
context_mount_function=lambda: mount, | ||
) | ||
|
@@ -801,8 +812,7 @@ def copy_local_file(self, local_path: Union[str, Path], remote_path: Union[str, | |
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | ||
return DockerfileSpec(commands=["FROM base", f"COPY {basename} {remote_path}"], context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
context_mount_function=lambda: _Mount._from_local_file(local_path, remote_path=f"/{basename}"), | ||
) | ||
|
@@ -843,7 +853,9 @@ def add_local_python_source( | |
``` | ||
""" | ||
mount = _Mount._from_local_python_packages(*modules, ignore=ignore) | ||
return self._add_mount_layer_or_copy(mount, copy=copy) | ||
img = self._add_mount_layer_or_copy(mount, copy=copy) | ||
img._added_python_source_set |= set(modules) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the key part of this change - track all |
||
return img | ||
|
||
def copy_local_dir( | ||
self, | ||
|
@@ -908,8 +920,7 @@ def copy_local_dir( | |
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | ||
return DockerfileSpec(commands=["FROM base", f"COPY . {remote_path}"], context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
context_mount_function=lambda: _Mount._add_local_dir( | ||
Path(local_path), PurePosixPath("/"), ignore=_ignore_fn(ignore) | ||
|
@@ -988,8 +999,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
gpu_config = parse_gpu_config(gpu) | ||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
gpu_config=gpu_config, | ||
|
@@ -1089,8 +1099,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
|
||
gpu_config = parse_gpu_config(gpu) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
secrets=secrets, | ||
gpu_config=gpu_config, | ||
|
@@ -1129,8 +1138,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
commands = [cmd.strip() for cmd in commands] | ||
return DockerfileSpec(commands=commands, context_files=context_files) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
gpu_config=parse_gpu_config(gpu), | ||
|
@@ -1191,8 +1199,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
|
||
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
secrets=secrets, | ||
|
@@ -1272,8 +1279,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
] | ||
return DockerfileSpec(commands=commands, context_files=context_files) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
secrets=secrets, | ||
|
@@ -1346,8 +1352,7 @@ def dockerfile_commands( | |
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | ||
return DockerfileSpec(commands=["FROM base", *cmds], context_files=context_files) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
secrets=secrets, | ||
gpu_config=parse_gpu_config(gpu), | ||
|
@@ -1394,8 +1399,7 @@ def run_commands( | |
def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | ||
return DockerfileSpec(commands=["FROM base"] + [f"RUN {cmd}" for cmd in cmds], context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
secrets=secrets, | ||
gpu_config=parse_gpu_config(gpu), | ||
|
@@ -1505,8 +1509,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
context_files = {} if spec_file is None else {remote_spec_file: os.path.expanduser(spec_file)} | ||
return DockerfileSpec(commands=commands, context_files=context_files) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
secrets=secrets, | ||
|
@@ -1820,8 +1823,7 @@ def build_dockerfile_python(version: ImageBuilderVersion) -> DockerfileSpec: | |
context_files = {CONTAINER_REQUIREMENTS_PATH: requirements_path} | ||
return DockerfileSpec(commands=commands, context_files=context_files) | ||
|
||
return _Image._from_args( | ||
base_images={"base": base_image}, | ||
return base_image._extend( | ||
dockerfile_function=build_dockerfile_python, | ||
context_mount_function=add_python_mount, | ||
force_build=force_build, | ||
|
@@ -1888,8 +1890,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
] | ||
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
force_build=self.force_build or force_build, | ||
gpu_config=parse_gpu_config(gpu), | ||
|
@@ -1954,7 +1955,7 @@ def my_build_function(): | |
|
||
info = FunctionInfo(raw_f) | ||
|
||
function = _Function.from_args( | ||
function = _Function.from_local( | ||
info, | ||
app=None, | ||
image=self, # type: ignore[reportArgumentType] # TODO: probably conflict with type stub? | ||
|
@@ -1980,8 +1981,7 @@ def my_build_function(): | |
build_function_input = api_pb2.FunctionInput(args=args_serialized, data_format=api_pb2.DATA_FORMAT_PICKLE) | ||
else: | ||
build_function_input = None | ||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
build_function=function, | ||
build_function_input=build_function_input, | ||
force_build=self.force_build or force_build, | ||
|
@@ -2004,8 +2004,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
commands = ["FROM base"] + [f"ENV {key}={shlex.quote(val)}" for (key, val) in vars.items()] | ||
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
) | ||
|
||
|
@@ -2028,8 +2027,7 @@ def build_dockerfile(version: ImageBuilderVersion) -> DockerfileSpec: | |
commands = ["FROM base", f"WORKDIR {shlex.quote(str(path))}"] | ||
return DockerfileSpec(commands=commands, context_files={}) | ||
|
||
return _Image._from_args( | ||
base_images={"base": self}, | ||
return self._extend( | ||
dockerfile_function=build_dockerfile, | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mwaskom let me know if you have ideas for other ways of phrasing this, and if we should mention the
include_source
option or not (I'm leaning towards no since it's not typically needed)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think that
include_source
is not relevant at this stage of the deprecation process; advising users on how to update their Image definitionsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested rephrasing of the prose: