diff --git a/libs/cli/langgraph_cli/util.py b/libs/cli/langgraph_cli/util.py index 61c2ae8b8..683ed44bc 100644 --- a/libs/cli/langgraph_cli/util.py +++ b/libs/cli/langgraph_cli/util.py @@ -19,7 +19,7 @@ def warn_non_wolfi_distro(config_json: dict) -> None: fg="yellow", ) click.secho( - " To switch, add '\"image_distro\": \"wolfi\"' to your langgraph.json config file.", + ' To switch, add \'"image_distro": "wolfi"\' to your langgraph.json config file.', fg="yellow", ) click.secho("") # Empty line for better readability diff --git a/libs/cli/schemas/schema.json b/libs/cli/schemas/schema.json index a8972fb0c..b47bf0013 100644 --- a/libs/cli/schemas/schema.json +++ b/libs/cli/schemas/schema.json @@ -119,6 +119,21 @@ ], "description": "Optional. Configuration for the built-in HTTP server, controlling which custom routes are exposed\nand how cross-origin requests are handled.\n" }, + "image_distro": { + "anyOf": [ + { + "type": "string", + "enum": [ + "debian", + "wolfi" + ] + }, + { + "type": "null" + } + ], + "description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n" + }, "store": { "anyOf": [ { @@ -257,6 +272,21 @@ ], "description": "Optional. Configuration for the built-in HTTP server, controlling which custom routes are exposed\nand how cross-origin requests are handled.\n" }, + "image_distro": { + "anyOf": [ + { + "type": "string", + "enum": [ + "debian", + "wolfi" + ] + }, + { + "type": "null" + } + ], + "description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n" + }, "store": { "anyOf": [ { diff --git a/libs/cli/schemas/schema.v0.json b/libs/cli/schemas/schema.v0.json index a8972fb0c..b47bf0013 100644 --- a/libs/cli/schemas/schema.v0.json +++ b/libs/cli/schemas/schema.v0.json @@ -119,6 +119,21 @@ ], "description": "Optional. Configuration for the built-in HTTP server, controlling which custom routes are exposed\nand how cross-origin requests are handled.\n" }, + "image_distro": { + "anyOf": [ + { + "type": "string", + "enum": [ + "debian", + "wolfi" + ] + }, + { + "type": "null" + } + ], + "description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n" + }, "store": { "anyOf": [ { @@ -257,6 +272,21 @@ ], "description": "Optional. Configuration for the built-in HTTP server, controlling which custom routes are exposed\nand how cross-origin requests are handled.\n" }, + "image_distro": { + "anyOf": [ + { + "type": "string", + "enum": [ + "debian", + "wolfi" + ] + }, + { + "type": "null" + } + ], + "description": "Optional. Linux distribution for the base image.\n\nMust be either 'debian' or 'wolfi'. If omitted, defaults to 'debian'.\n" + }, "store": { "anyOf": [ { diff --git a/libs/cli/tests/unit_tests/cli/test_cli.py b/libs/cli/tests/unit_tests/cli/test_cli.py index b9101fe76..f458cbcbc 100644 --- a/libs/cli/tests/unit_tests/cli/test_cli.py +++ b/libs/cli/tests/unit_tests/cli/test_cli.py @@ -462,7 +462,7 @@ def test_dockerfile_command_shows_wolfi_warning() -> None: # Assert command was successful assert result.exit_code == 0, result.output - + # Check that warning is shown assert "Security Recommendation" in result.output assert "Wolfi Linux" in result.output @@ -492,7 +492,7 @@ def test_dockerfile_command_no_wolfi_warning_when_wolfi_set() -> None: # Assert command was successful assert result.exit_code == 0, result.output - + # Check that warning is NOT shown assert "Security Recommendation" not in result.output assert "Wolfi Linux" not in result.output @@ -516,11 +516,17 @@ def test_build_command_shows_wolfi_warning() -> None: with runner.isolated_filesystem(): result = runner.invoke( cli, - ["build", "--tag", "test-image", "--config", str(temp_dir / "config.json")], + [ + "build", + "--tag", + "test-image", + "--config", + str(temp_dir / "config.json"), + ], catch_exceptions=True, ) - # The command will fail because docker isn't available or we're mocking, + # The command will fail because docker isn't available or we're mocking, # but we should still see the warning before it fails assert "Security Recommendation" in result.output assert "Wolfi Linux" in result.output diff --git a/libs/cli/tests/unit_tests/test_config.py b/libs/cli/tests/unit_tests/test_config.py index 6acdb00ca..060b4ab0d 100644 --- a/libs/cli/tests/unit_tests/test_config.py +++ b/libs/cli/tests/unit_tests/test_config.py @@ -124,12 +124,14 @@ def test_validate_config(): ) assert config["python_version"] == "3.12-slim" with pytest.raises(ValueError, match="Invalid http.app format"): - validate_config({ - "python_version": "3.12", - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - "http": {"app": "../../examples/my_app.py"}, - }) + validate_config( + { + "python_version": "3.12", + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + "http": {"app": "../../examples/my_app.py"}, + } + ) def test_validate_config_image_distro(): @@ -189,7 +191,7 @@ def test_validate_config_image_distro(): ) assert "Invalid image_distro: 'alpine'" in str(exc_info.value) - # Test base Node.js config with image distro + # Test base Node.js config with image distro config = validate_config( { "node_version": "20", @@ -970,124 +972,144 @@ def test_config_to_compose_end_to_end(): def test_docker_tag_image_distro(): """Test docker_tag function with different image_distro configurations.""" - + # Test 1: Default distro (debian) - no suffix - config = validate_config({ - "python_version": "3.11", - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - }) + config = validate_config( + { + "python_version": "3.11", + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + } + ) tag = docker_tag(config) assert tag == "langchain/langgraph-api:3.11" - + # Test 2: Explicit debian distro - no suffix (same as default) - config = validate_config({ - "python_version": "3.11", - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - "image_distro": "debian", - }) + config = validate_config( + { + "python_version": "3.11", + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + "image_distro": "debian", + } + ) tag = docker_tag(config) assert tag == "langchain/langgraph-api:3.11" - + # Test 3: Wolfi distro - should add suffix - config = validate_config({ - "python_version": "3.11", - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "python_version": "3.11", + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) assert tag == "langchain/langgraph-api:3.11-wolfi" - + # Test 4: Node.js with default distro - config = validate_config({ - "node_version": "20", - "graphs": {"agent": "./agent.js:graph"}, - }) + config = validate_config( + { + "node_version": "20", + "graphs": {"agent": "./agent.js:graph"}, + } + ) tag = docker_tag(config) assert tag == "langchain/langgraphjs-api:20" - + # Test 5: Node.js with wolfi distro - config = validate_config({ - "node_version": "20", - "graphs": {"agent": "./agent.js:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "node_version": "20", + "graphs": {"agent": "./agent.js:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) assert tag == "langchain/langgraphjs-api:20-wolfi" - + # Test 6: Custom base image with wolfi - config = validate_config({ - "python_version": "3.12", - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - "image_distro": "wolfi", - "base_image": "my-registry/custom-image", - }) + config = validate_config( + { + "python_version": "3.12", + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + "image_distro": "wolfi", + "base_image": "my-registry/custom-image", + } + ) tag = docker_tag(config, base_image="my-registry/custom-image") assert tag == "my-registry/custom-image:3.12-wolfi" def test_docker_tag_multiplatform_with_distro(): """Test docker_tag with multiplatform configs and image_distro.""" - + # Test 1: Multiplatform (Python + Node) with wolfi - config = validate_config({ - "python_version": "3.11", - "node_version": "20", - "dependencies": ["."], - "graphs": {"python": "./agent.py:graph", "js": "./agent.js:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "python_version": "3.11", + "node_version": "20", + "dependencies": ["."], + "graphs": {"python": "./agent.py:graph", "js": "./agent.js:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) # Should default to Python when both are present assert tag == "langchain/langgraph-api:3.11-wolfi" - + # Test 2: Node-only multiplatform with wolfi - config = validate_config({ - "node_version": "20", - "graphs": {"js": "./agent.js:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "node_version": "20", + "graphs": {"js": "./agent.js:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) assert tag == "langchain/langgraphjs-api:20-wolfi" def test_docker_tag_different_python_versions_with_distro(): """Test docker_tag with different Python versions and distros.""" - + versions_and_expected = [ ("3.11", "langchain/langgraph-api:3.11-wolfi"), ("3.12", "langchain/langgraph-api:3.12-wolfi"), - ("3.13", "langchain/langgraph-api:3.13-wolfi") + ("3.13", "langchain/langgraph-api:3.13-wolfi"), ] - + for python_version, expected_tag in versions_and_expected: - config = validate_config({ - "python_version": python_version, - "dependencies": ["."], - "graphs": {"agent": "./agent.py:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "python_version": python_version, + "dependencies": ["."], + "graphs": {"agent": "./agent.py:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) assert tag == expected_tag, f"Failed for Python {python_version}" def test_docker_tag_different_node_versions_with_distro(): """Test docker_tag with different Node.js versions and distros.""" - + versions_and_expected = [ ("20", "langchain/langgraphjs-api:20-wolfi"), ("21", "langchain/langgraphjs-api:21-wolfi"), - ("22", "langchain/langgraphjs-api:22-wolfi") + ("22", "langchain/langgraphjs-api:22-wolfi"), ] - + for node_version, expected_tag in versions_and_expected: - config = validate_config({ - "node_version": node_version, - "graphs": {"agent": "./agent.js:graph"}, - "image_distro": "wolfi", - }) + config = validate_config( + { + "node_version": node_version, + "graphs": {"agent": "./agent.js:graph"}, + "image_distro": "wolfi", + } + ) tag = docker_tag(config) assert tag == expected_tag, f"Failed for Node.js {node_version}"