From bb94a448779a7b78ac689e52fd2620c9c51103b8 Mon Sep 17 00:00:00 2001 From: Alan Bridgeman Date: Wed, 12 Feb 2025 13:04:52 -0600 Subject: [PATCH] Main idea was to add secrets vault. However, ended up with quite a bit of refactoring and changes --- README.md | 157 +++++++++++++++ create-helm-chart.py | 153 ++++++++++---- input.example.json | 16 +- src/AzureKeyVault.py | 25 +++ src/Deployment.py | 453 +++++++++++++++++++++++++++++------------- src/HashicorpVault.py | 161 +++++++++++++++ src/HelmChart.py | 154 ++++++++++++++ src/OAuth.py | 2 +- src/SecretsVault.py | 10 + 9 files changed, 946 insertions(+), 185 deletions(-) create mode 100644 src/AzureKeyVault.py create mode 100644 src/HashicorpVault.py create mode 100644 src/SecretsVault.py diff --git a/README.md b/README.md index 0b5dd96..6713257 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,161 @@ cp ./input.example.json /input.json Then in the directory you want ```sh create-helm-chart +``` + +## Inputs File (`input.json`) +The most basic version is below. Note values between `<>` should be replaced with appropriate values. + +```json +{ + "chart": { + "apiVersion": "v1", + "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": "" + } +} +``` + +### Database (Postgres) +To add a Postgres database include the following in the `inputs.json` file and fill out the values. + +```json +{ + "db": { + "name": "", + "host": "", + "user": "", + "password": "" + } +} +``` + +### Secrets Vault (Hashicorp Vault) +To add a Hashicorp secrets vault include the following in the `inputs.json` file and fill out the values + +```json +{ + "vault": { + "image": { + "repository": "", + "tag": "" + }, + "hostname": "", + "storageClass": "" + } +} +``` + +### NoSQL Storage (Mongo) +To add a Mongo instance include the following in the `inputs.json` file and fill out the values. + +```json +{ + "nosql": { + "dbName": "", + "user": "", + "password": "", + "tables": { + "": { + "name": "
", + "value": "" + } + } + } +} +``` + +### Cache Database (Redis) +To add a Redis instance include the following in the `inputs.json` file and fill out the values. + +```json +{ + "cache": { + "password": "" + } +} +``` + +### OAuth +To add the OAuth variables include the following in the `inputs.json` file and fill out the values. + +```json +{ + "oauth": { + "baseAppUrl": "", + "appAbbreviation": "", + "appName": "", + "serviceName": "", + "devPort": "" + } +} +``` + +### Third Party Services + +```json +{ + "thirdPartyServices": { + "openai": { + "apiKey": "" + }, + "stripe": { + "publicKey": "", + "secretKey": "", + "testPublicKey": "", + "testSecretKey": "" + } + } +} +``` + +### Extra/Other Environment Variables + +```json +{ + "extraEnvVars": { + "": { + "type": "Secret", + "name": "{{ .Release.Name }}-", + "key": "", + "description": "", + "value": "" + }, + "": { + "type": "ConfigMap", + "name": "{{ .Release.Name }}-", + "key": "", + "description": "", + "value": "" + }, + "": "''" + } +} +``` + +### Helm Resitry (for pushing) + +```json +{ + "registry": "" +} ``` \ No newline at end of file diff --git a/create-helm-chart.py b/create-helm-chart.py index 327a72d..e15037b 100644 --- a/create-helm-chart.py +++ b/create-helm-chart.py @@ -3,6 +3,7 @@ import json from src.Ingress import Ingress from src.Service import Service from src.Database import Database +from src.HashicorpVault import HashicorpVault from src.MongoDB import MongoDB from src.Redis import Redis from src.OAuth import OAuth @@ -36,45 +37,111 @@ if __name__ == '__main__': hostname = data['ingress']['hostname'] - db_name = data['db']['name'] - db_host = data['db']['host'] - db_user = data['db']['user'] - db_password = data['db']['password'] - - nosql_db_name = data['nosql']['dbName'] - nosql_user = data['nosql']['user'] - nosql_password = data['nosql']['password'] - - tables = data['nosql']['tables'] - - cache_password = data['cache']['password'] - - base_app_url = data['oauth']['baseAppUrl'] - app_abbreviation = data['oauth']['appAbbreviation'] - app_name = data['oauth']['appName'] - service_name = data['oauth']['serviceName'] - dev_port = data['oauth']['devPort'] - client_id = data['oauth']['clientId'] - client_secret = data['oauth']['clientSecret'] - - 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("'", '"') - - openai_api_key = data['thirdPartyServices']['openai']['apiKey'] - - helm_registry = data['registry'] - ingress = Ingress(hostname) service = Service() - db = Database(db_name, db_host, db_user, db_password) - mongo = MongoDB(nosql_db_name, nosql_user, nosql_password, tables) - redis = Redis(cache_password) - oauth = OAuth(base_app_url, app_abbreviation, app_name, service_name, dev_port, client_id, client_secret) - openai = ThirdPartyService('openai', True, api_key=openai_api_key) - deployment = Deployment(image_repository, image_pull_policy=image_pull_policy, uses_db=True, nosql=mongo, uses_cache=True, third_party_services=[openai], **extra_env_vars) - templates = [ingress, service, db, mongo, redis, oauth, deployment, openai] + + templates = [ingress, service] + + uses_db = False + uses_secrets_vault = False + nosql = None + uses_cache = False + third_party_services = [] + extra_env_vars = {} + + 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'] + + db = Database(db_name, db_host, db_user, db_password) + + uses_db = True + + templates.append(db) + + if 'vault' in data and data['vault'] != False: + vault_image = { + 'repository': data['vault']['image']['repository'], + 'tag': data['vault']['image']['tag'] + } + vault_hostname = data['vault']['hostname'] + vault_storage_class = data['vault']['storageClass'] + + vault = HashicorpVault(image=vault_image, hostname=vault_hostname, storage_class=vault_storage_class) + + uses_secrets_vault = True + + templates.append(vault) + + if 'nosql' in data and data['nosql'] != False: + nosql_db_name = data['nosql']['dbName'] + nosql_user = data['nosql']['user'] + nosql_password = data['nosql']['password'] + + tables = data['nosql']['tables'] + + mongo = MongoDB(nosql_db_name, nosql_user, nosql_password, tables) + + nosql = mongo + + templates.append(mongo) + + if 'cache' in data and data['cache'] != False: + cache_password = data['cache']['password'] + + redis = Redis(cache_password) + + uses_cache = True + + templates.append(redis) + + if 'oauth' in data and data['oauth'] != False: + base_app_url = data['oauth']['baseAppUrl'] + app_abbreviation = data['oauth']['appAbbreviation'] + app_name = data['oauth']['appName'] + service_name = data['oauth']['serviceName'] + dev_port = data['oauth']['devPort'] + + oauth = OAuth(base_app_url, app_abbreviation, app_name, service_name, dev_port) + + templates.append(oauth) + + if 'thirdPartyServices' in data: + if 'openai' in data['thirdPartyServices']: + openai_api_key = data['thirdPartyServices']['openai']['apiKey'] + + openai = ThirdPartyService('openai', False, api_key=openai_api_key) + + third_party_services.append(openai) + + 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'] + + 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) + + third_party_services.append(stripe) + + templates.append(stripe) + + 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) + 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.create_templates_folder() helmChart.write_yaml() @@ -84,11 +151,13 @@ if __name__ == '__main__': try: helmChart.package() - try: - helmChart.push(helm_registry) - except Exception as ex: - print('Push to the registry failed. Please check the error message below:') - print(ex) + if 'registry' in data: + helm_registry = data['registry'] + try: + helmChart.push(helm_registry) + except Exception as ex: + print('Push to the registry failed. Please check the error message below:') + print(ex) except Exception as e: print('Packaging the Helm chart failed. Please check the error message below:') print(e) diff --git a/input.example.json b/input.example.json index f8fac47..9958591 100644 --- a/input.example.json +++ b/input.example.json @@ -29,10 +29,18 @@ "user": "", "password": "" }, + "vault": { + "image": { + "repository": "", + "tag": "" + }, + "hostname": "", + "storageClass": "" + }, "nosql": { "dbName": "", "user": "", - "password": "", + "password": "", "tables": { "
": { "name": "
", @@ -55,6 +63,12 @@ "thirdPartyServices": { "openai": { "apiKey": "" + }, + "stripe": { + "publicKey": "", + "secretKey": "", + "testPublicKey": "", + "testSecretKey": "" } }, "extraEnvVars": { diff --git a/src/AzureKeyVault.py b/src/AzureKeyVault.py new file mode 100644 index 0000000..8a4a714 --- /dev/null +++ b/src/AzureKeyVault.py @@ -0,0 +1,25 @@ +from .SecretsVault import SecretsVault + +class AzureKeyVault(SecretsVault): + def __init__(self, name: str, client_id: str, client_secret: str, tenant_id: str): + super().__init__('azure') + + self.name = name + self.client_id = client_id + self.client_secret = client_secret + self.tenant_id = tenant_id + + def write(self): + with open('templates/vault-keyvault-secret.yaml', 'w') as f: + f.write('{{- if and (.Values.vault.enabled) (eq .Values.vault.type "azure") -}}' + '\n') + f.write('apiVersion: v1' + '\n') + f.write('kind: Secret' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n') + f.write('type: opaque' + '\n') + f.write('data:' + '\n') + f.write(' ' + 'client-id: {{ .Values.vault.clientId | b64enc }}' + '\n') + f.write(' ' + 'client-secret: {{ .Values.vault.clientSecret | b64enc }}' + '\n') + f.write(' ' + 'name: {{ .Values.vault.vaultName | b64enc }}' + '\n') + f.write(' ' + 'tenant-id: {{ .Values.vault.tenantId | b64enc }}' + '\n') + f.write('{{- end -}}') \ No newline at end of file diff --git a/src/Deployment.py b/src/Deployment.py index 3a34059..038bcb7 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, 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', 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: @@ -15,6 +15,7 @@ class Deployment (Template): env (str, Optional): The environment the app will be running in. Default 'production' 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 nosql (NoSQL, Optional): The NoSQL template. If set, Determines if NoSQL database related environment variables need to be set on the Deployment. We require the object to get table names to set appropriate environment variables on the Deployment. Default None uses_cache (bool, Optional): Whether or not a cache server is to be used. Determines if cache related environment variables need to be set on the Deployment. Default False third_party_services (list[ThirdPartyService], Optional): The third party services to be used. Determines if third party service related environment variables need to be set on the Deployment. Default empty list (`[]`) @@ -31,34 +32,291 @@ class Deployment (Template): self.port = port self.uses_oauth = uses_oauth self.uses_db = uses_db + self.uses_secrets_vault = uses_secrets_vault self.nosql = nosql self.uses_cache = uses_cache self.third_party_services = third_party_services self.extra_env_vars = extra_env_vars - def write(self): - """Write the Deployment template to a file.""" + def write_extra_env_vars_secret_file(self, env_var_details: dict[str, str]): + """Writes a Secret file for the extra environment variable. + + Args: + env_var_details (dict[str, str]): The details of the environment variable. + """ + + filename = env_var_details['name'] + if filename.startswith('{{ .Release.Name }}'): + filename = filename.replace('{{ .Release.Name }}-', '') + + camel_case_name = filename.split('-')[0] + for token in filename.split('-'): + if token != camel_case_name: + camel_case_name += token.capitalize() + + with open(f'templates/{filename}-secret.yaml', 'w') as f: + f.write('apiVersion: v1' + '\n') + f.write('kind: Secret' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + f'name: {env_var_details["name"]}' + '\n') + f.write('type: Opaque' + '\n') + f.write('data:' + '\n') + f.write(' ' + f'{env_var_details["key"]}: ' + '{{ .Values.' + camel_case_name + ' | b64enc }}' + '\n') + + def write_extra_env_vars_configmap_file(self, env_var_details: dict[str, str]): + """Writes a ConfigMap file for the extra environment variable. + + Args: + env_var_details (dict[str, str]): The details of the environment variable. + """ + + filename = env_var_details['name'] + if filename.startswith('{{ .Release.Name }}'): + filename = filename.replace('{{ .Release.Name }}-', '') + + camel_case_name = filename.split('-')[0] + for token in filename.split('-'): + if token != camel_case_name: + camel_case_name += token.capitalize() + + with open(f'templates/{filename}-configmap.yaml', 'w') as f: + f.write('apiVersion: v1' + '\n') + f.write('kind: ConfigMap' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + f'name: {env_var_details["name"]}' + '\n') + f.write('data:' + '\n') + f.write(' ' + f'{env_var_details["key"]}: {{ .Values.{camel_case_name} }}' + '\n') + + def write_extra_env_vars_files(self): + """Writes any needed secret or configmap files for the extra environment variables.""" for value in self.extra_env_vars.values(): + # We only need to crate a secret or configmap file if the value is a dictionary + # Because if it's a string we'll just use it as the value of the environment variable if isinstance(value, dict): if value['type'] == 'Secret': - filename = value['name'] - if filename.startswith('{{ .Release.Name }}'): - filename = filename.replace('{{ .Release.Name }}-', '') - - snake_case_name = filename.split('-')[0] - for token in filename.split('-'): - if token != snake_case_name: - snake_case_name += token.capitalize() + self.write_extra_env_vars_secret_file(value) + elif value['type'] == 'ConfigMap': + self.write_extra_env_vars_configmap_file(value) - with open(f'templates/{filename}-secret.yaml', 'w') as f: - f.write('apiVersion: v1' + '\n') - f.write('kind: Secret' + '\n') - f.write('metadata:' + '\n') - f.write(' ' + f'name: {value["name"]}' + '\n') - f.write('type: Opaque' + '\n') - f.write('data:' + '\n') - f.write(' ' + f'{value["key"]}: ' + '{{ .Values.' + snake_case_name + ' | b64enc }}' + '\n') + def create_extra_env_vars_deployment_env_vars(self) -> str: + """Creates the extra environment variables actual variables for the Deployment.""" + + output = '' + + for key, value in self.extra_env_vars.items(): + # Check if the value is a dictionary or a string + if isinstance(value, dict): + output += ' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + + if value['type'] == 'Secret': + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + elif value['type'] == 'ConfigMap': + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: ' + value['name'] + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: ' + value['key'] + '\n' + else: + # Because the value is a string just use the value literally + output += ' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + f'value: {value}' + '\n' + + return output + + def create_oauth_deployment_env_vars(self) -> str: + """Creates the OAuth related environment variables for the Deployment.""" + + 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' + + return output + + def create_db_deployment_env_vars(self) -> str: + """Creates the database related environment variables for the Deployment.""" + + 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' + + return output + + def create_nosql_deployment_env_vars(self) -> str: + """Creates the NoSQL related environment variables for the Deployment.""" + + 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' + + for key, value in self.nosql.tables.items(): + output += ' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-storage-tables' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {value["name"]}' + '\n' + + return output + + def create_secret_vault_deployment_env_vars(self) -> str: + """Creates the secret vault related environment variables for the Deployment.""" + + output = '' + + output += ' ' + ' ' + ' ' + ' ' + '# -- Secrets Vault (Hashicorp Vault OR Azure Key Vault) --' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- if .Values.vault.enabled }}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- if eq .Values.vault.type "azure" }}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: KEYVAULT_CLIENT_ID' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: client-id' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: KEYVAULT_CLIENT_SECRET' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: client-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: KEYVAULT_NAME' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: name' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: KEYVAULT_TENANT_ID' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: tenant-id' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- else if eq .Values.vault.type "hashicorp" }}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: VAULT_NAME' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: vault-name' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '- name: VAULT_PORT' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n' + output += ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: vault-port' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' + output += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' + + return output + + def create_cache_deployment_env_vars(self) -> str: + """Creates the cache related environment variables for the Deployment.""" + + 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' + + return output + + def create_third_party_services_deployment_env_vars(self) -> str: + """Creates the third party services related environment variables for the Deployment.""" + + output = '' + + 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 += ' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n' + + return output + + def write_deployment_file(self): + """Writes the Deployment file for the app.""" with open(f'templates/deployment.yaml', 'w') as f: f.write('apiVersion: apps/v1' + '\n') @@ -89,133 +347,46 @@ class Deployment (Template): f.write(' ' + ' ' + ' ' + ' ' + '- name: PORT' + '\n') f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: "{{ .Values.container.port }}"' + '\n') - for key, value in self.extra_env_vars.items(): - # Check if the value is a dictionary or a string - if isinstance(value, dict): - f.write(' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - if value['type'] == 'Secret': - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - elif value['type'] == 'ConfigMap': - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: ' + value['name'] + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: ' + value['key'] + '\n') - else: - f.write(' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + f'value: {value}' + '\n') - + # Add extra environment variables + f.write(self.create_extra_env_vars_deployment_env_vars()) + if self.uses_oauth: - 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(self.create_oauth_deployment_env_vars()) if self.uses_db: - f.write(' ' + ' ' + ' ' + ' ' + '# Database credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: DB_HOST' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-host' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: DB_NAME' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-name' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: DB_PASSWORD' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-password' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: password' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: DB_PORT' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-port' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: DB_USER' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-db-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: db-user' + '\n') + f.write(self.create_db_deployment_env_vars()) if self.nosql is not None: - f.write(' ' + ' ' + ' ' + ' ' + '# NoSQL Credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '{{- if eq .Values.nosql.type "mongodb" }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_CONNECTION_STRING' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-mongo-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: connection-string' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '{{- else if eq .Values.nosql.type "azure" }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_KEY' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-credentials' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: key' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: STORAGE_ACCOUNT_NAME' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-azure-tables-config' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: name' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '# NoSQL Table Names' + '\n') - - for key, value in self.nosql.tables.items(): - f.write(' ' + ' ' + ' ' + ' ' + f'- name: {key.upper()}' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-storage-tables' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {value["name"]}' + '\n') + f.write(self.create_nosql_deployment_env_vars()) + if self.uses_secrets_vault: + f.write(self.create_secret_vault_deployment_env_vars()) + if self.uses_cache: - f.write(' ' + ' ' + ' ' + ' ' + '# Caching Server Variables' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: CACHE_HOSTNAME' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Relese.name }}-cache-configmap' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: hostname' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: CACHE_PORT' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'configMapKeyRef:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-cache-configmap' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'key: port' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + '- name: CACHE_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(self.create_cache_deployment_env_vars()) - f.write(' ' + ' ' + ' ' + ' ' + '# Third-Party Integrations' + '\n') - for third_party in self.third_party_services: - f.write(' ' + ' ' + ' ' + ' ' + '{{- if .Values.thirdParty.' + third_party.name + '.enabled }}' + '\n') - - for var in third_party.vars: - f.write(' ' + ' ' + ' ' + ' ' + '- name: ' + third_party.name.upper() + '_' + var.upper() + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'valueFrom:' + '\n') - f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'secretKeyRef:' + '\n') - f.write(' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-' + third_party.name + '-secret' + '\n') - f.write(' ' + ' ' ' ' + ' ' + ' ' + ' ' + ' ' + f'key: {var.replace("_", "-")}' + '\n') - - f.write(' ' + ' ' + ' ' + ' ' + '{{- end }}' + '\n') \ No newline at end of file + if len(self.third_party_services) > 0: + f.write(self.create_third_party_services_deployment_env_vars()) + + # 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(' ' + ' ' + ' ' + ' ' + 'volumeMounts:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: role-vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /role_vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'readOnly: true' + '\n') + f.write(' ' + ' ' + ' ' + 'volumes:' + '\n') + f.write(' ' + ' ' + ' ' + '- name: role-vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'persistentVolumeClaim:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'claimName: {{ .Release.Name }}-vault-role-vars' + '\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 Deployment file + self.write_deployment_file() \ No newline at end of file diff --git a/src/HashicorpVault.py b/src/HashicorpVault.py new file mode 100644 index 0000000..90b2352 --- /dev/null +++ b/src/HashicorpVault.py @@ -0,0 +1,161 @@ +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'): + 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 + + def write_ingress(self): + with open('templates/vault-ingress.yaml', 'w') as f: + f.write('{{- if .Values.vault.create.ingress.enabled -}}' + '\n') + f.write('apiVersion: networking.k8s.io/v1' + '\n') + f.write('kind: Ingress' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault-ingress' + '\n') + f.write(' ' + 'labels:' + '\n') + f.write(' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write('spec:' + '\n') + f.write(' ' + 'ingressClassName: nginx' + '\n') + f.write(' ' + 'rules:' + '\n') + f.write(' ' + ' ' + '- host: {{ .Values.vault.create.ingress.host }}' + '\n') + f.write(' ' + ' ' + ' ' + 'http:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'paths:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- path: /' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'pathType: Prefix' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'backend:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'service:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'name: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'port:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + 'number: 80' + '\n') + f.write('{{- end -}}') + + def write_service(self): + with open('templates/vault-service.yaml', 'w') as f: + f.write('{{- if .Values.vault.create.enabled -}}' + '\n') + f.write('apiVersion: v1' + '\n') + f.write('kind: Service' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + 'labels:' + '\n') + f.write(' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write('spec:' + '\n') + f.write(' ' + 'selector:' + '\n') + f.write(' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + 'ports:' + '\n') + f.write(' ' + ' ' + '- protocol: TCP' + '\n') + f.write(' ' + ' ' + ' ' + 'port: 80' + '\n') + f.write(' ' + ' ' + ' ' + 'targetPort: 8200' + '\n') + f.write('{{- end -}}') + + def write_role_vars_persistent_volume_claim(self): + with open('templates/vault-role-vars-persistent-volume-claim.yaml', 'w') as f: + f.write('{{- if .Values.vault.create.enabled -}}' + '\n') + f.write('apiVersion: v1' + '\n') + f.write('kind: PersistentVolumeClaim' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault-role-vars' + '\n') + f.write(' ' + 'labels:' + '\n') + f.write(' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write('spec:' + '\n') + f.write(' ' + 'storageClassName: {{ .Values.vault.create.storage.storageClass }}' + '\n') + f.write(' ' + 'accessModes:' + '\n') + f.write(' ' + ' ' + '- ReadWriteMany' + '\n') + f.write(' ' + 'resources:' + '\n') + f.write(' ' + ' ' + 'requests:' + '\n') + f.write(' ' + ' ' + ' ' + 'storage: {{ .Values.vault.create.storage.size }}' + '\n') + f.write('{{- end -}}') + + def write_deployment(self): + with open('templates/vault-deployment.yaml', 'w') as f: + f.write('{{- if and (.Values.vault.create.enabled) (eq .Values.vault.type "hashicorp") -}}' + '\n') + f.write('apiVersion: apps/v1' + '\n') + f.write('kind: Deployment' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + 'labels:' + '\n') + f.write(' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write('spec:' + '\n') + f.write(' ' + 'replicas: 1' + '\n') + f.write(' ' + 'selector:' + '\n') + f.write(' ' + ' ' + 'matchLabels:' + '\n') + f.write(' ' + ' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + 'template:' + '\n') + f.write(' ' + ' ' + 'metadata:' + '\n') + f.write(' ' + ' ' + ' ' + 'labels:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'app: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + ' ' + 'spec:' + '\n') + f.write(' ' + ' ' + ' ' + 'containers:' + '\n') + f.write(' ' + ' ' + ' ' + '- name: {{ .Release.Name }}-vault' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'image: {{ .Values.vault.create.image.repository }}:{{ .Values.vault.create.image.tag }}' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'ports:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- containerPort: 8200' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- containerPort: 8201' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'env:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: VAULT_ADDR' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: http://0.0.0.0:8200' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: ROLE_ID_SECRET_NAME' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: VAULT_ROLE_ID' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: SECRET_ID_SECRET_NAME' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'value: VAULT_SECRET_ID' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'volumeMounts:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: vault-data' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /vault/data' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: vault-log' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /vault/logs' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: vault-creds' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /vault/creds' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- name: vault-role-vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'mountPath: /role_vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'capAdd:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + '- IPC_LOCK' + '\n') + f.write(' ' + ' ' + ' ' + 'volumes:' + '\n') + f.write(' ' + ' ' + ' ' + '- name: vault-data' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'emptyDir: {}' + '\n') + f.write(' ' + ' ' + ' ' + '- name: vault-log' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'emptyDir: {}' + '\n') + f.write(' ' + ' ' + ' ' + '- name: vault-creds' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'emptyDir: {}' + '\n') + f.write(' ' + ' ' + ' ' + '- name: vault-role-vars' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + 'persistentVolumeClaim:' + '\n') + f.write(' ' + ' ' + ' ' + ' ' + ' ' + 'claimName: {{ .Release.Name }}-vault-role-vars' + '\n') + f.write('{{- end -}}') + + def write_secret(self): + with open('templates/vault-hashicorp-secret.yaml', 'w') as f: + f.write('{{- if and (.Values.vault.enabled) (eq .Values.vault.type "hashicorp") -}}' + '\n') + f.write('apiVersion: v1' + '\n') + f.write('kind: Secret' + '\n') + f.write('metadata:' + '\n') + f.write(' ' + 'name: {{ .Release.Name }}-vault-secret' + '\n') + f.write('type: opaque' + '\n') + f.write('data:' + '\n') + f.write(' ' + '{{- if .Values.vault.create.enabled }}' + '\n') + f.write(' ' + '# Because we create the Hashicorp Vault instance as part of the Helm chart, ' + '\n') + f.write(' ' + '# we can use the name of the created resource (utilizing k8s built-in container connections)' + '\n') + f.write(' ' + '# to connect to the Vault instance without having to hard-code the Vault name.' + '\n') + f.write(' ' + 'vault-name: {{ printf "%s-vault" .Release.Name | b64enc }}' + '\n') + f.write(' ' + '# Because we create the Hashicorp Vault instance as part of the Helm chart,' + '\n') + f.write(' ' + '# We know the port that the Vault instance is running on.' + '\n') + f.write(' ' + 'vault-port: {{ printf "%d" 80 | b64enc }}' + '\n') + f.write(' ' + '{{- else }}' + '\n') + f.write(' ' + '# Because the Vault wasn\'t created as part of the Helm chart,' + '\n') + f.write(' ' + '# we need the deployer to specify the name of the Vault instance to connect to.' + '\n') + f.write(' ' + 'vault-name: {{ .Values.vault.vaultName | b64enc }}' + '\n') + f.write(' ' + '# Because the Vault wasn\'t created as part of the Helm chart,' + '\n') + f.write(' ' + '# we need the deployer to specify the port that the Vault instance is running on.' + '\n') + f.write(' ' + 'vault-port: {{ .Values.passVault.vaultPort | b64enc }}' + '\n') + f.write(' ' + '{{- end }}' + '\n') + 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 diff --git a/src/HelmChart.py b/src/HelmChart.py index 057e555..1d819f3 100644 --- a/src/HelmChart.py +++ b/src/HelmChart.py @@ -3,6 +3,9 @@ import os, subprocess from .Template import Template from .Ingress import Ingress from .Database import Database +from .SecretsVault import SecretsVault +from .HashicorpVault import HashicorpVault +from .AzureKeyVault import AzureKeyVault from .NoSQL import NoSQL from .MongoDB import MongoDB from .AzureTableStorage import AzureTableStorage @@ -294,6 +297,151 @@ class HelmChart: return output + def create_secrets_vault_section_of_values_yaml(self) -> str: + """Create the 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 = '' + + # Get the Secrets Vault template from the templates provided + secrets_vault_template = next(template for template in self.templates if isinstance(template, SecretsVault)) + + 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: + 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' + if isinstance(secrets_vault_template, AzureKeyVault): + 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' + if isinstance(secrets_vault_template, AzureKeyVault): + 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' + if isinstance(secrets_vault_template, AzureKeyVault): + output += ' ' + f'tenant-id: "{secrets_vault_template.tenant_id}"' + '\n' + else: + output += ' ' + '#tenant-id: ' + '\n' + + return output + def create_nosql_section_of_values_yaml(self) -> str: """Create the NoSQL section of the `values.yaml` file for the Helm chart. @@ -502,6 +650,8 @@ class HelmChart: output += ' ' + ' ' + f'{camel_case_name}: {value}' + '\n' output += ' ' + ' ' + '\n' + + return output def write_values_yaml(self): """Write the `values.yaml` file for the Helm chart. @@ -536,6 +686,10 @@ class HelmChart: if any(isinstance(template, Database) for template in self.templates): f.write(self.create_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_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_nosql_section_of_values_yaml()) diff --git a/src/OAuth.py b/src/OAuth.py index 33f10c9..a517a27 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, client_id: str, client_secret: str): + def __init__(self, base_app_url: str, app_abbreviation: str, app_name: str, service_name: str, dev_port: str): """A class for creating a/some template(s) related to OAuth implementation.""" self.base_app_url = base_app_url diff --git a/src/SecretsVault.py b/src/SecretsVault.py new file mode 100644 index 0000000..5021b4d --- /dev/null +++ b/src/SecretsVault.py @@ -0,0 +1,10 @@ +from .Template import Template + +class SecretsVault(Template): + def __init__(self, type: str): + super().__init__() + + self.type = type + + def write(self): + pass \ No newline at end of file