Source code for ironik.config_file_handler.deploy_template

"""
Definition and functions for spawning deployment templates.

:author: Jonathan Decker
"""

import logging
from dataclasses import dataclass, field
from pathlib import Path

import yaml
from rich import print

from ironik.util.exceptions import IronikFatalError

logger = logging.getLogger("logger")


[docs]@dataclass class RancherCredentials(yaml.YAMLObject): """ Dataclass for Rancher credentials that can be parsed in YAML. """ rancher_access_key: str rancher_secret_key: str yaml_tag = "!RancherCredentials" yaml_loader = yaml.SafeLoader
[docs]@dataclass class OpenStackCredentials(yaml.YAMLObject): """ Dataclass for OpenStack credentials that can be parsed in YAML. """ username: str password: str project_id: str yaml_tag = "!OpenStackCredentials" yaml_loader = yaml.SafeLoader
[docs]@dataclass class RancherConfig(yaml.YAMLObject): """ Dataclass for Rancher config that can be parsed in YAML. """ rancher_api_base_url: str ssh_user: str new_cluster_admin_user_name: str new_cluster_admin_user_password: str = "" engine_install_url: str = "https://releases.rancher.com/install-docker/20.10.sh" yaml_tag = "!RancherConfig" yaml_loader = yaml.SafeLoader
[docs]@dataclass class OpenStackConfig(yaml.YAMLObject): """ Dataclass for OpenStack configuration that can be parsed in YAML. """ openstack_auth_url: str user_domain_name: str project_domain_name: str lb_provider: str default_flavor_name: str default_image_name: str remote_ip_prefix: str private_network_id: str region_name: str = "RegionOne" use_octavia: bool = False security_group_name: str = "ironik-k8s-node" volume_size: int = 20 #volume_type: str = "ssd" yaml_tag = "!OpenStackConfig" yaml_loader = yaml.SafeLoader
[docs]@dataclass class NetworkConfig(yaml.YAMLObject): """ Dataclass for network configuration that can be parsed in YAML. """ required_tcp_ports: list[int] = field(default_factory=list) required_udp_ports: list[int] = field(default_factory=list) worker_port_range_min: int = 30000 worker_port_range_max: int = 32767 yaml_tag = "!NetworkConfig" yaml_loader = yaml.SafeLoader
[docs]@dataclass class KubernetesConfig(yaml.YAMLObject): """ Dataclass for Kubernetes configuration that can be parsed in YAML. """ master_node_roles: str = "master, etcd, worker" worker_node_roles: str = "worker" version: str = "v1.24.8-rancher1-1" number_master_nodes: int = 1 number_worker_nodes: int = 1 yaml_tag = "!KubernetesConfig" yaml_loader = yaml.SafeLoader
[docs]@dataclass class DeploymentOptions(yaml.YAMLObject): """ Dataclass for deployment options that can be parsed in YAML. """ deploy_example_workload: bool = False example_workload_image: str = "rancher/hello-world" example_workload_name: str = "hello-world-example" deploy_nginx_workload: bool = False nginx_ingress_version: str = "4.4.0" nginx_ingress_repo: str = "https://kubernetes.github.io/ingress-nginx" nginx_ingress_app_name: str = "nginx-ingress-lb" install_cinder_driver: bool = False deploy_example_volume: bool = False cleanup_example_workload: bool = True cleanup_nginx_ingress: bool = True cleanup_example_volume: bool = True yaml_tag = "!DeploymentOptions" yaml_loader = yaml.SafeLoader
[docs]@dataclass class DeployConfig(yaml.YAMLObject): """ Dataclass for holding together all other data classes that can be parsed in YAML. """ rancher_credentials: RancherCredentials = RancherCredentials("", "") openstack_credentials: OpenStackCredentials = OpenStackCredentials("", "", "") rancher_config: RancherConfig = RancherConfig("", "", "", "") openstack_config: OpenStackConfig = OpenStackConfig("", "", "", "", "", "", "", "", "") network_config: NetworkConfig = NetworkConfig( [22, 80, 443, 2376, 2379, 2380, 6443, 8443, 8472, 9913, 10250, 10254], [8443, 8472] ) kubernetes_config: KubernetesConfig = KubernetesConfig() deployment_options: DeploymentOptions = DeploymentOptions() yaml_tag = "!DeployConfig" yaml_loader = yaml.SafeLoader
[docs]def write_deploy_template(path: Path, overwrite: bool) -> None: """ :param path: :param overwrite: :return: """ logger.debug("Preparing to write deploy template.") exists = path.exists() if exists: if path.is_dir(): logger.debug("Path is dir, adding 'ironik_template.yaml' as file name.") path = path / "ironik_template.yaml" exists = path.exists() if exists: logger.info(f"File {path} already exists.") print(f"File {path} already exists.") if overwrite: logger.info("Replacing existing file with template.") print("Replacing existing file with template.") else: logger.info("Overwrite is not enabled, cannot proceed, stopping.") print("Overwrite is not enabled, cannot proceed, stopping.") return template = DeployConfig() logger.debug(f"Opening {path}...") with open(path, "w+", encoding="utf-8") as file: logger.debug("Attempting to dump template into file...") yaml.dump(template, file) logger.debug("Closed file.") logger.info(f"Template has been written to {path.absolute()}") print(f"Template has been written to {path.absolute()}")
# TODO: complete this
[docs]def get_template_schema() -> dict: schema = { 'deployment_options': {'type': 'string', 'anyof': [{"!DeploymentOptions"}]} } return schema
[docs]def load_deploy_template(path: Path) -> DeployConfig: """ :param path: :return: """ exists = path.exists() if not exists: logger.info(f"Given path does not exist: {path.absolute()}") print(f"Given path does not exist: {path.absolute()}") raise IronikFatalError(f"Given path does not exist: {path.absolute()}") is_file = path.is_file() if not is_file: logger.info(f"Given path is not a file: {path.absolute()}") print(f"Given path is not a file: {path.absolute()}") raise IronikFatalError(f"Given path is not a file: {path.absolute()}") logger.debug(f"Attempting to open {path.absolute()}") with open(path, "r", encoding="utf-8") as file: logger.debug("Attempting to parse file as yaml.") try: deploy_config = yaml.safe_load(file) except yaml.YAMLError as exception: logger.info(f"Parsing of yaml file failed with error: {exception}") print("Parsing of template failed with the following error:") print(exception) raise IronikFatalError(f"Parsing of yaml file failed with error: {exception}") from exception if not isinstance(deploy_config, DeployConfig): logger.info("Could not parse yaml file as Deployment Configuration. Make sure to use the template.") print("Could not parse yaml file as Deployment Configuration. Make sure to use the template.") logger.debug(f"Type of loaded yaml should be DeployConfig but is {type(DeployConfig)}") raise IronikFatalError( "Could not parse yaml file as Deployment Configuration. Make sure to use the " "template." ) # TODO: Instead of printing this should validate the config by checking if all attrs are not None and len > 0 print("Loaded template.") # print(deploy_config) # TODO: Validate that their is at least one master and one etcd and one worker return deploy_config
[docs]def validate_deploy_template(): pass