Source code for runway.core.components._module_type

"""Abstraction for the module 'type' value in a a Runway configuration."""
from __future__ import annotations

import logging
import os
import sys
from pathlib import Path
from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type, cast

from typing_extensions import Literal

from ...utils import load_object_from_string

if TYPE_CHECKING:
    from ...config.models.runway import RunwayModuleTypeTypeDef
    from ...module.base import RunwayModule

LOGGER = logging.getLogger(__name__)


RunwayModuleTypeExtensionsTypeDef = Literal["cdk", "cfn", "k8s", "sls", "tf", "web"]


[docs]class RunwayModuleType: """Runway configuration ``type`` settings object. The ``type`` property of a Runway configuration can be used to explicitly specify what module type you are intending to deploy. Runway determines the type of module you are trying to deploy in 3 different ways. First, it will check for the ``type`` property as described here, next it will look for a suffix as described in :ref:`Module Definition<runway-module>`, and finally it will attempt to autodetect your module type by scanning the files of the project. If none of those settings produces a valid result an error will occur. The following are valid explicit types: +--------------------+-----------------------------------------------+ | Type | IaC Tool/Framework | +====================+===============================================+ | ``cdk`` | AWS CDK | +--------------------+-----------------------------------------------+ | ``cloudformation`` | CloudFormation | +--------------------+-----------------------------------------------+ | ``serverless`` | Serverless Framework | +--------------------+-----------------------------------------------+ | ``terraform`` | Terraform | +--------------------+-----------------------------------------------+ | ``kubernetes`` | Kubernetes | +--------------------+-----------------------------------------------+ | ``static`` | :ref:`Static Site<mod-staticsite>` | +--------------------+-----------------------------------------------+ Even when specifying a module ``type`` the module structure needs to be conducive with that type of project. If the files contained within don't match the type then an error will occur. """ EXTENSION_MAP: ClassVar[Dict[str, str]] = { "cdk": "runway.module.cdk.CloudDevelopmentKit", "cfn": "runway.module.cloudformation.CloudFormation", "k8s": "runway.module.k8s.K8s", "sls": "runway.module.serverless.Serverless", "tf": "runway.module.terraform.Terraform", "web": "runway.module.staticsite.handler.StaticSite", } TYPE_MAP: ClassVar[Dict[str, str]] = { "cdk": EXTENSION_MAP["cdk"], "cloudformation": EXTENSION_MAP["cfn"], "kubernetes": EXTENSION_MAP["k8s"], "serverless": EXTENSION_MAP["sls"], "static": EXTENSION_MAP["web"], "terraform": EXTENSION_MAP["tf"], }
[docs] def __init__( self, path: Path, class_path: Optional[str] = None, type_str: Optional[RunwayModuleTypeTypeDef] = None, ) -> None: """Instantiate class. Keyword Args: path: The required path to the module class_path: A supplied class_path to override the autodetected one. type_str: An explicit type to assign to the RunwayModuleType """ self.path = path self.class_path = class_path self.type_str = type_str self.module_class = self._determine_module_class()
def _determine_module_class(self) -> Type[RunwayModule]: """Determine type of module and return deployment module class. Returns: object: The specified module class """ if self.class_path: LOGGER.debug( 'module class "%s" determined from explicit definition', self.class_path, ) if not self.class_path and self.type_str: self.class_path = self.TYPE_MAP.get(self.type_str, None) if self.class_path: LOGGER.debug( 'module class "%s" determined from explicit type', self.class_path ) if not self.class_path: self._set_class_path_based_on_extension() if self.class_path: LOGGER.debug( 'module class "%s" determined from path extension', self.class_path ) if not self.class_path: self._set_class_path_based_on_autodetection() if not self.class_path: LOGGER.error( 'module class could not be determined from path "%s"', os.path.basename(self.path), ) sys.exit(1) return cast(Type["RunwayModule"], load_object_from_string(self.class_path)) def _set_class_path_based_on_extension(self) -> None: """Based on the directory suffix set the class_path.""" basename = os.path.basename(self.path) basename_split = basename.split(".") extension = basename_split[len(basename_split) - 1] self.class_path = self.EXTENSION_MAP.get(extension, None) if self.class_path: LOGGER.debug( 'module class "%s" determined from path extension "%s"', self.class_path, extension, ) def _set_class_path_based_on_autodetection(self) -> None: """Based on the files detected in the base path set the class_path.""" if ( any( (self.path / sls).is_file() for sls in ["serverless.js", "serverless.ts", "serverless.yml"] ) and (self.path / "package.json").is_file() ): self.class_path = self.TYPE_MAP.get("serverless", None) elif next(self.path.glob("*.tf"), None): self.class_path = self.TYPE_MAP.get("terraform", None) elif (self.path / "cdk.json").is_file() and ( self.path / "package.json" ).is_file(): self.class_path = self.TYPE_MAP.get("cdk", None) elif (self.path / "overlays").is_dir() and self._find_kustomize_files(): self.class_path = self.TYPE_MAP.get("kubernetes", None) elif ( next(self.path.glob("*.env"), None) or next(self.path.glob("*.yaml"), None) or next(self.path.glob("*.yml"), None) ): self.class_path = self.TYPE_MAP.get("cloudformation", None) if self.class_path: LOGGER.debug( 'module class "%s" determined from autodetection', self.class_path ) def _find_kustomize_files(self) -> bool: """Return true if kustomize yaml file found. Returns: boolean: Whether the kustomize yaml exist """ for _root, _dirnames, filenames in os.walk(self.path): for filename in filenames: if filename == "kustomization.yaml": return True return False