Skip to content

jasonzh0/kubeman

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kubeman

PyPI - Version

A Python library for rendering Helm charts and Kubernetes resources with optional ArgoCD Application generation. Features an abstract Template base class for consistent template management.

Features

  • Abstract Template base class for all template types
  • HelmChart class for defining Helm charts
  • KubernetesResource class for raw Kubernetes resources (no Helm required)
  • TemplateRegistry for managing multiple templates (charts and resources)
  • Command-line interface (CLI) for rendering and applying manifests
  • Resource visualization with Graphviz DOT diagrams showing resource relationships
  • Automatic Docker image build and load steps (executed sequentially during template registration)
  • Git operations for manifest repository management
  • Docker image build and push utilities with custom Dockerfile support
  • Optional ArgoCD Application manifest generation (opt-in)

Installation

From Source

Using uv (recommended):

uv pip install -e .

Or using pip:

pip install -e .

Development Setup

Install development dependencies and format code:

uv sync --dev
uv tool run black .

Pre-commit Hooks

Install pre-commit globally:

uv tool install pre-commit
pre-commit install

This automatically formats code with black before commits. Run manually:

pre-commit run --all-files

Alternative: Install as dev dependency:

uv sync --dev
uv run python -m pre_commit install

Usage

Template Architecture

Both HelmChart and KubernetesResource inherit from the abstract Template base class, providing common functionality for ArgoCD Application generation (opt-in), manifest directory management, namespace/name properties, rendering to filesystem, and optional Docker image build steps executed sequentially during template registration.

Creating a Helm Chart

Subclass HelmChart and implement the required abstract methods:

from kubeman import HelmChart, TemplateRegistry

@TemplateRegistry.register
class MyChart(HelmChart):
    @property
    def name(self) -> str:
        return "my-chart"

    @property
    def repository(self) -> dict:
        """Return repository information"""
        return {
            "type": "classic",  # or "oci" or "none"
            "remote": "https://charts.example.com"
        }

    @property
    def namespace(self) -> str:
        return "my-namespace"

    @property
    def version(self) -> str:
        return "1.0.0"

    def generate_values(self) -> dict:
        """Generate values.yaml content"""
        return {
            "replicaCount": 3,
            "image": {
                "repository": "my-app",
                "tag": "latest"
            }
        }

Creating a Kubernetes Resource (Without Helm)

For projects that don't need Helm but want optional ArgoCD Application generation and manifest management, use KubernetesResource. Supports two patterns:

Pattern 1: Using Helper Methods (Recommended)

Use built-in helper methods to build Kubernetes resources:

from kubeman import KubernetesResource, TemplateRegistry

@TemplateRegistry.register
class DogBreedsDbChart(KubernetesResource):
    """Dog Breeds PostgreSQL database resources."""

    def __init__(self):
        super().__init__()
        self.namespace = "dog-breeds"

        # Add Namespace
        self.add_namespace(
            name="dog-breeds",
            labels={"app": "dog-breeds", "component": "database"},
        )

        # Add ConfigMap for database configuration
        self.add_configmap(
            name="dog-breeds-db-config",
            namespace="dog-breeds",
            data={
                "POSTGRES_DB": "dog_breeds_db",
                "POSTGRES_USER": "airflow",
            },
            labels={"app": "dog-breeds", "component": "database"},
        )

        # Add Secret for database password
        self.add_secret(
            name="dog-breeds-db-secret",
            namespace="dog-breeds",
            string_data={
                "POSTGRES_PASSWORD": "airflow",
            },
            labels={"app": "dog-breeds", "component": "database"},
        )

        # Add PersistentVolumeClaim
        self.add_persistent_volume_claim(
            name="dog-breeds-db-pvc",
            namespace="dog-breeds",
            access_modes=["ReadWriteOnce"],
            storage="5Gi",
            labels={"app": "dog-breeds", "component": "database"},
        )

        # Add Deployment
        self.add_deployment(
            name="dog-breeds-db",
            namespace="dog-breeds",
            replicas=1,
            strategy_type="Recreate",
            labels={"app": "dog-breeds", "component": "database"},
            containers=[
                {
                    "name": "postgres",
                    "image": "postgres:16-alpine",
                    "ports": [{"name": "postgres", "containerPort": 5432}],
                    "env": [
                        {
                            "name": "POSTGRES_PASSWORD",
                            "valueFrom": {
                                "secretKeyRef": {
                                    "name": "dog-breeds-db-secret",
                                    "key": "POSTGRES_PASSWORD",
                                },
                            },
                        },
                    ],
                    "volumeMounts": [
                        {"name": "postgres-storage", "mountPath": "/var/lib/postgresql/data"},
                    ],
                }
            ],
            volumes=[
                {"name": "postgres-storage", "persistentVolumeClaim": {"claimName": "dog-breeds-db-pvc"}},
            ],
            init_containers=[
                {
                    "name": "init-db",
                    "image": "busybox:latest",
                    "command": ["sh", "-c", "echo Initializing database schema"],
                }
            ],
        )

        # Add Service
        self.add_service(
            name="dog-breeds-db",
            namespace="dog-breeds",
            service_type="ClusterIP",
            selector={"app": "dog-breeds", "component": "database"},
            ports=[{"name": "postgres", "port": 5432, "targetPort": 5432}],
            labels={"app": "dog-breeds", "component": "database"},
        )

