From 31c05804d5135d332d97efb5305e4a822d771fb4 Mon Sep 17 00:00:00 2001 From: Kat Morgan Date: Sat, 21 Sep 2024 23:02:06 -0700 Subject: [PATCH] adopt new resource_helpers for metadata propagation compliance --- pulumi/core/README.md | 259 +++++++++++++-------- pulumi/core/resource_helpers.py | 309 ++++++++++++++++++++++++++ pulumi/core/types.py | 1 + pulumi/core/utils.py | 9 +- pulumi/modules/cert_manager/README.md | 172 ++++++++------ pulumi/modules/cert_manager/deploy.py | 211 ++++++++++-------- pulumi/modules/kubevirt/README.md | 189 ++++++++-------- pulumi/modules/kubevirt/deploy.py | 153 ++++++------- 8 files changed, 874 insertions(+), 429 deletions(-) create mode 100644 pulumi/core/resource_helpers.py diff --git a/pulumi/core/README.md b/pulumi/core/README.md index cfdb1c9..2af0724 100644 --- a/pulumi/core/README.md +++ b/pulumi/core/README.md @@ -1,30 +1,68 @@ -# Kargo Module Developmer Guide +# Core Module Developer Guide -This document provides an in-depth guide for developers, contributors, triage maintainers, and project maintainers utilizing the `core` module in the Kargo KubeVirt Kubernetes PaaS project. It includes a thorough overview of available functions, types, classes, and other core infrastructure details essential for productive module development. +Welcome to the **Core Module** of the Kargo KubeVirt Kubernetes PaaS project! This guide is designed to help both newcomers to DevOps and experienced module developers navigate and contribute to the core functionalities of the Kargo platform. Whether you're looking to understand the basics or dive deep into the module development, this guide has got you covered. + +--- ## Table of Contents - [Introduction](#introduction) -- [Core Module Structure](#core-module-structure) +- [Getting Started](#getting-started) +- [Core Module Overview](#core-module-overview) + - [Module Structure](#module-structure) + - [Key Components](#key-components) +- [Detailed Explanation of Core Files](#detailed-explanation-of-core-files) - [config.py](#configpy) - [deployment.py](#deploymentpy) - [metadata.py](#metadatapy) - - [utils.py](#utilspy) + - [resource_helpers.py](#resource_helperspy) - [types.py](#typespy) + - [utils.py](#utilspy) - [Best Practices](#best-practices) -- [Troubleshooting](#troubleshooting) -- [Contributing Guidelines](#contributing-guidelines) +- [Troubleshooting and FAQs](#troubleshooting-and-faqs) +- [Contributing to the Core Module](#contributing-to-the-core-module) - [Additional Resources](#additional-resources) --- ## Introduction -The `core` module is the backbone of the Kargo KubeVirt Kubernetes PaaS project. It houses essential functions, types, classes, and globals that facilitate the development and deployment of modules. This guide aims to equip you with the necessary knowledge to extend or maintain the core functionality effectively. +The Core Module is the heart of the Kargo KubeVirt Kubernetes PaaS project. It provides essential functionalities that facilitate the development, deployment, and management of modules within the Kargo ecosystem. This guide aims to make core concepts accessible to everyone, regardless of their experience level in DevOps. -## Core Module Structure +--- -The `core` module structure: +## Getting Started + +If you're new to Kargo or DevOps, start here! + +- **Prerequisites**: + - Basic understanding of Python and Kubernetes. + - [Pulumi CLI](https://www.pulumi.com/docs/get-started/) installed. + - Access to a Kubernetes cluster (minikube, kind, or cloud-based). + +- **Setup Steps**: + 1. **Clone the Repository**: + ```bash + git clone https://github.com/ContainerCraft/Kargo.git + cd Kargo/pulumi + ``` + 2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` + 3. **Configure Pulumi**: + ```bash + pulumi login + pulumi stack init dev + ``` + +--- + +## Core Module Overview + +### Module Structure + +The Core Module is organized as follows: ``` pulumi/core/ @@ -33,142 +71,187 @@ pulumi/core/ ├── config.py ├── deployment.py ├── metadata.py -├── utils.py -└── types.py +├── resource_helpers.py +├── types.py +└── utils.py ``` +### Key Components + +- **Configuration Management**: Handles loading and merging of user configurations. +- **Deployment Orchestration**: Manages the deployment of modules and resources. +- **Metadata Management**: Generates and applies global labels and annotations. +- **Utility Functions**: Provides helper functions for common tasks. +- **Type Definitions**: Contains shared data structures used across modules. + +--- + +## Detailed Explanation of Core Files + ### config.py -**Responsibilities:** +**Purpose**: Manages configuration settings for modules, including loading defaults and exporting deployment results. -- Handle all configuration-related functionalities. -- Load and merge user configurations with defaults. -- Load default versions for modules. -- Export deployment results. +**Key Functions**: -**Key Functions:** +- `get_module_config(module_name, config, default_versions)`: Retrieves and merges the configuration for a specific module. +- `load_default_versions(config, force_refresh=False)`: Loads default module versions, prioritizing user-specified sources. +- `export_results(versions, configurations, compliance)`: Exports deployment outputs for reporting and auditing. -- `get_module_config(module_name, config, default_versions)`: Retrieves and prepares the configuration for a module. -- `load_default_versions(config, force_refresh=False)`: Loads the default versions for modules based on the specified configuration settings. -- `export_results(versions, configurations, compliance)`: Exports the results of the deployment processes including versions, configurations, and compliance information. +**Usage Example**: -**Key Types:** +```python +from core.config import get_module_config -- `ComplianceConfig`: Central configuration object for compliance settings (imported from `types.py`). +module_config, is_enabled = get_module_config('cert_manager', config, default_versions) +if is_enabled: + # Proceed with deployment +``` --- ### deployment.py -**Responsibilities:** +**Purpose**: Orchestrates the deployment of modules, initializing providers and handling dependencies. + +**Key Functions**: -- Manage the deployment orchestration of modules. -- Initialize Pulumi and Kubernetes providers. -- Deploy individual modules based on configuration. -- Discover module-specific configuration classes and deploy functions. +- `initialize_pulumi()`: Sets up Pulumi configurations and Kubernetes provider. +- `deploy_module(module_name, config, ...)`: Deploys a specified module, handling its configuration and dependencies. -**Key Functions:** +**Usage Example**: -- `initialize_pulumi()`: Initializes Pulumi configuration, Kubernetes provider, and global resources. -- `deploy_module(module_name, config, default_versions, global_depends_on, k8s_provider, versions, configurations)`: Helper function to deploy a module based on configuration. -- `discover_config_class(module_name)`: Discovers and returns the configuration class from the module's `types.py`. -- `discover_deploy_function(module_name)`: Discovers and returns the deploy function from the module's `deploy.py`. +```python +from core.deployment import initialize_pulumi, deploy_module + +init = initialize_pulumi() +deploy_module('kubevirt', init['config'], ...) +``` --- ### metadata.py -**Responsibilities:** +**Purpose**: Manages global metadata, such as labels and annotations, ensuring consistency across resources. + +**Key Components**: -- Manage global metadata, labels, and annotations. -- Generate compliance and Git-related metadata. -- Sanitize label values to comply with Kubernetes naming conventions. +- **Singleton Pattern**: Ensures a single source of truth for metadata. +- **Metadata Functions**: + - `set_global_labels(labels)` + - `set_global_annotations(annotations)` + - `get_global_labels()` + - `get_global_annotations()` -**Key Classes and Functions:** +**Usage Example**: -- `MetadataSingleton`: Singleton class to store global labels and annotations. -- `set_global_labels(labels)`: Sets global labels. -- `set_global_annotations(annotations)`: Sets global annotations. -- `get_global_labels()`: Retrieves global labels. -- `get_global_annotations()`: Retrieves global annotations. -- `collect_git_info()`: Collects Git repository information. -- `generate_git_labels(git_info)`: Generates Git-related labels. -- `generate_git_annotations(git_info)`: Generates Git-related annotations. -- `generate_compliance_labels(compliance_config)`: Generates compliance labels. -- `generate_compliance_annotations(compliance_config)`: Generates compliance annotations. -- `sanitize_label_value(value)`: Sanitizes a label value to comply with Kubernetes naming conventions. +```python +from core.metadata import set_global_labels + +set_global_labels({'app': 'kargo', 'env': 'production'}) +``` --- -### utils.py +### resource_helpers.py + +**Purpose**: Provides helper functions for creating Kubernetes resources with consistent metadata. + +**Key Functions**: -**Responsibilities:** +- `create_namespace(name, labels, annotations, ...)` +- `create_custom_resource(name, args, ...)` +- `create_helm_release(name, args, ...)` -- Provide utility functions that are generic and reusable. -- Handle tasks like resource transformations and Helm interactions. -- Extract repository names from Git URLs. +**Usage Example**: -**Key Functions:** +```python +from core.resource_helpers import create_namespace -- `set_resource_metadata(metadata, global_labels, global_annotations)`: Updates resource metadata with global labels and annotations. -- `generate_global_transformations(global_labels, global_annotations)`: Generates global transformations for resources. -- `get_latest_helm_chart_version(url, chart_name)`: Fetches the latest stable version of a Helm chart from the given URL. -- `is_stable_version(version_str)`: Determines if a version string represents a stable version. -- `extract_repo_name(remote_url)`: Extracts the repository name from a Git remote URL. +namespace = create_namespace('kargo-system', labels={'app': 'kargo'}) +``` --- ### types.py -**Responsibilities:** +**Purpose**: Defines shared data structures and configurations used across modules. + +**Key Data Classes**: + +- `NamespaceConfig` +- `FismaConfig` +- `NistConfig` +- `ScipConfig` +- `ComplianceConfig` + +**Usage Example**: + +```python +from core.types import ComplianceConfig + +compliance_settings = ComplianceConfig(fisma=FismaConfig(enabled=True)) +``` + +--- + +### utils.py -- Define all shared data classes and types used across modules. +**Purpose**: Contains utility functions for common tasks such as version checking and resource transformations. -**Key Data Classes:** +**Key Functions**: -- `NamespaceConfig`: Configuration object for Kubernetes namespaces. -- `FismaConfig`: Configuration for FISMA compliance settings. -- `NistConfig`: Configuration for NIST compliance settings. -- `ScipConfig`: Configuration for SCIP compliance settings. -- `ComplianceConfig`: Central configuration object for compliance settings. +- `set_resource_metadata(metadata, global_labels, global_annotations)` +- `get_latest_helm_chart_version(url, chart_name)` +- `is_stable_version(version_str)` -**Methods:** +**Usage Example**: -- `ComplianceConfig.merge(user_config)`: Merges user-provided compliance configuration with default configuration. +```python +from core.utils import get_latest_helm_chart_version + +latest_version = get_latest_helm_chart_version('https://charts.jetstack.io', 'cert-manager') +``` --- ## Best Practices -- **Consistent Naming**: Follow consistent naming conventions for functions and variables. -- **Documentation**: Use detailed docstrings and comments to document your code. -- **Type Annotations**: Use type annotations to enhance readability and type safety. -- **Reusable Code**: Centralize reusable code in the `core` module to ensure consistency across modules. -- **Version Control**: Manage component versions to maintain consistency and avoid conflicts. +- **Consistency**: Use the core functions and types to ensure consistency across modules. +- **Modularity**: Keep module-specific logic separate from core functionalities. +- **Documentation**: Document your code and configurations to aid future developers. +- **Error Handling**: Use appropriate error handling and logging for better debugging. --- -## Troubleshooting +## Troubleshooting and FAQs + +**Q1: I get a `ConnectionError` when deploying modules. What should I do?** -- **Error Logging**: Use Pulumi's logging functions (`pulumi.log.info`, `pulumi.log.warn`, `pulumi.log.error`) to provide meaningful error messages. -- **Configuration Issues**: Ensure all configuration options are correctly set and validate input using type checks. -- **Module Dependencies**: Verify dependencies between modules are correctly resolved using Pulumi's dependency management features. -- **Resource Conflicts**: Be cautious of naming collisions in Kubernetes resources; use namespaces and labels appropriately. +- **A**: Ensure your Kubernetes context is correctly configured and that you have network access to the cluster. + +**Q2: How do I add a new module?** + +- **A**: Create a new directory under `pulumi/modules/`, define your `deploy.py` and `types.py`, and update the main deployment script. + +**Q3: The deployment hangs during resource creation.** + +- **A**: Check for resource conflicts or namespace issues. Use `kubectl` to inspect the current state. --- -## Contributing Guidelines +## Contributing to the Core Module + +We welcome contributions from the community! -- **Submit Issues**: Report bugs or request features via GitHub issues. -- **Pull Requests**: Submit pull requests with detailed descriptions and follow the project's coding standards. -- **Code Reviews**: Participate in code reviews to maintain code quality. -- **Testing**: Write unit tests for new functions and ensure existing tests pass. +- **Reporting Issues**: Use the GitHub issues page to report bugs or request features. +- **Submitting Pull Requests**: Follow the project's coding standards and ensure all tests pass. +- **Code Reviews**: Participate in reviews to maintain high code quality. --- ## Additional Resources -- **Kargo KubeVirt Documentation**: [GitHub Repository](https://github.com/containercraft/kargo) -- **Pulumi Documentation**: [Pulumi Docs](https://www.pulumi.com/docs/) -- **Kubernetes API Reference**: [Kubernetes API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/) -- **Python Dataclasses**: [Dataclasses Documentation](https://docs.python.org/3/library/dataclasses.html) +- **Kargo Project Documentation**: [Kargo GitHub Repository](https://github.com/ContainerCraft/Kargo) +- **Pulumi Documentation**: [Pulumi Official Docs](https://www.pulumi.com/docs/) +- **Kubernetes API Reference**: [Kubernetes API](https://kubernetes.io/docs/reference/generated/kubernetes-api/) diff --git a/pulumi/core/resource_helpers.py b/pulumi/core/resource_helpers.py new file mode 100644 index 0000000..e359436 --- /dev/null +++ b/pulumi/core/resource_helpers.py @@ -0,0 +1,309 @@ +# pulumi/core/resource_helpers.py + +import pulumi +import pulumi_kubernetes as k8s +from typing import Optional, Dict, Any, List, Callable +from .metadata import get_global_labels, get_global_annotations +from .utils import set_resource_metadata +from .types import NamespaceConfig + +def create_namespace( + name: str, + labels: Optional[Dict[str, str]] = None, + annotations: Optional[Dict[str, str]] = None, + finalizers: Optional[List[str]] = None, + custom_timeouts: Optional[Dict[str, str]] = None, + opts: Optional[pulumi.ResourceOptions] = None, + k8s_provider: Optional[k8s.Provider] = None, + depends_on: Optional[List[pulumi.Resource]] = None, +) -> k8s.core.v1.Namespace: + """ + Creates a Kubernetes Namespace with global labels and annotations. + + Args: + name (str): The name of the namespace. + labels (Optional[Dict[str, str]]): Additional labels to apply. + annotations (Optional[Dict[str, str]]): Additional annotations to apply. + finalizers (Optional[List[str]]): Finalizers for the namespace. + custom_timeouts (Optional[Dict[str, str]]): Custom timeouts for resource operations. + opts (Optional[pulumi.ResourceOptions]): Pulumi resource options. + k8s_provider (Optional[k8s.Provider]): Kubernetes provider. + depends_on (Optional[List[pulumi.Resource]]): Resources this resource depends on. + + Returns: + k8s.core.v1.Namespace: The created Namespace resource. + """ + if opts is None: + opts = pulumi.ResourceOptions() + if labels is None: + labels = {} + if annotations is None: + annotations = {} + if custom_timeouts is None: + custom_timeouts = {} + if depends_on is None: + depends_on = [] + + global_labels = get_global_labels() + global_annotations = get_global_annotations() + labels.update(global_labels) + annotations.update(global_annotations) + + metadata = { + "name": name, + "labels": labels, + "annotations": annotations, + } + + spec = {} + if finalizers: + spec["finalizers"] = finalizers + + opts = pulumi.ResourceOptions.merge( + opts, + pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + custom_timeouts=pulumi.CustomTimeouts( + create=custom_timeouts.get("create", "5m"), + update=custom_timeouts.get("update", "10m"), + delete=custom_timeouts.get("delete", "10m"), + ), + ), + ) + + return k8s.core.v1.Namespace( + name, + metadata=metadata, + spec=spec, + opts=opts, + ) + +def create_custom_resource( + name: str, + args: Dict[str, Any], + opts: Optional[pulumi.ResourceOptions] = None, + k8s_provider: Optional[k8s.Provider] = None, + depends_on: Optional[List[pulumi.Resource]] = None, +) -> k8s.apiextensions.CustomResource: + if opts is None: + opts = pulumi.ResourceOptions() + if depends_on is None: + depends_on = [] + + if 'kind' not in args or 'apiVersion' not in args: + raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.") + + global_labels = get_global_labels() + global_annotations = get_global_annotations() + + def custom_resource_transform(resource_args: pulumi.ResourceTransformationArgs): + props = resource_args.props + if 'metadata' in props: + set_resource_metadata(props['metadata'], global_labels, global_annotations) + return pulumi.ResourceTransformationResult(props, resource_args.opts) + + opts = pulumi.ResourceOptions.merge( + opts, + pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + transformations=[custom_resource_transform], + ), + ) + + # Extract required fields from args + api_version = args['apiVersion'] + kind = args['kind'] + metadata = args.get('metadata', None) + spec = args.get('spec', None) + + # Corrected constructor call + return k8s.apiextensions.CustomResource( + resource_name=name, + api_version=api_version, + kind=kind, + metadata=metadata, + spec=spec, + opts=opts + ) + +def create_helm_release( + name: str, + args: k8s.helm.v3.ReleaseArgs, + opts: Optional[pulumi.ResourceOptions] = None, + transformations: Optional[List[Callable[[pulumi.ResourceTransformationArgs], Optional[pulumi.ResourceTransformationResult]]]] = None, + k8s_provider: Optional[k8s.Provider] = None, + depends_on: Optional[List[pulumi.Resource]] = None, +) -> k8s.helm.v3.Release: + """ + Creates a Helm Release with global labels and annotations. + + Args: + name (str): The release name. + args (k8s.helm.v3.ReleaseArgs): Arguments for the Helm release. + opts (Optional[pulumi.ResourceOptions]): Pulumi resource options. + transformations (Optional[List[Callable]]): Additional transformations. + k8s_provider (Optional[k8s.Provider]): Kubernetes provider. + depends_on (Optional[List[pulumi.Resource]]): Resources this release depends on. + + Returns: + k8s.helm.v3.Release: The created Helm release. + """ + if opts is None: + opts = pulumi.ResourceOptions() + if transformations is None: + transformations = [] + if depends_on is None: + depends_on = [] + + global_labels = get_global_labels() + global_annotations = get_global_annotations() + + def helm_resource_transform(resource_args: pulumi.ResourceTransformationArgs): + props = resource_args.props + if 'metadata' in props: + set_resource_metadata(props['metadata'], global_labels, global_annotations) + elif 'spec' in props and isinstance(props['spec'], dict): + if 'metadata' in props['spec']: + set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + return pulumi.ResourceTransformationResult(props, resource_args.opts) + + transformations.append(helm_resource_transform) + + opts = pulumi.ResourceOptions.merge( + opts, + pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + transformations=transformations, + ), + ) + + return k8s.helm.v3.Release(name, args, opts=opts) + +def create_secret( + name: str, + args: Dict[str, Any], + opts: Optional[pulumi.ResourceOptions] = None, + k8s_provider: Optional[k8s.Provider] = None, + depends_on: Optional[List[pulumi.Resource]] = None, +) -> k8s.core.v1.Secret: + if opts is None: + opts = pulumi.ResourceOptions() + if depends_on is None: + depends_on = [] + + # Merge global labels and annotations (if any) + global_labels = get_global_labels() + global_annotations = get_global_annotations() + + def secret_resource_transform(resource_args: pulumi.ResourceTransformationArgs): + props = resource_args.props + if 'metadata' in props: + set_resource_metadata(props['metadata'], global_labels, global_annotations) + return pulumi.ResourceTransformationResult(props, resource_args.opts) + + # Merge resource options + opts = pulumi.ResourceOptions.merge( + opts, + pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + transformations=[secret_resource_transform], + ), + ) + + # Constructor call + return k8s.core.v1.Secret(name, opts, **args) + +def create_config_file( + name: str, + file: str, + opts: Optional[pulumi.ResourceOptions] = None, + transformations: Optional[List[Callable[[pulumi.ResourceTransformationArgs], Optional[pulumi.ResourceTransformationResult]]]] = None, + k8s_provider: Optional[k8s.Provider] = None, + depends_on: Optional[List[pulumi.Resource]] = None, +) -> k8s.yaml.ConfigFile: + """ + Creates Kubernetes resources from a YAML config file with global labels and annotations. + + Args: + name (str): The resource name. + file (str): The path to the YAML file. + opts (Optional[pulumi.ResourceOptions]): Pulumi resource options. + transformations (Optional[List[Callable]]): Additional transformations. + k8s_provider (Optional[k8s.Provider]): Kubernetes provider. + depends_on (Optional[List[pulumi.Resource]]): Resources these resources depend on. + + Returns: + k8s.yaml.ConfigFile: The created resources. + """ + if opts is None: + opts = pulumi.ResourceOptions() + if transformations is None: + transformations = [] + if depends_on is None: + depends_on = [] + + global_labels = get_global_labels() + global_annotations = get_global_annotations() + + def config_file_transform(resource_args: pulumi.ResourceTransformationArgs): + props = resource_args.props + if 'metadata' in props: + set_resource_metadata(props['metadata'], global_labels, global_annotations) + elif 'spec' in props and isinstance(props['spec'], dict): + if 'metadata' in props['spec']: + set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + return pulumi.ResourceTransformationResult(props, resource_args.opts) + + transformations.append(config_file_transform) + + opts = pulumi.ResourceOptions.merge( + opts, + pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + transformations=transformations, + ), + ) + + return k8s.yaml.ConfigFile(name, file, opts=opts) + +def create_meta_objectmeta( + name: str, + labels: Optional[Dict[str, str]] = None, + annotations: Optional[Dict[str, str]] = None, + namespace: Optional[str] = None, + **kwargs, +) -> k8s.meta.v1.ObjectMetaArgs: + """ + Creates an ObjectMetaArgs with global labels and annotations. + + Args: + name (str): The resource name. + labels (Optional[Dict[str, str]]): Additional labels to apply. + annotations (Optional[Dict[str, str]]): Additional annotations to apply. + namespace (Optional[str]): Namespace for the resource. + + Returns: + k8s.meta.v1.ObjectMetaArgs: The metadata arguments. + """ + if labels is None: + labels = {} + if annotations is None: + annotations = {} + + global_labels = get_global_labels() + global_annotations = get_global_annotations() + labels.update(global_labels) + annotations.update(global_annotations) + + return k8s.meta.v1.ObjectMetaArgs( + name=name, + labels=labels, + annotations=annotations, + namespace=namespace, + **kwargs, + ) diff --git a/pulumi/core/types.py b/pulumi/core/types.py index 1a07153..2377db5 100644 --- a/pulumi/core/types.py +++ b/pulumi/core/types.py @@ -7,6 +7,7 @@ within the Kargo PaaS platform. """ +import pulumi from dataclasses import dataclass, field from typing import Optional, List, Dict, Any diff --git a/pulumi/core/utils.py b/pulumi/core/utils.py index eae6361..b4e4762 100644 --- a/pulumi/core/utils.py +++ b/pulumi/core/utils.py @@ -49,8 +49,13 @@ def generate_global_transformations(global_labels: Dict[str, str], global_annota """ def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi.ResourceTransformationResult]: props = args.props - props.setdefault('metadata', {}) - set_resource_metadata(props['metadata'], global_labels, global_annotations) + + if 'metadata' in props: + set_resource_metadata(props['metadata'], global_labels, global_annotations) + elif 'spec' in props and isinstance(props['spec'], dict): + if 'metadata' in props['spec']: + set_resource_metadata(props['spec']['metadata'], global_labels, global_annotations) + return pulumi.ResourceTransformationResult(props, args.opts) pulumi.runtime.register_stack_transformation(global_transform) diff --git a/pulumi/modules/cert_manager/README.md b/pulumi/modules/cert_manager/README.md index 22b3fff..f3caa66 100644 --- a/pulumi/modules/cert_manager/README.md +++ b/pulumi/modules/cert_manager/README.md @@ -1,27 +1,25 @@ -# Cert Manager Module +# Cert Manager Module Guide -ContainerCraft Kargo Kubevirt PaaS Cert Manager module. - -The `cert_manager` module automates SSL certificates and certificate issuers in your platform using [cert-manager](https://cert-manager.io/). Cert Manager is a dependency of many components in Kargo Kubevirt PaaS including Containerized Data Importer, Kubevirt, Hostpath Provisioner, and more. Cert Manager improves the simplicity of issuing and renewing SSL/TLS certificates making PKI (Private Key Infrastructure) an integrated and automated feature of the platform. +Welcome to the **Cert Manager Module** for the Kargo KubeVirt Kubernetes PaaS! This guide is tailored for both newcomers to DevOps and experienced developers, providing a comprehensive overview of how to deploy and configure the Cert Manager module within the Kargo platform. --- ## Table of Contents - [Introduction](#introduction) -- [Prerequisites](#prerequisites) -- [Features](#features) +- [Why Use Cert Manager?](#why-use-cert-manager) +- [Getting Started](#getting-started) - [Enabling the Module](#enabling-the-module) -- [Configuration](#configuration) - - [Default Configuration](#default-configuration) - - [Custom Configuration](#custom-configuration) -- [Module Components](#module-components) +- [Configuration Options](#configuration-options) + - [Default Settings](#default-settings) + - [Customizing Your Deployment](#customizing-your-deployment) +- [Module Components Explained](#module-components-explained) - [Namespace Creation](#namespace-creation) - [Helm Chart Deployment](#helm-chart-deployment) - - [Self-Signed Cluster Issuer](#self-signed-cluster-issuer) -- [Integration with Kargo PaaS](#integration-with-kargo-paas) -- [Best Practices](#best-practices) -- [Troubleshooting](#troubleshooting) + - [Self-Signed Cluster Issuer Setup](#self-signed-cluster-issuer-setup) +- [Using the Module](#using-the-module) + - [Example Usage](#example-usage) +- [Troubleshooting and FAQs](#troubleshooting-and-faqs) - [Additional Resources](#additional-resources) - [Conclusion](#conclusion) @@ -29,128 +27,162 @@ The `cert_manager` module automates SSL certificates and certificate issuers in ## Introduction -This guide provides an overview of the Cert Manager module, instructions on how to enable and configure it, and explanations of its functionality within the Kargo PaaS platform. Whether you're new to Kubernetes, cert-manager, or ContainerCraft Kargo PaaS, this guide will help you get started. +The Cert Manager module automates the management of SSL/TLS certificates in your Kubernetes cluster using [cert-manager](https://cert-manager.io/). It simplifies the process of obtaining, renewing, and managing certificates, enhancing the security of your applications without manual intervention. --- -## Features +## Why Use Cert Manager? -- **Automated Deployment**: Installs cert-manager using Helm charts. -- **Version Management**: Supports explicit version pinning or accepts `latest`. -- **Namespace Isolation**: Deploys cert-manager in a dedicated namespace. -- **Self-Signed Certificates**: Sets up a self-signed ClusterIssuer for certificate management. -- **Customizable Configuration**: Allows setting overrides for customization. +- **Automation**: Automatically provisions and renews certificates. +- **Integration**: Works seamlessly with Kubernetes Ingress resources and other services. +- **Security**: Enhances security by ensuring certificates are always up-to-date. +- **Compliance**: Helps meet compliance requirements by managing PKI effectively. + +--- + +## Getting Started + +### Prerequisites + +- **Kubernetes Cluster**: Ensure you have access to a Kubernetes cluster. +- **Pulumi CLI**: Install the Pulumi CLI and configure it. +- **Kubeconfig**: Your kubeconfig file should be properly set up. + +### Setup Steps + +1. **Navigate to the Kargo Pulumi Directory**: + ```bash + cd Kargo/pulumi + ``` +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` +3. **Initialize Pulumi Stack**: + ```bash + pulumi stack init dev + ``` --- ## Enabling the Module -The Cert Manager module is enabled by default and executes with sane defaults. Customization can be configured in the Pulumi Stack Configuration. +The Cert Manager module is enabled by default. To verify or modify its enabled status, adjust your Pulumi configuration. -### Example Pulumi Configuration +### Verifying Module Enablement ```yaml # Pulumi..yaml config: cert_manager: - enabled: true # (default: true) + enabled: true # Set to false to disable ``` -Alternatively, you can set configuration values via the Pulumi CLI: +Alternatively, use the Pulumi CLI: ```bash -pulumi config set --path cert_manager.key value +pulumi config set --path cert_manager.enabled true ``` --- -## Configuration +## Configuration Options -### Default Configuration +### Default Settings -The module comes with sensible defaults to simplify deployment: +The module is designed to work out-of-the-box with default settings: - **Namespace**: `cert-manager` -- **Version**: Uses the default version specified in `default_versions.json` +- **Version**: Defined in `default_versions.json` - **Cluster Issuer Name**: `cluster-selfsigned-issuer` - **Install CRDs**: `true` -### Custom Configuration +### Customizing Your Deployment -You can customize the module's behavior by providing additional configuration options. +You can tailor the module to fit your specific needs by customizing its configuration. -#### Available Configuration Options +#### Available Configuration Parameters -- **namespace** *(string)*: The namespace where cert-manager will be deployed. -- **version** *(string)*: The version of the cert-manager Helm chart to deploy. Use `'latest'` to fetch the latest version. -- **cluster_issuer** *(string)*: The name of the ClusterIssuer to create. -- **install_crds** *(bool)*: Whether to install Custom Resource Definitions (CRDs). +- **enabled** *(bool)*: Enable or disable the module. +- **namespace** *(string)*: Kubernetes namespace for cert-manager. +- **version** *(string)*: Helm chart version to deploy. Use `'latest'` to fetch the most recent stable version. +- **cluster_issuer** *(string)*: Name of the ClusterIssuer resource. +- **install_crds** *(bool)*: Whether to install Custom Resource Definitions. #### Example Custom Configuration ```yaml -# Pulumi..yaml -# Default values are shown for reference - config: cert_manager: enabled: true + namespace: "my-cert-manager" version: "1.15.3" cluster_issuer: "my-cluster-issuer" - namespace: "custom-cert-manager" install_crds: true ``` --- -## Module Components +## Module Components Explained ### Namespace Creation -The module creates a dedicated namespace for cert-manager to ensure isolation and better management. +A dedicated namespace is created to isolate cert-manager resources. -- **Namespace**: Configurable via the `namespace` parameter. -- **Labels and Annotations**: Applied as per best practices for identification. +- **Why?**: Ensures better organization and avoids conflicts. +- **Customizable**: Change the namespace using the `namespace` parameter. ### Helm Chart Deployment -The module deploys cert-manager using the official Helm chart from Jetstack. +Deploys cert-manager using Helm. + +- **Chart Repository**: `https://charts.jetstack.io` +- **Version Management**: Specify a version or use `'latest'`. +- **Custom Values**: Resource requests and limits are set for optimal performance. -- **Chart Name**: `cert-manager` -- **Repository**: `https://charts.jetstack.io` -- **Custom Values**: Resources and replica counts are configured for optimal performance. -- **Version**: Configurable; defaults to the version specified in `default_versions.json`. +### Self-Signed Cluster Issuer Setup -### Self-Signed Cluster Issuer +Sets up a self-signed ClusterIssuer for certificate provisioning. + +- **Root ClusterIssuer**: Creates a root issuer. +- **CA Certificate**: Generates a CA certificate stored in a Kubernetes Secret. +- **Primary ClusterIssuer**: Issues certificates for your applications using the CA certificate. +- **Exported Values**: CA certificate data is exported for use in other modules. + +--- + +## Using the Module + +### Example Usage + +After enabling and configuring the module, deploy it using Pulumi: + +```bash +pulumi up +``` + +--- -To enable self signed local certificates, the module includes a self-signed ClusterIssuer chain of trust. +## Troubleshooting and FAQs -- **Root ClusterIssuer**: `cluster-selfsigned-issuer-root` -- **CA Certificate**: Generated and stored in a Kubernetes Secret. -- **Primary ClusterIssuer**: Uses the CA certificate to issue certificates for your applications. -- **Secret Export**: The CA certificate data is exported and available for other modules or applications. +**Q1: Cert-manager pods are not running.** -## Troubleshooting +- **A**: Check the namespace and ensure that CRDs are installed. Verify the Kubernetes version compatibility. -### Common Issues +**Q2: Certificates are not being issued.** -- **Connection Errors**: Ensure your `kubeconfig` and Kubernetes context are correctly configured. -- **Version Conflicts**: If deployment fails due to version issues, verify the specified version is available in the Helm repository. Alternatively use `'latest'` and Kargo will fetch the latest version. -- **CRD Issues**: If `install_crds` is set to false and are not otherwise installed, cert-manager components may fail install or function properly. +- **A**: Ensure that the ClusterIssuer is correctly configured and that your Ingress resources reference it. -### Debugging Steps +**Q3: How do I update cert-manager to a newer version?** -1. **Check Pulumi Logs**: Look for error messages during deployment. -2. **Verify Kubernetes Resources**: Use `kubectl` to inspect the cert-manager namespace and resources. -3. **Review Configuration**: Ensure all configuration options are correctly set in your Pulumi config or remove configuration to use defaults. +- **A**: Update the `version` parameter in your configuration and run `pulumi up`. --- ## Additional Resources -- **cert-manager Documentation**: [https://cert-manager.io/docs/](https://cert-manager.io/docs/) -- **Kargo Kubevirt PaaS IaC Documentation**: Refer to the main [Kargo README](../README.md) for developer guidelines. -- **Pulumi Kubernetes Provider**: [https://www.pulumi.com/docs/reference/pkg/kubernetes/](https://www.pulumi.com/docs/reference/pkg/kubernetes/) -- **Helm Charts**: [https://artifacthub.io/packages/helm/cert-manager/cert-manager](https://artifacthub.io/packages/helm/cert-manager/cert-manager) -- **Need Help?** If you have questions or need assistance, feel free to reach out to the community or maintainers on GitHub, Discord, or Twitter. +- **cert-manager Documentation**: [cert-manager.io/docs](https://cert-manager.io/docs/) +- **Kargo Project**: [Kargo GitHub Repository](https://github.com/ContainerCraft/Kargo) +- **Pulumi Kubernetes Provider**: [Pulumi Kubernetes Docs](https://www.pulumi.com/docs/reference/pkg/kubernetes/) +- **Helm Charts Repository**: [Artifact Hub - cert-manager](https://artifacthub.io/packages/helm/cert-manager/cert-manager) diff --git a/pulumi/modules/cert_manager/deploy.py b/pulumi/modules/cert_manager/deploy.py index 85dcd49..7891fbf 100644 --- a/pulumi/modules/cert_manager/deploy.py +++ b/pulumi/modules/cert_manager/deploy.py @@ -4,14 +4,19 @@ Deploys the CertManager module using Helm with labels and annotations. """ -from typing import List, Dict, Any, Tuple, Optional import pulumi import pulumi_kubernetes as k8s -from pulumi_kubernetes.apiextensions.CustomResource import CustomResource +from typing import List, Dict, Any, Tuple, Optional from core.types import NamespaceConfig -from core.metadata import get_global_annotations, get_global_labels -from core.utils import set_resource_metadata, get_latest_helm_chart_version +from core.metadata import get_global_labels, get_global_annotations +from core.utils import get_latest_helm_chart_version +from core.resource_helpers import ( + create_namespace, + create_helm_release, + create_custom_resource, + create_secret, +) from .types import CertManagerConfig def deploy_cert_manager_module( @@ -19,14 +24,20 @@ def deploy_cert_manager_module( global_depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, ) -> Tuple[str, pulumi.Resource, str]: - + """ + Deploys the CertManager module and returns the version, release resource, and CA certificate. + """ + # Deploy Cert Manager cert_manager_version, release, ca_cert_b64 = deploy_cert_manager( config_cert_manager=config_cert_manager, depends_on=global_depends_on, k8s_provider=k8s_provider, ) + # Export the CA certificate pulumi.export("cert_manager_selfsigned_cert", ca_cert_b64) + + # Update global dependencies global_depends_on.append(release) return cert_manager_version, release, ca_cert_b64 @@ -36,34 +47,33 @@ def deploy_cert_manager( depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, ) -> Tuple[str, k8s.helm.v3.Release, str]: - + """ + Deploys Cert Manager using Helm and sets up cluster issuers. + """ namespace = config_cert_manager.namespace version = config_cert_manager.version cluster_issuer_name = config_cert_manager.cluster_issuer install_crds = config_cert_manager.install_crds - # Create Namespace - namespace_resource = create_namespace(namespace, k8s_provider, depends_on) + # Create Namespace using the helper function + namespace_resource = create_namespace( + name=namespace, + k8s_provider=k8s_provider, + depends_on=depends_on, + ) # Get Helm Chart Version chart_name = "cert-manager" chart_url = "https://charts.jetstack.io" version = get_helm_chart_version(chart_url, chart_name, version) + # Generate Helm values helm_values = generate_helm_values(config_cert_manager) - global_labels = get_global_labels() - global_annotations = get_global_annotations() - - def helm_transform(args: pulumi.ResourceTransformationArgs): - metadata = args.props.get('metadata', {}) - set_resource_metadata(metadata, global_labels, global_annotations) - args.props['metadata'] = metadata - return pulumi.ResourceTransformationResult(args.props, args.opts) - - release = k8s.helm.v3.Release( - chart_name, - k8s.helm.v3.ReleaseArgs( + # Create Helm Release using the helper function + release = create_helm_release( + name=chart_name, + args=k8s.helm.v3.ReleaseArgs( chart=chart_name, version=version, namespace=namespace, @@ -72,33 +82,53 @@ def helm_transform(args: pulumi.ResourceTransformationArgs): values=helm_values, ), opts=pulumi.ResourceOptions( - provider=k8s_provider, parent=namespace_resource, - depends_on=[namespace_resource] + depends_on, - transformations=[helm_transform], custom_timeouts=pulumi.CustomTimeouts(create="8m", update="4m", delete="4m"), ), + k8s_provider=k8s_provider, + depends_on=[namespace_resource] + depends_on, ) + # Create Cluster Issuers using the helper function cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer = create_cluster_issuers( cluster_issuer_name, namespace, k8s_provider, release ) - ca_secret = k8s.core.v1.Secret( - "cluster-selfsigned-issuer-ca-secret", - metadata={"name": "cluster-selfsigned-issuer-ca", "namespace": namespace}, + # Create Secret using the helper function + #ca_secret = k8s.core.v1.Secret.get( + # "cluster-selfsigned-issuer-ca-secret", + # id=f"{namespace}/cluster-selfsigned-issuer-ca", + # opts=pulumi.ResourceOptions( + # parent=cluster_issuer, + # depends_on=[cluster_issuer], + # provider=k8s_provider, + # ) + #) + ca_secret = create_secret( + name="cluster-selfsigned-issuer-ca-secret", + args={ + "metadata": { + "name": "cluster-selfsigned-issuer-ca", + "namespace": namespace, + }, + }, opts=pulumi.ResourceOptions( - provider=k8s_provider, parent=cluster_issuer, - depends_on=[cluster_issuer], + custom_timeouts=pulumi.CustomTimeouts(create="2m", update="2m", delete="2m"), ), + k8s_provider=k8s_provider, + depends_on=[cluster_issuer], ) + # Extract the CA certificate from the secret ca_data_tls_crt_b64 = ca_secret.data.apply(lambda data: data["tls.crt"]) return version, release, ca_data_tls_crt_b64 def generate_helm_values(config_cert_manager: CertManagerConfig) -> Dict[str, Any]: + """ + Generates Helm values for the CertManager deployment. + """ return { 'replicaCount': 1, 'installCRDs': config_cert_manager.install_crds, @@ -109,6 +139,9 @@ def generate_helm_values(config_cert_manager: CertManagerConfig) -> Dict[str, An } def get_helm_chart_version(chart_url: str, chart_name: str, version: Optional[str]) -> str: + """ + Retrieves the Helm chart version. + """ if version == 'latest' or version is None: version = get_latest_helm_chart_version(f"{chart_url}/index.yaml", chart_name).lstrip("v") pulumi.log.info(f"Setting Helm release version to latest: {chart_name}/{version}") @@ -116,93 +149,85 @@ def get_helm_chart_version(chart_url: str, chart_name: str, version: Optional[st pulumi.log.info(f"Using Helm release version: {chart_name}/{version}") return version -def create_namespace( - namespace_name: str, - k8s_provider: k8s.Provider, - depends_on: List[pulumi.Resource], - ) -> k8s.core.v1.Namespace: - """ - Creates a Kubernetes namespace with the provided configuration. - """ - namespace_config = NamespaceConfig(name=namespace_name) - namespace_resource = k8s.core.v1.Namespace( - namespace_name, - metadata={ - "name": namespace_config.name, - "labels": namespace_config.labels, - "annotations": namespace_config.annotations, - }, - opts=pulumi.ResourceOptions( - provider=k8s_provider, - depends_on=depends_on, - custom_timeouts=pulumi.CustomTimeouts( - create=namespace_config.custom_timeouts.get("create", "5m"), - update=namespace_config.custom_timeouts.get("update", "10m"), - delete=namespace_config.custom_timeouts.get("delete", "10m"), - ), - ), - ) - return namespace_resource - def create_cluster_issuers( cluster_issuer_name: str, namespace: str, k8s_provider: k8s.Provider, - release: pulumi.Resource, - ) -> Tuple[CustomResource, CustomResource, CustomResource]: - - cluster_issuer_root = CustomResource( - "cluster-selfsigned-issuer-root", - api_version="cert-manager.io/v1", - kind="ClusterIssuer", - metadata={"name": "cluster-selfsigned-issuer-root"}, - spec={"selfSigned": {}}, + release: pulumi.Resource + ) -> Tuple[k8s.apiextensions.CustomResource, k8s.apiextensions.CustomResource, k8s.apiextensions.CustomResource]: + """ + Creates cluster issuers required for CertManager. + """ + # Create ClusterIssuer root using the helper function + cluster_issuer_root = create_custom_resource( + name="cluster-selfsigned-issuer-root", + args={ + "apiVersion": "cert-manager.io/v1", + "kind": "ClusterIssuer", + "metadata": { + "name": "cluster-selfsigned-issuer-root", + }, + "spec": {"selfSigned": {}}, + }, opts=pulumi.ResourceOptions( - provider=k8s_provider, parent=release, - depends_on=[release], custom_timeouts=pulumi.CustomTimeouts(create="5m", update="10m", delete="10m"), ), + k8s_provider=k8s_provider, + depends_on=[release], ) - cluster_issuer_ca_certificate = CustomResource( - "cluster-selfsigned-issuer-ca", - api_version="cert-manager.io/v1", - kind="Certificate", - metadata={"name": "cluster-selfsigned-issuer-ca", "namespace": namespace}, - spec={ - "commonName": "cluster-selfsigned-issuer-ca", - "duration": "2160h0m0s", - "isCA": True, - "issuerRef": { - "group": "cert-manager.io", - "kind": "ClusterIssuer", - "name": "cluster-selfsigned-issuer-root", + # Create ClusterIssuer CA Certificate using the helper function + cluster_issuer_ca_certificate = create_custom_resource( + name="cluster-selfsigned-issuer-ca", + args={ + "apiVersion": "cert-manager.io/v1", + "kind": "Certificate", + "metadata": { + "name": "cluster-selfsigned-issuer-ca", + "namespace": namespace, + }, + "spec": { + "commonName": "cluster-selfsigned-issuer-ca", + "duration": "2160h0m0s", + "isCA": True, + "issuerRef": { + "group": "cert-manager.io", + "kind": "ClusterIssuer", + "name": "cluster-selfsigned-issuer-root", + }, + "privateKey": {"algorithm": "ECDSA", "size": 256}, + "renewBefore": "360h0m0s", + "secretName": "cluster-selfsigned-issuer-ca", }, - "privateKey": {"algorithm": "ECDSA", "size": 256}, - "renewBefore": "360h0m0s", - "secretName": "cluster-selfsigned-issuer-ca", }, opts=pulumi.ResourceOptions( - provider=k8s_provider, parent=cluster_issuer_root, - depends_on=[cluster_issuer_root], custom_timeouts=pulumi.CustomTimeouts(create="5m", update="10m", delete="10m"), ), + k8s_provider=k8s_provider, + depends_on=[cluster_issuer_root], ) - cluster_issuer = CustomResource( - cluster_issuer_name, - api_version="cert-manager.io/v1", - kind="ClusterIssuer", - metadata={"name": cluster_issuer_name}, - spec={"ca": {"secretName": "cluster-selfsigned-issuer-ca"}}, + # Create ClusterIssuer using the helper function + cluster_issuer = create_custom_resource( + name=cluster_issuer_name, + args={ + "apiVersion": "cert-manager.io/v1", + "kind": "ClusterIssuer", + "metadata": { + "name": cluster_issuer_name, + }, + "spec": { + "ca": {"secretName": "cluster-selfsigned-issuer-ca"}, + }, + }, opts=pulumi.ResourceOptions( - provider=k8s_provider, parent=cluster_issuer_ca_certificate, - depends_on=[cluster_issuer_ca_certificate], custom_timeouts=pulumi.CustomTimeouts(create="4m", update="4m", delete="4m"), ), + k8s_provider=k8s_provider, + depends_on=[cluster_issuer_ca_certificate], ) return cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer diff --git a/pulumi/modules/kubevirt/README.md b/pulumi/modules/kubevirt/README.md index a9ba7b3..2869321 100644 --- a/pulumi/modules/kubevirt/README.md +++ b/pulumi/modules/kubevirt/README.md @@ -1,181 +1,186 @@ -# KubeVirt Module +# KubeVirt Module Guide -ContainerCraft Kargo Kubevirt PaaS KubeVirt module. - -The `kubevirt` module automates the deployment and configuration of KubeVirt in your Kubernetes environment using [KubeVirt](https://kubevirt.io/). KubeVirt is a Kubernetes-native solution to run and manage virtual machines alongside container workloads. This module simplifies the setup, configuration, and management of KubeVirt components for a seamless virtualization experience within Kubernetes. +Welcome to the **KubeVirt Module** for the Kargo KubeVirt Kubernetes PaaS! This guide is intended to help both newcomers and experienced developers understand, deploy, and customize the KubeVirt module within the Kargo platform. --- ## Table of Contents - [Introduction](#introduction) -- [Prerequisites](#prerequisites) -- [Features](#features) +- [Why Use KubeVirt?](#why-use-kubevirt) +- [Getting Started](#getting-started) - [Enabling the Module](#enabling-the-module) -- [Configuration](#configuration) - - [Default Configuration](#default-configuration) - - [Custom Configuration](#custom-configuration) -- [Module Components](#module-components) +- [Configuration Options](#configuration-options) + - [Default Settings](#default-settings) + - [Customizing Your Deployment](#customizing-your-deployment) +- [Module Components Explained](#module-components-explained) - [Namespace Creation](#namespace-creation) - - [KubeVirt Deployment](#kubevirt-deployment) - - [Custom Resource Creation](#custom-resource-creation) -- [Integration with Kargo PaaS](#integration-with-kargo-paas) -- [Best Practices](#best-practices) -- [Troubleshooting](#troubleshooting) + - [Operator Deployment](#operator-deployment) + - [Custom Resource Configuration](#custom-resource-configuration) +- [Using the Module](#using-the-module) + - [Example Usage](#example-usage) +- [Troubleshooting and FAQs](#troubleshooting-and-faqs) - [Additional Resources](#additional-resources) +- [Conclusion](#conclusion) --- ## Introduction -This guide provides an overview of the KubeVirt module, instructions on how to enable and configure it, and explanations of its functionality within the Kargo PaaS platform. Whether you're new to Kubernetes, KubeVirt, or ContainerCraft Kargo PaaS, this guide will help you get started. +The KubeVirt module enables you to run virtual machines (VMs) within your Kubernetes cluster using [KubeVirt](https://kubevirt.io/). It bridges the gap between containerized applications and traditional VM workloads, providing a unified platform for all your infrastructure needs. -## Prerequisites +--- -Before deploying the module, ensure the following prerequisites are met: +## Why Use KubeVirt? -- [Pulumi CLI](https://www.pulumi.com/docs/get-started/) -- Python 3.6 or higher -- Python dependencies (install via `pip install -r requirements.txt`) -- Kubernetes cluster (with `kubectl` configured) -- Properly configured `kubeconfig` file +- **Unified Platform**: Manage containers and VMs in a single Kubernetes cluster. +- **Flexibility**: Run legacy applications alongside cloud-native ones. +- **Scalability**: Leverage Kubernetes scaling features for VMs. +- **Ecosystem Integration**: Use Kubernetes tools and practices for VM management. --- -## Features +## Getting Started + +### Prerequisites + +- **Kubernetes Cluster**: Access to a cluster with appropriate resources. +- **Pulumi CLI**: Installed and configured. +- **Kubeconfig**: Properly set up for cluster access. -- **Automated Deployment**: Deploys KubeVirt using the official operator YAMLs. -- **Version Management**: Supports explicit version pinning or accepts `latest`. -- **Namespace Isolation**: Deploys KubeVirt in a dedicated namespace. -- **Customizable Configuration**: Allows setting overrides for customization, including emulation and feature gates. -- **Metadata Propagation**: Applies global labels and annotations consistently across resources. +### Setup Steps + +1. **Navigate to the Kargo Pulumi Directory**: + ```bash + cd Kargo/pulumi + ``` +2. **Install Dependencies**: + ```bash + pip install -r requirements.txt + ``` +3. **Initialize Pulumi Stack**: + ```bash + pulumi stack init dev + ``` --- ## Enabling the Module -The KubeVirt module is enabled by default. Customization can be configured in the Pulumi Stack Configuration. +The KubeVirt module is enabled by default. To confirm or adjust its status, modify your Pulumi configuration. -### Example Pulumi Configuration +### Verifying Module Enablement ```yaml # Pulumi..yaml config: kubevirt: - enabled: true # (default: true) + enabled: true # Set to false to disable ``` -Alternatively, you can set configuration values via the Pulumi CLI: +Alternatively, use the Pulumi CLI: ```bash -pulumi config set --path kubevirt.key value +pulumi config set --path kubevirt.enabled true ``` --- -## Configuration - -### Default Configuration +## Configuration Options -The module comes with sensible defaults to simplify deployment: +### Default Settings - **Namespace**: `kubevirt` -- **Version**: Uses the default version specified in `default_versions.json` -- **Use Emulation**: `false` -- **Labels and Annotations**: Derived from global configurations +- **Version**: Defined in `default_versions.json` +- **Use Emulation**: `false` (suitable for bare-metal environments) -### Custom Configuration +### Customizing Your Deployment -You can customize the module's behavior by providing additional configuration options. +#### Available Configuration Parameters -#### Available Configuration Options - -- **namespace** *(string)*: The namespace where KubeVirt will be deployed. -- **version** *(string)*: The version of the KubeVirt operator YAML to deploy. Use `'latest'` to fetch the latest version. -- **use_emulation** *(bool)*: Whether to enable KVM emulation. -- **labels** *(dict)*: Custom labels to apply to resources. -- **annotations** *(dict)*: Custom annotations to apply to resources. +- **enabled** *(bool)*: Enable or disable the module. +- **namespace** *(string)*: Kubernetes namespace for KubeVirt. +- **version** *(string)*: Specific version to deploy. Use `'latest'` for the most recent stable version. +- **use_emulation** *(bool)*: Enable if running in a nested virtualization environment. +- **labels** *(dict)*: Custom labels for resources. +- **annotations** *(dict)*: Custom annotations for resources. #### Example Custom Configuration ```yaml -# Pulumi..yaml -# Default values are shown for reference - config: kubevirt: enabled: true - version: "0.43.0" - namespace: "custom-kubevirt" + namespace: "kubevirt" + version: "1.3.1" use_emulation: true labels: - app.kubernetes.io/name: "kubevirt" + app: "kubevirt" annotations: - organization: "ContainerCraft" + owner: "dev-team" ``` --- -## Module Components +## Module Components Explained ### Namespace Creation -The module creates a dedicated namespace for KubeVirt to ensure isolation and better management. +A dedicated namespace is created for KubeVirt. -- **Namespace**: Configurable via the `namespace` parameter. -- **Labels and Annotations**: Applied as per best practices for identification. +- **Purpose**: Isolates KubeVirt resources for better management. +- **Customization**: Change using the `namespace` parameter. -### KubeVirt Deployment +### Operator Deployment -The module deploys KubeVirt using the official operator YAML from the KubeVirt GitHub repository. +Deploys the KubeVirt operator. -- **Operator YAML**: Downloaded from the specified version or the latest release. -- **Custom Values**: Includes configurations for namespaces, labels, and annotations. -- **Version**: Configurable; defaults to the version specified in `default_versions.json`. +- **Source**: Official KubeVirt operator YAML. +- **Version Management**: Specify a version or use `'latest'`. +- **Transformation**: YAML is adjusted to fit the specified namespace. -### Custom Resource Creation +### Custom Resource Configuration -To manage KubeVirt operational settings, the module creates custom resources with specified configurations. +Defines the KubeVirt CustomResource to configure KubeVirt settings. -- **KubeVirt CR**: Customizes KubeVirt to enable emulation and additional feature gates. -- **SMBIOS Configuration**: Adds specific values to the SMBIOS configuration for virtual machines. -- **Namespace and Resources**: The custom resource is applied in the specified namespace with the appropriate labels and annotations. +- **Emulation Mode**: Controlled by `use_emulation`. +- **Feature Gates**: Enables additional features like `HostDevices` and `ExpandDisks`. +- **SMBIOS Configuration**: Sets metadata for virtual machines. -## Integration with Kargo PaaS +--- -KubeVirt integrates seamlessly with the Kargo Kubevirt PaaS, making it simple to manage and run virtual machines alongside container workloads. The deployment process is optimized for consistency and simplicity, ensuring a smooth user experience within the Kubernetes ecosystem. +## Using the Module ---- +### Example Usage -## Best Practices +Deploy the module with your custom configuration: -- **Namespace Isolation**: Always deploy KubeVirt in a dedicated namespace to avoid conflicts. -- **Version Pinning**: Pin specific versions in production to avoid unintended issues with new releases. -- **Emulation Enablement**: Enable `use_emulation` only if you intend to run KubeVirt on non-bare-metal setups. +```bash +pulumi up +``` --- -## Troubleshooting +## Troubleshooting and FAQs + +**Q1: Virtual machines are not starting.** + +- **A**: Ensure that your nodes support virtualization. If running in a VM without the `/dev/kvm` device, set `use_emulation` to `true`. -### Common Issues +**Q2: Deployment fails with version errors.** -- **Connection Errors**: Ensure your `kubeconfig` and Kubernetes context are correctly configured. -- **Version Conflicts**: If deployment fails due to version issues, verify the specified version is available in the KubeVirt repository. Alternatively use `'latest'` and Kargo will fetch the latest version. -- **Namespace Issues**: Ensure the specified namespace is unique or does not conflict with existing namespaces. +- **A**: Verify that the specified version exists. Use `'latest'` to automatically fetch the latest stable version. -### Debugging Steps +**Q3: How do I enable additional feature gates?** -1. **Check Pulumi Logs**: Look for error messages during deployment. -2. **Verify Kubernetes Resources**: Use `kubectl` to inspect the KubeVirt namespace and resources. -3. **Review Configuration**: Ensure all configuration options are correctly set in your Pulumi config or remove configuration to use defaults. +- **A**: Modify the `featureGates` section in the `deploy.py` or submit a feature request to expose this via configuration. --- ## Additional Resources -- **KubeVirt Documentation**: [https://kubevirt.io/docs/](https://kubevirt.io/docs/) -- **Kargo Kubevirt PaaS IaC Documentation**: Refer to the main [Kargo README](../README.md) for project usage. -- **Pulumi Kubernetes Provider**: [https://www.pulumi.com/docs/reference/pkg/kubernetes/](https://www.pulumi.com/docs/reference/pkg/kubernetes/) -- **Helm Charts**: [https://artifacthub.io/packages/helm/jetstack/cert-manager](https://artifacthub.io/packages/helm/jetstack/cert-manager) -- **Need Help?** If you have questions or need assistance, feel free to reach out to the community or maintainers on GitHub, Discord, or Twitter. +- **KubeVirt Documentation**: [kubevirt.io/docs](https://kubevirt.io/docs/) +- **Kargo Project**: [Kargo GitHub Repository](https://github.com/ContainerCraft/Kargo) +- **Pulumi Kubernetes Provider**: [Pulumi Kubernetes Docs](https://www.pulumi.com/docs/reference/pkg/kubernetes/) +- **KubeVirt Releases**: [KubeVirt GitHub Releases](https://github.com/kubevirt/kubevirt/releases) diff --git a/pulumi/modules/kubevirt/deploy.py b/pulumi/modules/kubevirt/deploy.py index abecb4c..e9a19ae 100644 --- a/pulumi/modules/kubevirt/deploy.py +++ b/pulumi/modules/kubevirt/deploy.py @@ -12,12 +12,13 @@ import pulumi import pulumi_kubernetes as k8s -from pulumi_kubernetes.apiextensions.CustomResource import CustomResource -from pulumi_kubernetes.meta.v1 import ObjectMetaArgs -from core.types import NamespaceConfig from core.metadata import get_global_labels, get_global_annotations -from core.utils import set_resource_metadata +from core.resource_helpers import ( + create_namespace, + create_custom_resource, + create_config_file, +) from .types import KubeVirtConfig def deploy_kubevirt_module( @@ -25,22 +26,27 @@ def deploy_kubevirt_module( global_depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, ) -> Tuple[Optional[str], Optional[pulumi.Resource]]: - + """ + Deploys the KubeVirt module and returns the version and the deployed resource. + """ + # Create Namespace using the helper function namespace_resource = create_namespace( - config_kubevirt.namespace, - k8s_provider, - global_depends_on, + name=config_kubevirt.namespace, + k8s_provider=k8s_provider, + depends_on=global_depends_on, ) # Combine dependencies depends_on = global_depends_on + [namespace_resource] + # Deploy KubeVirt kubevirt_version, kubevirt_resource = deploy_kubevirt( config_kubevirt=config_kubevirt, depends_on=depends_on, k8s_provider=k8s_provider, ) + # Update global dependencies global_depends_on.append(kubevirt_resource) return kubevirt_version, kubevirt_resource @@ -50,49 +56,43 @@ def deploy_kubevirt( depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, ) -> Tuple[str, pulumi.Resource]: - + """ + Deploys KubeVirt operator and creates the KubeVirt CustomResource. + """ namespace = config_kubevirt.namespace version = config_kubevirt.version use_emulation = config_kubevirt.use_emulation - labels = config_kubevirt.labels - annotations = config_kubevirt.annotations + # Determine KubeVirt version if version == 'latest' or version is None: version = get_latest_kubevirt_version() pulumi.log.info(f"Setting KubeVirt release version to latest: {version}") else: pulumi.log.info(f"Using KubeVirt version: {version}") + # Download and transform KubeVirt operator YAML kubevirt_operator_yaml = download_kubevirt_operator_yaml(version) - transformed_yaml = _transform_yaml(kubevirt_operator_yaml, namespace) - def kubevirt_transform(obj: Dict[str, Any], opts: pulumi.ResourceOptions) -> pulumi.ResourceTransformationResult: - obj.setdefault("metadata", {}) - set_resource_metadata(obj["metadata"], labels, annotations) - if "spec" in obj and "template" in obj["spec"]: - template_meta = obj["spec"]["template"].setdefault("metadata", {}) - set_resource_metadata(template_meta, labels, annotations) - return pulumi.ResourceTransformationResult(obj, opts) - + # Write transformed YAML to a temporary file with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: yaml.dump_all(transformed_yaml, temp_file) temp_file_path = temp_file.name try: - operator = k8s.yaml.ConfigFile( - 'kubevirt-operator', + # Deploy KubeVirt operator using the helper function + operator = create_config_file( + name='kubevirt-operator', file=temp_file_path, - transformations=[kubevirt_transform], opts=pulumi.ResourceOptions( - provider=k8s_provider, - depends_on=depends_on, custom_timeouts=pulumi.CustomTimeouts( create="10m", update="5m", delete="5m", ), ), + k8s_provider=k8s_provider, + depends_on=depends_on, ) finally: os.unlink(temp_file_path) @@ -100,74 +100,53 @@ def kubevirt_transform(obj: Dict[str, Any], opts: pulumi.ResourceOptions) -> pul if use_emulation: pulumi.log.info("KVM Emulation enabled for KubeVirt.") - kubevirt_custom_resource_spec = { - "configuration": { - "developerConfiguration": { - "useEmulation": use_emulation, - "featureGates": [ - "HostDevices", - "ExpandDisks", - "AutoResourceLimitsGate", - ], + # Create KubeVirt CustomResource using the helper function + kubevirt_resource = create_custom_resource( + name="kubevirt", + args={ + "apiVersion": "kubevirt.io/v1", + "kind": "KubeVirt", + "metadata": { + "name": "kubevirt", + "namespace": namespace, }, - "smbios": { - "sku": "kargo-kc2", - "version": version, - "manufacturer": "ContainerCraft", - "product": "Kargo", - "family": "CCIO", + "spec": { + "configuration": { + "developerConfiguration": { + "useEmulation": use_emulation, + "featureGates": [ + "HostDevices", + "ExpandDisks", + "AutoResourceLimitsGate", + ], + }, + "smbios": { + "sku": "kargo-kc2", + "version": version, + "manufacturer": "ContainerCraft", + "product": "Kargo", + "family": "CCIO", + }, + }, }, - } - } - - kubevirt = CustomResource( - "kubevirt", - api_version="kubevirt.io/v1", - kind="KubeVirt", - metadata=ObjectMetaArgs( - name="kubevirt", - labels=labels, - namespace=namespace, - annotations=annotations, - ), - spec=kubevirt_custom_resource_spec, - opts=pulumi.ResourceOptions( - provider=k8s_provider, - depends_on=[operator], - ), - ) - - return version, kubevirt - -def create_namespace( - namespace_name: str, - k8s_provider: k8s.Provider, - depends_on: List[pulumi.Resource], - ) -> k8s.core.v1.Namespace: - """ - Creates a Kubernetes namespace with the provided configuration. - """ - namespace_config = NamespaceConfig(name=namespace_name) - namespace_resource = k8s.core.v1.Namespace( - namespace_name, - metadata={ - "name": namespace_config.name, - "labels": namespace_config.labels, - "annotations": namespace_config.annotations, }, opts=pulumi.ResourceOptions( - provider=k8s_provider, - depends_on=depends_on, custom_timeouts=pulumi.CustomTimeouts( - create=namespace_config.custom_timeouts.get("create", "5m"), - update=namespace_config.custom_timeouts.get("update", "10m"), - delete=namespace_config.custom_timeouts.get("delete", "10m"), + create="5m", + update="5m", + delete="5m", ), ), + k8s_provider=k8s_provider, + depends_on=[operator], ) - return namespace_resource + + return version, kubevirt_resource def get_latest_kubevirt_version() -> str: + """ + Retrieves the latest stable version of KubeVirt. + """ url = 'https://storage.googleapis.com/kubevirt-prow/release/kubevirt/kubevirt/stable.txt' response = requests.get(url) if response.status_code != 200: @@ -175,13 +154,19 @@ def get_latest_kubevirt_version() -> str: return response.text.strip().lstrip("v") def download_kubevirt_operator_yaml(version: str) -> Any: + """ + Downloads the KubeVirt operator YAML for the specified version. + """ url = f'https://github.com/kubevirt/kubevirt/releases/download/v{version}/kubevirt-operator.yaml' response = requests.get(url) if response.status_code != 200: raise Exception(f"Failed to download KubeVirt operator YAML from {url}") - return yaml.safe_load_all(response.text) + return list(yaml.safe_load_all(response.text)) def _transform_yaml(yaml_data: Any, namespace: str) -> List[Dict[str, Any]]: + """ + Transforms the YAML data to set the namespace and exclude Namespace resources. + """ transformed = [] for resource in yaml_data: if resource.get('kind') == 'Namespace':