python
exercises
exercises.py🐍python
"""
Docker and Deployment - Exercises
Practice containerization and deployment concepts.
Run with: pytest 28_docker_deployment/exercises.py -v
"""
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Callable
import os
import json
# =============================================================================
# EXERCISE 1: Configuration Manager
# =============================================================================
class ConfigManager:
"""
Implement a configuration manager for deployment.
Requirements:
- load_from_env(prefix) - load config from environment variables
- load_from_file(path) - load from JSON file
- load_from_dict(data) - load from dictionary
- get(key, default) - get config value with default
- require(key) - get required value, raise if missing
- validate(schema) - validate against schema dict
- to_dict() - export configuration
Priority: env vars > file > dict > defaults
Example:
config = ConfigManager()
config.load_from_dict({"port": 8000})
config.load_from_env("APP_") # APP_PORT=9000 in env
assert config.get("port") == "9000" # env takes priority
"""
def __init__(self):
# YOUR CODE HERE
pass
def load_from_env(self, prefix: str = "") -> 'ConfigManager':
# YOUR CODE HERE
pass
def load_from_file(self, path: str) -> 'ConfigManager':
# YOUR CODE HERE
pass
def load_from_dict(self, data: dict) -> 'ConfigManager':
# YOUR CODE HERE
pass
def get(self, key: str, default: Any = None) -> Any:
# YOUR CODE HERE
pass
def require(self, key: str) -> Any:
"""Get required value, raise KeyError if missing."""
# YOUR CODE HERE
pass
def validate(self, schema: dict) -> list[str]:
"""
Validate config against schema.
Schema format: {"key": {"type": str, "required": True}}
Returns list of validation errors.
"""
# YOUR CODE HERE
pass
def to_dict(self) -> dict:
# YOUR CODE HERE
pass
def test_config_manager(tmp_path):
"""Test the ConfigManager."""
# Create config file
config_file = tmp_path / "config.json"
config_file.write_text('{"host": "localhost", "port": 3000}')
# Set environment variable
os.environ["TEST_PORT"] = "8000"
os.environ["TEST_DEBUG"] = "true"
try:
config = ConfigManager()
config.load_from_dict({"port": 5000, "name": "myapp"})
config.load_from_file(str(config_file))
config.load_from_env("TEST_")
# Environment takes priority
assert config.get("port") == "8000"
assert config.get("host") == "localhost"
assert config.get("name") == "myapp"
assert config.get("missing", "default") == "default"
# Require
assert config.require("debug") == "true"
try:
config.require("nonexistent")
assert False, "Should raise KeyError"
except KeyError:
pass
finally:
del os.environ["TEST_PORT"]
del os.environ["TEST_DEBUG"]
# =============================================================================
# EXERCISE 2: Health Check System
# =============================================================================
@dataclass
class CheckResult:
"""Result of a health check."""
name: str
healthy: bool
message: str = ""
latency_ms: float = 0
metadata: dict = field(default_factory=dict)
class HealthCheckSystem:
"""
Implement a comprehensive health check system.
Requirements:
- register(name, check_func, critical=True) - register a check
- run_all() - run all checks, return overall result
- run_one(name) - run single check
- get_status() - return overall status ("healthy", "degraded", "unhealthy")
A check function returns True (healthy) or raises an exception.
"degraded" status when non-critical checks fail.
"unhealthy" status when any critical check fails.
Example:
health = HealthCheckSystem()
health.register("database", check_db, critical=True)
health.register("cache", check_cache, critical=False)
result = health.run_all()
print(result["status"]) # "healthy", "degraded", or "unhealthy"
"""
def __init__(self):
# YOUR CODE HERE
pass
def register(self, name: str, check_func: Callable[[], bool], critical: bool = True):
# YOUR CODE HERE
pass
def run_one(self, name: str) -> CheckResult:
# YOUR CODE HERE
pass
def run_all(self) -> dict:
"""
Run all checks and return result dict:
{
"status": "healthy" | "degraded" | "unhealthy",
"timestamp": "...",
"checks": [CheckResult, ...]
}
"""
# YOUR CODE HERE
pass
def get_status(self) -> str:
# YOUR CODE HERE
pass
def test_health_check_system():
"""Test the HealthCheckSystem."""
health = HealthCheckSystem()
# Healthy checks
health.register("db", lambda: True, critical=True)
health.register("cache", lambda: True, critical=False)
result = health.run_all()
assert result["status"] == "healthy"
assert len(result["checks"]) == 2
# Critical failure
health = HealthCheckSystem()
health.register("db", lambda: (_ for _ in ()).throw(Exception("DB down")), critical=True)
health.register("cache", lambda: True, critical=False)
result = health.run_all()
assert result["status"] == "unhealthy"
# Non-critical failure (degraded)
health = HealthCheckSystem()
health.register("db", lambda: True, critical=True)
health.register("cache", lambda: (_ for _ in ()).throw(Exception("Cache down")), critical=False)
result = health.run_all()
assert result["status"] == "degraded"
# =============================================================================
# EXERCISE 3: Environment Variable Parser
# =============================================================================
class EnvParser:
"""
Implement a typed environment variable parser.
Requirements:
- string(name, default) - get string value
- integer(name, default) - get integer value
- float(name, default) - get float value
- boolean(name, default) - get boolean value
- list(name, separator, default) - get list value
- json(name, default) - get JSON value
Boolean parsing: "true", "1", "yes" = True, others = False
Example:
env = EnvParser()
port = env.integer("PORT", default=8000)
debug = env.boolean("DEBUG", default=False)
hosts = env.list("ALLOWED_HOSTS", separator=",", default=[])
"""
def __init__(self, environ: dict = None):
# YOUR CODE HERE
pass
def string(self, name: str, default: str = "") -> str:
# YOUR CODE HERE
pass
def integer(self, name: str, default: int = 0) -> int:
# YOUR CODE HERE
pass
def float_(self, name: str, default: float = 0.0) -> float:
# YOUR CODE HERE
pass
def boolean(self, name: str, default: bool = False) -> bool:
# YOUR CODE HERE
pass
def list_(self, name: str, separator: str = ",", default: list = None) -> list:
# YOUR CODE HERE
pass
def json_(self, name: str, default: Any = None) -> Any:
# YOUR CODE HERE
pass
def test_env_parser():
"""Test the EnvParser."""
environ = {
"PORT": "8080",
"DEBUG": "true",
"RATIO": "0.75",
"HOSTS": "localhost,example.com,api.example.com",
"CONFIG": '{"key": "value"}',
"ENABLED": "yes",
"DISABLED": "no",
}
env = EnvParser(environ)
assert env.string("PORT") == "8080"
assert env.string("MISSING", "default") == "default"
assert env.integer("PORT") == 8080
assert env.integer("MISSING", 3000) == 3000
assert env.float_("RATIO") == 0.75
assert env.boolean("DEBUG") == True
assert env.boolean("ENABLED") == True
assert env.boolean("DISABLED") == False
assert env.boolean("MISSING", True) == True
hosts = env.list_("HOSTS")
assert len(hosts) == 3
assert "localhost" in hosts
config = env.json_("CONFIG")
assert config["key"] == "value"
# =============================================================================
# EXERCISE 4: Deployment Validator
# =============================================================================
@dataclass
class ValidationResult:
"""Deployment validation result."""
valid: bool
errors: list[str] = field(default_factory=list)
warnings: list[str] = field(default_factory=list)
class DeploymentValidator:
"""
Implement a pre-deployment validator.
Requirements:
- add_check(name, check_func, is_error=True) - add validation check
- validate() - run all checks, return ValidationResult
Check functions return (passed: bool, message: str)
is_error=True means failure is an error, False means warning
Example:
validator = DeploymentValidator()
validator.add_check("env_vars", check_required_env_vars)
validator.add_check("disk_space", check_disk_space, is_error=False)
result = validator.validate()
if not result.valid:
print("Cannot deploy:", result.errors)
"""
def __init__(self):
# YOUR CODE HERE
pass
def add_check(self, name: str, check_func: Callable[[], tuple[bool, str]], is_error: bool = True):
# YOUR CODE HERE
pass
def validate(self) -> ValidationResult:
# YOUR CODE HERE
pass
def test_deployment_validator():
"""Test the DeploymentValidator."""
validator = DeploymentValidator()
# All pass
validator.add_check("check1", lambda: (True, "OK"))
validator.add_check("check2", lambda: (True, "OK"))
result = validator.validate()
assert result.valid == True
assert len(result.errors) == 0
# Error check fails
validator = DeploymentValidator()
validator.add_check("critical", lambda: (False, "Missing SECRET_KEY"))
validator.add_check("optional", lambda: (True, "OK"), is_error=False)
result = validator.validate()
assert result.valid == False
assert "SECRET_KEY" in result.errors[0]
# Only warning fails
validator = DeploymentValidator()
validator.add_check("critical", lambda: (True, "OK"))
validator.add_check("disk", lambda: (False, "Low disk space"), is_error=False)
result = validator.validate()
assert result.valid == True
assert len(result.warnings) == 1
# =============================================================================
# EXERCISE 5: Secret Manager
# =============================================================================
class SecretManager:
"""
Implement a simple secret manager for deployments.
Requirements:
- set(key, value) - store a secret
- get(key) - retrieve a secret
- delete(key) - remove a secret
- exists(key) - check if secret exists
- list_keys() - list all secret keys (not values!)
- mask(value) - mask a secret value for logging
- to_env_file() - export as .env file format
Secrets should be stored encrypted (use simple XOR for demo).
Example:
secrets = SecretManager(encryption_key="mykey")
secrets.set("DATABASE_PASSWORD", "secret123")
password = secrets.get("DATABASE_PASSWORD")
masked = secrets.mask(password) # "sec*****23"
"""
def __init__(self, encryption_key: str = "default-key"):
# YOUR CODE HERE
pass
def _encrypt(self, value: str) -> str:
"""Simple XOR encryption for demo."""
# YOUR CODE HERE
pass
def _decrypt(self, value: str) -> str:
"""Simple XOR decryption for demo."""
# YOUR CODE HERE
pass
def set(self, key: str, value: str):
# YOUR CODE HERE
pass
def get(self, key: str) -> str | None:
# YOUR CODE HERE
pass
def delete(self, key: str) -> bool:
# YOUR CODE HERE
pass
def exists(self, key: str) -> bool:
# YOUR CODE HERE
pass
def list_keys(self) -> list[str]:
# YOUR CODE HERE
pass
def mask(self, value: str, visible_chars: int = 3) -> str:
"""Mask secret, showing only first and last visible_chars."""
# YOUR CODE HERE
pass
def to_env_file(self) -> str:
"""Export secrets as .env file format."""
# YOUR CODE HERE
pass
def test_secret_manager():
"""Test the SecretManager."""
secrets = SecretManager("test-key")
# Set and get
secrets.set("DB_PASSWORD", "super-secret-123")
assert secrets.get("DB_PASSWORD") == "super-secret-123"
# Exists
assert secrets.exists("DB_PASSWORD") == True
assert secrets.exists("NONEXISTENT") == False
# List keys
secrets.set("API_KEY", "key123")
keys = secrets.list_keys()
assert "DB_PASSWORD" in keys
assert "API_KEY" in keys
# Mask
masked = secrets.mask("super-secret-123", visible_chars=3)
assert masked.startswith("sup")
assert "*" in masked
assert masked.endswith("123")
# Delete
assert secrets.delete("DB_PASSWORD") == True
assert secrets.exists("DB_PASSWORD") == False
# Env file
env_content = secrets.to_env_file()
assert "API_KEY=" in env_content
# =============================================================================
# EXERCISE 6: Dockerfile Generator
# =============================================================================
class DockerfileGenerator:
"""
Implement a Dockerfile generator.
Requirements:
- base_image(image) - set base image
- workdir(path) - set working directory
- copy(src, dest) - add COPY instruction
- run(command) - add RUN instruction
- env(key, value) - add ENV instruction
- expose(port) - add EXPOSE instruction
- cmd(command) - set CMD instruction
- entrypoint(command) - set ENTRYPOINT instruction
- label(key, value) - add LABEL
- generate() - generate Dockerfile content
Example:
df = DockerfileGenerator()
content = (df
.base_image("python:3.12-slim")
.workdir("/app")
.copy("requirements.txt", ".")
.run("pip install -r requirements.txt")
.copy(".", ".")
.expose(8000)
.cmd("python app.py")
.generate())
"""
def __init__(self):
# YOUR CODE HERE
pass
def base_image(self, image: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def workdir(self, path: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def copy(self, src: str, dest: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def run(self, command: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def env(self, key: str, value: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def expose(self, port: int) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def cmd(self, command: str | list) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def entrypoint(self, command: str | list) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def label(self, key: str, value: str) -> 'DockerfileGenerator':
# YOUR CODE HERE
pass
def generate(self) -> str:
# YOUR CODE HERE
pass
def test_dockerfile_generator():
"""Test the DockerfileGenerator."""
df = DockerfileGenerator()
content = (df
.base_image("python:3.12-slim")
.label("maintainer", "dev@example.com")
.workdir("/app")
.copy("requirements.txt", ".")
.run("pip install --no-cache-dir -r requirements.txt")
.copy(".", ".")
.env("PYTHONUNBUFFERED", "1")
.expose(8000)
.cmd("python app.py")
.generate())
assert "FROM python:3.12-slim" in content
assert "WORKDIR /app" in content
assert "COPY requirements.txt ." in content
assert "RUN pip install" in content
assert "EXPOSE 8000" in content
assert "CMD" in content
# =============================================================================
# EXERCISE 7: Rollback Manager
# =============================================================================
@dataclass
class Deployment:
"""A deployment record."""
version: str
timestamp: datetime
status: str # "success", "failed", "rolled_back"
metadata: dict = field(default_factory=dict)
class RollbackManager:
"""
Implement a deployment rollback manager.
Requirements:
- record(version, status, metadata) - record a deployment
- get_current() - get current active deployment
- get_previous() - get previous successful deployment
- get_history(limit) - get deployment history
- rollback() - mark current as rolled back, return previous version
- can_rollback() - check if rollback is possible
Example:
manager = RollbackManager()
manager.record("v1.0.0", "success")
manager.record("v1.1.0", "success")
manager.record("v1.2.0", "failed")
if manager.can_rollback():
prev = manager.rollback() # Returns "v1.1.0"
"""
def __init__(self):
# YOUR CODE HERE
pass
def record(self, version: str, status: str, metadata: dict = None) -> Deployment:
# YOUR CODE HERE
pass
def get_current(self) -> Deployment | None:
# YOUR CODE HERE
pass
def get_previous(self) -> Deployment | None:
"""Get previous successful deployment."""
# YOUR CODE HERE
pass
def get_history(self, limit: int = 10) -> list[Deployment]:
# YOUR CODE HERE
pass
def can_rollback(self) -> bool:
# YOUR CODE HERE
pass
def rollback(self) -> str | None:
"""Rollback to previous version, return that version."""
# YOUR CODE HERE
pass
def test_rollback_manager():
"""Test the RollbackManager."""
manager = RollbackManager()
# Record deployments
manager.record("v1.0.0", "success")
manager.record("v1.1.0", "success")
manager.record("v1.2.0", "failed")
# Current is the failed one
current = manager.get_current()
assert current.version == "v1.2.0"
assert current.status == "failed"
# Previous is last successful
previous = manager.get_previous()
assert previous.version == "v1.1.0"
# Can rollback
assert manager.can_rollback() == True
# Do rollback
rollback_version = manager.rollback()
assert rollback_version == "v1.1.0"
# Current should now be marked as rolled back
history = manager.get_history()
assert any(d.status == "rolled_back" for d in history)
# =============================================================================
# RUN TESTS
# =============================================================================
if __name__ == "__main__":
import pytest
pytest.main([__file__, "-v"])