Available Helper Methods: add_namespace(), add_configmap(), add_secret(), add_persistent_volume_claim(), add_deployment(), add_statefulset(), add_service(), add_ingress(), add_service_account(), add_role(), add_role_binding(), add_cluster_role(), add_cluster_role_binding(), add_custom_resource()

Pattern 2: Override manifests() Method

For complex logic or custom manifest generation, override the manifests() method:

from kubeman import KubernetesResource, TemplateRegistry

@TemplateRegistry.register
class MyAppResources(KubernetesResource):
    def __init__(self):
        super().__init__()
        self.namespace = "production"

    def manifests(self) -> list[dict]:
        """Return list of Kubernetes manifests"""
        return [
            {
                "apiVersion": "v1",
                "kind": "ConfigMap",
                "metadata": {
                    "name": "my-app-config",
                    "namespace": "production"
                },
                "data": {
                    "DATABASE_URL": "postgres://db:5432/myapp",
                }
            },
            {
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {
                    "name": "my-app",
                    "namespace": "production"
                },
                "spec": {
                    "replicas": 3,
                    "selector": {"matchLabels": {"app": "my-app"}},
                    "template": {
                        "metadata": {"labels": {"app": "my-app"}},
                        "spec": {
                            "containers": [{
                                "name": "my-app",
                                "image": "gcr.io/my-project/my-app:v1.0.0",
                                "envFrom": [{
                                    "configMapRef": {"name": "my-app-config"}
                                }]
                            }]
                        }
                    }
                }
            }
        ]

Build and Load Steps

Templates can define Docker image build and load steps that execute automatically when templates are imported. Steps run sequentially in registration order: build steps first, then load steps.

Build Steps

Override the build() method to add build steps:

from kubeman import KubernetesResource, TemplateRegistry, DockerManager

@TemplateRegistry.register
class MyApp(KubernetesResource):
    def __init__(self):
        super().__init__()
        self.name = "my-app"
        self.namespace = "production"

    def build(self) -> None:
        """Build Docker images for this template."""
        docker = DockerManager()
        docker.build_image(
            component="my-app",
            context_path="./app",
            tag="latest",
            dockerfile="Dockerfile.prod"  # Optional: custom Dockerfile name
        )
        docker.tag_image(
            source_image=f"{docker.registry}/my-app",
            target_image="my-app",
            source_tag="latest"
        )

Load Steps

For local development with kind clusters, add load steps:

def load(self) -> None:
    """Load Docker images into kind cluster."""
    docker = DockerManager()
    docker.kind_load_image("my-app", tag="latest")

Key points: Build steps execute automatically when templates are imported. Load steps execute after build steps. Use --skip-build flag to skip build/load steps during render/apply. If a build or load fails, template registration fails with a clear error message.

Rendering Charts and Resources

Render templates using the CLI or Python API.

Using the CLI (Recommended)

# Render all templates from a Python file
kubeman render --file kubeman.py

# Render without executing build steps
kubeman render --file kubeman.py --skip-build

# Render and apply to Kubernetes cluster (builds execute automatically)
kubeman apply --file kubeman.py

# Apply without executing build steps
kubeman apply --file kubeman.py --skip-build

The CLI imports your template file (containing @TemplateRegistry.register decorated classes), discovers all registered templates, renders each to the manifests/ directory, and for apply, runs kubectl apply on the rendered manifests.

Example template file (kubeman.py):

from kubeman import KubernetesResource, TemplateRegistry

@TemplateRegistry.register
class MyAppResources(KubernetesResource):
    def __init__(self):
        super().__init__()
        self.namespace = "production"
        # ... add resources using helper methods ...

Using the Python API

from kubeman import TemplateRegistry

# Get all registered templates (charts and resources)
templates = TemplateRegistry.get_registered_templates()

# Render each template
for template_class in templates:
    template = template_class()
    template.render()  # Generates manifests and ArgoCD Application

For HelmChart: Renders to manifests/{chart-name}/{chart-name}-manifests.yaml, writes extra manifests to manifests/{chart-name}/, and generates ArgoCD Application to manifests/apps/{chart-name}-application.yaml (if enabled).

