diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..265bb2aaa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3767557fc..ae5c162da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ We appreciate your patience while we speedily work towards a stable release of t +### 0.64.227 (2024-10-25) + +- The `modal container list` CLI command now only shows containers for the active profile's environment if there is one, otherwise it uses the default environment. You can pass `--env` to list containers in other environments. + ### 0.64.223 (2024-10-24) * Fix for `modal serve` not showing progress when reloading apps on file changes since v0.63.79 diff --git a/modal/cli/config.py b/modal/cli/config.py index 949e685ac..ccb83eeaa 100644 --- a/modal/cli/config.py +++ b/modal/cli/config.py @@ -1,7 +1,6 @@ # Copyright Modal Labs 2022 -import pprint - import typer +from rich.console import Console from modal.config import _profile, _store_user_config, config @@ -17,10 +16,15 @@ ) -@config_cli.command(help="Show configuration values for the current profile (debug command).") -def show(): +@config_cli.command(help="Show current configuration values (debugging command).") +def show(redact: bool = typer.Option(True, help="Redact the `token_secret` value.")): # This is just a test command - pprint.pprint(config.to_dict()) + config_dict = config.to_dict() + if redact and config_dict.get("token_secret"): + config_dict["token_secret"] = "***" + + console = Console() + console.print(config_dict) SET_DEFAULT_ENV_HELP = """Set the default Modal environment for the active profile diff --git a/modal/client.py b/modal/client.py index cb4714623..dfa4c8b45 100644 --- a/modal/client.py +++ b/modal/client.py @@ -210,6 +210,11 @@ async def from_env(cls, _override_config=None) -> "_Client": token_id = c["token_id"] token_secret = c["token_secret"] if task_secret: + if token_id or token_secret: + warnings.warn( + "Modal tokens provided by MODAL_TOKEN_ID and MODAL_TOKEN_SECRET" + " (or through the config file) are ignored inside containers." + ) client_type = api_pb2.CLIENT_TYPE_CONTAINER credentials = None elif token_id and token_secret: diff --git a/modal/proxy.py b/modal/proxy.py index 2dc0dbc0a..e64c3203a 100644 --- a/modal/proxy.py +++ b/modal/proxy.py @@ -9,12 +9,10 @@ class _Proxy(_Object, type_prefix="pr"): - """ - Proxy objects are used to setup secure tunnel connections to a private remote address, for example - a database. + """Proxy objects give your Modal containers a static outbound IP address. - Currently `modal.Proxy` objects must be setup with the assistance of Modal staff. If you require a proxy - please contact us. + This can be used for connecting to a remote address with network whitelist, for example + a database. See [the guide](/docs/guide/proxy-ips) for more information. """ @staticmethod diff --git a/modal_proto/api.proto b/modal_proto/api.proto index 13150d670..6e0c4ae6f 100644 --- a/modal_proto/api.proto +++ b/modal_proto/api.proto @@ -78,6 +78,7 @@ enum CheckpointStatus { enum ClientType { CLIENT_TYPE_UNSPECIFIED = 0; CLIENT_TYPE_CLIENT = 1; + CLIENT_TYPE_WORKER = 2; CLIENT_TYPE_CONTAINER = 3; CLIENT_TYPE_WEB_SERVER = 5; } diff --git a/modal_version/_version_generated.py b/modal_version/_version_generated.py index 95fe5b1ec..f578adb7b 100644 --- a/modal_version/_version_generated.py +++ b/modal_version/_version_generated.py @@ -1,4 +1,4 @@ # Copyright Modal Labs 2024 # Note: Reset this value to -1 whenever you make a minor `0.X` release of the client. -build_number = 227 # git: 2fe452c +build_number = 233 # git: 76f36d3 diff --git a/setup.cfg b/setup.cfg index 52136ad04..7660ee70e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,6 +34,10 @@ install_requires = watchfiles typing_extensions~=4.6 +[options.packages.find] +exclude = + test* + [options.entry_points] console_scripts = modal = modal.__main__:main diff --git a/test/cli_test.py b/test/cli_test.py index f38309251..6a7065f4f 100644 --- a/test/cli_test.py +++ b/test/cli_test.py @@ -789,6 +789,19 @@ def test_profile_list(servicer, server_url_env, modal_config): del os.environ["MODAL_TOKEN_SECRET"] +def test_config_show(servicer, server_url_env, modal_config): + config = """ + [test-profile] + token_id = "ak-abc" + token_secret = "as-xyz" + active = true + """ + with modal_config(config): + res = _run(["config", "show"]) + assert "'token_id': 'ak-abc'" in res.stdout + assert "'token_secret': '***'" in res.stdout + + def test_app_list(servicer, mock_dir, set_env_client): res = _run(["app", "list"]) assert "my_app_foo" not in res.stdout diff --git a/test/client_test.py b/test/client_test.py index 7be39ba83..7e74453b5 100644 --- a/test/client_test.py +++ b/test/client_test.py @@ -20,11 +20,11 @@ def test_client_type(servicer, client): assert len(servicer.requests) == 1 assert isinstance(servicer.requests[0], Empty) - assert servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) def test_client_platform_string(servicer, client): - platform_str = servicer.client_create_metadata["x-modal-platform"] + platform_str = servicer.last_metadata["x-modal-platform"] system, release, machine = platform_str.split("-") if platform.system() == "Darwin": assert system == "macOS" @@ -39,7 +39,7 @@ def test_client_platform_string(servicer, client): async def test_container_client_type(servicer, container_client): assert len(servicer.requests) == 1 # no heartbeat, just ClientHello assert isinstance(servicer.requests[0], Empty) - assert servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER) + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER) @pytest.mark.asyncio @@ -160,7 +160,7 @@ def test_client_token_auth_in_container(servicer, credentials, monkeypatch) -> N """ monkeypatch.setenv("MODAL_IS_REMOTE", "1") _client = client_from_env(servicer.client_addr, credentials) - assert servicer.client_create_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) def test_multiple_profile_error(servicer, modal_config): @@ -216,12 +216,16 @@ def test_from_env_container(servicer, container_env): servicer.required_creds = {} # Disallow default client creds Client.from_env() # TODO(erikbern): once we no longer run ClientHello by default, add a ping here + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER) def test_from_env_container_with_tokens(servicer, container_env, token_env): + # Even if MODAL_TOKEN_ID and MODAL_TOKEN_SECRET are set, if we're in a containers, ignore those servicer.required_creds = {} # Disallow default client creds - Client.from_env() + with pytest.warns(match="token"): + Client.from_env() # TODO(erikbern): once we no longer run ClientHello by default, add a ping here + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CONTAINER) def test_from_credentials_client(servicer, set_env_client, server_url_env, token_env): @@ -231,6 +235,7 @@ def test_from_credentials_client(servicer, set_env_client, server_url_env, token servicer.required_creds = {token_id: token_secret} Client.from_credentials(token_id, token_secret) # TODO(erikbern): once we no longer run ClientHello by default, add a ping here + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) def test_from_credentials_container(servicer, container_env): @@ -239,3 +244,4 @@ def test_from_credentials_container(servicer, container_env): servicer.required_creds = {token_id: token_secret} Client.from_credentials(token_id, token_secret) # TODO(erikbern): once we no longer run ClientHello by default, add a ping here + assert servicer.last_metadata["x-modal-client-type"] == str(api_pb2.CLIENT_TYPE_CLIENT) diff --git a/test/config_test.py b/test/config_test.py index 93be4e7a7..035754305 100644 --- a/test/config_test.py +++ b/test/config_test.py @@ -35,7 +35,7 @@ def _cli(args, env={}): def _get_config(env={}): - stdout = _cli(["config", "show"], env=env) + stdout = _cli(["config", "show", "--no-redact"], env=env) return eval(stdout) diff --git a/test/conftest.py b/test/conftest.py index 55cff9ca2..075f7282a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -247,6 +247,7 @@ def __init__(self, blob_host, blobs, credentials): token_id, token_secret = credentials self.required_creds = {token_id: token_secret} # Any of this will be accepted + self.last_metadata = None @self.function_body def default_function_body(*args, **kwargs): @@ -254,6 +255,7 @@ def default_function_body(*args, **kwargs): async def recv_request(self, event: RecvRequest): # Make sure metadata is correct + self.last_metadata = event.metadata for header in [ "x-modal-python-version", "x-modal-client-version", @@ -642,7 +644,6 @@ async def ClassGet(self, stream): async def ClientHello(self, stream): request: Empty = await stream.recv_message() self.requests.append(request) - self.client_create_metadata = stream.metadata client_version = stream.metadata["x-modal-client-version"] warning = "" assert stream.user_agent.startswith(f"modal-client/{__version__} ")