From 37baf7e41078b407614705d761c482d2665f887d Mon Sep 17 00:00:00 2001 From: Alan Bridgeman Date: Tue, 30 Dec 2025 19:01:55 -0600 Subject: [PATCH] Pretty massive overhaul so that generated charts follow better current iterations (including making use of subcharts etc...) --- create-helm-chart.py | 608 +++++++++++++-- input.example.json | 27 +- src/Cache.py | 2 +- src/Database.py | 166 ++-- src/Dependency.py | 7 + src/Deployment.py | 322 +++++--- src/HashicorpVault.py | 24 +- src/HelmChart.py | 1294 ++++++++++++++++++++++++------- src/LoggingSidecar.py | 9 + src/MongoDB.py | 204 ++--- src/NoSQL.py | 14 +- src/OAuth.py | 41 +- src/Redis.py | 141 ++-- src/Service.py | 2 +- src/ThirdPartyService.py | 16 +- test/.gitignore | 1 + test/non-input-json/random.json | 97 +++ 17 files changed, 2240 insertions(+), 735 deletions(-) create mode 100644 src/Dependency.py create mode 100644 src/LoggingSidecar.py create mode 100644 test/.gitignore create mode 100644 test/non-input-json/random.json diff --git a/create-helm-chart.py b/create-helm-chart.py index e15037b..d998575 100644 --- a/create-helm-chart.py +++ b/create-helm-chart.py @@ -1,151 +1,621 @@ -import json +import os, sys, json from src.Ingress import Ingress from src.Service import Service +from src.Dependency import Dependency from src.Database import Database from src.HashicorpVault import HashicorpVault from src.MongoDB import MongoDB +from src.AzureTableStorage import AzureTableStorage from src.Redis import Redis +from src.LoggingSidecar import LoggingSidecar from src.OAuth import OAuth from src.ThirdPartyService import ThirdPartyService from src.Deployment import Deployment from src.HelmChart import HelmChart -if __name__ == '__main__': - with open('input.json', 'r') as f: - data = json.load(f) +def print_help(): + """Print help message.""" + + print('Usage: python create-helm-chart.py [input_file]') + print() + print('This script generates a Helm chart based on an input JSON file (default - input.json).') + +def parse_args(args: list[str]) -> dict[str, str]: + """Parse command line arguments. + + Args: + args (list[str]): List of command line arguments. + + Returns: + dict[str, str]: Dictionary of results from parsing. + """ + + input_file = 'input.json' + + # Check if any arguments were provided + if len(args) > 1: + # Verify the correct number of arguments were provided + if len(args) == 2: + if args[1] == '--help': + # if the user requested help, display it and exit + print_help() + + exit(0) + else: + # If a single argument was provided, assume it's the input file + input_file = args[1] + else: + print(f'Invalid number of arguments: {len(sys.argv)}') + print() + print_help() + + exit(1) + + return { 'input_file': input_file } + +def get_chart_info(data: dict[str, any]) -> dict[str, any]: + """Extract Helm chart information from input JSON data. + + Note, this function also performs validation on the input data to ensure required fields are present. + And it also provides default values as needed. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + dict[str, any]: Extracted Helm chart information. + """ + + # Verify top level 'chart' field exists + if 'chart' not in data: + raise Exception('No chart information found in input JSON file. But chart information is required to create a Helm chart.') + + # --- Required Fields --- + + # Verify 'name' field exists + if 'name' not in data['chart']: + raise Exception('No chart name found in input JSON file. But chart name is required to create a Helm chart.') - # The API version of the Helm chart itself - api_version = data['chart']['apiVersion'] - # The version of the application that the Helm chart is deploying - app_version = data['chart']['appVersion'] - # A description of the Helm chart - chart_description = data['chart']['description'] - # The URL of the Helm chart's home page - chart_homepage = data['chart']['homepage'] - # The maintainers of the Helm chart - maintainers = data['chart']['maintainers'] # The name of the Helm chart chart_name = data['chart']['name'] - # The sources of the Helm chart - sources = data['chart']['sources'] - # The version of the Helm chart - chart_version = data['chart']['version'] + # --- Required Fields with Defaults --- + + # The API version of the Helm chart itself (defaults to `v2` if not specified) + # `v2` is the default because we make heavy use of dependencies, which are not supported (or supported as well) in `v1` charts + api_version = 'v2' + if 'apiVersion' in data['chart']: + api_version = data['chart']['apiVersion'] + + # The version of the Helm chart (defaults to `1.0.0` if not specified) + # `1.0.0` is the default because it's a reasonable starting version for a new chart + chart_version = '1.0.0' + if 'version' in data['chart']: + chart_version = data['chart']['version'] + + # --- Optional Fields --- + + # The version of the application that the Helm chart is deploying + # Note, can be unset (variable being `None`) + app_version = None + if 'appVersion' in data['chart']: + app_version = data['chart']['appVersion'] + + # A description of the Helm chart + # Note, can be unset (variable being `None`) + chart_description = None + if 'description' in data['chart']: + chart_description = data['chart']['description'] + + # The URL of the Helm chart's home page + # Note, can be unset (variable being `None`) + chart_homepage = None + if 'homepage' in data['chart']: + chart_homepage = data['chart']['homepage'] + + # The maintainers of the Helm chart + # Note, can be unset (variable being `None`) + maintainers = None + if 'maintainers' in data['chart']: + maintainers = data['chart']['maintainers'] + + # The sources of the Helm chart + sources = None + if 'sources' in data['chart']: + sources = data['chart']['sources'] + + return { + 'api_version': api_version, + 'app_version': app_version, + 'chart_description': chart_description, + 'chart_homepage': chart_homepage, + 'maintainers': maintainers, + 'chart_name': chart_name, + 'sources': sources, + 'chart_version': chart_version + } + +def get_image_info(data: dict[str, any]) -> dict[str, str]: + """Extract image information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + dict[str, str]: Extracted image information. + """ + + # Verify top level 'image' field exists + if 'image' not in data: + raise Exception('No image information found in input JSON file. But image information is required to create a Helm chart.') + + # Verify 'repository' field exists + if 'repository' not in data['image']: + raise Exception('No image repository found in input JSON file. But image repository is required to create a Helm chart.') + + # Note, the 'repository' can be in a few different forms: + # 1. Just the registry/repository itself, specifying `name` separately + # 2. As an encompassing value, representing some combination of `/` + # + # Note, shouldn't use `:` and specify `name` separately as it causes incorrect output and is confusing. + # But `/` and `` separately is perfectly valid. + # + # This multi-format nature is why it and only it is required, while `name` and `tag` are optional. + # image_repository = data['image']['repository'] - image_pull_policy = data['image']['pullPolicy'] + + # Append 'name' field to repository if it exists + if 'name' in data['image']: + image_repository += '/' + data['image']['name'] + + if 'tag' not in data['image']: + raise Exception('No image tag found in input JSON file. But image tag is required to create a Helm chart.') + + image_tag = data['image']['tag'] + + # Append 'tag' field to repository if it exists + #if 'tag' in data['image']: + # image_repository += ':' + data['image']['tag'] + + # The image pull policy (defaults to `IfNotPresent` if not specified) + # `IfNotPresent` is the default because it's a reasonable default for most use cases + image_pull_policy = 'IfNotPresent' + if 'pullPolicy' in data['image']: + image_pull_policy = data['image']['pullPolicy'] + + return { + 'image_repository': image_repository, + 'image_tag': image_tag, + 'image_pull_policy': image_pull_policy + } + +def get_ingress_info(data: dict[str, any]) -> dict[str, str]: + """Extract ingress information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + dict[str, str]: Extracted ingress information. + """ + + # Verify top level 'ingress' field exists + if 'ingress' not in data: + raise Exception('No ingress information found in input JSON file. But ingress information is required to create a Helm chart.') + + # Verify 'hostname' field exists + if 'hostname' not in data['ingress']: + raise Exception('No ingress hostname found in input JSON file. But ingress hostname is required to create a Helm chart.') hostname = data['ingress']['hostname'] - ingress = Ingress(hostname) - service = Service() + return { + 'hostname': hostname + } - templates = [ingress, service] +def get_db_info(data: dict[str, any]) -> tuple[bool, dict[str, Database | Dependency | None]]: + """Extract database information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, dict[str, Database | Dependency]]: A tuple where the first element indicates whether a database is used, + and the second element is a dictionary containing the database object and the dependency object. + """ uses_db = False - uses_secrets_vault = False - nosql = None - uses_cache = False - third_party_services = [] - extra_env_vars = {} + db = None + db_dependency = None if 'db' in data and data['db'] != False: - db_name = data['db']['name'] - db_host = data['db']['host'] - db_user = data['db']['user'] - db_password = data['db']['password'] + if 'name' not in data['db']: + raise Exception('No database name found in input JSON file. But database name is required to create the Helm chart with database support.') - db = Database(db_name, db_host, db_user, db_password) + db_name = data['db']['name'] + + if 'host' not in data['db']: + raise Exception('No database host found in input JSON file. But database host is required to create the Helm chart with database support.') + + db_host = data['db']['host'] + + if 'user' not in data['db']: + raise Exception('No database user found in input JSON file. But database user is required to create the Helm chart with database support.') + + db_user = data['db']['user'] + + if 'password' not in data['db']: + raise Exception('No database password found in input JSON file. But database password is required to create the Helm chart with database support.') + + db_password = data['db']['password'] uses_db = True - templates.append(db) + db = Database(db_name, db_host, db_user, db_password) + db_dependency = Dependency('db-deploy', '1.0.2', 'https://helm.bridgemanaccessible.ca/', 'database', 'database.enabled') + return uses_db, { 'db': db, 'db_dependency': db_dependency } + +def get_vault_info(data: dict[str, any]) -> tuple[bool, dict[str, HashicorpVault | Dependency | None]]: + """Extract HashiCorp Vault information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, dict[str, HashicorpVault | Dependency]]: A tuple where the first element indicates whether Vault is used, + and the second element is a dictionary containing the Vault object and the dependency object. + """ + + uses_vault = False + vault = None + vault_dependency = None + if 'vault' in data and data['vault'] != False: + if 'image' not in data['vault']: + raise Exception('No Vault image information found in input JSON file. But Vault image information is required to create the Helm chart with Vault support.') + + if 'repository' not in data['vault']['image'] or 'tag' not in data['vault']['image']: + raise Exception('Incomplete Vault image information found in input JSON file. Both repository and tag are required to create the Helm chart with Vault support.') + vault_image = { 'repository': data['vault']['image']['repository'], 'tag': data['vault']['image']['tag'] } + + if 'hostname' not in data['vault']: + raise Exception('No Vault hostname found in input JSON file. But Vault hostname is required to create the Helm chart with Vault support.') + vault_hostname = data['vault']['hostname'] - vault_storage_class = data['vault']['storageClass'] + #vault_storage_class = data['vault']['storageClass'] + + if 'policyCapabilities' not in data['vault']: + raise Exception('No Vault policy capabilities found in input JSON file. But Vault policy capabilities are required to create the Helm chart with Vault support.') - vault = HashicorpVault(image=vault_image, hostname=vault_hostname, storage_class=vault_storage_class) + vault_policy_capabilities = data['vault']['policyCapabilities'] + + uses_vault = True - uses_secrets_vault = True - - templates.append(vault) + vault = HashicorpVault(image=vault_image, hostname=vault_hostname, policy_capabilities=vault_policy_capabilities) + vault_dependency = Dependency('ba-custom-hashicorp-vault', '1.0.6', 'https://helm.bridgemanaccessible.ca/', 'vault', 'vault.enabled') + return uses_vault, { 'vault': vault, 'vault_dependency': vault_dependency } + +def get_nosql_info(data: dict[str, any]) -> tuple[bool, dict[str, MongoDB | AzureTableStorage | Dependency | None]]: + """Extract NoSQL information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, Database | None]: A tuple where the first element indicates whether a NoSQL database is used, + and the second element is the NoSQL database object if used, otherwise None. + """ + + nosql = None + nosql_dependency = None + if 'nosql' in data and data['nosql'] != False: + # Default to MongoDB + nosql_type = 'mongodb' + if 'type' in data['nosql']: + nosql_type = data['nosql']['type'] + + if 'dbName' not in data['nosql']: + raise Exception('No NoSQL database name found in input JSON file. But NoSQL database name is required to create the Helm chart with NoSQL database support.') + nosql_db_name = data['nosql']['dbName'] - nosql_user = data['nosql']['user'] - nosql_password = data['nosql']['password'] - tables = data['nosql']['tables'] + if 'groupings' not in data['nosql']: + raise Exception('No NoSQL groupings found in input JSON file. But NoSQL groupings are required to create the Helm chart with NoSQL database support.') - mongo = MongoDB(nosql_db_name, nosql_user, nosql_password, tables) + groupings = data['nosql']['groupings'] - nosql = mongo + if nosql_type == 'mongodb': + if 'user' not in data['nosql']: + raise Exception('No NoSQL database user found in input JSON file. But NoSQL database user is required to create the Helm chart with NoSQL (MongoDB) database support.') + + nosql_user = data['nosql']['user'] + + if 'password' not in data['nosql']: + raise Exception('No NoSQL database password found in input JSON file. But NoSQL database password is required to create the Helm chart with NoSQL (MongoDB) database support.') - templates.append(mongo) + nosql_password = data['nosql']['password'] + + mongo = MongoDB(nosql_db_name, nosql_user, nosql_password, groupings) + + nosql = mongo + elif nosql_type == 'az_table_storage': + nosql_key = data['nosql']['key'] + + az_table_storage = AzureTableStorage(nosql_db_name, nosql_key, groupings) + + nosql = az_table_storage + + nosql_dependency = Dependency('nosql-deploy', '1.0.5', 'https://helm.bridgemanaccessible.ca/', 'nosql', 'nosql.enabled') + + return nosql is not None, { 'nosql': nosql, 'nosql_dependency': nosql_dependency } + +def get_cache_info(data: dict[str, any]) -> tuple[bool, dict[str, Redis | Dependency | None]]: + """Extract cache information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, dict[str, Redis | Dependency]]: A tuple where the first element indicates whether a cache is used, + and the second element is a dictionary containing the cache object and the dependency object. + """ + + uses_cache = False + cache = None + cache_dependency = None if 'cache' in data and data['cache'] != False: - cache_password = data['cache']['password'] + if 'password' not in data['cache']: + raise Exception('No cache password found in input JSON file. But cache password is required to create the Helm chart with cache support.') - redis = Redis(cache_password) + cache_password = data['cache']['password'] uses_cache = True - templates.append(redis) + cache = Redis(cache_password) + cache_dependency = Dependency('cache-deploy', '1.0.7', 'https://helm.bridgemanaccessible.ca/', 'cache', 'cache.enabled') + return uses_cache, { 'cache': cache, 'cache_dependency': cache_dependency } + +def get_oauth_info(data: dict[str, any]) -> tuple[bool, dict[str, OAuth | None]]: + """Extract OAuth information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, dict[str, OAuth | None]]: A tuple where the first element indicates whether OAuth is used, + and the second element is a dictionary containing the OAuth object if used, otherwise None. + """ + + uses_oauth = False + oauth = None + if 'oauth' in data and data['oauth'] != False: + if 'baseAppUrl' not in data['oauth']: + raise Exception('No OAuth base application URL found in input JSON file. But OAuth base application URL is required to create the Helm chart with OAuth support.') + base_app_url = data['oauth']['baseAppUrl'] + + if 'appAbbreviation' not in data['oauth']: + raise Exception('No OAuth application abbreviation found in input JSON file. But OAuth application abbreviation is required to create the Helm chart with OAuth support.') + app_abbreviation = data['oauth']['appAbbreviation'] + + if 'appName' not in data['oauth']: + raise Exception('No OAuth application name found in input JSON file. But OAuth application name is required to create the Helm chart with OAuth support.') + app_name = data['oauth']['appName'] + + if 'serviceName' not in data['oauth']: + raise Exception('No OAuth service name found in input JSON file. But OAuth service name is required to create the Helm chart with OAuth support.') + service_name = data['oauth']['serviceName'] + + if 'devPort' not in data['oauth']: + raise Exception('No OAuth development port found in input JSON file. But OAuth development port is required to create the Helm chart with OAuth support.') + dev_port = data['oauth']['devPort'] - oauth = OAuth(base_app_url, app_abbreviation, app_name, service_name, dev_port) + if 'appRegContactEmail' not in data['oauth']: + raise Exception('No OAuth application registration contact email found in input JSON file. But OAuth application registration contact email is required to create the Helm chart with OAuth support.') - templates.append(oauth) + app_reg_contact_email = data['oauth']['appRegContactEmail'] + + uses_oauth = True + + oauth = OAuth(base_app_url, app_abbreviation, app_name, service_name, dev_port, app_reg_contact_email) + + return uses_oauth, { 'oauth': oauth } + +def get_logging_info(data: dict[str, any]) -> tuple[bool, dict[str, LoggingSidecar | Dependency | None]]: + """Extract logging information from input JSON data. + Args: + data (dict[str, any]): Input JSON data. + + Returns: + tuple[bool, dict[str, LoggingSidecar | Dependency | None]]: A tuple where the first element indicates whether logging is used, + and the second element is a dictionary containing the Logging object if used, otherwise None. + """ + + uses_logging = False + logging = None + logging_dependency = None + + if 'logging' in data and data['logging'] != False: + uses_logging = True + + log_aggregator_username = data['logging']['username'] + log_aggregator_password = data['logging']['password'] + + logging = LoggingSidecar(log_aggregator_username, log_aggregator_password) + + logging_dependency = Dependency('ba-logging-sidecar', '1.0.2', 'https://helm.bridgemanaccessible.ca/', 'loggingSidecar', 'loggingSidecar.enabled') + + return uses_logging, { 'logging': logging, 'logging_dependency': logging_dependency } + +def get_third_party_services_info(data: dict[str, any]) -> list[ThirdPartyService]: + """Extract third-party services information from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + list[ThirdPartyService]: Extracted third-party services. + """ + + third_party_services = [] + if 'thirdPartyServices' in data: - if 'openai' in data['thirdPartyServices']: - openai_api_key = data['thirdPartyServices']['openai']['apiKey'] + for service_name in data['thirdPartyServices'].keys(): + service_vars = {} - openai = ThirdPartyService('openai', False, api_key=openai_api_key) + for key in data['thirdPartyServices'][service_name].keys(): + # If the key is already in snake_case, skip it + if '_' not in key: + # Convert from camelCase to snake_case + new_key = '' + for i, c in enumerate(key): + if c.isupper() and i != 0: + new_key += '_' + c.lower() + else: + new_key += c.lower() + + # Add the snake_case key to the dictionary with the value of the camelCase key + service_vars[new_key] = data['thirdPartyServices'][service_name][key] - third_party_services.append(openai) + service = ThirdPartyService(service_name, True, **service_vars) #merchant_id=moneris_merchant_id, store_id=moneris_store_id, ht_profile_id=moneris_ht_profile_id, test_merchant_id=moneris_test_merchant_id, test_store_id=moneris_test_store_id, test_ht_profile_id=moneris_test_ht_profile_id) - templates.append(openai) - - if 'stripe' in data['thirdPartyServices']: - stripe_public_key = data['thirdPartyServices']['stripe']['publicKey'] - stripe_secret_key = data['thirdPartyServices']['stripe']['secretKey'] - stripe_test_public_key = data['thirdPartyServices']['stripe']['testPublicKey'] - stripe_test_secret_key = data['thirdPartyServices']['stripe']['testSecretKey'] + third_party_services.append(service) + + return third_party_services - stripe = ThirdPartyService('stripe', True, public_key=stripe_public_key, secret_key=stripe_secret_key, test_public_key=stripe_test_public_key, test_secret_key=stripe_test_secret_key) +def get_extra_env_vars(data: dict[str, any]) -> dict[str, str]: + """Extract extra environment variables from input JSON data. + + Args: + data (dict[str, any]): Input JSON data. + + Returns: + dict[str, str]: Extracted extra environment variables. + """ - third_party_services.append(stripe) - - templates.append(stripe) + extra_env_vars = {} if 'extraEnvVars' in data: extra_env_vars = data['extraEnvVars'] for key, value in extra_env_vars.items(): if not isinstance(value, dict) and value.find("'") != -1: extra_env_vars[key] = value.replace("'", '"') - - deployment = Deployment(image_repository, image_pull_policy=image_pull_policy, uses_db=uses_db, uses_secrets_vault=uses_secrets_vault, nosql=nosql, uses_cache=uses_cache, third_party_services=third_party_services, **extra_env_vars) + return extra_env_vars + +if __name__ == '__main__': + # Parse command line arguments + command_line_parsing_results = parse_args(sys.argv) + input_file = command_line_parsing_results['input_file'] + + # Verify the input file exists + if not os.path.exists(input_file): + print(f'{input_file} file not found. Please create/specify a valid {input_file} (should be based on the input.example.json file).') + exit(1) + + # Load the input JSON file + # Note, we ASSUME the JSON file is valid JSON (we have no error handling for invalid JSON here) + with open(input_file, 'r') as f: + data = json.load(f) + + # Extract Helm chart information from input JSON data + chart_data = get_chart_info(data) + # Extract image information from input JSON data + image_data = get_image_info(data) + # Extract ingress information from input JSON data + ingress_data = get_ingress_info(data) + + ingress = Ingress(ingress_data['hostname']) + service = Service() + + templates = [ingress, service] + + dependencies = [] + + uses_db, db_objs = get_db_info(data) + uses_secrets_vault, vault_objs = get_vault_info(data) + uses_nosql, nosql_objs = get_nosql_info(data) + uses_cache, cache_objs = get_cache_info(data) + uses_oauth, oauth_objs = get_oauth_info(data) + uses_logging, logging_objs = get_logging_info(data) + third_party_services = get_third_party_services_info(data) + extra_env_vars = get_extra_env_vars(data) + + if uses_db: + templates.append(db_objs['db']) + dependencies.append(db_objs['db_dependency']) + + if uses_secrets_vault: + templates.append(vault_objs['vault']) + dependencies.append(vault_objs['vault_dependency']) + + if uses_nosql: + templates.append(nosql_objs['nosql']) + dependencies.append(nosql_objs['nosql_dependency']) + + if uses_cache: + templates.append(cache_objs['cache']) + dependencies.append(cache_objs['cache_dependency']) + + if uses_oauth: + templates.append(oauth_objs['oauth']) + + if uses_logging: + templates.append(logging_objs['logging']) + dependencies.append(logging_objs['logging_dependency']) + + if len(third_party_services) > 0: + for third_party_service in third_party_services: + templates.append(third_party_service) + + deployment = Deployment( + image_data['image_repository'], + image_data['image_tag'], + port=data['port'] if 'port' in data else 8080, + image_pull_policy=image_data['image_pull_policy'], + uses_oauth=uses_oauth, + uses_db=uses_db, + uses_secrets_vault=uses_secrets_vault, + nosql=nosql_objs['nosql'], + uses_cache=uses_cache, + third_party_services=third_party_services, + **extra_env_vars + ) + templates.append(deployment) - - #templates = [ingress, service, db, vault, mongo, redis, oauth, deployment, stripe, openai] - helmChart = HelmChart(chart_name, chart_description, maintainers, chart_homepage, sources, app_version, chart_version, api_version, *templates) + helmChart = HelmChart( + chart_data['chart_name'], + chart_data['chart_version'], + chart_data['api_version'], + chart_data['app_version'], + chart_data['chart_description'], + chart_data['maintainers'], + chart_data['chart_homepage'], + chart_data['sources'], + dependencies, + *templates + ) helmChart.create_templates_folder() helmChart.write_yaml() helmChart.write_values_yaml() + helmChart.write_filled_in_values_yaml() helmChart.write_helmignore() try: diff --git a/input.example.json b/input.example.json index 9958591..6b86c8f 100644 --- a/input.example.json +++ b/input.example.json @@ -1,6 +1,6 @@ { "chart": { - "apiVersion": "v1", + "apiVersion": "v2", "appVersion": "1.0.0", "description": "A Helm chart for deploying .", "homepage": "", @@ -35,13 +35,20 @@ "tag": "" }, "hostname": "", - "storageClass": "" + "storageClass": "", + "policyCapabilities": [ + "create", + "read", + "update", + "delete", + "list" + ] }, "nosql": { "dbName": "", "user": "", "password": "", - "tables": { + "groupings": { "": { "name": "
", "value": "" @@ -51,6 +58,10 @@ "cache": { "password": "" }, + "logging": { + "username": "", + "password": "" + }, "oauth": { "baseAppUrl": "", "appAbbreviation": "", @@ -61,14 +72,8 @@ "clientSecret": "" }, "thirdPartyServices": { - "openai": { - "apiKey": "" - }, - "stripe": { - "publicKey": "", - "secretKey": "", - "testPublicKey": "", - "testSecretKey": "" + "": { + "": "" } }, "extraEnvVars": { diff --git a/src/Cache.py b/src/Cache.py index dd0c3fc..512f12d 100644 --- a/src/Cache.py +++ b/src/Cache.py @@ -39,7 +39,7 @@ class Cache (Template): f.write(' ' + '{{- else }}' + '\n') f.write(' ' + 'hostname: {{ .Values.cache.hostname }}' + '\n') f.write(' ' + '{{- end }}' + '\n') - f.write(' ' + 'port: {{ .Values.cache.port }}' + '\n') + f.write(' ' + 'port: {{ .Values.cache.port | quote }}' + '\n') # Create the credentials secret file with open('templates/cache-credentials-secret.yaml', 'w') as f: diff --git a/src/Database.py b/src/Database.py index cbc7122..7e6b7f2 100644 --- a/src/Database.py +++ b/src/Database.py @@ -49,94 +49,100 @@ class Database (Template): self.instance_id = instance_id def write(self): + # =========================================================== + # DEPRECATED FILES - USING HELM DEPENDENCY (SUBCHART) INSTEAD + # =========================================================== + + pass + # Config Map file for use within the Postgres Controller namespace # This is required by the operator to function properly - with open('templates/db-credentials-config-map-postgres-controller.yaml', 'w') as f: - f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') - f.write('apiVersion: v1' + '\n') - f.write('kind: ConfigMap' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + 'namespace: postgres-controller' + '\n') - f.write('data:' + '\n') - f.write(' ' + 'db-host: {{ .Values.database.host }}' + '\n') - f.write(' ' + 'db-name: {{ .Values.database.name }}' + '\n') - f.write(' ' + 'db-user: {{ .Values.database.user }}' + '\n') - f.write(' ' + '{{- if .Values.database.port }}' + '\n') - f.write(' ' + 'db-port: {{ .Values.database.port | quote }}' + '\n') - f.write(' ' + '{{- else }}' + '\n') - f.write(' ' + 'db-port: "5432"' + '\n') - f.write(' ' + '{{- end }}' + '\n') - f.write('{{- end -}}' + '\n') + #with open('templates/db-credentials-config-map-postgres-controller.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') + # f.write('apiVersion: v1' + '\n') + # f.write('kind: ConfigMap' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') + # f.write(' ' + 'namespace: postgres-controller' + '\n') + # f.write('data:' + '\n') + # f.write(' ' + 'db-host: {{ .Values.database.host }}' + '\n') + # f.write(' ' + 'db-name: {{ .Values.database.name }}' + '\n') + # f.write(' ' + 'db-user: {{ .Values.database.user }}' + '\n') + # f.write(' ' + '{{- if .Values.database.port }}' + '\n') + # f.write(' ' + 'db-port: {{ .Values.database.port | quote }}' + '\n') + # f.write(' ' + '{{- else }}' + '\n') + # f.write(' ' + 'db-port: "5432"' + '\n') + # f.write(' ' + '{{- end }}' + '\n') + # f.write('{{- end -}}' + '\n') # Config Map file in the same namespace as the app - with open('templates/db-credentials-config-map.yaml', 'w') as f: - f.write('apiVersion: v1' + '\n') - f.write('kind: ConfigMap' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write('data:' + '\n') - f.write(' ' + 'db-host: {{ .Values.database.host }}' + '\n') - f.write(' ' + 'db-name: {{ .Values.database.name }}' + '\n') - f.write(' ' + 'db-user: {{ .Values.database.user }}' + '\n') - f.write(' ' + '{{- if .Values.database.port }}' + '\n') - f.write(' ' + 'db-port: {{ .Values.database.port | quote }}' + '\n') - f.write(' ' + '{{- else }}' + '\n') - f.write(' ' + 'db-port: "5432"' + '\n') - f.write(' ' + '{{- end }}' + '\n') + #with open('templates/db-credentials-config-map.yaml', 'w') as f: + # f.write('apiVersion: v1' + '\n') + # f.write('kind: ConfigMap' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') + # f.write('data:' + '\n') + # f.write(' ' + 'db-host: {{ .Values.database.host }}' + '\n') + # f.write(' ' + 'db-name: {{ .Values.database.name }}' + '\n') + # f.write(' ' + 'db-user: {{ .Values.database.user }}' + '\n') + # f.write(' ' + '{{- if .Values.database.port }}' + '\n') + # f.write(' ' + 'db-port: {{ .Values.database.port | quote }}' + '\n') + # f.write(' ' + '{{- else }}' + '\n') + # f.write(' ' + 'db-port: "5432"' + '\n') + # f.write(' ' + '{{- end }}' + '\n') # Secret file for the password to access the database for use within the Postgres Controller namespace # This is required by the operator to function properly - with open('templates/db-password-secret-postgres-controller.yaml', 'w') as f: - f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') - f.write('apiVersion: v1' + '\n') - f.write('kind: Secret' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-db-password' + '\n') - f.write(' ' + 'namespace: postgres-controller' + '\n') - f.write('type: Opaque' + '\n') - f.write('data:' + '\n') - f.write(' ' + 'password: {{ .Values.database.password | b64enc }}' + '\n') - f.write('{{- end -}}' + '\n') + #with open('templates/db-password-secret-postgres-controller.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') + # f.write('apiVersion: v1' + '\n') + # f.write('kind: Secret' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-db-password' + '\n') + # f.write(' ' + 'namespace: postgres-controller' + '\n') + # f.write('type: Opaque' + '\n') + # f.write('data:' + '\n') + # f.write(' ' + 'password: {{ .Values.database.password | b64enc }}' + '\n') + # f.write('{{- end -}}' + '\n') # Secret file for the password to access the database in the same namespace as the app - with open('templates/db-password-secret.yaml', 'w') as f: - f.write('apiVersion: v1' + '\n') - f.write('kind: Secret' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-db-password' + '\n') - f.write('type: Opaque' + '\n') - f.write('data:' + '\n') - f.write(' ' + 'password: {{ .Values.database.password | b64enc }}' + '\n') + #with open('templates/db-password-secret.yaml', 'w') as f: + # f.write('apiVersion: v1' + '\n') + # f.write('kind: Secret' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-db-password' + '\n') + # f.write('type: Opaque' + '\n') + # f.write('data:' + '\n') + # f.write(' ' + 'password: {{ .Values.database.password | b64enc }}' + '\n') # Custom Resource Definition (CRD) file to create the database using the operator - with open('templates/database.yaml', 'w') as f: - f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') - f.write('apiVersion: postgresql.org/v1' + '\n') - f.write('kind: PostgresDatabase' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-db' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('spec:' + '\n') - f.write(' ' + 'dbName:' + '\n') - f.write(' ' + ' ' + 'envFrom:' + '\n') - f.write(' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-name' + '\n') - f.write(' ' + 'dbRoleName:' + '\n') - f.write(' ' + ' ' + 'envFrom:' + '\n') - f.write(' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-user' + '\n') - f.write(' ' + 'dbRolePassword:' + '\n') - f.write(' ' + ' ' + 'envFrom:' + '\n') - f.write(' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-password' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') - f.write('{{- if .Values.database.instance_id }}' + '\n') - f.write(' ' + 'dbInstanceId: {{ .Values.database.instance_id }}' + '\n') - f.write('{{- end }}' + '\n') - f.write('{{- end -}}' + '\n') \ No newline at end of file + #with open('templates/database.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.database.type "postgres") (.Values.database.create) -}}' + '\n') + # f.write('apiVersion: postgresql.org/v1' + '\n') + # f.write('kind: PostgresDatabase' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-db' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('spec:' + '\n') + # f.write(' ' + 'dbName:' + '\n') + # f.write(' ' + ' ' + 'envFrom:' + '\n') + # f.write(' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-credentials' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-name' + '\n') + # f.write(' ' + 'dbRoleName:' + '\n') + # f.write(' ' + ' ' + 'envFrom:' + '\n') + # f.write(' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-credentials' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-user' + '\n') + # f.write(' ' + 'dbRolePassword:' + '\n') + # f.write(' ' + ' ' + 'envFrom:' + '\n') + # f.write(' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-db-password' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'namespace: postgres-controller' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') + # f.write('{{- if .Values.database.instance_id }}' + '\n') + # f.write(' ' + 'dbInstanceId: {{ .Values.database.instance_id }}' + '\n') + # f.write('{{- end }}' + '\n') + # f.write('{{- end -}}' + '\n') \ No newline at end of file diff --git a/src/Dependency.py b/src/Dependency.py new file mode 100644 index 0000000..10a56e4 --- /dev/null +++ b/src/Dependency.py @@ -0,0 +1,7 @@ +class Dependency: + def __init__(self, name: str, version: str, repository: str, alias: str = None, conditional_var: str = None): + self.name = name + self.version = version + self.repository = repository + self.alias = alias + self.conditional_var = conditional_var \ No newline at end of file diff --git a/src/Deployment.py b/src/Deployment.py index 038bcb7..d9d66ad 100644 --- a/src/Deployment.py +++ b/src/Deployment.py @@ -3,7 +3,7 @@ from .NoSQL import NoSQL from .ThirdPartyService import ThirdPartyService class Deployment (Template): - def __init__(self, image_repository: str, image_tag: str = 'v1.0.0', image_pull_policy: str = 'IfNotPresent', replica_count: int = 1, port: int = 8080, env: str = 'production', uses_oauth: bool = True, uses_db: bool = False, uses_secrets_vault: bool = False, nosql: NoSQL | None = None, uses_cache: bool = False, third_party_services: list[ThirdPartyService] = [], **extra_env_vars: dict[str, str | dict[str, str]]): + def __init__(self, image_repository: str, image_tag: str = 'v1.0.0', image_pull_policy: str = 'IfNotPresent', replica_count: int = 1, port: int = 8080, env: str = 'production', resources: dict[str, any] = { 'requests': { 'cpu': '200m', 'memory': '512Mi', 'ephemeralStorage': '50Mi' }, 'limits': { 'cpu': '1000m', 'memory': '512Mi', 'ephemeralStorage': '1Gi' } }, uses_oauth: bool = True, uses_db: bool = False, uses_secrets_vault: bool = False, nosql: NoSQL | None = None, uses_cache: bool = False, third_party_services: list[ThirdPartyService] = [], **extra_env_vars: dict[str, str | dict[str, str]]): """A class for creating a/some template(s) related to the Deployment for the app. Args: @@ -13,6 +13,7 @@ class Deployment (Template): replica_count (int, Optional): The number of replicas of the app to be running. Default 1 port (int, Optional): The port the app will be running on. Default 8080 env (str, Optional): The environment the app will be running in. Default 'production' + resources (dict[str, any], Optional): The resources to be allocated to the Deployment. Default { 'requests': { 'cpu': '200m', 'memory': '512Mi', 'ephemeralStorage': '50Mi' }, 'limits': { 'cpu': '1000m', 'memory': '512Mi', 'ephemeralStorage': '1Gi' } } uses_oauth (bool, Optional): Whether or not OAuth is to be used. Determines if OAuth related environment variables need to be set on the Deployment. Default True uses_db (bool, Optional): Whether or not a database is to be used. Determines if database related environment variables need to be set on the Deployment. Default False uses_secrets_vault (bool, Optional): Whether or not a secrets vault is to be used. Determines if secrets vault related environment variables need to be set on the Deployment. Default False @@ -30,6 +31,7 @@ class Deployment (Template): self.replica_count = replica_count self.env = env self.port = port + self.resources = resources self.uses_oauth = uses_oauth self.uses_db = uses_db self.uses_secrets_vault = uses_secrets_vault @@ -129,32 +131,35 @@ class Deployment (Template): output = '' - output += ' ' + ' ' + ' ' + ' ' + '# OAuth Implementation Stuff' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: BASE_APP_URL' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: base-app-url' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: APP_ABBRV' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: app-abbreviation' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: APP_NAME' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: app-name' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: SERVICE_NAME' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: service-name' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DEV_PORT' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: dev-port' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- /* This injects the YAML defined in the `_oauth.tpl` file */ -}}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- include "oauth.envVars" . | nindent 8 }}' + '\n' + + #output += ' ' + ' ' + ' ' + ' ' + '# OAuth Implementation Stuff' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: BASE_APP_URL' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: base-app-url' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: APP_ABBRV' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: app-abbreviation' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: APP_NAME' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: app-name' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: SERVICE_NAME' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: service-name' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DEV_PORT' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: dev-port' + '\n' return output @@ -163,32 +168,34 @@ class Deployment (Template): output = '' - output += ' ' + ' ' + ' ' + ' ' + '# Database credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DB_HOST' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-host' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DB_NAME' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-name' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DB_PASSWORD' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-password' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DB_PORT' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-port' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: DB_USER' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-user' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- include "db.envVars" . | nindent 8 }}' + '\n' + + #output += ' ' + ' ' + ' ' + ' ' + '# Database credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DB_HOST' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-host' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DB_NAME' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-name' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DB_PASSWORD' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-password' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DB_PORT' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-port' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: DB_USER' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-user' + '\n' return output @@ -197,32 +204,35 @@ class Deployment (Template): output = '' - output += ' ' + ' ' + ' ' + ' ' + '# NoSQL Credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '{{- if eq .Values.nosql.type "mongodb" }}' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_CONNECTION_STRING' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: connection-string' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '{{- else if eq .Values.nosql.type "azure" }}' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_KEY' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: key' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_NAME' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-config' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: name' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '# NoSQL Table Names' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- include "nosql.envVars" . | nindent 8 }}' + '\n' + + #output += ' ' + ' ' + ' ' + ' ' + '# NoSQL Credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '{{- if eq .Values.nosql.type "mongodb" }}' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_CONNECTION_STRING' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: connection-string' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '{{- else if eq .Values.nosql.type "azure" }}' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_KEY' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: key' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_NAME' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-config' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: name' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' - for key, value in self.nosql.tables.items(): + output += ' ' + ' ' + ' ' + ' ' + '# NoSQL Grouping Names' + '\n' + + for key, value in self.nosql.groupings.items(): output += ' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n' output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-storage-tables' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-nosql-grouping-config' + '\n' output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {value["name"]}' + '\n' return output @@ -232,8 +242,8 @@ class Deployment (Template): output = '' - output += ' ' + ' ' + ' ' + ' ' + '# -- Secrets Vault (Hashicorp Vault OR Azure Key Vault) --' + '\n' output += ' ' + ' ' + ' ' + ' ' + '{{- if .Values.vault.enabled }}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '# -- Secrets Vault (Hashicorp Vault OR Azure Key Vault) --' + '\n' output += ' ' + ' ' + ' ' + ' ' + '{{- if eq .Values.vault.type "azure" }}' + '\n' output += ' ' + ' ' + ' ' + ' ' + '- name: KEYVAULT_CLIENT_ID' + '\n' output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' @@ -276,22 +286,24 @@ class Deployment (Template): output = '' - output += ' ' + ' ' + ' ' + ' ' + '# Caching Server Variables' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_HOSTNAME' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Relese.name }}-cache-configmap' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: hostname' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_PORT' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-configmap' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: port' + '\n' - output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_PASSWORD' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-credentials' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- include "cache.envVars" . | nindent 8 }}' + '\n' + + #output += ' ' + ' ' + ' ' + ' ' + '# Caching Server Variables' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_HOSTNAME' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-configmap' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: hostname' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_PORT' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-configmap' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: port' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + '- name: CACHE_PASSWORD' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-credentials' + '\n' + #output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n' return output @@ -303,13 +315,16 @@ class Deployment (Template): output += ' ' + ' ' + ' ' + ' ' + '# Third-Party Integrations' + '\n' for third_party in self.third_party_services: output += ' ' + ' ' + ' ' + ' ' + '{{- if .Values.thirdParty.' + third_party.name + '.enabled }}' + '\n' - - for var in third_party.vars: - output += ' ' + ' ' + ' ' + ' ' + '- name: ' + third_party.name.upper() + '_' + var.upper() + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' - output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' - output += ' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-' + third_party.name + '-secret' + '\n' - output += ' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {var.replace("_", "-")}' + '\n' + + output += ' ' + ' ' + ' ' + ' ' + '{{- /* This injects the YAML defined in the `_thirdParty.tpl` file */ -}}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{ include "' + third_party.name.lower() + '.envVars" . | nindent 8 }}' + '\n' + + #for var in third_party.vars: + # output += ' ' + ' ' + ' ' + ' ' + '- name: ' + third_party.name.upper() + '_' + var.upper() + '\n' + # output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + # output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + # output += ' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-' + third_party.name + '-secret' + '\n' + # output += ' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {var.replace("_", "-")}' + '\n' output += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' @@ -337,15 +352,15 @@ class Deployment (Template): f.write(' ' + ' ' + 'spec:' + '\n') f.write(' ' + ' ' + ' ' + 'containers:' + '\n') f.write(' ' + ' ' + ' ' + '- name: {{ .Release.Name }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + 'image: {{ .Values.image.repository }}:{{ .Values.image.tag }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + 'imagePullPolicy: {{ .Values.image.pullPolicy }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'image: {{ .Values.app.image.repository }}:{{ .Values.app.image.tag }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'imagePullPolicy: {{ .Values.app.image.pullPolicy }}' + '\n') f.write(' ' + ' ' + ' ' + ' ' + 'ports:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.container.port }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.app.container.port }}' + '\n') f.write(' ' + ' ' + ' ' + ' ' + 'env:' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: NODE_ENV' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: {{ .Values.container.env }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: {{ .Values.app.container.env }}' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: PORT' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: "{{ .Values.container.port }}"' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: "{{ .Values.app.container.port }}"' + '\n') # Add extra environment variables f.write(self.create_extra_env_vars_deployment_env_vars()) @@ -368,25 +383,124 @@ class Deployment (Template): if len(self.third_party_services) > 0: f.write(self.create_third_party_services_deployment_env_vars()) + f.write(' ' + ' ' + ' ' + ' ' + '{{- if .Values.loggingSidecar.enabled }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +'{{- include "logging.envVars" . | nindent 8 }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +'{{- end }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '{{- if ne .Values.app.restoreFromBackup "" }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '# Due to subtleties related to how the entrypoint scripts detects how/when to proceed' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '# This environment variable indicates if the entrypoint should wait for a restore to complete' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: RESTORE_FROM_BACKUP' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: {{ .Values.app.restoreFromBackup | quote }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') + # Because of the way we implement Hashicorp Vault we need to mount the role_vars shared volume # This is because the Vault container populates this shared volume with the app credentials. # It's done this way because we don't know the credentials needed to access the vault at start time (because their generated by the Vault container) # So, we need a mechanism to get these credentials in relatively real-time once they've been generated if self.uses_secrets_vault: + f.write(' ' + ' ' + ' ' + ' ' + '{{- if .Values.vault.create.enabled }}' + '\n') f.write(' ' + ' ' + ' ' + ' ' + 'volumeMounts:' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: role-vars' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /role_vars' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'readOnly: true' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') + + f.write(' ' + ' ' + ' ' + ' ' + 'resources:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + 'requests:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'cpu: {{ .Values.app.resources.requests.cpu }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'memory: {{ .Values.app.resources.requests.memory }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'ephemeral-storage: {{ .Values.app.resources.requests.ephemeralStorage }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + 'limits:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'cpu: {{ .Values.app.resources.limits.cpu }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'memory: {{ .Values.app.resources.limits.memory }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' +' ' + ' ' + 'ephemeral-storage: {{ .Values.app.resources.limits.ephemeralStorage }}' + '\n') + + f.write(' ' + ' ' + ' ' + '{{- if .Values.loggingSidecar.enabled }}' + '\n') + f.write(' ' + ' ' + ' ' + '# Logging sidecar for sending logs to a log aggregator' + '\n') + f.write(' ' + ' ' + ' ' + '{{ include "logging.sidecar" . | nindent 6 }}' + '\n') + f.write(' ' + ' ' + ' ' + '{{- end }}' + '\n') + + f.write(' ' + ' ' + ' ' + '{{- if .Values.backupSidecar.enabled }}' + '\n') + f.write(' ' + ' ' + ' ' + '# Backup sidecar for backing up service data' + '\n') + f.write(' ' + ' ' + ' ' + '{{- /* This injects the YAML defined in the `_sidecar.tpl` file */ -}}' + '\n') + f.write(' ' + ' ' + ' ' + '{{ include "backupSidecar" . | nindent 6 }}' + '\n') + f.write(' ' + ' ' + ' ' + '{{- end }}' + '\n') + + if self.uses_secrets_vault: f.write(' ' + ' ' + ' ' + 'volumes:' + '\n') f.write(' ' + ' ' + ' ' + '- name: role-vars' + '\n') f.write(' ' + ' ' + ' ' + ' ' + 'persistentVolumeClaim:' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'claimName: {{ .Release.Name }}-vault-role-vars' + '\n') + f.write(' ' + ' ' + ' ' + '{{- if .Values.vault.create.snapshotServer.enabled }}' + '\n') + f.write(' ' + ' ' + ' ' + '- name: creds' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'persistentVolumeClaim:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'claimName: {{ .Release.Name }}-vault-creds' + '\n') + f.write(' ' + ' ' + ' ' + '{{- end }}' + '\n') + def write_sidecars_template_functions_file(self): + with open('templates/_sidecars.tpl', 'w') as f: + f.write('{{- define "backupSidecar" -}}' + '\n') + f.write('- name: {{ .Values.backupSidecar.name }}' + '\n') + f.write(' ' + 'image: {{ .Values.backupSidecar.image.repository }}:{{ .Values.backupSidecar.image.tag }}' + '\n') + f.write(' ' + 'imagePullPolicy: {{ .Values.backupSidecar.image.pullPolicy }}' + '\n') + f.write(' ' + 'ports:' + '\n') + f.write(' ' + '- containerPort: {{ .Values.backupSidecar.port }}' + '\n') + f.write(' ' + 'env:' + '\n') + f.write(' ' + '# Release name (used to identify the service/release the backups came from in remote storage)' + '\n') + f.write(' ' + '- name: RELEASE_NAME' + '\n') + f.write(' ' + ' ' + 'value: {{ .Release.Name }}' + '\n') + f.write(' ' + '{{- include "db.envVars" . | nindent 2 -}}' + '\n') + f.write(' ' + '{{- if .Values.vault.create.snapshotServer.enabled }}' + '\n') + f.write(' ' + '- name: VAULT_NAME' + '\n') + f.write(' ' + ' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'key: vault-name' + '\n') + f.write(' ' + '- name: VAULT_SNAPSHOT_SERVER_PORT' + '\n') + f.write(' ' + ' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-snapshot-config' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'key: port' + '\n') + f.write(' ' + '{{- end }}' + '\n') + f.write(' ' + '{{- include "nosql.envVars" . | nindent 2 }}' + '\n') + f.write(' ' + '# Redis is used for BullMQ, which is how we schedule backups' + '\n') + f.write(' ' + '# We use this instead of, for instance cron jobs, as it lets us deal with failures' + '\n') + f.write(' ' + '{{- include "cache.envVars" . | nindent 2 }}' + '\n') + f.write(' ' + 'resources:' + '\n') + f.write(' ' + ' ' + 'requests:' + '\n') + f.write(' ' + ' ' + ' ' + 'cpu: {{ .Values.backupSidecar.resources.requests.cpu }}' + '\n') + f.write(' ' + ' ' + ' ' + 'memory: {{ .Values.backupSidecar.resources.requests.memory }}' + '\n') + f.write(' ' + ' ' + ' ' + 'ephemeral-storage: {{ .Values.backupSidecar.resources.requests.ephemeralStorage }}' + '\n') + f.write(' ' + ' ' + 'limits:' + '\n') + f.write(' ' + ' ' + ' ' + 'cpu: {{ .Values.backupSidecar.resources.limits.cpu }}' + '\n') + f.write(' ' + ' ' + ' ' + 'memory: {{ .Values.backupSidecar.resources.limits.memory }}' + '\n') + f.write(' ' + ' ' + ' ' + 'ephemeral-storage: {{ .Values.backupSidecar.resources.limits.ephemeralStorage }}' + '\n') + f.write(' ' + '{{- if .Values.vault.create.snapshotServer.enabled }}' + '\n') + f.write(' ' + 'volumeMounts:' + '\n') + f.write(' ' + '# Mount for a shared volume for Vault credentials' + '\n') + f.write(' ' + '# This is separate from the app\'s `role vars` volume because it includes other credentials' + '\n') + f.write(' ' + '# In particular, the unseal keys which we require when/if we restore from the backup' + '\n') + f.write(' ' + '# This volume is also read-only where the `role-vars` is read-write (see description below for why)' + '\n') + f.write(' ' + '- name: creds' + '\n') + f.write(' ' + ' ' + 'mountPath: /vault-creds' + '\n') + f.write(' ' + ' ' + 'readOnly: true' + '\n') + f.write(' ' + '# Mount for a shared volume for the Vault\'s role variables for the app' + '\n') + f.write(' ' + '# This is required by the backup sidecar because if a restart of the app occurs AFTER a vault has been reset (ex. vault using a different container instance),' + '\n') + f.write(' ' + '# despite the vault data being restored the app would receive incorrect credentials (because this is ONLY written during setup of the vault)' + '\n') + f.write(' ' + '# The backup sidecar mitigates this by doing it\'s own write (to overwrite) once it\'s done a restore' + '\n') + f.write(' ' + '- name: role-vars' + '\n') + f.write(' ' + ' ' + 'mountPath: /role_vars' + '\n') + f.write(' ' + '{{- end }}' + '\n') + f.write('{{- end -}}' + '\n') + def write(self): """Writes files related to the Deployment of the app.""" # Create any needed secrets or configmaps for the extra environment variables self.write_extra_env_vars_files() + # Create the sidecar template functions file + self.write_sidecars_template_functions_file() + # Create the Deployment file self.write_deployment_file() \ No newline at end of file diff --git a/src/HashicorpVault.py b/src/HashicorpVault.py index 90b2352..341e0c7 100644 --- a/src/HashicorpVault.py +++ b/src/HashicorpVault.py @@ -1,15 +1,15 @@ from .SecretsVault import SecretsVault class HashicorpVault(SecretsVault): - def __init__(self, create: bool = True, image: dict[str, str] | None = None, hostname: str | None = None, port: int = 8200, storage_class: str | None = None, storage_size: str = '512Mi'): + def __init__(self, create: bool = True, image: dict[str, str] | None = None, hostname: str | None = None, port: int = 8200, snapshot_server: bool = True, policy_capabilities: list[str] = ['read', 'create', 'update']): super().__init__('hashicorp') self.create = create self.image = image self.hostname = hostname self.port = port - self.storage_class = storage_class - self.storage_size = storage_size + self.snapshot_server = snapshot_server + self.policy_capabilities = policy_capabilities def write_ingress(self): with open('templates/vault-ingress.yaml', 'w') as f: @@ -99,6 +99,8 @@ class HashicorpVault(SecretsVault): f.write(' ' + ' ' + ' ' + ' ' + 'env:' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: VAULT_ADDR' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: http://0.0.0.0:8200' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: POLICY_CAPABILITIES' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: {{ .Values.vault.create.policyCapabilities | join "," }}' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: ROLE_ID_SECRET_NAME' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: VAULT_ROLE_ID' + '\n') f.write(' ' + ' ' + ' ' + ' ' + '- name: SECRET_ID_SECRET_NAME' + '\n') @@ -154,8 +156,14 @@ class HashicorpVault(SecretsVault): f.write('{{- end -}}') def write(self): - self.write_ingress() - self.write_service() - self.write_role_vars_persistent_volume_claim() - self.write_deployment() - self.write_secret() \ No newline at end of file + # =========================================================== + # DEPRECATED FILES - USING HELM DEPENDENCY (SUBCHART) INSTEAD + # =========================================================== + + pass + + #self.write_ingress() + #self.write_service() + #self.write_role_vars_persistent_volume_claim() + #self.write_deployment() + #self.write_secret() \ No newline at end of file diff --git a/src/HelmChart.py b/src/HelmChart.py index 1d819f3..c410218 100644 --- a/src/HelmChart.py +++ b/src/HelmChart.py @@ -1,6 +1,7 @@ import os, subprocess from .Template import Template +from .Dependency import Dependency from .Ingress import Ingress from .Database import Database from .SecretsVault import SecretsVault @@ -13,20 +14,22 @@ from .Redis import Redis from .OAuth import OAuth from .ThirdPartyService import ThirdPartyService from .Deployment import Deployment +from .LoggingSidecar import LoggingSidecar class HelmChart: - def __init__(self, chartName: str, chartDescription: str, maintainers: list[dict[str, str]], chartHomepage: str, sources: list[str], appVersion: str = '1.0.0', chartVersion: str = '1.0.0', apiVersion: str = 'v1', *templates: Template): + def __init__(self, chartName: str, chartVersion: str = '1.0.0', apiVersion: str = 'v2', appVersion: str | None = None, chartDescription: str | None = None, maintainers: list[dict[str, str]] | None = None, chartHomepage: str | None = None, sources: list[str] | None = None, dependencies: list[Dependency] = [], *templates: Template): """A class for creating a Helm chart. Args: chartName (str): The name of the Helm chart. - chartDescription (str): A description of the Helm chart. - maintainers (list[dict[str, str]]): The maintainers of the Helm chart. - chartHomepage (str): The URL of the Helm chart's home page - sources (list[str]): The sources of the Helm chart. - appVersion (str, Optional): The version of the application that the Helm chart is deploying. Default '1.0.0' chartVersion (str, Optional): The version of the Helm chart. Default '1.0.0' - apiVersion (str, Optional): The API version of the Helm chart itself. Default 'v1' + apiVersion (str, Optional): The API version of the Helm chart itself. Default 'v2' + appVersion (str | None, Optional): The version of the application that the Helm chart is deploying. Default None + chartDescription (str | None, Optional): A description of the Helm chart. Default None + maintainers (list[dict[str, str]] | None, Optional): The maintainers of the Helm chart. Default None + chartHomepage (str | None, Optional): The URL of the Helm chart's home page. Default None + sources (list[str] | None, Optional): The sources of the Helm chart. Default None + dependencies (list[Dependency], Optional): The dependencies of the Helm chart. Default [] Templates (Template, Optional): The templates for the Helm chart. Default None """ @@ -38,13 +41,17 @@ class HelmChart: self.appVersion = appVersion self.chartVersion = chartVersion self.apiVersion = apiVersion + self.dependencies = dependencies self.templates = templates def create_templates_folder(self): """Create the templates folder for the Helm chart.""" - os.mkdir('templates') + # If the templates folder doesn't exist, create it + if not os.path.exists('templates'): + os.mkdir('templates') + # Loop over the templates and write each one for template in self.templates: template.write() @@ -56,18 +63,39 @@ class HelmChart: with open('Chart.yaml', 'w') as f: f.write(f'apiVersion: {self.apiVersion}' + '\n') - f.write(f'appVersion: "{self.appVersion}"' + '\n') - f.write(f'description: {self.chartDescription}' + '\n') - f.write(f'home: {self.chartHomepage}' + '\n') - f.write('maintainers:' + '\n') - for maintainer in self.maintainers: - f.write(' ' + f'- email: {maintainer["email"]}' + '\n') - f.write(' ' + ' ' + f'name: {maintainer["name"]}' + '\n') + + if self.appVersion is not None: + f.write(f'appVersion: "{self.appVersion}"' + '\n') + + if self.chartDescription is not None: + f.write(f'description: {self.chartDescription}' + '\n') + + if self.chartHomepage is not None: + f.write(f'home: {self.chartHomepage}' + '\n') + + if self.maintainers is not None: + f.write('maintainers:' + '\n') + for maintainer in self.maintainers: + f.write(' ' + f'- email: {maintainer["email"]}' + '\n') + f.write(' ' + ' ' + f'name: {maintainer["name"]}' + '\n') + f.write(f'name: {self.chartName}' + '\n') - f.write('sources:' + '\n') - for source in self.sources: - f.write(f'- {source}' + '\n') + + if self.sources is not None: + f.write('sources:' + '\n') + for source in self.sources: + f.write(f'- {source}' + '\n') + f.write(f'version: "{self.chartVersion}"' + '\n') + + if len(self.dependencies) > 0: + f.write('dependencies:' + '\n') + for dependency in self.dependencies: + f.write(f'- name: {dependency.name}' + '\n') + f.write(' ' + f'version: {dependency.version}' + '\n') + f.write(' ' + f'repository: "{dependency.repository}"' + '\n') + f.write(' ' + f'alias: {dependency.alias}' + '\n') + f.write(' ' + f'condition: {dependency.conditional_var}' + '\n') def create_replicas_section_of_values_yaml(self) -> str: """Create the replicas section of the `values.yaml` file for the Helm chart. @@ -93,6 +121,34 @@ class HelmChart: return output + def create_generic_image_section_of_values_yaml(self) -> str: + """Create a generalized image section of the `values.yaml` file for the Helm chart. + + The image section is used to define the image that the app will use. + + Returns: + str: The image section of the `values.yaml` file + """ + + output = '' + + output += 'image:' + '\n' + output += ' ' + '# The repository of the image to use for the app' + '\n' + output += ' ' + '# Should be in the format `/`' + '\n' + output += ' ' + 'repository: "/"' + '\n' + output += ' ' + '\n' + output += ' ' + '# The specific image tag to use. It\'s recommended to use some kind of versioning tag scheme as it makes updating the container without having to fully redeploy easier.' + '\n' + output += ' ' + '# Ex. v1.0.0' + '\n' + output += ' ' + 'tag: "v1.0.0"' + '\n' + output += ' ' + '\n' + output += ' ' + '# How often the image should be pulled. The possible values are "Always", "Never", and "IfNotPresent"' + '\n' + output += ' ' + '# It\'s recommended for production to use "IfNotPresent" to avoid pulling the image every time the pod starts' + '\n' + output += ' ' + '# Though, for development, "Always" is recommended to ensure the latest changes are being tested' + '\n' + output += ' ' + 'pullPolicy: "IfNotPresent"' + '\n' + output += '\n' + + return output + def create_image_section_of_values_yaml(self) -> str: """Create the image section of the `values.yaml` file for the Helm chart. @@ -108,20 +164,41 @@ class HelmChart: deployment_template = next(template for template in self.templates if isinstance(template, Deployment)) output += 'image:' + '\n' - output += ' ' + '# The repository of the image to use for the app' + '\n' - output += ' ' + '# Should be in the format `/`' + '\n' + output += ' ' + '# The repository of the image to use for the app.' + '\n' output += ' ' + f'repository: "{deployment_template.image_repository}"' + '\n' - output += ' ' + '# The specific image tag to use. It\'s recommended to use some kind of versioning tag scheme as it makes updating the container without having to fully redeploy easier.' + '\n' - output += ' ' + '# Ex. v1.0.0' + '\n' + output += ' ' + '\n' + output += ' ' + '# The specific image tag to use.' + '\n' output += ' ' + f'tag: "{deployment_template.image_tag}"' + '\n' - output += ' ' + '# How often the image should be pulled. The possible values are "Always", "Never", and "IfNotPresent"' + '\n' - output += ' ' + '# It\'s recommended for production to use "IfNotPresent" to avoid pulling the image every time the pod starts' + '\n' - output += ' ' + '# Though, for development, "Always" is recommended to ensure the latest changes are being tested' + '\n' + output += ' ' + '\n' + output += ' ' + '# How often the image should be pulled. ' + '\n' output += ' ' + f'pullPolicy: "{deployment_template.image_pull_policy}"' + '\n' output += '\n' return output + def create_generic_container_section_of_values_yaml(self) -> str: + """Create a generalized container section of the `values.yaml` file for the Helm chart. + + The container section is used to define the environment that the container is running in. + Ex. the environment type (development, production, etc...) and the port that the container listens on. + + Returns: + str: The container section of the `values.yaml` file + """ + + output = '' + + output += 'container:' + '\n' + output += ' ' + '# The port that the container listens on (Ex. 8080)' + '\n' + output += ' ' + 'port: 8080' + '\n' + output += ' ' + '\n' + output += ' ' + '# The environment that the container is running in (Ex. development, production, etc...)' + '\n' + output += ' ' + '# This is used for the NODE_ENV environment variable' + '\n' + output += ' ' + 'env: "production"' + '\n' + output += '\n' + + return output + def create_container_section_of_values_yaml(self) -> str: """Create the container section of the `values.yaml` file for the Helm chart. @@ -138,16 +215,158 @@ class HelmChart: deployment_template = next(template for template in self.templates if isinstance(template, Deployment)) output += 'container:' + '\n' - output += ' ' + '# The port that the container listens on (Ex. 8080)' + '\n' + output += ' ' + '# The port that the container listens on' + '\n' output += ' ' + f'port: {deployment_template.port}' + '\n' output += ' ' + '\n' - output += ' ' + '# The environment that the container is running in (Ex. development, production, etc...)' + '\n' - output += ' ' + '# This is used for the NODE_ENV environment variable' + '\n' + output += ' ' + '# The environment that the container is running in' + '\n' output += ' ' + f'env: "{deployment_template.env}"' + '\n' output += '\n' return output + def create_generic_app_resources_section_of_values_yaml(self) -> str: + """Create a generalized app resources section of the `values.yaml` file for the Helm chart. + + The app resources section is used to define the resources that the app container itself will use. + This includes CPU, memory, and ephemeral storage requests and limits. + + Returns: + str: The app resources section of the `values.yaml` file + """ + + output = '' + + output += 'resources:' + '\n' + output += ' ' + 'requests:' + '\n' + output += ' ' + ' ' + '# The amount of CPU that the app requests' + '\n' + output += ' ' + ' ' + '# Ex. 200m = 0.2 CPU' + '\n' + output += ' ' + ' ' + 'cpu: 200m' + '\n' + output += ' ' + ' ' + '# The amount of memory that the app requests' + '\n' + output += ' ' + ' ' + '# Ex. 512Mi = 512 Megabytes' + '\n' + output += ' ' + ' ' + 'memory: 512Mi' + '\n' + output += ' ' + ' ' + '# The amount of ephemeral storage that the app requests' + '\n' + output += ' ' + ' ' + '# Ex. 50Mi = 50 Megabytes' + '\n' + output += ' ' + ' ' + 'ephemeralStorage: 50Mi' + '\n' + output += ' ' + 'limits:' + '\n' + output += ' ' + ' ' + '# The maximum amount of CPU that the app can use' + '\n' + output += ' ' + ' ' + '# Ex. 1000m = 1 CPU' + '\n' + output += ' ' + ' ' + 'cpu: 1000m' + '\n' + output += ' ' + ' ' + '# The maximum amount of memory that the app can use' + '\n' + output += ' ' + ' ' + '# Ex. 512Mi = 512 Megabytes' + '\n' + output += ' ' + ' ' + f'memory: 512Mi' + '\n' + output += ' ' + ' ' + '# The maximum amount of ephemeral storage that the app can use' + '\n' + output += ' ' + ' ' + '# Ex. 1Gi = 1 Gigabyte' + '\n' + output += ' ' + ' ' + f'ephemeralStorage: 1Gi' + '\n' + output += '\n' + + return output + + def create_app_resources_section_of_values_yaml(self) -> str: + """Create the app resources section of the `values.yaml` file for the Helm chart. + + The app resources section is used to define the resources that the app container itself will use. + This includes CPU, memory, and ephemeral storage requests and limits. + + Returns: + str: The app resources section of the `values.yaml` file + """ + + output = '' + + # Get the Deployment templates from the templates provided + deployment_template = next(template for template in self.templates if isinstance(template, Deployment)) + + output += 'resources:' + '\n' + output += ' ' + 'requests:' + '\n' + output += ' ' + ' ' + '# The amount of CPU that the app requests' + '\n' + output += ' ' + ' ' + f'cpu: {deployment_template.resources["requests"]["cpu"]}' + '\n' + output += ' ' + ' ' + '# The amount of memory that the app requests' + '\n' + output += ' ' + ' ' + f'memory: {deployment_template.resources["requests"]["memory"]}' + '\n' + output += ' ' + ' ' + '# The amount of ephemeral storage that the app requests' + '\n' + output += ' ' + ' ' + f'ephemeralStorage: {deployment_template.resources["requests"]["ephemeralStorage"]}' + '\n' + output += ' ' + 'limits:' + '\n' + output += ' ' + ' ' + '# The maximum amount of CPU that the app can use' + '\n' + output += ' ' + ' ' + f'cpu: {deployment_template.resources["limits"]["cpu"]}' + '\n' + output += ' ' + ' ' + '# The maximum amount of memory that the app can use' + '\n' + output += ' ' + ' ' + f'memory: {deployment_template.resources["limits"]["memory"]}' + '\n' + output += ' ' + ' ' + '# The maximum amount of ephemeral storage that the app can use' + '\n' + output += ' ' + ' ' + f'ephemeralStorage: {deployment_template.resources["limits"]["ephemeralStorage"]}' + '\n' + output += '\n' + + return output + + def create_generic_app_section_of_values_yaml(self) -> str: + """Create a generalized app section of the `values.yaml` file for the Helm chart. + + The app section is used to define the configuration for the app itself. + + Returns: + str: The app section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the app itself' + '\n' + output += 'app:' + '\n' + output += ' ' + self.create_replicas_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_replicas_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_generic_image_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_generic_image_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_generic_container_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_generic_container_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_generic_app_resources_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_generic_app_resources_section_of_values_yaml().count('\n') - 1) + output += ' ' + '# The timestamp of the backup that the entrypoint script should wait for a restore to complete' + '\n' + output += ' ' + 'restoreFromBackup: ""' + '\n' + output += '\n' + + return output + + def create_app_section_of_values_yaml(self) -> str: + """Create the app section of the `values.yaml` file for the Helm chart. + + The app section is used to define the configuration for the app itself. + + Returns: + str: The app section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the app itself' + '\n' + output += 'app:' + '\n' + output += ' ' + self.create_replicas_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_replicas_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_image_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_image_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_container_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_container_section_of_values_yaml().count('\n') - 1) + output += ' ' + self.create_app_resources_section_of_values_yaml().replace('\n', '\n' + ' ', self.create_app_resources_section_of_values_yaml().count('\n') - 1) + output += ' ' + '# The timestamp of the backup that the entrypoint script should wait for a restore to complete' + '\n' + output += ' ' + 'restoreFromBackup: ""' + '\n' + output += '\n' + + return output + + def create_generic_ingress_section_of_values_yaml(self) -> str: + """Create a generalized ingress section of the `values.yaml` file for the Helm chart. + + The ingress section is used to define the ingress resource that the app will use. + That is how the app will be accessed from outside the cluster. + + Returns: + str: The ingress section of the `values.yaml` file + """ + + output = '' + + output += 'ingress:' + '\n' + output += ' ' + '# We want an ingress resource if we are deploying to a cluster that has a ingress controller/load balancer' + '\n' + output += ' ' + '# This includes most public cloud providers like EKS, GKE, and AKS' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# The DNS Name (Ex. app.example.com) where the app will be accessible' + '\n' + output += ' ' + 'host: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The class of the ingress controller that is being used (defaulted here to an NGINX ingress controller as it\'s popular for Kubernetes clusters)' + '\n' + output += ' ' + 'class: nginx' + '\n' + output += '\n' + + return output + def create_ingress_section_of_values_yaml(self) -> str: """Create the ingress section of the `values.yaml` file for the Helm chart. @@ -164,17 +383,59 @@ class HelmChart: ingress_template = next(template for template in self.templates if isinstance(template, Ingress)) output += 'ingress:' + '\n' - output += ' ' + '# We want an ingress resource if we are deploying to a cluster that has a ingress controller/load balancer' + '\n' - output += ' ' + '# This includes most public cloud providers like EKS, GKE, and AKS' + '\n' + output += ' ' + '# If to create an ingress resource' + '\n' output += ' ' + 'enabled: true' + '\n' - output += ' ' + '# The DNS Name (Ex. app.example.com) where the app will be accessible' + '\n' + output += ' ' + '\n' + output += ' ' + '# The DNS Name where the app will be accessible' + '\n' output += ' ' + f'host: "{ingress_template.hostname}"' + '\n' - output += ' ' + '# The class of the ingress controller that is being used (defaulted here to an NGINX ingress controller as it\'s popular for Kubernetes clusters)' + '\n' + output += ' ' + '\n' + output += ' ' + '# The class of the ingress controller that is being used' + '\n' output += ' ' + 'class: nginx' + '\n' output += '\n' return output + def create_generic_deployment_extra_vars_section_of_values_yaml(self) -> str: + """Create a generalized extra environment variables for the deployment section of the `values.yaml` file for the Helm chart. + + Somewhat similar to the "replicas" section, the "extra environment variables" aren't a real "section" in the sense that they don't have subfields of an `extraEnvVars` field. + + The extra environment variables are used to define additional environment variables that the deployment will use. + This is useful for providing extra configuration to the app that is being deployed. + + Returns: + str: The extra environment variables section of the `values.yaml` file + """ + + output = '' + + # Get the Deployment templates from the templates provided + deployment_template = next(template for template in self.templates if isinstance(template, Deployment)) + + for value in deployment_template.extra_env_vars.values(): + if isinstance(value, dict): + # If a description is provided for the environment variable than we want to include it as a comment above the value in the `values.yaml` file. + if 'description' in value: + output += f'# {value["description"]}' + '\n' + + # Because the name given within the value in the dictionary is intended as the unique name of the configmap/secret that contains the value + # It usually contains a reference to the Helm release name. + # However, here we want the name without this component so we do a string replacement to remove it. + var_name = value["name"].replace('{{ .Release.Name }}-', '') + + # We want to convert the name to camelCase because this is our convention for `values.yaml` keys + camel_case_name = var_name.split('-')[0] + for token in var_name.split('-'): + if token != camel_case_name: + camel_case_name += token.capitalize() + + output += f'{camel_case_name}: "<{camel_case_name}>"' + '\n' + + # For readability of the `values.yaml` file we put an extra line between each extra deployment environment variable + output += '\n' + + return output + def create_deployment_extra_vars_section_of_values_yaml(self) -> str: """Create the extra environment variables for the deployment section of the `values.yaml` file for the Helm chart. @@ -216,6 +477,32 @@ class HelmChart: return output + def create_generic_oauth_section_of_values_yaml(self) -> str: + """Create a generalized OAuth section of the `values.yaml` file for the Helm chart. + + The OAuth section is used to define the OAuth configuration that the app will use. + This is useful for providing OAuth configuration to the app that is being deployed. + + Note, this assumes the app uses the Bridgeman Accessible OAuth implementation etc... + + Returns: + str: The OAuth section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for using OAuth within the app' + '\n' + output += 'oauth:' + '\n' + output += ' ' + 'baseAppUrl: ""' + '\n' + output += ' ' + 'appAbbreviation: ""' + '\n' + output += ' ' + 'appName: ""' + '\n' + output += ' ' + 'serviceName: ""' + '\n' + output += ' ' + 'devPort: ""' + '\n' + output += ' ' + 'appRegContactEmail: ""' + '\n' + output += '\n' + + return output + def create_oauth_section_of_values_yaml(self) -> str: """Create the OAuth section of the `values.yaml` file for the Helm chart. @@ -240,6 +527,50 @@ class HelmChart: output += ' ' + f'appName: "{oauth_template.app_name}"' + '\n' output += ' ' + f'serviceName: "{oauth_template.service_name}"' + '\n' output += ' ' + f'devPort: "{oauth_template.dev_port}"' + '\n' + output += ' ' + f'appRegContactEmail: "{oauth_template.app_reg_contact_email}"' + '\n' + output += '\n' + + return output + + def create_generic_database_section_of_values_yaml(self) -> str: + """Create a generalized Database section of the `values.yaml` file for the Helm chart. + + The Database section is used to define the database configuration that the app will use. + + Returns: + str: The Database section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the relational database' + '\n' + output += '# See the [Database Deployment Helm Chart](https://git.bridgemanaccessible.ca/Bridgeman-Accessible/db-deploy-helm) for more information' + '\n' + output += 'database:' + '\n' + output += ' ' + '# If a relational database should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output == ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `database`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "db"' + '\n' + output += ' ' + '\n' + output += ' ' + '# The type of the relational database that is used.' + '\n' + output += ' ' + 'type: "postgres"' + '\n' + output += ' ' + '\n' + output += ' ' + '# If set to `true`, the database will be created as part of the deployment' + '\n' + output += ' ' + '# This uses the [`postgres-controller` CRD](https://github.com/AlanBridgeman/postgres-controller) to create the database' + '\n' + output += ' ' + 'create: false' + '\n' + output += ' ' + '\n' + output += ' ' + '# The host that the database is located on ' + '\n' + output += ' ' + f'host: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the database to be used' + '\n' + output += ' ' + f'name: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The user that is used to access the database' + '\n' + output += ' ' + f'user: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The password that is used to access the database' + '\n' + output += ' ' + f'password: ""' + '\n' + output += ' ' + '\n' output += '\n' return output @@ -260,20 +591,16 @@ class HelmChart: output += '# Configuration for the relational database' + '\n' output += 'database:' + '\n' + output += ' ' + '# If a relational database should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output == ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `database`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "db"' + '\n' + output += ' ' + '\n' output += ' ' + '# The type of the relational database that is used.' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# The following table lists the possible values for this field:' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# | Value | Description |' + '\n' - output += ' ' + '# | ---------- | ------------------------------------------ |' + '\n' - output += ' ' + '# | `postgres` | Uses PostgreSQL as the relational database |' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# Note, for use of `postgres`, it uses a [`postgres-controller` CRD](https://github.com/AlanBridgeman/postgres-controller) to create the database' + '\n' - output += ' ' + '# ' + '\n' output += ' ' + f'type: "{database_template.type}"' + '\n' output += ' ' + '\n' - output += ' ' + '# If set to `true`, the database will be created as part of the deployment' + '\n' - output += ' ' + '# This uses the [`postgres-controller` CRD](https://github.com/AlanBridgeman/postgres-controller) to create the database' + '\n' + output += ' ' + '# If the database should be created as part of the deployment' + '\n' output += ' ' + f'create: {str(database_template.create).lower()}' + '\n' output += ' ' + '\n' output += ' ' + '# The host that the database is located on ' + '\n' @@ -287,12 +614,127 @@ class HelmChart: output += ' ' + '\n' output += ' ' + '# The password that is used to access the database' + '\n' output += ' ' + f'password: "{database_template.password}"' + '\n' + output += '\n' + + return output + + def create_generic_secrets_vault_section_of_values_yaml(self) -> str: + """Create a generalized Secrets Vault section of the `values.yaml` file for the Helm chart. + + The Secrets Vault section is used to define the Secrets Vault configuration that the app will use. + + Returns: + str: The Secrets Vault section of the `values.yaml` file + """ + + output = '' + + output += '# Configurations for the secrets vault' + '\n' + output += '# See the [Customized Hashicorp Vault Implementation - Helm Chart](https://git.bridgemanaccessible.ca/Bridgeman-Accessible/custom-hashicorp-vault-helm) for more information' + '\n' + output += 'vault:' + '\n' + output += ' ' + '# If a secrets vault should be used' + '\n' + output += ' ' + '# That is, if a dedicated software for secret management should be used' + '\n' + output += ' ' + '# This should virtually always be true if storing any kind of sensitive information as it\'s the most secure option' + '\n' + output += ' ' + 'enabled: true' + '\n' output += ' ' + '\n' - output += ' ' + '# The port that the database listens on' + '\n' - output += ' ' + f'#port: {database_template.port}' + '\n' + output += ' ' + '# The type of secrets vault (or storage if `enabled` is `false`) to use.' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# Vaults' + '\n' + output += ' ' + '# ------' + '\n' + output += ' ' + '# The following table lists the supported vault types:' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# | Type | Description |' + '\n' + output += ' ' + '# | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- |' + '\n' + output += ' ' + '# | `hashicorp` | [Customized Hashicorp Vault Implementation - Helm Chart](https://git.bridgemanaccessible.ca/Bridgeman-Accessible/custom-hashicorp-vault-helm) |' + '\n' + output += ' ' + '# | `azure` | Uses Azure Key Vault (Required fields: `vaultName`, `clientId`, `clientSecret`, `tenantId`) |' + "\n" + output += ' ' + '# ' + '\n' + output += ' ' + '# Storage' + '\n' + output += ' ' + '# -------' + '\n' + output += ' ' + '# The following table lists the supported storage types.' + '\n' + output += ' ' + '# These are methods OUTSIDE of a dedicated "vault" software.' + '\n' + output += ' ' + '# These are generally **discouraged** as they are less secure.' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# | Type | Description | Current Status | Required Fields |' + '\n' + output += ' ' + '# | ----------- | ------------------------------- | -------------- | ---------------- |' + '\n' + output += ' ' + '# | `file` | Uses a file | To-Do | `path` |' + '\n' + output += ' ' + '# | `mem` | Uses in-memory (no persistance) | To-Do | N/A |' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + 'type: ""' + '\n' output += ' ' + '\n' - output += ' ' + '# Allows for distinguishing between multiple database instances/servers' + '\n' - output += ' ' + f'#instance_id: "{database_template.instance_id}"' + '\n' + output += ' ' + '# The name of the vault instance to connect to' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# This is relevant if type is set to `hashicorp` or `azure`' + '\n' + output += ' ' + '# Note, if `create` is true this is ignored' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# For `hashicorp`, this is generally the hostname of the Hashicorp Vault instance to connect to' + '\n' + output += ' ' + '# For `azure`, this is the name of the Azure Key Vault instance to connect to' + '\n' + output += ' ' + '#vaultName: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The client ID of the Azure Key Vault instance' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + output += ' ' + '#client-id: ' + '\n' + output += ' ' + '\n' + output += ' ' + '# The client secret of the Azure Key Vault instance' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + output += ' ' + '#client-secret: ' + '\n' + output += ' ' + '\n' + output += ' ' + '# The tenant ID of the Azure Key Vault instance' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + output += ' ' + '#tenant-id: ' + '\n' + output += ' ' + '\n' + output += ' ' + '# Configurations to create a Hashicorp Vault instance as part of the Helm chart' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# THIS IS ONLY RELEVANT IF `type` IS SET TO `hashicorp`' + '\n' + output += ' ' + '# ' + '\n' + output += ' ' + '# See subchart\'s `values.yaml` for more information' + '\n' + output += ' ' + 'create:' + '\n' + output += ' ' + ' ' + '# If a Hashicorp Vault instance should be created as part of the Helm chart' + '\n' + output += ' ' + ' ' + 'enabled: ' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + '# Configurations for the image to use if creating the Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + '# as part of the Helm chart' + '\n' + output += ' ' + ' ' + 'image:' + '\n' + output += ' ' + ' ' + ' ' + '# The repository of the image to use' + '\n' + output += ' ' + ' ' + ' ' + 'repository: ' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The tag of the image to use' + '\n' + output += ' ' + ' ' + ' ' + 'tag: ' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + '# Configurations for the ingress of the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + 'ingress:' + '\n' + output += ' ' + ' ' + ' ' + '# If an ingress should be created for the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + ' ' + 'enabled: ' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The host of the ingress for the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + ' ' + 'host: ' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + '# As part of the custom Hashicorp Vault image it includes a "snapshot server"' + '\n' + output += ' ' + ' ' + '# This allows for the triggering and receiving of manual backups ("snapshots") and restoration via HTTP (which is needed for the backup sidecar)' + '\n' + output += ' ' + ' ' + 'snapshotServer:' + '\n' + output += ' ' + ' ' + ' ' + '# If the snapshot server should be enabled/running' + '\n' + output += ' ' + ' ' + ' ' + 'enabled: true' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The external port that is opened via the service resource' + '\n' + output += ' ' + ' ' + ' ' + 'externalPort: 81' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The internal port that the snapshot server listens on' + '\n' + output += ' ' + ' ' + ' ' + 'internalPort: 8300' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + 'appRole:' + '\n' + output += ' ' + ' ' + ' ' + '# The name of the environment variable/secret that contains the Role ID for the AppRole used by the app' + '\n' + output += ' ' + ' ' + ' ' + 'roleIDSecretName: VAULT_ROLE_ID' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The name of the environment variable/secret that contains the Secret ID for the AppRole used by the app' + '\n' + output += ' ' + ' ' + ' ' + 'secretIDSecretName: VAULT_SECRET_ID' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + '# The capabilities of the policy to create for the app' + '\n' + output += ' ' + ' ' + 'policyCapabilities:' + '\n' + output += ' ' + ' ' + '- read' + '\n' + output += ' ' + ' ' + '- create' + '\n' + output += ' ' + ' ' + '- update' + '\n' output += '\n' return output @@ -313,132 +755,146 @@ class HelmChart: output += '# Configurations for the secrets vault' + '\n' output += 'vault:' + '\n' + output += ' ' + '# If a secrets vault should be used' + '\n' - output += ' ' + '# That is, if a dedicated software for secret management should be used' + '\n' - output += ' ' + '# This should virtually always be true if storing any kind of sensitive information as it\'s the most secure option' + '\n' output += ' ' + 'enabled: true' + '\n' output += ' ' + '\n' + output += ' ' + '# The type of secrets vault to use.' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# Vaults' + '\n' - output += ' ' + '# ------' + '\n' - output += ' ' + '# The following table lists the supported vault types:' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# | Type | Description | Current Status | Required Fields |' + '\n' - output += ' ' + '# | ----------- | -------------------- | -------------- | --------------------------------------------------- |' + '\n' - output += ' ' + '# | `hashicorp` | Uses Hashicorp Vault | Implemented | `vaultName` (if `create` not true) |' + '\n' - output += ' ' + '# | `azure` | Uses Azure Key Vault | Implemented | `vaultName`, `clientId`, `clientSecret`, `tenantId` |' + '\n' - output += ' ' + '# ' + '\n' output += ' ' + f'type: "{secrets_vault_template.type}"' + '\n' output += ' ' + '\n' - output += ' ' + '# Configurations to create a Hashicorp Vault instance as part of the Helm chart' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# THIS IS ONLY RELEVANT IF `type` IS SET TO `hashicorp`' + '\n' - output += ' ' + 'create:' + '\n' - output += ' ' + ' ' + '# If a Hashicorp Vault instance should be created as part of the Helm chart' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - output += ' ' + ' ' + f'enabled: {str(secrets_vault_template.create).lower()}' + '\n' - else: - output += ' ' + ' ' + 'enabled: ' + '\n' - output += ' ' + ' ' + '\n' - output += ' ' + ' ' + '# Configurations for the image to use if creating the Hashicorp Vault instance' + '\n' - output += ' ' + ' ' + '# as part of the Helm chart' + '\n' - output += ' ' + ' ' + 'image:' + '\n' - output += ' ' + ' ' + ' ' + '# The repository of the image to use' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - output += ' ' + ' ' + ' ' + f'repository: {secrets_vault_template.image["repository"]}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'repository: ' + '\n' - output += ' ' + ' ' + ' ' + '\n' - output += ' ' + ' ' + ' ' + '# The tag of the image to use' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - output += ' ' + ' ' + ' ' + f'tag: {secrets_vault_template.image["tag"]}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'tag: ' + '\n' - output += ' ' + ' ' + '\n' - output += ' ' + ' ' + '# Configurations for the ingress of the created Hashicorp Vault instance' + '\n' - output += ' ' + ' ' + 'ingress:' + '\n' - output += ' ' + ' ' + ' ' + '# If an ingress should be created for the created Hashicorp Vault instance' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - output += ' ' + ' ' + ' ' + f'enabled: {str(secrets_vault_template.create and secrets_vault_template.hostname != None).lower()}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'enabled: ' + '\n' - output += ' ' + ' ' + ' ' + '\n' - output += ' ' + ' ' + ' ' + '# The host of the ingress for the created Hashicorp Vault instance' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - output += ' ' + ' ' + ' ' + f'host: {secrets_vault_template.hostname}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'host: ' + '\n' - output += ' ' + ' ' + '\n' - output += ' ' + ' ' + '# Configurations for the storage of the created Hashicorp Vault instance' + '\n' - output += ' ' + ' ' + 'storage:' + '\n' - output += ' ' + ' ' + ' ' + '# The storage class to use for the created Hashicorp Vault instance\'s Persistent Volume Claim' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - if secrets_vault_template.create: - output += ' ' + ' ' + ' ' + f'class: {secrets_vault_template.storage_class}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'class: ' + '\n' - else: - output += ' ' + ' ' + ' ' + 'class: ' + '\n' - output += ' ' + ' ' + ' ' + '\n' - output += ' ' + ' ' + ' ' + '# The size of the created Hashicorp Vault instance\'s Persistent Volume Claim' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - if secrets_vault_template.create: - output += ' ' + ' ' + ' ' + f'size: {secrets_vault_template.storage_size}' + '\n' - else: - output += ' ' + ' ' + ' ' + 'size: ' + '\n' - else: - output += ' ' + ' ' + ' ' + 'size: ' + '\n' - output += ' ' + '\n' - output += ' ' + '# The name of the vault instance to connect to' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# This is relevant if type is set to `hashicorp` or `azure`' + '\n' - output += ' ' + '# Note, if `create` is true this is ignored' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# For `hashicorp`, this is generally the hostname of the Hashicorp Vault instance to connect to' + '\n' - output += ' ' + '# For `azure`, this is the name of the Azure Key Vault instance to connect to' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - if secrets_vault_template.create: - output += ' ' + f'#vaultName: ""' + '\n' - else: + + if (isinstance(secrets_vault_template, HashicorpVault) and not secrets_vault_template.create) or isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + '# The name of the vault instance to connect to' + '\n' + + if isinstance(secrets_vault_template, HashicorpVault) and not secrets_vault_template.create: output += ' ' + f'vaultName: "{secrets_vault_template.hostname}"' + '\n' - elif isinstance(secrets_vault_template, AzureKeyVault): - output += ' ' + f'vaultName: "{secrets_vault_template.name}"' + '\n' - output += ' ' + '\n' - output += ' ' + '# The port of the vault instance to connect to' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# ONLY RELEVANT iF `type` IS SET TO `hashicorp` AND `create` IS NOT TRUE' + '\n' - if isinstance(secrets_vault_template, HashicorpVault): - if not secrets_vault_template.create: - output += ' ' + f'#vaultPort: {secrets_vault_template.port}' + '\n' - else: - output += ' ' + f'vaultPort: {secrets_vault_template.port}' + '\n' - else: - output += ' ' + '#vaultPort: ' + '\n' - output += ' ' + '\n' - output += ' ' + '# The client ID of the Azure Key Vault instance' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + elif isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + f'vaultName: "{secrets_vault_template.name}"' + '\n' + + output += ' ' + '\n' + + if isinstance(secrets_vault_template, HashicorpVault) and not secrets_vault_template.create: + output += ' ' + '# The port of the vault instance to connect to' + '\n' + output += ' ' + f'vaultPort: {secrets_vault_template.port}' + '\n' + output += ' ' + '\n' + if isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + '# The client ID of the Azure Key Vault instance' + '\n' output += ' ' + f'client-id: "{secrets_vault_template.client_id}"' + '\n' - else: - output += ' ' + '#client-id: ' + '\n' - output += ' ' + '\n' - output += ' ' + '# The client secret of the Azure Key Vault instance' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + output += ' ' + '\n' + if isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + '# The client secret of the Azure Key Vault instance' + '\n' output += ' ' + f'client-secret: "{secrets_vault_template.client_secret}"' + '\n' - else: - output += ' ' + '#client-secret: ' + '\n' - output += ' ' + '\n' - output += ' ' + '# The tenant ID of the Azure Key Vault instance' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# ONLY RELEVANT IF `type` IS SET TO `azure`' + '\n' + output += ' ' + '\n' + if isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + '# The tenant ID of the Azure Key Vault instance' + '\n' output += ' ' + f'tenant-id: "{secrets_vault_template.tenant_id}"' + '\n' - else: - output += ' ' + '#tenant-id: ' + '\n' + output += ' ' + '\n' + + if isinstance(secrets_vault_template, HashicorpVault): + output += ' ' + '# Configurations to create a Hashicorp Vault instance as part of the Helm chart' + '\n' + output += ' ' + 'create:' + '\n' + output += ' ' + ' ' + '# If a Hashicorp Vault instance should be created as part of the Helm chart' + '\n' + output += ' ' + ' ' + f'enabled: {str(secrets_vault_template.create).lower()}' + '\n' + if secrets_vault_template.create: + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + '# Configurations for the image to use if creating the Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + '# as part of the Helm chart' + '\n' + output += ' ' + ' ' + 'image:' + '\n' + output += ' ' + ' ' + ' ' + '# The repository of the image to use' + '\n' + output += ' ' + ' ' + ' ' + f'repository: {secrets_vault_template.image["repository"]}' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The tag of the image to use' + '\n' + output += ' ' + ' ' + ' ' + f'tag: {secrets_vault_template.image["tag"]}' + '\n' + output += ' ' + ' ' + '\n' + + output += ' ' + ' ' + '# Configurations for the ingress of the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + 'ingress:' + '\n' + output += ' ' + ' ' + ' ' + '# If an ingress should be created for the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + ' ' + f'enabled: {str(secrets_vault_template.create and secrets_vault_template.hostname != None).lower()}' + '\n' + if secrets_vault_template.create and secrets_vault_template.hostname != None: + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The host of the ingress for the created Hashicorp Vault instance' + '\n' + output += ' ' + ' ' + ' ' + f'host: {secrets_vault_template.hostname}' + '\n' + output += ' ' + ' ' + '\n' + + output += ' ' + ' ' + '# Configurations for the "snapshot server"' + '\n' + output += ' ' + ' ' + 'snapshotServer:' + '\n' + output += ' ' + ' ' + ' ' + '# If the snapshot server should be enabled/running' + '\n' + output += ' ' + ' ' + ' ' + f'enabled: {secrets_vault_template.snapshot_server}' + '\n' + if secrets_vault_template.snapshot_server: + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The external port that is opened via the service resource' + '\n' + output += ' ' + ' ' + ' ' + 'externalPort: 81' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The internal port that the snapshot server listens on' + '\n' + output += ' ' + ' ' + ' ' + 'internalPort: 8300' + '\n' + output += ' ' + ' ' + '\n' + + output += ' ' + ' ' + 'appRole:' + '\n' + output += ' ' + ' ' + ' ' + '# The name of the environment variable/secret that contains the Role ID for the AppRole used by the app' + '\n' + output += ' ' + ' ' + ' ' + 'roleIDSecretName: VAULT_ROLE_ID' + '\n' + output += ' ' + ' ' + ' ' + '\n' + output += ' ' + ' ' + ' ' + '# The name of the environment variable/secret that contains the Secret ID for the AppRole used by the app' + '\n' + output += ' ' + ' ' + ' ' + 'secretIDSecretName: VAULT_SECRET_ID' + '\n' + output += ' ' + ' ' + '\n' + + output += ' ' + ' ' + '# The capabilities of the policy to create for the app' + '\n' + output += ' ' + ' ' + 'policyCapabilities:' + '\n' + for capability in secrets_vault_template.policy_capabilities: + output += ' ' + ' ' + f'- {capability}' + '\n' + + output += '\n' + + return output + + def create_generic_nosql_section_of_values_yaml(self) -> str: + """Create a generalized NoSQL section of the `values.yaml` file for the Helm chart. + + The NoSQL section is used to define the NoSQL storage configuration that the app will use. + + Returns: + str: The NoSQL section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration the NoSQL database' + '\n' + output += '# Within the parlance of the system these are often called "properties" databases (and store less structured data)' + '\n' + output += '# See the [NoSQL Deployment Helm Chart](https://git.bridgemanaccessible.ca/Bridgeman-Accessible/nosql-deploy-helm) for more information' + '\n' + output += 'nosql:' + '\n' + output += ' ' + '# If a NoSQL database should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# Determines the type of NoSQL storage that is used' + '\n' + output += ' ' + 'type: "mongodb" # ' + '\n' + output += ' ' + '\n' + output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' + output += ' ' + '# This uses the [MongoDBCommunity CRD](https://github.com/mongodb/mongodb-kubernetes-operator) to create the resource' + '\n' + output += ' ' + 'create:' + '\n' + output += ' ' + ' ' + '# If to create a resource as part of the deployment process' + '\n' + output += ' ' + ' ' + 'enabled: true # ' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the NoSQL database' + '\n' + output += ' ' + 'name: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The username used to access the NoSQL database' + '\n' + output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' + output += ' ' + 'user: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# The password used to access the NoSQL database' + '\n' + output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' + output += ' ' + 'password: ""' + '\n' + output += ' ' + '\n' + output += ' ' + '# Configurable NoSQL information groupings' + '\n' + output += ' ' + '# For Azure Table Storage these are table names' + '\n' + output += ' ' + '# For MongoDB these are collection names' + '\n' + output += ' ' + 'grouping: {}' + '\n' + output += ' ' + ' ' + f'#Elements in the format:
: ""' + '\n' + output += '\n' return output @@ -456,96 +912,97 @@ class HelmChart: nosql_template = next(template for template in self.templates if isinstance(template, NoSQL)) output += '# Configuration the NoSQL database' + '\n' - output += '# Within the parlance of the system these are often called "properties" databases (and store less structured data)' + '\n' output += 'nosql:' + '\n' + output += ' ' + '# If a NoSQL database should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' output += ' ' + '# Determines the type of NoSQL storage that is used' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# The following table lists the possible values for this field:' + '\n' - output += ' ' + '# ' + '\n' - output += ' ' + '# | Value | Description |' + '\n' - output += ' ' + '# | --------- | ------------------------------------------------------------------------------------------ |' + '\n' - output += ' ' + '# | `mongodb` | Uses MongoDB as the NoSQL database for the default account properties database |' + '\n' - output += ' ' + '# | `azure` | Uses Azure Table Storage as the NoSQL database for the default account properties database |' + '\n' - output += ' ' + '# ' + '\n' output += ' ' + f'type: {nosql_template.type}' + '\n' output += ' ' + '\n' - output += ' ' + '# If to create a resource as part of the deployment process' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' - output += ' ' + '# This uses the [MongoDBCommunity CRD](https://github.com/mongodb/mongodb-kubernetes-operator) to create the resource' + '\n' - output += ' ' + f'create: {str(nosql_template.create).lower()}' + '\n' + output += ' ' + f'create:' + '\n' + output += ' ' + ' ' + '# If to create a resource as part of the deployment process' + '\n' + output += ' ' + ' ' + f'enabled: {str(nosql_template.create).lower()}' + '\n' output += ' ' + '\n' - - output += ' ' + '# The number of replicas/members as part of the Mongo deployment' + '\n' - output += ' ' + '# See the `member` parameter of the [MongoDBCommunity CRD](https://github.com/mongodb/mongodb-kubernetes-operator) for more information' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb` and `create` is set to `true`' + '\n' - if isinstance(nosql_template, MongoDB): - output += ' ' + f'replicaCount: {nosql_template.replica_count}' + '\n' - else: - output += ' ' + '#replicaCount: ' + '\n' - output += ' ' + '\n' - - output += ' ' + '# The TLS configuration for the connection to the NoSQL database' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb` and `create` is set to `true`' + '\n' - output += ' ' + 'tls:' + '\n' - output += ' ' + ' ' + '# If to use TLS for the connection to the NoSQL database' + '\n' - if isinstance(nosql_template, MongoDB): - output += ' ' + ' ' + f'enabled: {str(nosql_template.tls_enabled).lower()}' + '\n' - else: - output += ' ' + ' ' + 'enabled: ' + '\n' - output += ' ' + '\n' - - output += ' ' + '# The connection string used to access the NoSQL database' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb` and `create` is set to `false`' + '\n' - output += ' ' + '# Should be in the following format: `mongodb://:`' + '\n' - output += ' ' + '#connectionString: "mongodb://mongo.example.com:27017"' + '\n' - output += ' ' + '\n' - - output += ' ' + '# The key used to access the NoSQL database' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `azure`' + '\n' + + if isinstance(nosql_template, MongoDB) and not nosql_template.create: + output += ' ' + '# The connection string used to access the NoSQL database' + '\n' + output += ' ' + '# ONLY relevant if `type` is set to `mongodb` and `create` is set to `false`' + '\n' + output += ' ' + '# Should be in the following format: `mongodb://:`' + '\n' + output += ' ' + 'connectionString: "mongodb://mongo.example.com:27017"' + '\n' + output += ' ' + '\n' + if isinstance(nosql_template, AzureTableStorage): + output += ' ' + '# The key used to access the NoSQL database' + '\n' output += ' ' + f'key: "{nosql_template.key}"' + '\n' - else: - output += ' ' + '#key: ""' + '\n' - output += ' ' + '\n' + output += ' ' + '\n' output += ' ' + '# The name of the NoSQL database' + '\n' output += ' ' + f'name: "{nosql_template.db_name}"' + '\n' output += ' ' + '\n' - - output += ' ' + '# The username used to access the NoSQL database' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' + if isinstance(nosql_template, MongoDB): + output += ' ' + '# The username used to access the NoSQL database' + '\n' output += ' ' + f'user: "{nosql_template.user}"' + '\n' - else: - output += ' ' + 'user: ""' + '\n' - output += ' ' + '\n' - - output += ' ' + '# The password used to access the NoSQL database' + '\n' - output += ' ' + '# ONLY relevant if `type` is set to `mongodb`' + '\n' + output += ' ' + '\n' + if isinstance(nosql_template, MongoDB): + output += ' ' + '# The password used to access the NoSQL database' + '\n' output += ' ' + f'password: "{nosql_template.password}"' + '\n' - else: - output += ' ' + 'password: ""' + '\n' - output += '\n' + output += ' ' + '\n' - output += '# Configurable NoSQL information groupings' + '\n' - output += '# For Azure Table Storage these are table names' + '\n' - output += '# For MongoDB these are collection names' + '\n' - output += 'tables:' + '\n' + output += ' ' + '# Configurable NoSQL information groupings' + '\n' + output += ' ' + 'grouping:' + '\n' - for value in nosql_template.tables.values(): + for value in nosql_template.groupings.values(): camel_case_name = value['name'].split('-')[0] for token in value['name'].split('-'): if token != camel_case_name: camel_case_name += token.capitalize() - output += ' ' + f'{camel_case_name}: "{value["value"]}"' + '\n' + output += ' ' + ' ' + f'{camel_case_name}: "{value["value"]}"' + '\n' output += '\n' return output + def create_generic_cache_section_of_values_yaml(self) -> str: + """Create a generalized Cache section of the `values.yaml` file for the Helm chart. + + The Cache section is used to define the cache (usually Redis) configuration that the app will use. + + Returns: + str: The Cache section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for cache server' + '\n' + output += '# See the [Cache Deployment Helm Chart](https://git.bridgemanaccessible.ca/Bridgeman-Accessible/cache-deploy-helm) for more information' + '\n' + output += 'cache:' + '\n' + output += ' ' + '# If a cache (Redis) should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `cache`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "redis"' + '\n' + output += ' ' + '\n' + output += ' ' + 'type: "redis"' + '\n' + output += ' ' + '\n' + output += ' ' + '# Configurations if creating Redis resources as part of the deployment' + '\n' + output += ' ' + 'create:' + '\n' + output += ' ' + ' ' + '# If to create a Redis instance/resource as part of the deployment process' + '\n' + output += ' ' + ' ' + 'enabled: false' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + 'redisData:' + '\n' + output += ' ' + ' ' + ' ' + 'size: 2Gi' + '\n' + output += ' ' + '\n' + output += ' ' + '# The password to use for the Redis server' + '\n' + output += ' ' + 'password: ""' + '\n' + output += ' ' + '\n' + output += '\n' + + return output + def create_cache_section_of_values_yaml(self) -> str: """Create the Cache section of the `values.yaml` file for the Helm chart. @@ -562,63 +1019,59 @@ class HelmChart: output += '# Configuration for cache server' + '\n' output += 'cache:' + '\n' + output += ' ' + '# If a cache (Redis) should be used' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `cache`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "redis"' + '\n' + output += ' ' + '\n' output += ' ' + 'type: "redis"' + '\n' output += ' ' + '\n' - - output += ' ' + '# If to create a Redis instance/resource as part of the deployment process' + '\n' - output += ' ' + f'create: {str(redis_template.create).lower()}' + '\n' + output += ' ' + '# Configurations if creating Redis resources as part of the deployment' + '\n' + output += ' ' + 'create:' + '\n' + output += ' ' + ' ' + '# If to create a Redis instance/resource as part of the deployment process' + '\n' + output += ' ' + ' ' + f'enabled: {str(redis_template.create).lower()}' + '\n' + output += ' ' + ' ' + '\n' + output += ' ' + ' ' + 'redisData:' + '\n' + output += ' ' + ' ' + ' ' + 'size: 2Gi' + '\n' output += ' ' + '\n' - - output += ' ' + '# The image to use for the Redis instance' + '\n' - output += ' ' + '# ONLY relevant if `create` is set to `true`' + '\n' - # If the image dictionary is not empty than we want to include the image values - if redis_template.create and len(redis_template.image) > 0: - output += ' ' + 'image' + '\n' - - # Loop through the image dictionary and write the image values - for key, value in redis_template.image.items(): - output += ' ' + ' ' + f'{key}: "{value}"' + '\n' - else: - output += ' ' + 'image: {}' + '\n' - output += ' ' + '\n' - - output += ' ' + '# The number of replicas of the Redis instance' + '\n' - output += ' ' + '# ONLY relevant if `create` is set to `true`' + '\n' - if redis_template.create: - output += ' ' + f'replicaCount: {redis_template.replicaCount}' + '\n' - else: - output += ' ' + '#replicaCount: ' + '\n' - output += ' ' + '\n' - - output += ' ' + '# Hostname of the Redis server' + '\n' - output += ' ' + '# ONLY relevant if `create` is set to `false`' + '\n' - if redis_template.create: - output += ' ' + '#hostName: ""' + '\n' - else: - output += ' ' + f'hostName: "{redis_template.hostName}"' + '\n' - output += ' ' + '\n' - output += ' ' + '# The password to use for the Redis server' + '\n' output += ' ' + f'password: "{redis_template.password}"' + '\n' output += ' ' + '\n' + output += '\n' - output += ' ' + '# The port of the Redis server' + '\n' - output += ' ' + f'port: "{redis_template.port}"' + '\n' - output += ' ' + '\n' + return output + + def create_generic_third_party_service_section_of_values_yaml(self) -> str: + """Create a generalized Third Party Service section of the `values.yaml` file for the Helm chart. - output += ' ' + '# Redis TLS Configurations' + '\n' - output += ' ' + 'tls:' + '\n' - output += ' ' + ' ' + '# If TLS is enabled for the Redis instance' + '\n' - output += ' ' + ' ' + f'enabled: {str(redis_template.tls_enabled).lower()}' + '\n' - output += ' ' + ' ' + '\n' - output += ' ' + ' ' + '# The port of the Redis instance for TLS' + '\n' - output += ' ' + ' ' + '# ONLY relevant if `tls.enabled` is set to `true`' + '\n' - if redis_template.tls_enabled: - output += ' ' + ' ' + f'port: "{redis_template.tls_port}"' + '\n' - else: - output += ' ' + ' ' + '#port: ""' + '\n' - output += ' ' + '\n' + The Third Party Service section is used to define any third-party service configurations that the app will use. + Ex. OpenAI, Stripe, etc... + Returns: + str: The Third Party Service section of the `values.yaml` file + """ + + output = '' + + output += '# Configurations for integration with third-party services' + '\n' + output += 'thirdParty:' + '\n' + for template in self.templates: + if isinstance(template, ThirdPartyService): + output += ' ' + '# Configurations for the ' + template.name.capitalize() + ' integration' + '\n' + output += ' ' + f'{template.name}:' + '\n' + output += ' ' + ' ' + '# If the integration is enabled' + '\n' + output += ' ' + ' ' + f'enabled: {str(template.enabled).lower()}' + '\n' + output += ' ' + ' ' + '\n' + for key, value in template.vars.items(): + camel_case_name = key.split('_')[0] + for token in key.split('_'): + if token != camel_case_name: + camel_case_name += token.capitalize() + + output += ' ' + ' ' + f'{camel_case_name}: <{camel_case_name}>' + '\n' + output += ' ' + ' ' + '\n' + return output def create_third_party_service_section_of_values_yaml(self) -> str: @@ -653,6 +1106,187 @@ class HelmChart: return output + def create_generic_logging_sidecar_section_of_values_yaml(self) -> str: + """Create a generalized Logging Sidecar section of the `values.yaml` file for the Helm chart. + + The Logging Sidecar section is used to define the logging sidecar configuration that will be used. + + Returns: + str: The Logging Sidecar section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the logging sidecar' + '\n' + output += 'loggingSidecar:' + '\n' + output += ' ' + '# If the logging sidecar should be used (enabled)' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `loggingSidecar`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "logging-sidecar"' + '\n' + output += ' ' + '\n' + output += ' ' + '# The image used for the logging sidecar' + '\n' + output += ' ' + 'image:' + '\n' + output += ' ' + ' ' + 'repository: "/"' + '\n' + output += ' ' + ' ' + 'tag: v1.0.0' + '\n' + output += ' ' + ' ' + 'pullPolicy: IfNotPresent' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the logging sidecar container' + '\n' + output += ' ' + 'name: logging-sidecar' + '\n' + output += ' ' + '\n' + output += ' ' + '# The port that the logging sidecar listens on' + '\n' + output += ' ' + 'port: 3000' + '\n' + output += ' ' + '\n' + output += ' ' + '# Log aggretator authentication details' + '\n' + output += ' ' + 'auth:' + '\n' + output += ' ' + ' ' + 'username: ""' + '\n' + output += ' ' + ' ' + 'password: ""' + '\n' + output += ' ' + '\n' + output += ' ' + 'resources:' + '\n' + output += ' ' + ' ' + 'requests:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 20m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 128Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 50Mi' + '\n' + output += ' ' + ' ' + 'limits:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 200m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 256Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 200Mi' + '\n' + output += '\n' + + return output + + def create_logging_sidecar_section_of_values_yaml(self) -> str: + """Create the Logging Sidecar section of the `values.yaml` file for the Helm chart. + + The Logging Sidecar section is used to define the logging sidecar configuration that will be used. + + Returns: + str: The Logging Sidecar section of the `values.yaml` file + """ + + output = '' + + # Get the Redis template from the templates provided + logging_sidecar_template = next(template for template in self.templates if isinstance(template, LoggingSidecar)) + + output += '# Configuration for the logging sidecar' + '\n' + output += 'loggingSidecar:' + '\n' + output += ' ' + '# If the logging sidecar should be used (enabled)' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# This override tells the helper: "Ignore the alias (Chart Name - `loggingSidecar`), use this string for K8s resources instead"' + '\n' + output += ' ' + 'nameOverride: "logging-sidecar"' + '\n' + output += ' ' + '\n' + output += ' ' + '# The image used for the logging sidecar' + '\n' + output += ' ' + 'image:' + '\n' + output += ' ' + ' ' + 'repository: "containers.bridgemanaccessible.ca/k8s/ba-logging-sidecar"' + '\n' + output += ' ' + ' ' + 'tag: v1.0.4' + '\n' + output += ' ' + ' ' + 'pullPolicy: Always' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the logging sidecar container' + '\n' + output += ' ' + 'name: logging-sidecar' + '\n' + output += ' ' + '\n' + output += ' ' + '# The port that the logging sidecar listens on' + '\n' + output += ' ' + 'port: 3000' + '\n' + output += ' ' + '\n' + output += ' ' + '# Log aggretator authentication details' + '\n' + output += ' ' + 'auth:' + '\n' + output += ' ' + ' ' + f'username: "{logging_sidecar_template.log_aggregator_username}"' + '\n' + output += ' ' + ' ' + f'password: "{logging_sidecar_template.log_aggregator_password}"' + '\n' + output += ' ' + '\n' + output += ' ' + 'resources:' + '\n' + output += ' ' + ' ' + 'requests:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 20m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 128Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 50Mi' + '\n' + output += ' ' + ' ' + 'limits:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 200m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 256Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 200Mi' + '\n' + output += '\n' + + return output + + def create_generic_backup_sidecar_section_of_values_yaml(self) -> str: + """Create a generalized Backup Sidecar section of the `values.yaml` file for the Helm chart. + + The Backup Sidecar section is used to define the backup sidecar configuration that will be used. + + Returns: + str: The Backup Sidecar section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the backup sidecar' + '\n' + output += 'backupSidecar:' + '\n' + output += ' ' + '# If the backup sidecar should be used (enabled)' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# The image used for the backup sidecar' + '\n' + output += ' ' + 'image:' + '\n' + output += ' ' + ' ' + 'repository: "/"' + '\n' + output += ' ' + ' ' + 'tag: v1.0.0' + '\n' + output += ' ' + ' ' + 'pullPolicy: IfNotPresent' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the backup sidecar container' + '\n' + output += ' ' + 'name: backup-sidecar' + '\n' + output += ' ' + '\n' + output += ' ' + '# The port that the backup sidecar listens on' + '\n' + output += ' ' + 'port: 3001' + '\n' + output += ' ' + '\n' + output += ' ' + 'resources:' + '\n' + output += ' ' + ' ' + 'requests:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 20m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 128Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 50Mi' + '\n' + output += ' ' + ' ' + 'limits:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 500m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 256Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 200Mi' + '\n' + + return output + + def create_backup_sidecar_section_of_values_yaml(self) -> str: + """Create the Backup Sidecar section of the `values.yaml` file for the Helm chart. + + The Backup Sidecar section is used to define the backup sidecar configuration that will be used. + + Returns: + str: The Backup Sidecar section of the `values.yaml` file + """ + + output = '' + + output += '# Configuration for the backup sidecar' + '\n' + output += 'backupSidecar:' + '\n' + output += ' ' + '# If the backup sidecar should be used (enabled)' + '\n' + output += ' ' + 'enabled: true' + '\n' + output += ' ' + '\n' + output += ' ' + '# The image used for the backup sidecar' + '\n' + output += ' ' + 'image:' + '\n' + output += ' ' + ' ' + 'repository: "containers.bridgemanaccessible.ca/k8s/backup-sidecar"' + '\n' + output += ' ' + ' ' + 'tag: v1.0.65' + '\n' + output += ' ' + ' ' + 'pullPolicy: Always' + '\n' + output += ' ' + '\n' + output += ' ' + '# The name of the backup sidecar container' + '\n' + output += ' ' + 'name: backup-sidecar' + '\n' + output += ' ' + '\n' + output += ' ' + '# The port that the backup sidecar listens on' + '\n' + output += ' ' + 'port: 3001' + '\n' + output += ' ' + '\n' + output += ' ' + 'resources:' + '\n' + output += ' ' + ' ' + 'requests:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 20m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 128Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 50Mi' + '\n' + output += ' ' + ' ' + 'limits:' + '\n' + output += ' ' + ' ' + ' ' + 'cpu: 500m' + '\n' + output += ' ' + ' ' + ' ' + 'memory: 256Mi' + '\n' + output += ' ' + ' ' + ' ' + 'ephemeralStorage: 200Mi' + '\n' + + return output + def write_values_yaml(self): """Write the `values.yaml` file for the Helm chart. @@ -663,16 +1297,59 @@ class HelmChart: """ with open('values.yaml', 'w') as f: - # replicas section (mostly just `replicaCount` but...) - f.write(self.create_replicas_section_of_values_yaml()) + # App section + f.write(self.create_generic_app_section_of_values_yaml()) - # image section - f.write(self.create_image_section_of_values_yaml()) + # Ingress section + f.write(self.create_generic_ingress_section_of_values_yaml()) - # container section - f.write(self.create_container_section_of_values_yaml()) + # Add the extra environment variables for the deployment to the `values.yaml` file + f.write(self.create_generic_deployment_extra_vars_section_of_values_yaml()) + + # If a OAuth template is included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, OAuth) for template in self.templates): + f.write(self.create_generic_oauth_section_of_values_yaml()) - # ingress section + # If a Database template is included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, Database) for template in self.templates): + f.write(self.create_generic_database_section_of_values_yaml()) + + # If a Secrets Vault template is included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, SecretsVault) for template in self.templates): + f.write(self.create_generic_secrets_vault_section_of_values_yaml()) + + # If a Database template is included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, NoSQL) for template in self.templates): + f.write(self.create_generic_nosql_section_of_values_yaml()) + + # If a Redis template is included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, Redis) for template in self.templates): + f.write(self.create_generic_cache_section_of_values_yaml()) + + # If any Third Party Service templates are included in the provided templates than we want to include the appropriate section to the `values.yaml` file. + if any(isinstance(template, ThirdPartyService) for template in self.templates): + f.write(self.create_generic_third_party_service_section_of_values_yaml()) + + # Logging Sidecar section + f.write(self.create_generic_logging_sidecar_section_of_values_yaml()) + + # Backup Sidecar section + f.write(self.create_generic_backup_sidecar_section_of_values_yaml()) + + def write_filled_in_values_yaml(self): + """Write the `values.yaml` file for the Helm chart. + + Note, that this generates a `values.yaml` file that is specific to the templates provided. + This means, that it's generally better for usage than distribution. + + In other words, because it's likely it could contain sensitive information, it's not recommended to distribute this file (or include it in git etc...) + """ + + with open('values.filled.yaml', 'w') as f: + # App section + f.write(self.create_app_section_of_values_yaml()) + + # Ingress section f.write(self.create_ingress_section_of_values_yaml()) # Add the extra environment variables for the deployment to the `values.yaml` file @@ -701,7 +1378,13 @@ class HelmChart: # If any Third Party Service templates are included in the provided templates than we want to include the appropriate section to the `values.yaml` file. if any(isinstance(template, ThirdPartyService) for template in self.templates): f.write(self.create_third_party_service_section_of_values_yaml()) - + + # Logging Sidecar section + f.write(self.create_logging_sidecar_section_of_values_yaml()) + + # Backup Sidecar section + f.write(self.create_backup_sidecar_section_of_values_yaml()) + def write_helmignore(self): """Write the .helmignore file for the Helm chart. @@ -712,32 +1395,77 @@ class HelmChart: with open('.helmignore', 'w') as f: f.write('# Ignore the ignore file' + '\n') f.write('.helmignore' + '\n') + f.write('\n') f.write('# Ignore the Helm chart\'s packaged tarball' + '\n') - f.write('*.tgz' + '\n') + f.write(f'{self.chartName}-*.tgz' + '\n') + f.write('\n') + + f.write('# Ignore the filled in values file' + '\n') + f.write('values.filled.yaml' + '\n') + f.write('\n') if os.path.exists('.git'): - f.write('# Ignore git files (In case done in the same directory as code)' + '\n') + f.write('# Ignore git files' + '\n') f.write('.git' + '\n') if os.path.exists('.gitignore'): f.write('.gitignore' + '\n') - if os.path.exists('README.md'): - f.write('# Ignore the README file (In case done in the same directory as code)' + '\n') - f.write('README.md' + '\n') + if os.path.exists('.git') or os.path.exists('.gitignore'): + f.write('\n') + + if os.path.exists('.github') or os.path.exists('.forgejo'): + f.write('# Ignore the GitHub/Forgejo workflow files' + '\n') + + if os.path.exists('.github'): + f.write('.github' + '\n') + + if os.path.exists('.forgejo'): + f.write('.forgejo' + '\n') + + f.write('\n') + + if os.path.exists('.dockerignore') or os.path.exists('Dockerfile'): + f.write('# Ignore Docker files' + '\n') + + if os.path.exists('.dockerignore'): + f.write('.dockerignore' + '\n') + + if os.path.exists('Dockerfile'): + f.write('Dockerfile' + '\n') + + f.write('\n') if os.path.exists('requirements.txt'): f.write('# Ignore the requirements file (In case done in the same directory as code)' + '\n') f.write('requirements.txt' + '\n') + f.write('\n') if os.path.exists('create-helm-chart.py'): f.write('# Ignore this file (In case done in the same directory as code)' + '\n') f.write('create-helm-chart.py' + '\n') + f.write('\n') def package(self): """Package the Helm chart for publishing.""" + print('Building Helm chart dependencies...') + + result = subprocess.run(['helm', 'dependency', 'build']) + + if result.returncode != 0: + raise Exception('Failed to build Helm chart dependencies.') + + print('Dependencies built successfully. Linting Helm chart...') + + result = subprocess.run(['helm', 'lint', '.']) + + if result.returncode != 0: + raise Exception('Helm chart linting failed.') + + print('Helm chart linted successfully. Packaging Helm chart...') + result = subprocess.run(['helm', 'package', '.']) if result.returncode != 0: diff --git a/src/LoggingSidecar.py b/src/LoggingSidecar.py new file mode 100644 index 0000000..b9e1202 --- /dev/null +++ b/src/LoggingSidecar.py @@ -0,0 +1,9 @@ +from .Template import Template + +class LoggingSidecar(Template): + def __init__(self, log_aggregator_username: str, log_aggregator_password: str): + self.log_aggregator_username = log_aggregator_username + self.log_aggregator_password = log_aggregator_password + + def write(self): + pass \ No newline at end of file diff --git a/src/MongoDB.py b/src/MongoDB.py index 8f97e14..0096039 100644 --- a/src/MongoDB.py +++ b/src/MongoDB.py @@ -1,8 +1,8 @@ from .NoSQL import NoSQL class MongoDB (NoSQL): - def __init__(self, db_name: str, user: str, password: str, tables: dict[str, str], create: bool = True, replica_count: int = 3, tls_enabled: bool = False): - super().__init__('mongodb', db_name, tables, create) + def __init__(self, db_name: str, user: str, password: str, collections: dict[str, str], create: bool = True, replica_count: int = 3, tls_enabled: bool = False): + super().__init__('mongodb', db_name, collections, create) self.user = user self.password = password @@ -10,105 +10,111 @@ class MongoDB (NoSQL): self.tls_enabled = tls_enabled def write(self): - super().write() + # =========================================================== + # DEPRECATED FILES - USING HELM DEPENDENCY (SUBCHART) INSTEAD + # =========================================================== + + pass + + #super().write() - with open('templates/mongo-service-account-database.yaml', 'w') as f: - f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') - f.write('apiVersion: v1' + '\n') - f.write('kind: ServiceAccount' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: mongodb-database' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('{{- end -}}') + #with open('templates/mongo-service-account-database.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') + # f.write('apiVersion: v1' + '\n') + # f.write('kind: ServiceAccount' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: mongodb-database' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('{{- end -}}') - with open('templates/mongo-role-database.yaml', 'w') as f: - f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') - f.write('apiVersion: rbac.authorization.k8s.io/v1' + '\n') - f.write('kind: Role' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: mongodb-database' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('rules:' + '\n') - f.write(' ' + '- apiGroups:' + '\n') - f.write(' ' + ' ' + '- ""' + '\n') - f.write(' ' + ' ' + 'resources:' + '\n') - f.write(' ' + ' ' + '- secrets' + '\n') - f.write(' ' + ' ' + 'verbs:' + '\n') - f.write(' ' + ' ' + '- get' + '\n') - f.write(' ' + '- apiGroups:' + '\n') - f.write(' ' + ' ' + '- ""' + '\n') - f.write(' ' + ' ' + 'resources:' + '\n') - f.write(' ' + ' ' + '- pods' + '\n') - f.write(' ' + ' ' + 'verbs:' + '\n') - f.write(' ' + ' ' + '- patch' + '\n') - f.write(' ' + ' ' + '- delete' + '\n') - f.write(' ' + ' ' + '- get' + '\n') - f.write('{{- end -}}') + #with open('templates/mongo-role-database.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') + # f.write('apiVersion: rbac.authorization.k8s.io/v1' + '\n') + # f.write('kind: Role' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: mongodb-database' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('rules:' + '\n') + # f.write(' ' + '- apiGroups:' + '\n') + # f.write(' ' + ' ' + '- ""' + '\n') + # f.write(' ' + ' ' + 'resources:' + '\n') + # f.write(' ' + ' ' + '- secrets' + '\n') + # f.write(' ' + ' ' + 'verbs:' + '\n') + # f.write(' ' + ' ' + '- get' + '\n') + # f.write(' ' + '- apiGroups:' + '\n') + # f.write(' ' + ' ' + '- ""' + '\n') + # f.write(' ' + ' ' + 'resources:' + '\n') + # f.write(' ' + ' ' + '- pods' + '\n') + # f.write(' ' + ' ' + 'verbs:' + '\n') + # f.write(' ' + ' ' + '- patch' + '\n') + # f.write(' ' + ' ' + '- delete' + '\n') + # f.write(' ' + ' ' + '- get' + '\n') + # f.write('{{- end -}}') - with open('templates/mongo-role-binding-database.yaml', 'w') as f: - f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') - f.write('apiVersion: rbac.authorization.k8s.io/v1' + '\n') - f.write('kind: RoleBinding' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: mongodb-database' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('subjects:' + '\n') - f.write('- kind: ServiceAccount' + '\n') - f.write(' ' + 'name: mongodb-database' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('roleRef:' + '\n') - f.write(' ' + 'kind: Role' + '\n') - f.write(' ' + 'name: mongodb-database' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write(' ' + 'apiGroup: rbac.authorization.k8s.io' + '\n') - f.write('{{- end -}}') + #with open('templates/mongo-role-binding-database.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') + # f.write('apiVersion: rbac.authorization.k8s.io/v1' + '\n') + # f.write('kind: RoleBinding' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: mongodb-database' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('subjects:' + '\n') + # f.write('- kind: ServiceAccount' + '\n') + # f.write(' ' + 'name: mongodb-database' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('roleRef:' + '\n') + # f.write(' ' + 'kind: Role' + '\n') + # f.write(' ' + 'name: mongodb-database' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write(' ' + 'apiGroup: rbac.authorization.k8s.io' + '\n') + # f.write('{{- end -}}') - with open('templates/mongo-credentials-secret.yaml', 'w') as f: - f.write('{{- if eq .Values.nosql.type "mongodb" -}}' + '\n') - f.write('apiVersion: v1' + '\n') - f.write('kind: Secret' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n') - f.write('type: Opaque' + '\n') - f.write('data:' + '\n') - f.write(' ' + 'user: {{ .Values.nosql.user | b64enc }}' + '\n') - f.write(' ' + 'password: {{ .Values.nosql.password | b64enc }}' + '\n') - f.write(' ' + '{{- if and (.Values.nosql.connectionString) (not .values.nosql.create) }}' + '\n') - f.write(' ' + 'connection-string: {{ .Values.nosql.connectionString | b64enc }}' + '\n') - f.write(' ' + '{{- else if .Values.nosql.create }}' + '\n') - f.write(' ' + 'connection-string: {{ printf "mongodb://%s:%s@%s-mongo-svc.%s.svc.cluster.local:27017/%s?replicaSet=%s-mongo" .Values.nosql.user .Values.nosql.password .Release.Name .Release.Namespace .Values.nosql.name .Release.Name | b64enc }}' + '\n') - f.write(' ' + '{{- end }}' + '\n') - f.write('{{- end -}}') + #with open('templates/mongo-credentials-secret.yaml', 'w') as f: + # f.write('{{- if eq .Values.nosql.type "mongodb" -}}' + '\n') + # f.write('apiVersion: v1' + '\n') + # f.write('kind: Secret' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n') + # f.write('type: Opaque' + '\n') + # f.write('data:' + '\n') + # f.write(' ' + 'user: {{ .Values.nosql.user | b64enc }}' + '\n') + # f.write(' ' + 'password: {{ .Values.nosql.password | b64enc }}' + '\n') + # f.write(' ' + '{{- if and (.Values.nosql.connectionString) (not .values.nosql.create) }}' + '\n') + # f.write(' ' + 'connection-string: {{ .Values.nosql.connectionString | b64enc }}' + '\n') + # f.write(' ' + '{{- else if .Values.nosql.create }}' + '\n') + # f.write(' ' + 'connection-string: {{ printf "mongodb://%s:%s@%s-mongo-svc.%s.svc.cluster.local:27017/%s?replicaSet=%s-mongo" .Values.nosql.user .Values.nosql.password .Release.Name .Release.Namespace .Values.nosql.name .Release.Name | b64enc }}' + '\n') + # f.write(' ' + '{{- end }}' + '\n') + # f.write('{{- end -}}') - with open('templates/mongo.yaml', 'w') as f: - f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') - f.write('apiVersion: mongodbcommunity.mongodb.com/v1' + '\n') - f.write('kind: MongoDBCommunity' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-mongo' + '\n') - f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') - f.write('spec:' + '\n') - f.write(' ' + 'members: {{ .Values.nosql.replicaCount }}' + '\n') - f.write(' ' + 'type: ReplicaSet' + '\n') - f.write(' ' + 'version: 4.4.0' + '\n') - f.write(' ' + 'security:' + '\n') - f.write(' ' + ' ' + 'authentication:' + '\n') - f.write(' ' + ' ' + ' ' + 'ignoreUnknownUsers: true' + '\n') - f.write(' ' + ' ' + ' ' + 'modes:' + '\n') - f.write(' ' + ' ' + ' ' + '- SCRAM' + '\n') - f.write(' ' + ' ' + 'tls:' + '\n') - f.write(' ' + ' ' + ' ' + 'enabled: {{ .Values.nosql.tls.enabled }}' + '\n') - f.write(' ' + 'readinessProbe:' + '\n') - f.write(' ' + ' ' + 'initialDelaySeconds: 30' + '\n') - f.write(' ' + ' ' + 'periodSeconds: 10' + '\n') - f.write(' ' + 'users:' + '\n') - f.write(' ' + ' ' + '- name: {{ .Values.nosql.user }}' + '\n') - f.write(' ' + ' ' + ' ' + 'db: {{ .Values.nosql.name }}' + '\n') - f.write(' ' + ' ' + ' ' + 'passwordSecretRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') - f.write(' ' + ' ' + ' ' + 'roles:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: readWrite' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'db: {{ .Values.nosql.name }}' + '\n') - f.write(' ' + ' ' + ' ' + 'scramCredentialsSecretName: {{ .Release.Name }}-mongo-scram' + '\n') - f.write('{{- end -}}') \ No newline at end of file + #with open('templates/mongo.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.nosql.type "mongodb") (.Values.nosql.create) -}}' + '\n') + # f.write('apiVersion: mongodbcommunity.mongodb.com/v1' + '\n') + # f.write('kind: MongoDBCommunity' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-mongo' + '\n') + # f.write(' ' + 'namespace: {{ .Release.Namespace }}' + '\n') + # f.write('spec:' + '\n') + # f.write(' ' + 'members: {{ .Values.nosql.replicaCount }}' + '\n') + # f.write(' ' + 'type: ReplicaSet' + '\n') + # f.write(' ' + 'version: 4.4.0' + '\n') + # f.write(' ' + 'security:' + '\n') + # f.write(' ' + ' ' + 'authentication:' + '\n') + # f.write(' ' + ' ' + ' ' + 'ignoreUnknownUsers: true' + '\n') + # f.write(' ' + ' ' + ' ' + 'modes:' + '\n') + # f.write(' ' + ' ' + ' ' + '- SCRAM' + '\n') + # f.write(' ' + ' ' + 'tls:' + '\n') + # f.write(' ' + ' ' + ' ' + 'enabled: {{ .Values.nosql.tls.enabled }}' + '\n') + # f.write(' ' + 'readinessProbe:' + '\n') + # f.write(' ' + ' ' + 'initialDelaySeconds: 30' + '\n') + # f.write(' ' + ' ' + 'periodSeconds: 10' + '\n') + # f.write(' ' + 'users:' + '\n') + # f.write(' ' + ' ' + '- name: {{ .Values.nosql.user }}' + '\n') + # f.write(' ' + ' ' + ' ' + 'db: {{ .Values.nosql.name }}' + '\n') + # f.write(' ' + ' ' + ' ' + 'passwordSecretRef:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') + # f.write(' ' + ' ' + ' ' + 'roles:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: readWrite' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'db: {{ .Values.nosql.name }}' + '\n') + # f.write(' ' + ' ' + ' ' + 'scramCredentialsSecretName: {{ .Release.Name }}-mongo-scram' + '\n') + # f.write('{{- end -}}') \ No newline at end of file diff --git a/src/NoSQL.py b/src/NoSQL.py index 682a70e..9b571d6 100644 --- a/src/NoSQL.py +++ b/src/NoSQL.py @@ -1,13 +1,13 @@ from .Template import Template class NoSQL (Template): - def __init__(self, type: str, db_name: str, tables: dict[str, dict[str, str]], create: bool = True): + def __init__(self, type: str, db_name: str, groupings: dict[str, dict[str, str]], create: bool = True): """Use of a NoSQL storage system Args: type (str): The type of NoSQL storage to use db_name (str): The name of the NoSQL storage to connect to - tables (dict[str, dict[str, str]]): A dictionary of NoSQL tables/collections + groupings (dict[str, dict[str, str]]): A dictionary of NoSQL groupings (tables/collections) create (bool, optional): Whether to create the NoSQL resources as part of the Helm deployment. Defaults to True. """ @@ -15,24 +15,24 @@ class NoSQL (Template): self.type = type self.db_name = db_name - self.tables = tables + self.groupings = groupings self.create = create def write(self): - with open('templates/storage-tables-config-map.yaml', 'w') as f: + with open('templates/nosql-grouping-config-map.yaml', 'w') as f: f.write('apiVersion: v1' + '\n') f.write('kind: ConfigMap' + '\n') f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-storage-tables' +'\n') + f.write(' ' + 'name: {{ .Release.Name }}-nosql-grouping' +'\n') f.write(' ' + 'labels:' + '\n') f.write(' ' + ' ' + 'app: {{ .Release.Name }}' + '\n') f.write('data:' + '\n') # Loop over tables dictionary and get the values - for value in self.tables.values(): + for value in self.groupings.values(): snake_case_name = value['name'].split('-')[0] for token in value['name'].split('-'): if token != snake_case_name: snake_case_name += token.capitalize() - f.write(' ' + value['name'] + ': {{ .Values.tables.' + snake_case_name + ' }}' + '\n') \ No newline at end of file + f.write(' ' + value['name'] + ': {{ .Values.nosql.grouping.' + snake_case_name + ' }}' + '\n') \ No newline at end of file diff --git a/src/OAuth.py b/src/OAuth.py index a517a27..d7f3482 100644 --- a/src/OAuth.py +++ b/src/OAuth.py @@ -1,7 +1,7 @@ from .Template import Template class OAuth (Template): - def __init__(self, base_app_url: str, app_abbreviation: str, app_name: str, service_name: str, dev_port: str): + def __init__(self, base_app_url: str, app_abbreviation: str, app_name: str, service_name: str, dev_port: str, app_reg_contact_email: str): """A class for creating a/some template(s) related to OAuth implementation.""" self.base_app_url = base_app_url @@ -9,6 +9,7 @@ class OAuth (Template): self.app_name = app_name self.service_name = service_name self.dev_port = dev_port + self.app_reg_contact_email = app_reg_contact_email def write(self): with open('templates/oauth-credentials-config-map.yaml', 'w') as f: @@ -23,4 +24,40 @@ class OAuth (Template): f.write(' ' + 'app-abbreviation: {{ .Values.oauth.appAbbreviation }}' + '\n') f.write(' ' + 'app-name: {{ .Values.oauth.appName }}' + '\n') f.write(' ' + 'service-name: {{ .Values.oauth.serviceName }}' + '\n') - f.write(' ' + 'dev-port: {{ .Values.oauth.devPort | quote }}' + '\n') \ No newline at end of file + f.write(' ' + 'dev-port: {{ .Values.oauth.devPort | quote }}' + '\n') + f.write(' ' + 'app-reg-contact-email: {{ .Values.oauth.appRegContactEmail }}' + '\n') + + with open('templates/_oauth.tpl', 'w') as f: + f.write('{{- define "oauth.envVars" -}}' + '\n') + f.write('# OAuth Implementation Stuff' + '\n') + f.write('- name: BASE_APP_URL' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: base-app-url' + '\n') + f.write('- name: APP_ABBRV' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: app-abbreviation' + '\n') + f.write('- name: APP_NAME' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: app-name' + '\n') + f.write('- name: SERVICE_NAME' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: service-name' + '\n') + f.write('- name: DEV_PORT' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: dev-port' + '\n') + f.write('- name: APP_REG_CONTACT_EMAIL' + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'configMapKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-oauth-credentials' + '\n') + f.write(' ' + ' ' + ' ' + 'key: app-reg-contact-email' + '\n') + f.write('{{- end -}}') \ No newline at end of file diff --git a/src/Redis.py b/src/Redis.py index 5e5b36e..f58df38 100644 --- a/src/Redis.py +++ b/src/Redis.py @@ -23,76 +23,81 @@ class Redis (Cache): self.image = image def write(self): + # =========================================================== + # DEPRECATED FILES - USING HELM DEPENDENCY (SUBCHART) INSTEAD + # =========================================================== + + pass + # Call parent class's method/function to write the generic cache templates - super().write_generic_cache_templates('redis', '{{ .Release.Name }}-redis') + #super().write_generic_cache_templates('redis', '{{ .Release.Name }}-redis') # Create the Redis service file - with open('templates/redis-service.yaml', 'w') as f: - f.write('{{- if and (.Values.cache.type "redis") (.Values.cache.create) -}}' + '\n') - f.write('apiVersion: v1' + '\n') - f.write('kind: Service' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-redis' + '\n') - f.write(' ' + 'labels:' + '\n') - f.write(' ' + ' ' + 'app: redis' + '\n') - f.write('spec:' + '\n') - f.write(' ' + 'ports:' + '\n') - f.write(' ' + ' ' + '- port: {{ .Values.cache.port }}' + '\n') - f.write(' ' + ' ' + ' ' + 'targetPort: {{ .Values.cache.port }}' + '\n') - f.write(' ' + 'selector:' + '\n') - f.write(' ' + ' ' + 'app: redis' + '\n') - f.write(' ' + 'type: ClusterIP' + '\n') - f.write('{{- end -}}') - + #with open('templates/redis-service.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.cache.type "redis") (.Values.cache.create) -}}' + '\n') + # f.write('apiVersion: v1' + '\n') + # f.write('kind: Service' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-redis' + '\n') + # f.write(' ' + 'labels:' + '\n') + # f.write(' ' + ' ' + 'app: redis' + '\n') + # f.write('spec:' + '\n') + # f.write(' ' + 'ports:' + '\n') + # f.write(' ' + ' ' + '- port: {{ .Values.cache.port }}' + '\n') + # f.write(' ' + ' ' + ' ' + 'targetPort: {{ .Values.cache.port }}' + '\n') + # f.write(' ' + 'selector:' + '\n') + # f.write(' ' + ' ' + 'app: redis' + '\n') + # f.write(' ' + 'type: ClusterIP' + '\n') + # f.write('{{- end -}}') # Create the Redis deployment file - with open('templates/redis-deployment.yaml', 'w') as f: - f.write('{{- if and (.Values.cache.type "redis") (.Values.cache.create) -}}' + '\n') - f.write('apiVersion: apps/v1' + '\n') - f.write('kind: Deployment' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-redis' + '\n') - f.write(' ' + 'labels:' + '\n') - f.write(' ' + ' ' + 'app: redis' + '\n') - f.write('spec:' + '\n') - f.write(' ' + 'replicas: {{ .Values.cache.replicaCount }}' + '\n') - f.write(' ' + 'selector:' + '\n') - f.write(' ' + ' ' + 'matchLabels:' + '\n') - f.write(' ' + ' ' + ' ' + 'app: redis' + '\n') - f.write(' ' + 'template:' + '\n') - f.write(' ' + ' ' + 'metadata:' + '\n') - f.write(' ' + ' ' + ' ' + 'labels:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + 'app: redis' + '\n') - f.write(' ' + ' ' + 'spec:' + '\n') - f.write(' ' + ' ' + ' ' + 'containers:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: redis' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'image: {{ .Values.cache.image.repository | default "bitnami/redis" }}:{{ .Values.cache.image.tag | default "7.0.5" }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'ports:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.cache.port }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '{{- if .Values.cache.tls.enabled }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.cache.tls.port }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'env:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: ALLOW_EMPTY_PASSWORD' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'value: "false"' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: REDIS_PASSWORD' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: REDIS_DISABLE_COMMANDS' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'value: "FLUSHDB,FLUSHALL"' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# TLS configuration' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_ENABLED' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "{{ .Values.cache.tls.enabled }}"' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_AUTH_CLIENTS' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "yes"' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_PORT_NUMBER' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "{{ .Values.cache.tls.port }}"' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'volumeMounts:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: redis-data' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /bitnami/redis' + '\n') - f.write(' ' + ' ' + ' ' + 'volumes:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: redis-data' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'emptyDir: {}' + '\n') - f.write('{{- end -}}') \ No newline at end of file + #with open('templates/redis-deployment.yaml', 'w') as f: + # f.write('{{- if and (eq .Values.cache.type "redis") (.Values.cache.create) -}}' + '\n') + # f.write('apiVersion: apps/v1' + '\n') + # f.write('kind: Deployment' + '\n') + # f.write('metadata:' + '\n') + # f.write(' ' + 'name: {{ .Release.Name }}-redis' + '\n') + # f.write(' ' + 'labels:' + '\n') + # f.write(' ' + ' ' + 'app: redis' + '\n') + # f.write('spec:' + '\n') + # f.write(' ' + 'replicas: {{ .Values.cache.replicaCount }}' + '\n') + # f.write(' ' + 'selector:' + '\n') + # f.write(' ' + ' ' + 'matchLabels:' + '\n') + # f.write(' ' + ' ' + ' ' + 'app: redis' + '\n') + # f.write(' ' + 'template:' + '\n') + # f.write(' ' + ' ' + 'metadata:' + '\n') + # f.write(' ' + ' ' + ' ' + 'labels:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + 'app: redis' + '\n') + # f.write(' ' + ' ' + 'spec:' + '\n') + # f.write(' ' + ' ' + ' ' + 'containers:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: redis' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'image: {{ .Values.cache.image.repository | default "bitnami/redis" }}:{{ .Values.cache.image.tag | default "7.0.5" }}' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'ports:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.cache.port }}' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '{{- if .Values.cache.tls.enabled }}' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- containerPort: {{ .Values.cache.tls.port }}' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'env:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: ALLOW_EMPTY_PASSWORD' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'value: "false"' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: REDIS_PASSWORD' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-credentials' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: REDIS_DISABLE_COMMANDS' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'value: "FLUSHDB,FLUSHALL"' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# TLS configuration' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_ENABLED' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "{{ .Values.cache.tls.enabled }}"' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_AUTH_CLIENTS' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "yes"' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '#- name: REDIS_TLS_PORT_NUMBER' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '# value: "{{ .Values.cache.tls.port }}"' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'volumeMounts:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '- name: redis-data' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /bitnami/redis' + '\n') + # f.write(' ' + ' ' + ' ' + 'volumes:' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + '- name: redis-data' + '\n') + # f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'emptyDir: {}' + '\n') + # f.write('{{- end -}}') \ No newline at end of file diff --git a/src/Service.py b/src/Service.py index 920d01e..53fac93 100644 --- a/src/Service.py +++ b/src/Service.py @@ -20,4 +20,4 @@ class Service (Template): f.write(' ' + 'ports:' + '\n') f.write(' ' + ' ' + '- protocol: TCP' + '\n') f.write(' ' + ' ' + ' ' + 'port: 80' + '\n') - f.write(' ' + ' ' + ' ' + 'targetPort: {{ .Values.container.port }}' + '\n') \ No newline at end of file + f.write(' ' + ' ' + ' ' + 'targetPort: {{ .Values.app.container.port }}' + '\n') \ No newline at end of file diff --git a/src/ThirdPartyService.py b/src/ThirdPartyService.py index 51a2f68..e9d811e 100644 --- a/src/ThirdPartyService.py +++ b/src/ThirdPartyService.py @@ -14,7 +14,7 @@ class ThirdPartyService (Template): f.write('apiVersion: v1' + '\n') f.write('kind: Secret' + '\n') f.write('metadata:' + '\n') - f.write(' ' + 'name: {{ .Release.Name }}-' + self.name + '-secret' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-' + self.name.lower() + '-secret' + '\n') f.write(' ' + 'labels:' + '\n') f.write(' ' + ' ' + 'app: {{ .Release.Name }}' + '\n') f.write('type: Opaque' + '\n') @@ -27,4 +27,16 @@ class ThirdPartyService (Template): f.write(' ' + key.replace('_', '-') + ': {{ .Values.thirdParty.' + self.name + '.' + snake_case_name + ' | b64enc }}' + '\n') - f.write('{{- end -}}' + '\n') \ No newline at end of file + f.write('{{- end -}}' + '\n') + + with open('templates/_thirdParty.tpl', 'a') as f: + f.write('{{- define "' + self.name.lower() + '.envVars" -}}' + '\n') + f.write(f'# {self.name} Environment Variables' + '\n') + for key, value in self.vars.items(): + f.write('- name: ' + self.name.upper() + '_' + key.upper() + '\n') + f.write(' ' + 'valueFrom:' + '\n') + f.write(' ' + ' ' + 'secretKeyRef:' + '\n') + f.write(' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-' + self.name.lower() + '-secret' + '\n') + f.write(' ' + ' ' + ' ' + 'key: ' + key.replace('_', '-') + '\n') + f.write('{{- end -}}' + '\n') + f.write('\n') \ No newline at end of file diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..affbf72 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +real-values \ No newline at end of file diff --git a/test/non-input-json/random.json b/test/non-input-json/random.json new file mode 100644 index 0000000..6b86c8f --- /dev/null +++ b/test/non-input-json/random.json @@ -0,0 +1,97 @@ +{ + "chart": { + "apiVersion": "v2", + "appVersion": "1.0.0", + "description": "A Helm chart for deploying .", + "homepage": "", + "maintainers": [ + { + "name": "", + "email": "" + } + ], + "name": "", + "sources": [ + "" + ], + "version": "1.0.0" + }, + "image": { + "repository": "/", + "pullPolicy": "IfNotPresent" + }, + "ingress": { + "hostname": "" + }, + "db": { + "name": "", + "host": "", + "user": "", + "password": "" + }, + "vault": { + "image": { + "repository": "", + "tag": "" + }, + "hostname": "", + "storageClass": "", + "policyCapabilities": [ + "create", + "read", + "update", + "delete", + "list" + ] + }, + "nosql": { + "dbName": "", + "user": "", + "password": "", + "groupings": { + "
": { + "name": "
", + "value": "" + } + } + }, + "cache": { + "password": "" + }, + "logging": { + "username": "", + "password": "" + }, + "oauth": { + "baseAppUrl": "", + "appAbbreviation": "", + "appName": "", + "serviceName": "", + "devPort": "", + "clientId": "", + "clientSecret": "" + }, + "thirdPartyServices": { + "": { + "": "" + } + }, + "extraEnvVars": { + "": { + "type": "Secret", + "name": "{{ .Release.Name }}-", + "key": "", + "description": "", + "value": "" + }, + "": { + "type": "ConfigMap", + "name": "{{ .Release.Name }}-", + "key": "", + "description": "", + "value": "" + }, + "": "''" + }, + "registry": "" +} \ No newline at end of file