Visualizing Resource Relationships

Generate Graphviz DOT diagrams to visualize Kubernetes resources and their relationships:

# Generate visualization and save to file
kubeman visualize --file kubeman.py --output diagram.dot

# Output to stdout
kubeman visualize --file kubeman.py

# Include CustomResourceDefinitions in visualization
kubeman visualize --file kubeman.py --output diagram.dot --show-crds

# Use custom manifests directory
kubeman visualize --file kubeman.py --output-dir ./custom-manifests --output diagram.dot

The visualization command:

  1. Renders templates to generate manifests (build steps are automatically skipped)
  2. Analyzes rendered manifests to detect relationships (ConfigMap references, Service selectors, network connections, etc.)
  3. Generates a Graphviz DOT diagram showing resource hierarchy and relationships
  4. Outputs to a file or stdout

Rendering the diagram:

After generating the DOT file, render it to an image using Graphviz:

# Generate PNG image
dot -Tpng diagram.dot -o diagram.png

# Generate SVG image
dot -Tsvg diagram.dot -o diagram.svg

Visualization features:

  • Shows resources grouped by namespace
  • Displays relationships between resources (uses, selects, connects-to)
  • Includes connection addresses (e.g., database URLs from ConfigMaps)
  • Filters to only show resources from templates in your kubeman.py file
  • Optionally includes CustomResourceDefinitions (hidden by default)

For KubernetesResource: Writes each manifest to manifests/{name}/{manifest-name}-{kind}.yaml and generates ArgoCD Application to manifests/apps/{name}-application.yaml (if enabled).

Advanced Chart Configuration

Custom Repository Package Name

@property
def repository_package(self) -> str:
    return "different-package-name"

OCI Registry Support

@property
def repository(self) -> dict:
    return {
        "type": "oci",
        "remote": "oci://registry.example.com/charts"
    }

Extra Manifests

def extra_manifests(self) -> list[dict]:
    return [
        {
            "apiVersion": "v1",
            "kind": "ConfigMap",
            "metadata": {"name": "my-config"},
            "data": {"key": "value"}
        }
    ]

Enabling ArgoCD Application Generation (Opt-In)

ArgoCD Application generation is disabled by default. Enable via environment variable:

export ARGOCD_APP_REPO_URL="https://github.com/org/manifests-repo"

Or override enable_argocd() method:

@TemplateRegistry.register
class MyChart(HelmChart):
    def enable_argocd(self) -> bool:
        """Enable ArgoCD Application generation for this chart"""
        return True

If ARGOCD_APP_REPO_URL is not set and enable_argocd() returns True, override application_repo_url() to provide the repository URL.

Custom ArgoCD Application Settings

def enable_argocd(self) -> bool:
    """Enable ArgoCD Application generation (opt-in)"""
    return True

def application_repo_url(self) -> str:
    """Override the repository URL for ArgoCD applications"""
    return "https://github.com/org/manifests-repo"

def application_target_revision(self) -> str:
    """Override the target revision (defaults to current branch)"""
    return "main"

def managed_namespace_metadata(self) -> dict:
    """Add labels to managed namespaces"""
    return {
        "app.kubernetes.io/managed-by": "argocd"
    }

def argo_ignore_spec(self) -> list:
    """Configure ArgoCD ignore differences"""
    return [
        {
            "group": "apps",
            "kind": "Deployment",
            "jsonPointers": ["/spec/replicas"]
        }
    ]

Git Operations

The GitManager class provides utilities for working with Git repositories:

from kubeman import GitManager

git = GitManager()

# Get current commit hash (from STABLE_GIT_COMMIT env var)
commit_hash = git.fetch_commit_hash()

# Get current branch name (from STABLE_GIT_BRANCH env var)
branch_name = git.fetch_branch_name()

# Push rendered manifests to a repository
git.push_manifests(repo_url="https://github.com/org/manifests-repo")

The push_manifests() method clones the manifests repository, checks out or creates the branch matching STABLE_GIT_BRANCH, copies rendered manifests from RENDERED_MANIFEST_DIR, and commits and pushes the changes.

Docker Operations

The DockerManager class helps build and push Docker images:

from kubeman import DockerManager

# Initialize (uses DOCKER_REGISTRY environment variable if set)
# Set DOCKER_REGISTRY="us-central1-docker.pkg.dev/my-project/my-repo" to use a registry
docker = DockerManager()

# Or pass registry directly
docker = DockerManager(registry="us-central1-docker.pkg.dev/my-project/my-repo")

# Build an image
image_name = docker.build_image(
    component="frontend",
    context_path="./frontend",
    tag="v1.0.0"
)

