Skip to main content

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

Project description

Kubeman

A Python library for rendering Helm charts and Kubernetes resources with automatic 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)
  • Git operations for manifest repository management
  • Docker image build and push utilities
  • Automatic ArgoCD Application manifest generation

Installation

From Source

Using uv (recommended):

uv pip install -e .

Or using pip:

pip install -e .

Development Setup

Install development dependencies:

uv sync --dev

Format code:

uv tool run black .

Pre-commit Hooks

This project uses pre-commit hooks to automatically format code with black before commits.

Install pre-commit as a tool (recommended):

uv tool install pre-commit

This installs pre-commit globally and makes it available in your PATH. Then install the git hooks:

pre-commit install

Now, every time you commit, black will automatically format your Python files. You can also run the hooks manually:

pre-commit run --all-files

Alternative: If you prefer to use pre-commit from the virtual environment, you can install it as a dev dependency and use the Python module:

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

Usage

Template Architecture

Both HelmChart and KubernetesResource inherit from the abstract Template base class, which provides common functionality for:

  • ArgoCD Application manifest generation
  • Manifest directory management
  • Namespace and name properties
  • Rendering to filesystem

This shared base class ensures consistent behavior across all template types while allowing each subclass to implement its specific rendering logic.

Creating a Helm Chart

To create 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 still want ArgoCD Application generation and manifest management, use the KubernetesResource class. This class supports two usage patterns:

Pattern 1: Using Helper Methods (Recommended)

Use the 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"}},
            ],
        )

        # 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() - Create a Namespace
  • add_configmap() - Create a ConfigMap
  • add_secret() - Create a Secret
  • add_persistent_volume_claim() - Create a PVC
  • add_deployment() - Create a Deployment
  • add_statefulset() - Create a StatefulSet
  • add_service() - Create a Service (ClusterIP, NodePort, LoadBalancer)
  • add_ingress() - Create an Ingress
  • add_service_account() - Create a ServiceAccount
  • add_role() - Create a Role
  • add_role_binding() - Create a RoleBinding
  • add_cluster_role() - Create a ClusterRole
  • add_cluster_role_binding() - Create a ClusterRoleBinding
  • add_custom_resource() - Add any custom Kubernetes resource

Pattern 2: Override manifests() Method

For more 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"}
                                }]
                            }]
                        }
                    }
                }
            }
        ]

The KubernetesResource class provides a simpler interface than HelmChart when you don't need Helm's templating capabilities. It still generates ArgoCD Applications and integrates with the TemplateRegistry system.

Rendering Charts and Resources

Once your charts and resources are registered, you can render them:

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

The render() method will:

For HelmChart:

  1. Render the Helm chart templates to manifests/{chart-name}/{chart-name}-manifests.yaml
  2. Write any extra manifests to manifests/{chart-name}/
  3. Generate an ArgoCD Application manifest to manifests/apps/{chart-name}-application.yaml

For KubernetesResource:

  1. Write each Kubernetes manifest to manifests/{name}/{manifest-name}-{kind}.yaml
  2. Generate an ArgoCD Application manifest to manifests/apps/{name}-application.yaml

Advanced Chart Configuration

Custom Repository Package Name

If your repository uses a different package name than the chart name:

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

OCI Registry Support

For OCI-based Helm repositories:

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

Extra Manifests

Add additional Kubernetes manifests alongside your Helm chart:

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

Custom ArgoCD Application Settings

Customize the ArgoCD Application generation:

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 will:

  1. Clone the manifests repository
  2. Checkout or create the branch matching STABLE_GIT_BRANCH
  3. Copy rendered manifests from RENDERED_MANIFEST_DIR
  4. Commit and push the changes

Docker Operations

The DockerManager class helps build and push Docker images to Google Container Registry:

from kubeman import DockerManager

# Initialize with project ID (or set GOOGLE_PROJECT_ID env var)
docker = DockerManager(
    project_id="my-gcp-project",
    repository_name="my-repo"  # Optional, defaults to "default"
)

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

# 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"
)

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())

Required for ArgoCD Applications

  • ARGOCD_APP_REPO_URL - Repository URL for ArgoCD applications (or override application_repo_url())
  • ARGOCD_APPS_SUBDIR - Subdirectory for applications (defaults to "apps")

Required for Docker Operations

  • GOOGLE_PROJECT_ID - Google Cloud project ID (or pass to DockerManager constructor)
  • GOOGLE_REGION - GCP region (defaults to "us-central1")
  • DOCKER_REPOSITORY_NAME - Docker repository name (defaults to "default")
  • GITHUB_REPOSITORY - GitHub repository name (optional)

Complete Example

Here's a complete example that ties everything together, 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"
            }
        }

# 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"}}]
                            }]
                        }
                    }
                }
            }
        ]

# Build and push Docker images
docker = DockerManager()
docker.build_and_push("my-app", "./app", tag="v1.0.0")

# Render all registered templates (charts and resources)
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. A version tag is pushed (e.g., v0.1.0, v1.0.0)
  3. Manual trigger via the GitHub Actions workflow

Setup for Publishing

The workflow uses PyPI trusted publishing (OIDC). To enable it:

  1. Go to PyPI Account Settings
  2. Navigate to "API tokens" → "Add a pending project"
  3. Add your project name: kubeman
  4. Copy the "Trusted publisher" configuration
  5. In your GitHub repository, go to Settings → Secrets and variables → Actions
  6. Add the PyPI project name and repository owner as environment variables (if needed)

Alternatively, you can use an API token:

  1. Create an API token on PyPI
  2. Add it as a secret named PYPI_API_TOKEN in your GitHub repository

Manual Publishing

To publish manually:

# Build the package
python -m build

# Check the package
python -m twine check dist/*

# Upload to PyPI (requires credentials)
python -m twine upload dist/*

Installing from PyPI

Once published, users can install the package with:

pip install kubeman

Or with uv:

uv pip install kubeman

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kubeman-0.2.2.tar.gz (50.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

kubeman-0.2.2-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

Details for the file kubeman-0.2.2.tar.gz.

File metadata

  • Download URL: kubeman-0.2.2.tar.gz
  • Upload date:
  • Size: 50.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kubeman-0.2.2.tar.gz
Algorithm Hash digest
SHA256 1b386dd2a687296807e7f8098d0cb09d7d4dd62c6316c4074873b49b1f1aff43
MD5 f3944a347d150c2fb6ca4f1c4b513b7f
BLAKE2b-256 01d9ccc01a534d6bf5808d8b26a7606e90e9ca0d0f9ca7e1138dca47a32bddbc

See more details on using hashes here.

Provenance

The following attestation bundles were made for kubeman-0.2.2.tar.gz:

Publisher: publish.yml on jasonzh0/kubeman

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file kubeman-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: kubeman-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 15.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for kubeman-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 45ca0ed5e309dadde866879f2d7a8523cfaff029af5757b757a7a3526a504a07
MD5 049754b3f13ee54878600a8b98c0d2fa
BLAKE2b-256 14e341b1e7df5bfb18b0860dc3aef26bb1e7ce99387bf9249e88cc58ca35c0ef

See more details on using hashes here.

Provenance

The following attestation bundles were made for kubeman-0.2.2-py3-none-any.whl:

Publisher: publish.yml on jasonzh0/kubeman

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page