diff --git a/pulumi/core/config.py b/pulumi/core/config.py index e32eb3a..4fffc2e 100644 --- a/pulumi/core/config.py +++ b/pulumi/core/config.py @@ -18,7 +18,7 @@ # Default versions URL template DEFAULT_VERSIONS_URL_TEMPLATE = 'https://raw.githubusercontent.com/ContainerCraft/Kargo/rerefactor/pulumi/' -# Default module enabled settings +# Module enabled defaults: Setting a module to True enables the module by default DEFAULT_ENABLED_CONFIG = { "cert_manager": True, "kubevirt": True, diff --git a/pulumi/core/resource_helpers.py b/pulumi/core/resource_helpers.py index e359436..a7b508f 100644 --- a/pulumi/core/resource_helpers.py +++ b/pulumi/core/resource_helpers.py @@ -5,7 +5,6 @@ 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, @@ -15,6 +14,7 @@ def create_namespace( custom_timeouts: Optional[Dict[str, str]] = None, opts: Optional[pulumi.ResourceOptions] = None, k8s_provider: Optional[k8s.Provider] = None, + parent: Optional[pulumi.Resource] = None, depends_on: Optional[List[pulumi.Resource]] = None, ) -> k8s.core.v1.Namespace: """ @@ -43,6 +43,8 @@ def create_namespace( custom_timeouts = {} if depends_on is None: depends_on = [] + if parent is None: + parent = [] global_labels = get_global_labels() global_annotations = get_global_annotations() @@ -64,6 +66,7 @@ def create_namespace( pulumi.ResourceOptions( provider=k8s_provider, depends_on=depends_on, + parent=parent, custom_timeouts=pulumi.CustomTimeouts( create=custom_timeouts.get("create", "5m"), update=custom_timeouts.get("update", "10m"), @@ -86,47 +89,62 @@ def create_custom_resource( 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 = [] + """ + Creates a Kubernetes CustomResource with global labels and annotations. - if 'kind' not in args or 'apiVersion' not in args: - raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.") + Args: + name (str): The name of the custom resource. + args (Dict[str, Any]): Arguments for creating the custom resource. + opts (Optional[pulumi.ResourceOptions]): Pulumi resource options. + k8s_provider (Optional[k8s.Provider]): Kubernetes provider. + depends_on (Optional[List[pulumi.Resource]]): Resources this custom resource depends on. - global_labels = get_global_labels() - global_annotations = get_global_annotations() + Returns: + k8s.apiextensions.CustomResource: The created CustomResource. + """ + try: + if 'kind' not in args or 'apiVersion' not in args: + raise ValueError("The 'args' dictionary must include 'kind' and 'apiVersion' keys.") + + if opts is None: + opts = pulumi.ResourceOptions() + if depends_on is None: + depends_on = [] + + 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], + ), + ) - 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) + # Ensure metadata and spec are included if specified + metadata = args.get('metadata', {}) + spec = args.get('spec', {}) - opts = pulumi.ResourceOptions.merge( - opts, - pulumi.ResourceOptions( - provider=k8s_provider, - depends_on=depends_on, - transformations=[custom_resource_transform], - ), - ) + return k8s.apiextensions.CustomResource( + resource_name=name, + api_version=args['apiVersion'], + kind=args['kind'], + metadata=metadata, + spec=spec, + opts=opts, + ) - # 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 - ) + except Exception as e: + pulumi.log.error(f"Failed to create custom resource '{name}': {e}") + raise def create_helm_release( name: str, @@ -189,6 +207,19 @@ def create_secret( k8s_provider: Optional[k8s.Provider] = None, depends_on: Optional[List[pulumi.Resource]] = None, ) -> k8s.core.v1.Secret: + """ + Creates a Kubernetes Secret with global labels and annotations. + + Args: + name (str): The name of the secret. + args (Dict[str, Any]): Arguments for creating the secret. + opts (Optional[pulumi.ResourceOptions]): Pulumi resource options. + k8s_provider (Optional[k8s.Provider]): Kubernetes provider. + depends_on (Optional[List[pulumi.Resource]]): Resources this secret depends on. + + Returns: + k8s.core.v1.Secret: The created Secret. + """ if opts is None: opts = pulumi.ResourceOptions() if depends_on is None: @@ -279,13 +310,13 @@ def create_meta_objectmeta( **kwargs, ) -> k8s.meta.v1.ObjectMetaArgs: """ - Creates an ObjectMetaArgs with global labels and annotations. + Creates a Kubernetes ObjectMetaArgs with global labels and annotations. Args: - name (str): The resource name. + name (str): The name of the resource. 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. + namespace (Optional[str]): The namespace of the resource. Returns: k8s.meta.v1.ObjectMetaArgs: The metadata arguments. diff --git a/pulumi/core/utils.py b/pulumi/core/utils.py index b4e4762..0a7eee6 100644 --- a/pulumi/core/utils.py +++ b/pulumi/core/utils.py @@ -8,25 +8,23 @@ """ import re +import os +import tempfile import pulumi import pulumi_kubernetes as k8s -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, List import requests import logging import yaml from packaging.version import parse as parse_version, InvalidVersion, Version + # Set up basic logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_annotations: Dict[str, str]): """ Updates resource metadata with global labels and annotations. - - Args: - metadata (Any): Metadata to update. - global_labels (Dict[str, str]): Global labels to apply. - global_annotations (Dict[str, str]): Global annotations to apply. """ if isinstance(metadata, dict): metadata.setdefault('labels', {}).update(global_labels) @@ -42,10 +40,6 @@ def set_resource_metadata(metadata: Any, global_labels: Dict[str, str], global_a def generate_global_transformations(global_labels: Dict[str, str], global_annotations: Dict[str, str]): """ Generates global transformations for resources. - - Args: - global_labels (Dict[str, str]): Global labels to apply. - global_annotations (Dict[str, str]): Global annotations to apply. """ def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi.ResourceTransformationResult]: props = args.props @@ -60,20 +54,22 @@ def global_transform(args: pulumi.ResourceTransformationArgs) -> Optional[pulumi pulumi.runtime.register_stack_transformation(global_transform) -def get_latest_helm_chart_version(url: str, chart_name: str) -> str: +def get_latest_helm_chart_version(repo_url: str, chart_name: str) -> str: """ - Fetches the latest stable version of a Helm chart from the given URL. + Fetches the latest stable version of a Helm chart from the given repository URL. Args: - url (str): The URL of the Helm repository index. + repo_url (str): The base URL of the Helm repository. chart_name (str): The name of the Helm chart. Returns: str: The latest stable version of the chart. """ try: - logging.info(f"Fetching URL: {url}") - response = requests.get(url) + index_url = repo_url.rstrip('/') + '/index.yaml' + + logging.info(f"Fetching Helm repository index from URL: {index_url}") + response = requests.get(index_url) response.raise_for_status() index = yaml.safe_load(response.content) @@ -84,14 +80,17 @@ def get_latest_helm_chart_version(url: str, chart_name: str) -> str: logging.info(f"No stable versions found for chart '{chart_name}'.") return "Chart not found" latest_chart = max(stable_versions, key=lambda x: parse_version(x['version'])) - return latest_chart['version'] + return latest_chart['version'].lstrip('v') else: logging.info(f"No chart named '{chart_name}' found in repository.") return "Chart not found" except requests.RequestException as e: - logging.error(f"Error fetching data: {e}") + logging.error(f"Error fetching Helm repository index: {e}") return f"Error fetching data: {e}" + except yaml.YAMLError as e: + logging.error(f"Error parsing Helm repository index YAML: {e}") + return f"Error parsing YAML: {e}" def is_stable_version(version_str: str) -> bool: """ @@ -123,3 +122,102 @@ def extract_repo_name(remote_url: str) -> str: if match: return match.group(1) return remote_url + + +# Set up basic logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + +def wait_for_crds(crd_names: List[str], k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> List[pulumi.Resource]: + """ + Waits for the specified CRDs to be present and ensures dependencies. + + Args: + crd_names (List[str]): A list of CRD names. + k8s_provider (k8s.Provider): The Kubernetes provider. + depends_on (List[pulumi.Resource]): A list of dependencies. + parent (pulumi.Resource): The parent resource. + + Returns: + List[pulumi.Resource]: The CRD resources or an empty list during preview. + """ + crds = [] + + for crd_name in crd_names: + try: + crd = k8s.apiextensions.v1.CustomResourceDefinition.get( + resource_name=crd_name, + id=crd_name, + opts=pulumi.ResourceOptions( + provider=k8s_provider, + depends_on=depends_on, + parent=parent, + ), + ) + crds.append(crd) + except Exception: + pulumi.log.info(f"CRD {crd_name} not found, creating dummy CRD.") + dummy_crd = create_dummy_crd(crd_name, k8s_provider, depends_on, parent) + if dummy_crd: + crds.append(dummy_crd) + + return crds + +def create_dummy_crd(crd_name: str, k8s_provider: k8s.Provider, depends_on: List[pulumi.Resource], parent: pulumi.Resource) -> Optional[k8s.yaml.ConfigFile]: + """ + Create a dummy CRD definition to use during preview runs. + + Args: + crd_name (str): The name of the CRD. + k8s_provider (k8s.Provider): The Kubernetes provider. + depends_on (List[pulumi.Resource]): A list of dependencies. + parent (pulumi.Resource): The parent resource. + + Returns: + Optional[k8s.yaml.ConfigFile]: The dummy CRD resource. + """ + parts = crd_name.split('.') + plural = parts[0] + group = '.'.join(parts[1:]) + kind = ''.join(word.title() for word in plural.split('_')) + + dummy_crd_yaml_template = """ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: {metadata_name} +spec: + group: {group} + names: + plural: {plural} + kind: {kind} + scope: Namespaced + versions: + - name: v1 + served: true + storage: true +""" + + dummy_crd_yaml = dummy_crd_yaml_template.format( + metadata_name=f"{plural}.{group}", + group=group, + plural=plural, + kind=kind, + ) + + try: + with tempfile.NamedTemporaryFile(delete=False, mode='w') as temp_file: + temp_file.write(dummy_crd_yaml) + temp_file_path = temp_file.name + + dummy_crd = k8s.yaml.ConfigFile( + "dummy-crd-{}".format(crd_name), + file=temp_file_path, + opts=pulumi.ResourceOptions( + parent=parent, + depends_on=depends_on, + provider=k8s_provider, + ) + ) + return dummy_crd + finally: + os.unlink(temp_file_path) diff --git a/pulumi/modules/cert_manager/deploy.py b/pulumi/modules/cert_manager/deploy.py index 7891fbf..20222d9 100644 --- a/pulumi/modules/cert_manager/deploy.py +++ b/pulumi/modules/cert_manager/deploy.py @@ -1,16 +1,16 @@ # pulumi/modules/cert_manager/deploy.py """ -Deploys the CertManager module using Helm with labels and annotations. +Deploys the cert-manager module with proper dependency management. """ +# Necessary imports +import logging import pulumi import pulumi_kubernetes as k8s -from typing import List, Dict, Any, Tuple, Optional - +from typing import List, Dict, Any, Tuple, Optional, cast from core.types import NamespaceConfig -from core.metadata import get_global_labels, get_global_annotations -from core.utils import get_latest_helm_chart_version +from core.utils import get_latest_helm_chart_version, wait_for_crds from core.resource_helpers import ( create_namespace, create_helm_release, @@ -19,36 +19,39 @@ ) from .types import CertManagerConfig + def deploy_cert_manager_module( config_cert_manager: CertManagerConfig, global_depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, - ) -> Tuple[str, pulumi.Resource, str]: + ) -> Tuple[str, k8s.helm.v3.Release, str]: """ - Deploys the CertManager module and returns the version, release resource, and CA certificate. + Deploys the cert-manager module and returns the version, release resource, and CA certificate. """ - # Deploy Cert Manager + # add k8s_provider to module dependencies + # TODO: Create module specific dependencies object to avoid blocking global resources on k8s_provider or other module specific dependencies + + # Deploy cert-manager cert_manager_version, release, ca_cert_b64 = deploy_cert_manager( config_cert_manager=config_cert_manager, - depends_on=global_depends_on, + depends_on=global_depends_on, # Correctly pass the global dependencies 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 + def deploy_cert_manager( config_cert_manager: CertManagerConfig, 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. + Deploys cert-manager using Helm and sets up cluster issuers, + ensuring that CRDs are available before creating custom resources. """ namespace = config_cert_manager.namespace version = config_cert_manager.version @@ -59,13 +62,19 @@ def deploy_cert_manager( namespace_resource = create_namespace( name=namespace, k8s_provider=k8s_provider, + parent=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) + chart_repo_url = "https://charts.jetstack.io" + + if version == 'latest' or version is None: + version = get_latest_helm_chart_version(chart_repo_url, chart_name) + pulumi.log.info(f"Setting cert-manager chart version to latest: {version}") + else: + pulumi.log.info(f"Using cert-manager chart version: {version}") # Generate Helm values helm_values = generate_helm_values(config_cert_manager) @@ -78,7 +87,7 @@ def deploy_cert_manager( version=version, namespace=namespace, skip_await=False, - repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_url), + repository_opts=k8s.helm.v3.RepositoryOptsArgs(repo=chart_repo_url), values=helm_values, ), opts=pulumi.ResourceOptions( @@ -89,145 +98,166 @@ def deploy_cert_manager( 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 + # Wait for the CRDs to be registered + crds = wait_for_crds( + crd_names=[ + "certificaterequests.cert-manager.io", + "certificates.cert-manager.io", + "challenges.acme.cert-manager.io", + "clusterissuers.cert-manager.io", + "issuers.cert-manager.io", + "orders.acme.cert-manager.io", + ], + k8s_provider=k8s_provider, + depends_on=[release], + parent=release ) - # 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( - parent=cluster_issuer, - custom_timeouts=pulumi.CustomTimeouts(create="2m", update="2m", delete="2m"), - ), - k8s_provider=k8s_provider, - depends_on=[cluster_issuer], + # Create Cluster Issuers using the helper function + cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer, ca_secret = create_cluster_issuers( + cluster_issuer_name, namespace, release, crds, k8s_provider ) # Extract the CA certificate from the secret - ca_data_tls_crt_b64 = ca_secret.data.apply(lambda data: data["tls.crt"]) + if ca_secret: + ca_data_tls_crt_b64 = ca_secret.data.apply(lambda data: data["tls.crt"]) + else: + ca_data_tls_crt_b64 = "" 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, - 'resources': { - 'limits': {'cpu': '500m', 'memory': '1024Mi'}, - 'requests': {'cpu': '250m', 'memory': '512Mi'}, - }, - } - -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}") - else: - pulumi.log.info(f"Using Helm release version: {chart_name}/{version}") - return version - def create_cluster_issuers( cluster_issuer_name: str, namespace: str, + release: k8s.helm.v3.Release, + crds: List[pulumi.Resource], k8s_provider: k8s.Provider, - release: pulumi.Resource - ) -> Tuple[k8s.apiextensions.CustomResource, k8s.apiextensions.CustomResource, k8s.apiextensions.CustomResource]: - """ - Creates cluster issuers required for CertManager. +) -> Tuple[ + Optional[k8s.apiextensions.CustomResource], + Optional[k8s.apiextensions.CustomResource], + Optional[k8s.apiextensions.CustomResource], + Optional[k8s.core.v1.Secret], +]: """ - # 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( - parent=release, - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="10m", delete="10m"), - ), - k8s_provider=k8s_provider, - depends_on=[release], - ) + Creates cluster issuers required for cert-manager, ensuring dependencies on CRDs. - # 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", + Args: + cluster_issuer_name (str): The name of the cluster issuer. + namespace (str): The Kubernetes namespace. + release (k8s.helm.v3.Release): The Helm release resource. + crds (List[pulumi.Resource]): List of CRDs. + k8s_provider (k8s.Provider): Kubernetes provider. + + Returns: + Tuple containing: + - ClusterIssuer for the self-signed root. + - ClusterIssuer's CA certificate. + - Primary ClusterIssuer. + - The secret resource containing the CA certificate. + """ + try: + # SelfSigned Root Issuer + 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", }, - "privateKey": {"algorithm": "ECDSA", "size": 256}, - "renewBefore": "360h0m0s", - "secretName": "cluster-selfsigned-issuer-ca", + "spec": {"selfSigned": {}}, }, - }, - opts=pulumi.ResourceOptions( - parent=cluster_issuer_root, - custom_timeouts=pulumi.CustomTimeouts(create="5m", update="10m", delete="10m"), - ), - k8s_provider=k8s_provider, - depends_on=[cluster_issuer_root], - ) + opts=pulumi.ResourceOptions( + parent=release, + provider=k8s_provider, + depends_on=crds, + custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="5m"), + ), + ) - # 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, + # CA Certificate Issuer + 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": "RSA", "size": 2048}, + "renewBefore": "360h0m0s", + "secretName": "cluster-selfsigned-issuer-ca", + }, }, - "spec": { - "ca": {"secretName": "cluster-selfsigned-issuer-ca"}, + opts=pulumi.ResourceOptions( + parent=cluster_issuer_root, + provider=k8s_provider, + depends_on=[cluster_issuer_root], + custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="10m"), + ), + ) + + # Main Cluster Issuer + 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( - parent=cluster_issuer_ca_certificate, - custom_timeouts=pulumi.CustomTimeouts(create="4m", update="4m", delete="4m"), - ), - k8s_provider=k8s_provider, - depends_on=[cluster_issuer_ca_certificate], - ) + opts=pulumi.ResourceOptions( + parent=cluster_issuer_ca_certificate, + provider=k8s_provider, + depends_on=[cluster_issuer_ca_certificate], + custom_timeouts=pulumi.CustomTimeouts(create="5m", update="5m", delete="5m"), + ), + ) + + # Fetch CA Secret if not in dry-run + if not pulumi.runtime.is_dry_run(): + ca_secret = k8s.core.v1.Secret.get( + resource_name="cluster-selfsigned-issuer-ca", + id=f"{namespace}/cluster-selfsigned-issuer-ca", + opts=pulumi.ResourceOptions( + parent=cluster_issuer_ca_certificate, + provider=k8s_provider, + depends_on=[cluster_issuer_ca_certificate], + ) + ) + else: + ca_secret = None + + return cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer, ca_secret - return cluster_issuer_root, cluster_issuer_ca_certificate, cluster_issuer + except Exception as e: + pulumi.log.error(f"Error during the creation of cluster issuers: {str(e)}") + return None, None, None, None + + +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, + 'resources': { + 'limits': {'cpu': '500m', 'memory': '1024Mi'}, + 'requests': {'cpu': '250m', 'memory': '512Mi'}, + }, + } diff --git a/pulumi/modules/kubevirt/deploy.py b/pulumi/modules/kubevirt/deploy.py index e9a19ae..799ad9e 100644 --- a/pulumi/modules/kubevirt/deploy.py +++ b/pulumi/modules/kubevirt/deploy.py @@ -4,6 +4,7 @@ Deploys the KubeVirt module. """ +# Import necessary modules import requests import yaml import tempfile @@ -13,6 +14,7 @@ import pulumi import pulumi_kubernetes as k8s +from core.utils import wait_for_crds from core.metadata import get_global_labels, get_global_annotations from core.resource_helpers import ( create_namespace, @@ -25,29 +27,20 @@ def deploy_kubevirt_module( config_kubevirt: KubeVirtConfig, global_depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, - ) -> Tuple[Optional[str], Optional[pulumi.Resource]]: + ) -> Tuple[Optional[str], k8s.apiextensions.CustomResource]: """ Deploys the KubeVirt module and returns the version and the deployed resource. """ - # Create Namespace using the helper function - namespace_resource = create_namespace( - 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, + depends_on=global_depends_on, k8s_provider=k8s_provider, ) - # Update global dependencies - global_depends_on.append(kubevirt_resource) + # Update global dependencies if not None + if kubevirt_resource: + global_depends_on.append(kubevirt_resource) return kubevirt_version, kubevirt_resource @@ -55,10 +48,21 @@ def deploy_kubevirt( config_kubevirt: KubeVirtConfig, depends_on: List[pulumi.Resource], k8s_provider: k8s.Provider, - ) -> Tuple[str, pulumi.Resource]: + ) -> Tuple[str, Optional[pulumi.Resource]]: """ - Deploys KubeVirt operator and creates the KubeVirt CustomResource. + Deploys KubeVirt operator and creates the KubeVirt CustomResource, + ensuring that the CRD is available before creating the CustomResource. """ + # Create Namespace using the helper function + namespace_resource = create_namespace( + name=config_kubevirt.namespace, + k8s_provider=k8s_provider, + parent=k8s_provider, + depends_on=depends_on, + ) + + # Combine dependencies + depends_on = depends_on + [namespace_resource] namespace = config_kubevirt.namespace version = config_kubevirt.version use_emulation = config_kubevirt.use_emulation @@ -79,12 +83,14 @@ def deploy_kubevirt( yaml.dump_all(transformed_yaml, temp_file) temp_file_path = temp_file.name + operator = None try: # Deploy KubeVirt operator using the helper function operator = create_config_file( name='kubevirt-operator', file=temp_file_path, opts=pulumi.ResourceOptions( + parent=namespace_resource, custom_timeouts=pulumi.CustomTimeouts( create="10m", update="5m", @@ -97,10 +103,18 @@ def deploy_kubevirt( finally: os.unlink(temp_file_path) - if use_emulation: - pulumi.log.info("KVM Emulation enabled for KubeVirt.") + # Wait for the CRDs to be registered + crds = wait_for_crds( + crd_names=[ + "kubevirts.kubevirt.io", + # Add other required CRD names here if needed + ], + k8s_provider=k8s_provider, + depends_on=depends_on, + parent=operator + ) - # Create KubeVirt CustomResource using the helper function + # Create the KubeVirt resource always kubevirt_resource = create_custom_resource( name="kubevirt", args={ @@ -131,18 +145,20 @@ def deploy_kubevirt( }, }, opts=pulumi.ResourceOptions( + provider=k8s_provider, + parent=operator, + depends_on=depends_on + crds, custom_timeouts=pulumi.CustomTimeouts( create="5m", update="5m", delete="5m", ), ), - k8s_provider=k8s_provider, - depends_on=[operator], ) return version, kubevirt_resource + def get_latest_kubevirt_version() -> str: """ Retrieves the latest stable version of KubeVirt. diff --git a/pulumi/mypy.ini b/pulumi/mypy.ini new file mode 100644 index 0000000..d0a1d65 --- /dev/null +++ b/pulumi/mypy.ini @@ -0,0 +1,7 @@ +[mypy] +python_version = 3.9 +disallow_untyped_calls = True +disallow_untyped_defs = True +check_untyped_defs = True +warn_unused_ignores = True +