# Build with custom Dockerfile name
image_name = docker.build_image(
    component="frontend",
    context_path="./frontend",
    tag="v1.0.0",
    dockerfile="Dockerfile.prod"  # Optional: defaults to "Dockerfile"
)

docker.tag_image(
    source_image=f"{docker.registry}/frontend",
    target_image="frontend",
    source_tag="v1.0.0",
    target_tag="latest"  # Optional: defaults to source_tag
)

# Load image into kind cluster (for local development)
docker.kind_load_image(
    image_name="frontend",
    tag="latest",
    cluster_name="my-cluster"  # Optional: auto-detected from kubectl context
)

# Push an image
docker.push_image(component="frontend", tag="v1.0.0")

# Build and push in one step
image_name = docker.build_and_push(
    component="backend",
    context_path="./backend",
    tag="latest",
    dockerfile="Dockerfile"  # Optional: custom Dockerfile name
)

Environment Variables

Required for Git Operations

  • STABLE_GIT_COMMIT - Current git commit hash
  • STABLE_GIT_BRANCH - Current git branch name
  • RENDERED_MANIFEST_DIR - Path to directory containing rendered manifests
  • MANIFEST_REPO_URL - Git repository URL for pushing manifests (optional if passed to push_manifests())

Optional for ArgoCD Applications

ArgoCD Application generation is opt-in and disabled by default. To enable:

  • ARGOCD_APP_REPO_URL - Repository URL for ArgoCD applications (or override application_repo_url()). Required if ArgoCD is enabled.
  • ARGOCD_APPS_SUBDIR - Subdirectory for applications (defaults to "apps")

You can also enable ArgoCD by overriding the enable_argocd() method in your template class to return True.

Required for Docker Operations (when pushing images)

  • DOCKER_REGISTRY - Full registry URL (e.g., "us-central1-docker.pkg.dev/my-project/my-repo")
  • GITHUB_REPOSITORY - GitHub repository name (optional)

Note: DOCKER_REGISTRY is only required when pushing images. Building images works without it (uses local image names).

Complete Example

Complete example using both HelmChart and KubernetesResource:

from kubeman import HelmChart, KubernetesResource, TemplateRegistry, GitManager, DockerManager

# Define a Helm chart for a third-party application
@TemplateRegistry.register
class PostgresChart(HelmChart):
    @property
    def name(self) -> str:
        return "postgres"

    @property
    def repository(self) -> dict:
        return {
            "type": "classic",
            "remote": "https://charts.bitnami.com/bitnami"
        }

    @property
    def namespace(self) -> str:
        return "database"

    @property
    def version(self) -> str:
        return "12.5.0"

    def generate_values(self) -> dict:
        return {
            "auth": {
                "postgresPassword": "changeme"
            },
            "persistence": {
                "enabled": True,
                "size": "10Gi"
            }
        }

    def enable_argocd(self) -> bool:
        """Enable ArgoCD Application generation (opt-in)"""
        return True

# Define custom Kubernetes resources for your application
@TemplateRegistry.register
class MyAppResources(KubernetesResource):
    @property
    def name(self) -> str:
        return "my-app"

    @property
    def namespace(self) -> str:
        return "production"

    def manifests(self) -> list[dict]:
        return [
            {
                "apiVersion": "v1",
                "kind": "ConfigMap",
                "metadata": {"name": "my-app-config", "namespace": "production"},
                "data": {"DATABASE_HOST": "postgres.database.svc.cluster.local"}
            },
            {
                "apiVersion": "apps/v1",
                "kind": "Deployment",
                "metadata": {"name": "my-app", "namespace": "production"},
                "spec": {
                    "replicas": 3,
                    "selector": {"matchLabels": {"app": "my-app"}},
                    "template": {
                        "metadata": {"labels": {"app": "my-app"}},
                        "spec": {
                            "containers": [{
                                "name": "my-app",
                                "image": "gcr.io/my-project/my-app:v1.0.0",
                                "envFrom": [{"configMapRef": {"name": "my-app-config"}}]
                            }]
                        }
                    }
                }
            }
        ]

# Option 1: Use CLI to render and apply
# Build steps execute automatically when templates are imported
# kubeman render --file kubeman.py
# kubeman apply --file kubeman.py

# Option 2: Skip build steps if images are already built
# kubeman render --file kubeman.py --skip-build
# kubeman apply --file kubeman.py --skip-build

# Option 3: Render programmatically
for template_class in TemplateRegistry.get_registered_templates():
    template = template_class()
    template.render()

# Push manifests to repository
git = GitManager()
git.push_manifests()

Publishing

This package is automatically published to PyPI via GitHub Actions when:

  1. A new release is published on GitHub
  2. Manual trigger via the GitHub Actions workflow

License

MIT