Transport API¶
Transport layer for local and remote execution.
Overview¶
Transport abstracts command execution and file operations:
- LocalTransport: Execute commands locally
- SSHTransport: Execute commands via SSH (future)
- NullTransport: No-op transport for testing
Resources use transport for all system interactions, enabling the same configuration to work locally or remotely.
Transport Interface¶
All transports implement these methods:
File Operations¶
# Check if file exists
exists: bool = transport.file_exists("/path/to/file")
# Read file content
content: bytes = transport.read_file("/path/to/file")
# Write file content
transport.write_file("/path/to/file", b"content")
Command Execution¶
# Run command
output: str
code: int
output, code = transport.run_command(["ls", "-la", "/tmp"])
# Run shell command
output, code = transport.run_shell("echo 'hello' > /tmp/test.txt")
LocalTransport¶
Execute commands on the local system.
Usage¶
from cook.transport import LocalTransport
transport = LocalTransport()
# File operations
exists = transport.file_exists("/etc/hosts")
content = transport.read_file("/etc/hosts")
# Command execution
output, code = transport.run_command(["uname", "-a"])
print(f"Output: {output}")
print(f"Exit code: {code}")
Configuration¶
LocalTransport is the default for all resources:
Examples¶
File Operations¶
from cook.transport import LocalTransport
transport = LocalTransport()
# Check existence
if transport.file_exists("/etc/motd"):
# Read content
content = transport.read_file("/etc/motd")
print(content.decode())
# Write content
transport.write_file("/tmp/test.txt", b"Hello World")
Command Execution¶
transport = LocalTransport()
# Run command
output, code = transport.run_command(["ls", "-la", "/tmp"])
if code == 0:
print(f"Directory listing:\n{output}")
# Run shell command
output, code = transport.run_shell("cat /etc/os-release | grep VERSION")
print(output)
SSHTransport¶
Execute commands on remote systems via SSH (future implementation).
Planned Usage¶
from cook.transport import SSHTransport
# Connect to remote host
transport = SSHTransport(
host="server.example.com",
username="deploy",
key_file="/home/user/.ssh/id_rsa"
)
# Use with resources
from cook import File
file = File("/etc/motd", content="Welcome")
file._transport = transport
# Execute remotely
from cook.core.executor import get_executor
from cook.core import Platform
executor = get_executor()
platform = Platform.detect(transport)
executor.execute(platform)
Planned Features¶
- SSH key authentication
- Password authentication
- Connection pooling
- SFTP for file transfer
- Command output streaming
NullTransport¶
No-op transport for testing.
Usage¶
from cook.transport import NullTransport
transport = NullTransport()
# All operations return empty/false
exists = transport.file_exists("/any/path") # Always False
content = transport.read_file("/any/path") # Empty bytes
output, code = transport.run_command(["ls"]) # Empty output, code 0
Testing Example¶
from cook import File
from cook.transport import NullTransport
from cook.core import Platform, Action
# Create resource
file = File("/etc/motd", content="Welcome")
# Use null transport (no actual operations)
file._transport = NullTransport()
# Check state (won't actually check file)
platform = Platform.detect()
state = file.check(platform)
print(state) # {"exists": False, ...}
Using Transport in Resources¶
Resources access transport via self._transport:
File Operations¶
class MyResource(Resource):
def check(self, platform: Platform) -> Dict[str, Any]:
# Check if file exists
if self._transport.file_exists("/path/to/file"):
# Read file
content = self._transport.read_file("/path/to/file")
return {"exists": True, "content": content.decode()}
return {"exists": False}
def apply(self, plan: Plan, platform: Platform) -> None:
# Write file
content = "Hello World".encode()
self._transport.write_file("/path/to/file", content)
Command Execution¶
class MyResource(Resource):
def check(self, platform: Platform) -> Dict[str, Any]:
# Run command
output, code = self._transport.run_command(["systemctl", "is-active", "nginx"])
return {"running": code == 0}
def apply(self, plan: Plan, platform: Platform) -> None:
# Start service
output, code = self._transport.run_command(["systemctl", "start", "nginx"])
if code != 0:
raise RuntimeError(f"Failed to start service: {output}")
Shell Commands¶
class MyResource(Resource):
def apply(self, plan: Plan, platform: Platform) -> None:
# Run shell command with pipes/redirects
output, code = self._transport.run_shell(
"cat /var/log/app.log | grep ERROR > /tmp/errors.txt"
)
Transport Methods¶
file_exists()¶
Check if file or directory exists.
Parameters:
- path: File or directory path
Returns:
- bool: True if exists, False otherwise
Example:
read_file()¶
Read file content as bytes.
Parameters:
- path: File path
Returns:
- bytes: File content
Raises:
- FileNotFoundError: If file doesn't exist
Example:
content = transport.read_file("/etc/hostname")
hostname = content.decode().strip()
print(f"Hostname: {hostname}")
write_file()¶
Write content to file.
Parameters:
- path: File path
- content: File content as bytes
Example:
run_command()¶
Run command with arguments.
Parameters:
- args: List of command and arguments
Returns:
- Tuple of (output: str, exit_code: int)
Example:
output, code = transport.run_command(["apt-get", "update"])
if code == 0:
print("Package cache updated")
else:
print(f"Update failed: {output}")
run_shell()¶
Run shell command (supports pipes, redirects, etc.).
Parameters:
- command: Shell command string
Returns:
- Tuple of (output: str, exit_code: int)
Security Warning: Shell commands can be dangerous. Validate all inputs.
Example:
# Pipe command output
output, code = transport.run_shell("cat /var/log/app.log | grep ERROR")
# Redirect output
output, code = transport.run_shell("echo 'data' > /tmp/output.txt")
# Command chaining
output, code = transport.run_shell("cd /tmp && ls -la")
Transport Best Practices¶
Use run_command() When Possible¶
Prefer run_command() over run_shell():
# Good: Safe from injection
output, code = transport.run_command(["ls", "-la", directory])
# Avoid: Shell injection risk
output, code = transport.run_shell(f"ls -la {directory}")
Check Exit Codes¶
Always check command exit codes:
output, code = transport.run_command(["systemctl", "start", "nginx"])
if code != 0:
raise RuntimeError(f"Command failed: {output}")
Handle Binary Files¶
Files are transferred as bytes:
# Read binary file
content = transport.read_file("/path/to/image.png")
# Write binary file
transport.write_file("/path/to/image.png", binary_data)
Text File Encoding¶
Handle text file encoding explicitly:
# Read text file
content_bytes = transport.read_file("/etc/motd")
content_text = content_bytes.decode("utf-8")
# Write text file
content_bytes = "Welcome".encode("utf-8")
transport.write_file("/etc/motd", content_bytes)
Error Handling¶
File Not Found¶
try:
content = transport.read_file("/nonexistent/file")
except FileNotFoundError:
print("File does not exist")
Command Failure¶
output, code = transport.run_command(["false"])
if code != 0:
print(f"Command failed with exit code {code}")
Permission Denied¶
output, code = transport.run_command(["cat", "/root/secret"])
if code != 0:
print(f"Permission denied: {output}")
Testing with Mock Transport¶
Create mock transport for unit tests:
class MockTransport:
def __init__(self):
self.files = {}
self.commands = []
def file_exists(self, path: str) -> bool:
return path in self.files
def read_file(self, path: str) -> bytes:
if path not in self.files:
raise FileNotFoundError(path)
return self.files[path]
def write_file(self, path: str, content: bytes) -> None:
self.files[path] = content
def run_command(self, args: list) -> tuple[str, int]:
self.commands.append(args)
return "", 0
def run_shell(self, command: str) -> tuple[str, int]:
self.commands.append(command)
return "", 0
# Use in tests
def test_resource():
transport = MockTransport()
resource = MyResource("test")
resource._transport = transport
# Test operations
platform = Platform.detect()
resource.check(platform)
# Verify
assert "/path/to/file" in transport.files
assert ["systemctl", "start", "myservice"] in transport.commands
API Reference¶
See cook/transport.py for complete transport implementation.