Repository: NeonGeckoCom/neon-diana-utils (language: python)

Issue:
Implement FastChat Deployment
# Description
Add fastchat template with RMQ Config

# Issues
<!-- If this is related to or closes an issue/other PR, please note them here -->

# Other Notes
<!-- Note any breaking changes, WIP changes, requests for input, etc. here -->
Kubernetes Support
## Objective
Support deployment of packages to Kubernetes

## Initial Implementation
- [x] Initial PoC conversion of deployed API server to K8s
- [x] Update and add util methods to `diana` entry point to generate K8s configs
- [x] Refactoring of config to ConfigMap/Secrets instead of nfs shares
- [x] Implement and document or automate proxy config for k8s



Discussion / hints:

#16 

Fix patch:
diff --git a/neon_diana_utils/cli.py b/neon_diana_utils/cli.py
index cb72e4b2..1ad116aa 100644
--- a/neon_diana_utils/cli.py
+++ b/neon_diana_utils/cli.py
@@ -24,18 +24,9 @@
 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-import json
-import shutil
 import click
-import yaml
 
-from os import getenv, makedirs, walk, remove
-from os.path import isdir, join, expanduser, isfile, dirname, abspath, exists
-from pprint import pformat
 from click_default_group import DefaultGroup
-from ovos_utils.xdg_utils import xdg_config_home
-from neon_diana_utils.constants import valid_mq_services, default_mq_services, \
-    Orchestrator
 from neon_diana_utils.version import __version__
 
 
@@ -51,220 +42,32 @@ def neon_diana_cli(version: bool = False):
 
 
 # Backend
-@neon_diana_cli.command(help="Configure a Diana Backend")
-@click.option('--service', '-s', multiple=True,
-              help="Optional service to configure")
-@click.option('--default', '-d', is_flag=True, default=False,
-              help="Configure the Default MQ Backend Services")
-@click.option('--complete', '-c', '-a', is_flag=True, default=False,
-              help="Configure All MQ Backend Services. (NOTE: This includes packages that require authentication)")
-@click.option('--user', '-u', default="admin",
-              help="Username to configure for administration")
-@click.option('--password', '-p',
-              help="Password associated with admin user to login to console")
-@click.option('--volume-driver', default='none',
-              help="Docker Volume Driver to use. 'none' and 'nfs' are currently supported")
-@click.option('--volume-path', default=None,
-              help="Optional fully-qualified path to config (i.e. /opt/diana, 192.168.1.10:/opt/diana")
-@click.option('--skip-config', is_flag=True, default=False,
-              help="Skip backend configuration and just generate orchestrator definitions")
-@click.option('--namespace', '-n', default='default',
-              help="Kubernetes namespace to configure services to run in")
-@click.option('--mq-namespace', default=None,
-              help="Kubernetes namespace to configure MQ services to run in")
-@click.option('--http-namespace', default=None,
-              help="Kubernetes namespace to configure HTTP services to run in")
-@click.option('--http', is_flag=True, default=False,
-              help="Configure HTTP backend services in addition to MQ")
-@click.argument('config_path', default=getenv("NEON_CONFIG_DIR", "~/.config/neon/"))
-def configure_backend(config_path, service, default, complete, user, password,
-                      volume_driver, volume_path, skip_config, namespace, http,
-                      mq_namespace, http_namespace):
-    from neon_diana_utils.utils.backend import cli_configure_backend
-    # Determine Services to Configure
-    if complete:
-        services_to_configure = valid_mq_services()
-    elif default:
-        services_to_configure = default_mq_services()
-    else:
-        services_to_configure = None
-    if service:
-        if services_to_configure:
-            click.echo("Service set and individual services specified\n"
-                       "Configuring specified services only.")
-        services_to_configure = set(service)
-
-    # Call setup
-    import sys
-    std_out = sys.stdout
-    try:
-        sys.stdout = open("/dev/null", 'a+')
-        mq_namespace = mq_namespace or namespace
-        http_namespace = http_namespace or namespace
-        cli_configure_backend(config_path, services_to_configure, user,
-                              password, http, volume_driver, volume_path,
-                              skip_config, mq_namespace, http_namespace)
-        sys.stdout = std_out
-        if not skip_config:
-            click.echo(f"Configured RabbitMQ Administrator: {user}")
-            click.echo(f"Remember to place `ngi_auth_vars.yml` in {config_path}")
-        click.echo(f"Configured API Services: {services_to_configure}")
-        click.echo(f"Configuration was written to: {config_path}")
-    except Exception as e:
-        sys.stdout = std_out
-        if not isinstance(e, ValueError):
-            click.echo("An unexpected error occurred during configuration:")
-        click.echo(e)
-        click.echo("Call `diana configure-backend --help` for more info")
-
-
-@neon_diana_cli.command(help="Start a Diana Backend")
-@click.option('--attach', '-a', is_flag=True, default=False,
-              help="Attach terminal to the started containers")
-@click.option('--orchestrator', '-o', default="docker",
-              help="Orchestrator (docker|kubernetes|openshift")
-@click.argument('config_path', default=getenv("NEON_CONFIG_DIR", "~/.config/neon/"))
-def start_backend(config_path, attach, orchestrator):
-    from neon_diana_utils.utils.backend import cli_start_backend
-
-    # Determine requested orchestrator
-    if orchestrator == "docker":
-        orchestrator = Orchestrator.DOCKER
-    elif orchestrator == "kubernetes":
-        orchestrator = Orchestrator.KUBERNETES
-    elif orchestrator == "openshift":
-        orchestrator = Orchestrator.OPENSHIFT
-    else:
-        click.echo(f"Invalid orchestrator specified: {orchestrator}")
-        return
-
-    # Start the backend
-    click.echo(f"Starting backend")
-    try:
-        cli_start_backend(config_path, attach, orchestrator)
-        if not attach:
-            click.echo("Diana Backend Started")
-    except Exception as e:
-        if not isinstance(e, ValueError):
-            click.echo("An unexpected error occurred during configuration:")
-        click.echo(e)
-
-
-@neon_diana_cli.command(help="Stop a Diana Backend")
-@click.option('--orchestrator', '-o', default="docker",
-              help="Orchestrator (docker|kubernetes|openshift")
-@click.argument('config_path', default=getenv("NEON_CONFIG_DIR", "~/.config/neon/"))
-def stop_backend(config_path, orchestrator):
-    from neon_diana_utils.utils.backend import cli_stop_backend
-
-    # Determine requested orchestrator
-    if orchestrator == "docker":
-        orchestrator = Orchestrator.DOCKER
-    elif orchestrator == "kubernetes":
-        orchestrator = Orchestrator.KUBERNETES
-    elif orchestrator == "openshift":
-        orchestrator = Orchestrator.OPENSHIFT
-    else:
-        click.echo(f"Invalid orchestrator specified: {orchestrator}")
-        return
-
-    try:
-        cli_stop_backend(config_path, orchestrator)
-        click.echo("Diana Backend Stopped")
-    except Exception as e:
-        if not isinstance(e, ValueError):
-            click.echo("An unexpected error occurred during configuration:")
-        click.echo(e)
-
-
-# Kubernetes
 @neon_diana_cli.command(help="Configure RabbitMQ and export user credentials")
 @click.option("--username", "-u", help="RabbitMQ username")
 @click.option("--password", "-p", help="RabbitMQ password")
 @click.argument("output_path", default=None, required=False)
-def configure_mq_backend(username, password, output_path):
-    output_path = expanduser(output_path or join(xdg_config_home(), "diana"))
-    if exists(output_path):
-        click.echo(f"Path exists: {output_path}")
-        return
-    elif not isdir(dirname(output_path)):
-        makedirs(dirname(output_path))
+def configure_backend(username, password, output_path):
+    from neon_diana_utils.configuration import configure_backend
+    configure_backend(username, password, output_path)
 
-    # Get Helm charts in output directory for deployment
-    shutil.copytree(join(dirname(__file__), "helm_charts"),
-                    join(output_path))
-    chart_path = join(output_path, "diana-backend")
-    # Cleanup any leftover build files
-    for root, _, files in walk(output_path):
-        for file in files:
-            if any((file.endswith(x) for x in (".lock", ".tgz"))):
-                remove(join(root, file))
-    try:
-        from neon_diana_utils.utils.kubernetes_utils import \
-            cli_make_github_secret
-        from neon_diana_utils.utils.backend import generate_rmq_config, \
-            generate_mq_auth_config
-        rmq_config = generate_rmq_config()
-        username = username or click.prompt("RabbitMQ Admin Username", type=str)
-        password = password or click.prompt("RabbitMQ Admin Password", type=str,
-                                            hide_input=True)
 
-        rmq_config['users'].append({'name': username,
-                                    'password': password,
-                                    'tags': ['administrator']})
-        with open(join(chart_path, "rabbitmq.json"), 'w+') as f:
-            json.dump(rmq_config, f, indent=2)
-        click.echo(f"Generated RabbitMQ config at {chart_path}/rabbitmq.json")
-        mq_auth_config = generate_mq_auth_config(rmq_config)
-        click.echo(f"Generated auth for services: {set(mq_auth_config.keys())}")
-        if click.confirm("Configure GitHub token for private services?"):
-            gh_username = click.prompt("GitHub username", type=str)
-            gh_token = click.prompt("GitHub Token with `read:packages` "
-                                    "permission", type=str)
-            cli_make_github_secret(gh_username, gh_token, output_path)
-            gh_secret_path = join(chart_path, "templates",
-                                  "secret_gh_token.yaml")
-            shutil.move(join(output_path, "k8s_secret_github.yml"),
-                        gh_secret_path)
-            click.echo(f"Generated GH secret at {gh_secret_path}")
-
-        keys_config = _make_keys_config(False)
-
-        mq_url = "neon-rabbitmq"
-        mq_port = 5672
-
-        # Assume ingress is configured as recommended
-        # confirmed = False
-        # mq_url = None
-        # mq_port = None
-        # while not confirmed:
-        #     mq_url = click.prompt("MQ Service Name or URL", type=str,
-        #                           default="neon-rabbitmq")
-        #     mq_port = click.prompt("MQ Client Port", type=int, default=5672)
-        #     click.echo(f"{mq_url}:{mq_port}")
-        #     confirmed = click.confirm("Is this MQ Address Correct?")
-        config = {**{"MQ": {"users": mq_auth_config,
-                            "server": mq_url,
-                            "port": mq_port}},
-                  **keys_config}
-        output_file = join(chart_path, "diana.yaml")
-        click.echo(f"Writing configuration to {output_file}")
-        with open(output_file, 'w+') as f:
-            yaml.dump(config, f)
-        click.echo(f"Helm charts generated in {output_path}")
-    except Exception as e:
-        click.echo(e)
+@neon_diana_cli.command(help="Configure backend services for deployment")
+@click.option("--username", "-u", help="RabbitMQ username")
+@click.option("--password", "-p", help="RabbitMQ password")
+@click.argument("output_path", default=None, required=False)
+def configure_mq_backend(username, password, output_path):
+    # TODO: Deprecate in favor of `configure_backend`
+    from neon_diana_utils.configuration import configure_backend
+    configure_backend(username, password, output_path)
 
 
 @neon_diana_cli.command(help="Generate RabbitMQ definitions")
+@click.option("--username", "-u", help="RabbitMQ username")
+@click.option("--password", "-p", help="RabbitMQ password")
 @click.argument("output_file", default=None, required=False)
-def make_rmq_config(output_file):
-    if isfile(output_file):
-        click.echo(f"{output_file} already exists")
-        return
-    if not isdir(dirname(output_file)):
-        makedirs(dirname(output_file))
-    # TODO: Generate random passwords
+def make_rmq_config(username, password, output_file):
+    from neon_diana_utils.configuration import generate_rmq_config
+    generate_rmq_config(username, password, output_file)
 
 
 @neon_diana_cli.command(help="Generate a configuration file with access keys")
@@ -272,150 +75,5 @@ def make_rmq_config(output_file):
               is_flag=True)
 @click.argument("output_file", default=None, required=False)
 def make_keys_config(skip_write, output_file):
-    _make_keys_config(not skip_write, output_file)
-
-
-@neon_diana_cli.command(help="Generate Kubernetes secret for Github images")
-@click.option("--username", "-u",
-              help="Github username")
-@click.option("--token", "-t",
-              help="Github PAT with read_packages permission")
-@click.argument('output_path', default=getenv("NEON_CONFIG_DIR", "~/.config/neon/"))
-def make_github_secret(username, token, output_path):
-    from neon_diana_utils.utils.kubernetes_utils import cli_make_github_secret
-    try:
-        output_path = cli_make_github_secret(username, token, output_path)
-        click.echo(f"Generated outputs in {output_path}")
-    except Exception as e:
-        click.echo(e)
-
-
-def _make_keys_config(write_config: bool, output_file: str = None):
-    """
-    Interactive configuration tool to prompt user for expected API keys and
-    service accounts to be included in Configuration.
-    :param write_config: If true, write config to `output_file`
-    :param output_file: Configuration file to write keys to
-    """
-    if write_config:
-        output_file = expanduser(abspath((output_file or join(xdg_config_home(),
-                                                              "diana",
-                                                              "diana.yaml"))))
-        if isfile(output_file):
-            click.echo(f"File already exists: {output_file}")
-            return
-        elif not isdir(dirname(output_file)):
-            makedirs(dirname(output_file))
-
-    api_services = dict()
-    if click.confirm("Configure API Proxy Services?"):
-        keys_confirmed = False
-        while not keys_confirmed:
-            wolfram_key = click.prompt("Wolfram|Alpha API Key", type=str)
-            alphavantage_key = click.prompt("AlphaVantage API Key",
-                                            type=str)
-            owm_key = click.prompt("OpenWeatherMap API Key", type=str)
-            api_services = {
-                "wolfram_alpha": {"api_key": wolfram_key},
-                "alpha_vantage": {"api_key": alphavantage_key},
-                "open_weather_map": {"api_key": owm_key}
-            }
-            click.echo(pformat(api_services))
-            keys_confirmed = click.confirm("Are these keys correct?")
-
-    email_config = dict()
-    if click.confirm("Configure Email Service?"):
-        config_confirmed = False
-        while not config_confirmed:
-            email_addr = click.prompt("Email Address", type=str)
-            email_password = click.prompt("Password", type=str)
-            smtp_host = click.prompt("SMTP URL", type=str,
-                                     default="smtp.gmail.com")
-            smtp_port = click.prompt("SMTP Port", type=str,
-                                     default="465")
-            email_config = {"mail": email_addr,
-                            "pass": email_password,
-                            "host": smtp_host,
-                            "port": smtp_port}
-            click.echo(pformat(email_config))
-            config_confirmed = \
-                click.confirm("Is this configuration correct?")
-
-    brands_config = dict()
-    if click.confirm("Configure Brands/Coupons Service?"):
-        config_confirmed = False
-        while not config_confirmed:
-            server_host = click.prompt("SQL Host Address", type=str,
-                                       default="trackmybrands.com")
-            sql_database = click.prompt("SQL Database", type=str,
-                                        default="admintr1_drup1")
-            sql_username = click.prompt("SQL Username", type=str)
-            sql_password = click.prompt("SQL Password", type=str)
-            brands_config = {"host": server_host,
-                             "database": sql_database,
-                             "user": sql_username,
-                             "password": sql_password}
-            click.echo(pformat(brands_config))
-            config_confirmed = \
-                click.confirm("Is this configuration correct?")
-
-    chatgpt_config = dict()
-    if click.confirm("Configure ChatGPT Service?"):
-        config_confirmed = False
-        while not config_confirmed:
-            gpt_key = click.prompt("ChatGPT API Key", type=str)
-            gpt_model = click.prompt("ChatGPT Model", type=str,
-                                     default="gpt-3.5-turbo")
-            gpt_role = click.prompt("ChatGPT Role", type=str,
-                                    default="You are trying to give a short "
-                                            "answer in less than 40 words.")
-            gpt_context = click.prompt("ChatGPT Context depth", type=int,
-                                       default=3)
-            max_tokens = click.prompt("Maximum tokens in responses", type=int,
-                                      default=100)
-            chatgpt_config = {
-                "key": gpt_key,
-                "model": gpt_model,
-                "role": gpt_role,
-                "context_depth": gpt_context,
-                "max_tokens": max_tokens
-            }
-            click.echo(pformat(chatgpt_config))
-            config_confirmed = click.confirm("Is this configuration correct?")
-
-    fastchat_config = dict()
-    if click.confirm("Configure FastChat Service?"):
-        config_confirmed = False
-        while not config_confirmed:
-            model = click.prompt("FastChat Model", type=str,
-                                 default="fastchat")
-            context = click.prompt("FastChat context depth", type=int,
-                                   default=3)
-            max_tokens = click.prompt("Max number of tokens in responses",
-                                      type=int, default=128)
-            num_processes = click.prompt(
-                "Number of queries to handle in parallel",
-                type=int, default=1)
-            num_threads = click.prompt("Number of threads to use per query",
-                                       type=int, default=2)
-            fastchat_config = {
-                "model": model,
-                "context_depth": context,
-                "max_tokens": max_tokens,
-                "num_parallel_processes": num_processes,
-                "num_threads_per_process": num_threads
-            }
-            click.echo(pformat(fastchat_config))
-            config_confirmed = click.confirm("Is this configuration correct?")
-
-    config = {"keys": {"api_services": api_services,
-                       "emails": email_config,
-                       "track_my_brands": brands_config},
-              "ChatGPT": chatgpt_config,
-              "FastChat": fastchat_config
-              }
-    if write_config:
-        click.echo(f"Writing configuration to {output_file}")
-        with open(output_file, 'w+') as f:
-            yaml.dump(config, f)
-    return config
+    from neon_diana_utils.configuration import make_keys_config
+    make_keys_config(not skip_write, output_file)
diff --git a/neon_diana_utils/configuration.py b/neon_diana_utils/configuration.py
new file mode 100644
index 00000000..59bbb550
--- /dev/null
+++ b/neon_diana_utils/configuration.py
@@ -0,0 +1,301 @@
+# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
+# All trademark and other rights reserved by their respective owners
+# Copyright 2008-2021 Neongecko.com Inc.
+# BSD-3
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+# 1. Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from this
+#    software without specific prior written permission.
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import click
+import yaml
+import json
+import secrets
+import shutil
+
+from pprint import pformat
+from typing import Optional
+from os import makedirs, listdir, walk, remove
+from os.path import expanduser, join, abspath, isfile, isdir, dirname
+from ovos_utils.xdg_utils import xdg_config_home
+from ovos_utils.log import LOG
+
+
+def validate_output_path(output_path: str) -> bool:
+    """
+    Ensure the requested output path is available to be written
+    @returns: True if path is valid, else False
+    """
+    if isfile(output_path):
+        LOG.warning(f"File already exists: {output_path}")
+        return False
+    elif isdir(output_path) and listdir(output_path):
+        LOG.warning(f"Directory is not empty: {output_path}")
+        return False
+    elif not isdir(dirname(output_path)):
+        makedirs(dirname(output_path))
+    return True
+
+
+def make_keys_config(write_config: bool,
+                     output_file: str = None) -> Optional[dict]:
+    """
+    Interactive configuration tool to prompt user for expected API keys and
+    service accounts to be included in Configuration.
+    @param write_config: If true, write config to `output_file`
+    @param output_file: Configuration file to write keys to
+    @returns: dict configuration
+    """
+    if write_config:
+        output_file = expanduser(abspath((output_file or join(xdg_config_home(),
+                                                              "diana",
+                                                              "diana.yaml"))))
+        if not validate_output_path(output_file):
+            click.echo(f"File already exists: {output_file}")
+            return
+
+    api_services = dict()
+    if click.confirm("Configure API Proxy Services?"):
+        keys_confirmed = False
+        while not keys_confirmed:
+            wolfram_key = click.prompt("Wolfram|Alpha API Key", type=str)
+            alphavantage_key = click.prompt("AlphaVantage API Key",
+                                            type=str)
+            owm_key = click.prompt("OpenWeatherMap API Key", type=str)
+            api_services = {
+                "wolfram_alpha": {"api_key": wolfram_key},
+                "alpha_vantage": {"api_key": alphavantage_key},
+                "open_weather_map": {"api_key": owm_key}
+            }
+            click.echo(pformat(api_services))
+            keys_confirmed = click.confirm("Are these keys correct?")
+
+    email_config = dict()
+    if click.confirm("Configure Email Service?"):
+        config_confirmed = False
+        while not config_confirmed:
+            email_addr = click.prompt("Email Address", type=str)
+            email_password = click.prompt("Password", type=str)
+            smtp_host = click.prompt("SMTP URL", type=str,
+                                     default="smtp.gmail.com")
+            smtp_port = click.prompt("SMTP Port", type=str,
+                                     default="465")
+            email_config = {"mail": email_addr,
+                            "pass": email_password,
+                            "host": smtp_host,
+                            "port": smtp_port}
+            click.echo(pformat(email_config))
+            config_confirmed = \
+                click.confirm("Is this configuration correct?")
+
+    brands_config = dict()
+    if click.confirm("Configure Brands/Coupons Service?"):
+        config_confirmed = False
+        while not config_confirmed:
+            server_host = click.prompt("SQL Host Address", type=str,
+                                       default="trackmybrands.com")
+            sql_database = click.prompt("SQL Database", type=str,
+                                        default="admintr1_drup1")
+            sql_username = click.prompt("SQL Username", type=str)
+            sql_password = click.prompt("SQL Password", type=str)
+            brands_config = {"host": server_host,
+                             "database": sql_database,
+                             "user": sql_username,
+                             "password": sql_password}
+            click.echo(pformat(brands_config))
+            config_confirmed = \
+                click.confirm("Is this configuration correct?")
+
+    chatgpt_config = dict()
+    if click.confirm("Configure ChatGPT Service?"):
+        config_confirmed = False
+        while not config_confirmed:
+            gpt_key = click.prompt("ChatGPT API Key", type=str)
+            gpt_model = click.prompt("ChatGPT Model", type=str,
+                                     default="gpt-3.5-turbo")
+            gpt_role = click.prompt("ChatGPT Role", type=str,
+                                    default="You are trying to give a short "
+                                            "answer in less than 40 words.")
+            gpt_context = click.prompt("ChatGPT Context depth", type=int,
+                                       default=3)
+            max_tokens = click.prompt("Maximum tokens in responses", type=int,
+                                      default=100)
+            chatgpt_config = {
+                "key": gpt_key,
+                "model": gpt_model,
+                "role": gpt_role,
+                "context_depth": gpt_context,
+                "max_tokens": max_tokens
+            }
+            click.echo(pformat(chatgpt_config))
+            config_confirmed = \
+                click.confirm("Is this configuration correct?")
+
+    fastchat_config = dict()
+    if click.confirm("Configure FastChat Service?"):
+        config_confirmed = False
+        while not config_confirmed:
+            model = click.prompt("FastChat Model", type=str,
+                                 default="fastchat")
+            context = click.prompt("FastChat context depth", type=int,
+                                   default=3)
+            max_tokens = click.prompt("Max number of tokens in responses",
+                                      type=int, default=128)
+            num_processes = click.prompt(
+                "Number of queries to handle in parallel",
+                type=int, default=1)
+            num_threads = click.prompt("Number of threads to use per query",
+                                       type=int, default=2)
+            fastchat_config = {
+                "model": model,
+                "context_depth": context,
+                "max_tokens": max_tokens,
+                "num_parallel_processes": num_processes,
+                "num_threads_per_process": num_threads
+            }
+            click.echo(pformat(fastchat_config))
+            config_confirmed = click.confirm("Is this configuration correct?")
+
+    config = {"keys": {"api_services": api_services,
+                       "emails": email_config,
+                       "track_my_brands": brands_config},
+              "ChatGPT": chatgpt_config,
+              "FastChat": fastchat_config
+              }
+    if write_config:
+        click.echo(f"Writing configuration to {output_file}")
+        with open(output_file, 'w+') as f:
+            yaml.dump(config, f)
+    return config
+
+
+def generate_rmq_config(admin_username: str, admin_password: str,
+                        output_file: str = None) -> dict:
+    """
+    Generate a default configuration for RabbitMQ. This defines all default
+    users, vhosts, and permissions that may be used with a deployment.
+    @param admin_username: Username for admin account
+    @param admin_password: Password for admin account
+    @param output_file: Optional path to write configuration to
+    @returns: dict RabbitMQ Configuration
+    """
+    base_config_file = join((dirname(__file__)), "templates",
+                            "rmq_backend_config.yml")
+    with open(base_config_file) as f:
+        base_config = yaml.safe_load(f)
+    for user in base_config['users']:
+        if user["password"]:
+            # Skip users with defined passwords
+            continue
+        user['password'] = secrets.token_urlsafe(32)
+
+    base_config['users'].append({'name': admin_username,
+                                 'password': admin_password,
+                                 'tags': ['administrator']})
+
+    if output_file and validate_output_path(output_file):
+        with open(output_file, 'w+') as f:
+            json.dump(base_config, f, indent=2)
+    return base_config
+
+
+def generate_mq_auth_config(rmq_config: dict) -> dict:
+    """
+    Generate an MQ auth config from RabbitMQ config
+    :param rmq_config: RabbitMQ definitions, i.d. from `generate_rmq_config
+    :returns: Configuration for Neon MQ-Connector
+    """
+    mq_user_mapping_file = join(dirname(__file__), "templates",
+                                "mq_user_mapping.yml")
+    with open(mq_user_mapping_file) as f:
+        mq_user_mapping = yaml.safe_load(f)
+
+    mq_config = dict()
+    LOG.debug(rmq_config.keys())
+    for user in rmq_config['users']:
+        username = user['name']
+        for service in mq_user_mapping.get(username, []):
+            mq_config[service] = {"user": username,
+                                  "password": user['password']}
+    return mq_config
+
+
+def configure_backend(username: str = None,
+                      password: str = None,
+                      output_path: str = None):
+    """
+    Generate DIANA backend definitions
+    @param username: RabbitMQ Admin username to configure
+    @param password: RabbitMQ Admin password to configure
+    @param output_path: directory to write output definitions to
+    """
+    from neon_diana_utils.kubernetes_utils import create_github_secret
+
+    # Validate output paths
+    output_path = expanduser(output_path or join(xdg_config_home(), "diana"))
+    if not validate_output_path(output_path):
+        click.echo(f"Path exists: {output_path}")
+        return
+
+    # Get Helm charts in output directory for deployment
+    shutil.copytree(join(dirname(__file__), "helm_charts"),
+                    join(output_path))
+    chart_path = join(output_path, "diana-backend")
+    # Cleanup any leftover build files
+    for root, _, files in walk(output_path):
+        for file in files:
+            if any((file.endswith(x) for x in (".lock", ".tgz"))):
+                remove(join(root, file))
+    try:
+        # Generate RabbitMQ config
+        username = username or click.prompt("RabbitMQ Admin Username", type=str)
+        password = password or click.prompt("RabbitMQ Admin Password", type=str,
+                                            hide_input=True)
+        rmq_file = join(chart_path, "rabbitmq.json")
+        rmq_config = generate_rmq_config(username, password, rmq_file)
+        click.echo(f"Generated RabbitMQ config at {chart_path}/rabbitmq.json")
+
+        # Generate MQ Auth config
+        mq_auth_config = generate_mq_auth_config(rmq_config)
+        click.echo(f"Generated auth for services: {set(mq_auth_config.keys())}")
+
+        # Generate GH Auth config secret
+        if click.confirm("Configure GitHub token for private services?"):
+            gh_username = click.prompt("GitHub username", type=str)
+            gh_token = click.prompt("GitHub Token with `read:packages` "
+                                    "permission", type=str)
+            gh_secret_path = join(chart_path, "templates",
+                                  "secret_gh_token.yaml")
+            create_github_secret(gh_username, gh_token, gh_secret_path)
+            click.echo(f"Generated GH secret at {gh_secret_path}")
+
+        # Generate `diana.yaml` output
+        keys_config = make_keys_config(False)
+        config = {**{"MQ": {"users": mq_auth_config,
+                            "server": "neon-rabbitmq",
+                            "port": 5672}},
+                  **keys_config}
+        output_file = join(chart_path, "diana.yaml")
+        click.echo(f"Writing configuration to {output_file}")
+        with open(output_file, 'w+') as f:
+            yaml.dump(config, f)
+        click.echo(f"Helm charts generated in {output_path}")
+    except Exception as e:
+        click.echo(e)
diff --git a/neon_diana_utils/helm_charts/diana-backend/Chart.yaml b/neon_diana_utils/helm_charts/diana-backend/Chart.yaml
index f518e304..23ef9b8e 100644
--- a/neon_diana_utils/helm_charts/diana-backend/Chart.yaml
+++ b/neon_diana_utils/helm_charts/diana-backend/Chart.yaml
@@ -35,5 +35,5 @@ dependencies:
     version: 0.0.1
     repository: file://../http-services
   - name: diana-mq
-    version: 0.0.2
+    version: 0.0.3
     repository: file://../mq-services
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/base-http/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/base-http/Chart.yaml
index 5ccad82c..171b7450 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/base-http/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/base-http/Chart.yaml
@@ -3,5 +3,5 @@ name: base-http
 description: Library chart for basic HTTP Services
 type: library
 
-version: 0.0.1
+version: 0.0.2
 appVersion: "0.0.6a11"
diff --git a/neon_diana_utils/helm_charts/http-services/charts/base-http/templates/_deployment.tpl b/neon_diana_utils/helm_charts/http-services/charts/base-http/templates/_deployment.tpl
index f20a9019..2c579fd9 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/base-http/templates/_deployment.tpl
+++ b/neon_diana_utils/helm_charts/http-services/charts/base-http/templates/_deployment.tpl
@@ -13,6 +13,8 @@ spec:
     type: Recreate
   template:
     metadata:
+      annotations:
+        releaseTime: {{ dateInZone "2006-01-02 15:04:05Z" (now) "UTC"| quote }}
       labels:
         neon.diana.service: {{ .Chart.Name }}
         neon.project.name: diana
diff --git a/neon_diana_utils/helm_charts/http-services/charts/libretranslate/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/libretranslate/Chart.yaml
index d921fb2b..cc1254b8 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/libretranslate/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/libretranslate/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-http
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-coqui/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-coqui/Chart.yaml
index a7a10115..29541fc2 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-coqui/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-coqui/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-coqui
 description: Deploy a Coqui TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-glados/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-glados/Chart.yaml
index 72c3b095..8aba1322 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-glados/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-glados/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-glados
 description: Deploy a Glados TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-larynx/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-larynx/Chart.yaml
index fd54be3d..3a9d04ff 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-larynx/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-larynx/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-larynx
 description: Deploy a Larynx TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-ljspeech/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-ljspeech/Chart.yaml
index 30097f66..fc481389 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-ljspeech/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-ljspeech/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-ljspeech
 description: Deploy a LJSpeech TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-mozilla/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-mozilla/Chart.yaml
index 48ee89d3..4696c750 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-mozilla/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-mozilla/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-mozilla
 description: Deploy a Mozilla TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/tts-nancy/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/tts-nancy/Chart.yaml
index eafc19e3..01ff9538 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/tts-nancy/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/tts-nancy/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: tts-nancy
 description: Deploy a Nancy TTS Server
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/http-services/charts/ww-snowboy/Chart.yaml b/neon_diana_utils/helm_charts/http-services/charts/ww-snowboy/Chart.yaml
index 016e8545..72897bb1 100644
--- a/neon_diana_utils/helm_charts/http-services/charts/ww-snowboy/Chart.yaml
+++ b/neon_diana_utils/helm_charts/http-services/charts/ww-snowboy/Chart.yaml
@@ -2,23 +2,11 @@ apiVersion: v2
 name: ww-snowboy
 description: Deploy Snowboy Wake Word Trainer
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
+
+dependencies:
+  - name: base-http
+    version: 0.0.2
+    repository: file://../base-http
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/base-mq/templates/_deployment.tpl b/neon_diana_utils/helm_charts/mq-services/charts/base-mq/templates/_deployment.tpl
index 16ec0fae..5c1a63a8 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/base-mq/templates/_deployment.tpl
+++ b/neon_diana_utils/helm_charts/mq-services/charts/base-mq/templates/_deployment.tpl
@@ -13,6 +13,8 @@ spec:
     type: Recreate
   template:
     metadata:
+      annotations:
+        releaseTime: {{ dateInZone "2006-01-02 15:04:05Z" (now) "UTC"| quote }}
       labels:
         neon.diana.service: {{ .Chart.Name }}
         neon.project.name: diana
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-api-proxy/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-api-proxy/Chart.yaml
index 173917b9..82784f92 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-api-proxy/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-api-proxy/Chart.yaml
@@ -2,28 +2,11 @@ apiVersion: v2
 name: neon-api-proxy
 description: Deploy an MQ API Proxy
 
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
 type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
 version: 0.0.1
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
 appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-brands-service/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-brands-service/Chart.yaml
index 82d904b9..32fe86d5 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-brands-service/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-brands-service/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-email-proxy/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-email-proxy/Chart.yaml
index 25ff286f..847cc892 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-email-proxy/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-email-proxy/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-chatgpt/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-chatgpt/Chart.yaml
index e6606776..2cfd3179 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-chatgpt/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-chatgpt/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-fastchat/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-fastchat/Chart.yaml
index 5fd11a88..78fbc76a 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-fastchat/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-llm-fastchat/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-metrics-service/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-metrics-service/Chart.yaml
index 99523e3b..524896c2 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-metrics-service/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-metrics-service/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/helm_charts/mq-services/charts/neon-script-parser/Chart.yaml b/neon_diana_utils/helm_charts/mq-services/charts/neon-script-parser/Chart.yaml
index 27aa25b4..ebef6d4d 100644
--- a/neon_diana_utils/helm_charts/mq-services/charts/neon-script-parser/Chart.yaml
+++ b/neon_diana_utils/helm_charts/mq-services/charts/neon-script-parser/Chart.yaml
@@ -8,5 +8,5 @@ appVersion: "0.0.6a11"
 
 dependencies:
   - name: base-mq
-    version: 0.0.1
+    version: 0.0.2
     repository: file://../base-mq
\ No newline at end of file
diff --git a/neon_diana_utils/constants.py b/neon_diana_utils/kubernetes_utils.py
similarity index 53%
rename from neon_diana_utils/constants.py
rename to neon_diana_utils/kubernetes_utils.py
index a56382d4..9ac3572b 100644
--- a/neon_diana_utils/constants.py
+++ b/neon_diana_utils/kubernetes_utils.py
@@ -24,58 +24,47 @@
 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 # SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from enum import Enum, IntEnum
-from os.path import join, dirname
 import yaml
 
+from typing import Optional
+from os import getenv
+from os.path import join, expanduser
 
-class Orchestrator(IntEnum):
-    DOCKER = 0
-    KUBERNETES = 1
-    OPENSHIFT = 2
+from neon_diana_utils.configuration import validate_output_path
 
 
-class ServiceClass(Enum):
-    MQ = "mq-backend"
-    HTTP = "http-backend"
-    CORE = "neon-core"
-
-
-def _get_services_by_class() -> dict:
-    """
-    Gets a dict of service names by ServiceClass
-    """
-    service_mappings = join(dirname(__file__), "templates", "service_mappings.yml")
-    with open(service_mappings) as f:
-        services = yaml.safe_load(f)
-    # valid_service_classes = set([svc.get("service_class") for name, svc in services.items()])
-    services_by_class = dict()
-    for name, data in services.items():
-        if data["service_class"] not in services_by_class:
-            services_by_class[data["service_class"]] = list()
-        services_by_class[data["service_class"]].append(name)
-    return services_by_class
-
-
-def valid_mq_services() -> set:
-    """
-    Get the set of valid MQ services that can be automatically configured
-    """
-    return set(_get_services_by_class()[ServiceClass.MQ.value])
-
-
-def valid_http_services() -> set:
-    """
-    Get the set of valid HTTP services that can be automatically configured
-    """
-    return set(_get_services_by_class()[ServiceClass.HTTP.value])
-
-
-def default_mq_services() -> set:
+def create_github_secret(username: str, token: str,
+                         output_file: Optional[str] = None) -> str:
     """
-    Return a specified set of default MQ services
+    Generate a Kubernetes Secret to authenticate to GitHub for image pulls
+    :param username: GitHub username
+    :param token: GitHub token with read_packages permission
+    :param output_file: output file to write
+    :returns: path to written Kubernetes config file
     """
-    return {"neon-rabbitmq",
-            "neon-api-proxy",
-            "neon-email-proxy",
-            "neon-metrics-service"}
+    import json
+    from base64 import b64encode
+    output_file = output_file or join(getenv("NEON_CONFIG_PATH",
+                                             "~/.config/neon"),
+                                      f"k8s_secret_github.yml")
+    output_file = expanduser(output_file)
+    if not validate_output_path(output_file):
+        raise FileExistsError(output_file)
+    encoded_auth = b64encode(f"{username}:{token}".encode())
+    auth_dict = {"auths": {"ghcr.io": {"auth": encoded_auth.decode()}}}
+    auth_str = json.dumps(auth_dict)
+    encoded_config = b64encode(auth_str.encode())
+    secret_spec = {
+        "kind": "Secret",
+        "type": "kubernetes.io/dockerconfigjson",
+        "apiVersion": "v1",
+        "metadata": {
+            "name": "github-auth"
+        },
+        "data": {
+            ".dockerconfigjson": encoded_config.decode()
+        }
+    }
+    with open(output_file, 'w+') as f:
+        yaml.dump(secret_spec, f)
+    return output_file
diff --git a/neon_diana_utils/templates/docker-compose.yml b/neon_diana_utils/templates/docker-compose.yml
deleted file mode 100644
index 0a8e20d9..00000000
--- a/neon_diana_utils/templates/docker-compose.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-version: "3.1"
-
-networks:
-  mq-backend:
-
-volumes: {}
diff --git a/neon_diana_utils/templates/k8s_config_map.yml b/neon_diana_utils/templates/k8s_config_map.yml
deleted file mode 100644
index 6db335a7..00000000
--- a/neon_diana_utils/templates/k8s_config_map.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
-  name: ""
-data: {}
\ No newline at end of file
diff --git a/neon_diana_utils/templates/k8s_secret.yml b/neon_diana_utils/templates/k8s_secret.yml
deleted file mode 100644
index 3b59cd85..00000000
--- a/neon_diana_utils/templates/k8s_secret.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: v1
-kind: Secret
-metadata:
-  name: ""
-type: Opaque
-stringData: {}
\ No newline at end of file
diff --git a/neon_diana_utils/templates/kubernetes.yml b/neon_diana_utils/templates/kubernetes.yml
deleted file mode 100644
index ed3417fe..00000000
--- a/neon_diana_utils/templates/kubernetes.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-apiVersion: v1
-kind: List
-metadata: {}
-items:
-  - kind: Namespace
-    apiVersion: v1
-    metadata:
-      name: ${MQ_NAMESPACE}
-      labels:
-        name: ${MQ_NAMESPACE}
\ No newline at end of file
diff --git a/neon_diana_utils/templates/rmq_backend_config.yml b/neon_diana_utils/templates/rmq_backend_config.yml
index 516629c5..799c0153 100644
--- a/neon_diana_utils/templates/rmq_backend_config.yml
+++ b/neon_diana_utils/templates/rmq_backend_config.yml
@@ -1,31 +1,49 @@
 users:
   - name: "neon_api_utils"
     password: "Klatchat2021"
-    tags: [ ]
+    tags:
+      - backend
+      - user
   - name: "neon_metrics"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_coupons"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_email"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_script_parser"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_api"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_libretranslate"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - service
   - name: "neon_llm_chatgpt"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - llm
   - name: "neon_llm_fastchat"
     password:
-    tags: [ ]
+    tags:
+      - backend
+      - llm
 vhosts:
   - name: "/neon_emails"
   - name: "/neon_api"
diff --git a/neon_diana_utils/templates/service_mappings.yml b/neon_diana_utils/templates/service_mappings.yml
deleted file mode 100644
index c9053ba9..00000000
--- a/neon_diana_utils/templates/service_mappings.yml
+++ /dev/null
@@ -1,244 +0,0 @@
-# MQ Services
-neon-rabbitmq:
-  container_image: &image_rabbitmq rabbitmq:3-management
-  container_name: &name_rabbitmq neon-rabbitmq
-  service_class: mq-backend
-  docker_compose:
-    container_name: *name_rabbitmq
-    image: *image_rabbitmq
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: LoadBalancer
-    ports:
-      - 15672:15672
-      - 5672:5672
-    environment:
-      - RABBITMQ_CONFIG_FILE=/config/rabbitmq.conf
-    volumes:
-      - config:/config
-
-neon-api-proxy:
-  container_image: &image_api_proxy ghcr.io/neongeckocom/neon_api_proxy:dev
-  container_name: &name_api_proxy neon-api-proxy
-  service_class: mq-backend
-  mq:
-    mq_service_name: neon_api_connector
-    mq_username: neon_api
-    mq_user_permissions:
-      neon_api:
-        /neon_api:
-          read: .*
-          write: .*
-          configure: .*
-      neon_api_utils:
-        /neon_api:
-          read: ./*
-          write: ./*
-          configure: ./*
-    mq_vhosts:
-    - /neon_api
-  docker_compose:
-    container_name: *name_api_proxy
-    image: *image_api_proxy
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: headless
-    volumes:
-      - config:/config
-neon-brands-service:
-  container_image: &image_brands_service ghcr.io/neongeckocom/neon_brands_service:dev
-  container_name: &name_brands_service neon-brands-service
-  service_class: mq-backend
-  mq:
-    mq_service_name: neon_coupon_connector
-    mq_username: neon_coupons
-    mq_user_permissions:
-      neon_coupons:
-        /neon_coupons:
-          read: .*
-          write: .*
-          configure: .*
-      neon_api_utils:
-        /neon_coupons:
-          read: .*
-          write: .*
-          configure: .*
-    mq_vhosts:
-    - /neon_coupons
-  docker_compose:
-    container_name: *name_brands_service
-    image: *image_brands_service
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: headless
-    volumes:
-      - config:/config
-neon-email-proxy:
-  container_image: &image_email_proxy ghcr.io/neongeckocom/neon_email_proxy:dev
-  container_name: &name_email_proxy neon-email-proxy
-  service_class: mq-backend
-  mq:
-    mq_service_name: neon_email_proxy
-    mq_username: neon_email
-    mq_user_permissions:
-      neon_email:
-        /neon_emails:
-          read: .*
-          write: .*
-          configure: .*
-      neon_api_utils:
-        /neon_emails:
-          read: "^(?!neon_emails_input).*"
-          write: .*
-          configure: .*
-    mq_vhosts:
-    - /neon_emails
-  docker_compose:
-    container_name: *name_email_proxy
-    image: *image_email_proxy
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: headless
-    volumes:
-      - config:/config
-neon-script-parser:
-  container_image: &image_script_parser ghcr.io/neongeckocom/neon-script-parser:dev
-  container_name: &name_script_parser neon-script-parser
-  service_class: mq-backend
-  mq:
-    mq_service_name: neon_script_parser_service
-    mq_username: neon_script_parser
-    mq_user_permissions:
-      neon_script_parser:
-        /neon_script_parser:
-          read: .*
-          write: .*
-          configure: .*
-      neon_api_utils:
-        /neon_script_parser:
-          read: .*
-          write: .*
-          configure: .*
-    mq_vhosts:
-    - /neon_script_parser
-  docker_compose:
-    container_name: *name_script_parser
-    image: *image_script_parser
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: headless
-    volumes:
-      - config:/config
-neon-metrics-service:
-  container_image: &image_metrics_service ghcr.io/neongeckocom/neon_metrics_service:dev
-  container_name: &name_metrics_service neon-metrics-service
-  service_class: mq-backend
-  mq:
-    mq_service_name: neon_metrics_connector
-    mq_username: neon_metrics
-    mq_user_permissions:
-      neon_metrics:
-        /neon_metrics:
-          read: .*
-          write: .*
-          configure: .*
-      neon_api_utils:
-        /neon_metrics:
-          read: ""
-          write: .*
-          configure: .*
-    mq_vhosts:
-    - /neon_metrics
-  docker_compose:
-    container_name: *name_metrics_service
-    image: *image_metrics_service
-    restart: always
-    networks:
-      - mq-backend
-    labels:
-      kompose.service.type: headless
-    volumes:
-      - config:/config
-      - metrics:/metrics
-
-# HTTP Services
-tts-larynx:
-  container_image: &image_larynx_server rhasspy/larynx:latest
-  container_name: &name_larynx_server tts-larynx
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_larynx_server
-    image: *image_larynx_server
-    restart: always
-    ports:
-      - 5002:5002
-tts-mozilla:
-  container_image: &image_mozilla_tts synesthesiam/mozilla-tts
-  container_name: &name_mozilla_tts tts-mozilla
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_mozilla_tts
-    image: *image_mozilla_tts
-    restart: always
-    ports:
-      - 15002:5002
-tts-glados:
-  container_image: &image_glados_tts ghcr.io/neongeckocom/neon-tts-plugin-glados:dev
-  container_name: &name_glados_tts tts-glados
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_glados_tts
-    image: *image_glados_tts
-    restart: always
-    ports:
-      - 9666:9666
-tts-mimic2-nancy:
-  container_image: &image_mimic2_nancy_tts ghcr.io/openvoiceos/mimic2-nancy:dev
-  container_name: &name_mimic2_nancy_tts tts-mimic2-nancy
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_mimic2_nancy_tts
-    image: *image_mimic2_nancy_tts
-    restart: always
-    ports:
-      - 9000:9000
-tts-mimic2-ljspeech:
-  container_image: &image_mimic2_ljspeech_tts ghcr.io/openvoiceos/mimic2-ljspeech:dev
-  container_name: &name_mimic2_ljspeech_tts tts-mimic2-ljspeech
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_mimic2_ljspeech_tts
-    image: *image_mimic2_ljspeech_tts
-    restart: always
-    ports:
-      - 9001:9000
-lang-libretranslate:
-  container_image: &image_libretranslate libretranslate/libretranslate:main
-  container_name: &name_libretranslate lang-libretranslate
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_libretranslate
-    image: *image_libretranslate
-    restart: always
-    ports:
-      - 5000:5000
-ww-snowboy:
-  container_image: &image_snowboy rhasspy/snowboy-seasalt
-  container_name: &name_snowboy ww-snowboy
-  service_class: http-backend
-  docker_compose:
-    container_name: *name_snowboy
-    image: *image_snowboy
-    restart: always
-    ports:
-      - 8000:8000
diff --git a/neon_diana_utils/utils/__init__.py b/neon_diana_utils/utils/__init__.py
deleted file mode 100644
index d782cbbb..00000000
--- a/neon_diana_utils/utils/__init__.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/neon_diana_utils/utils/backend.py b/neon_diana_utils/utils/backend.py
deleted file mode 100644
index 0322585a..00000000
--- a/neon_diana_utils/utils/backend.py
+++ /dev/null
@@ -1,414 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import yaml
-import json
-import os
-import itertools
-import subprocess
-import secrets
-
-from os import makedirs, getenv
-from os.path import expanduser, isdir, join, dirname, basename, exists, isfile
-from typing import Optional, Set
-from ovos_utils.log import LOG
-from ovos_utils.xdg_utils import xdg_config_home
-from ovos_utils.json_helper import merge_dict
-
-from neon_diana_utils.constants import valid_http_services, Orchestrator
-from neon_diana_utils.rabbitmq_api import RabbitMQAPI
-from neon_diana_utils.utils.docker_utils import write_docker_compose
-from neon_diana_utils.utils.kubernetes_utils import write_kubernetes_spec, \
-    generate_secret
-
-
-def cli_configure_backend(config_path: str, mq_services: Set[str],
-                          admin_user: str, admin_pass: str, http: bool,
-                          volume_driver: str, volume_path: str,
-                          skip_config: bool, mq_namespace: str,
-                          http_namespace: str):
-    """
-    Handle `configure-backend` CLI Command.
-    """
-    if not config_path:
-        raise ValueError("config_path not specified")
-    # Get actual config path
-    config_path = expanduser(config_path)
-    if isfile(config_path):
-        raise FileExistsError(f"Specified output path is a file")
-    if not isdir(config_path):
-        makedirs(config_path)
-
-    # Validate requested password
-    if not skip_config and not admin_pass:
-        raise ValueError("Null password")
-
-    # Validate services
-    if not mq_services and not http:
-        raise ValueError("No services specified")
-
-    # Parse volume paths
-    volumes = {"config": join(volume_path, "config") if volume_path
-               else config_path,
-               "metrics": join(volume_path, "metrics") if volume_path
-               else join(config_path, "metrics")}
-
-    # Parse Namespaces
-    namespaces = {"MQ_NAMESPACE": mq_namespace,
-                  "HTTP_NAMESPACE": http_namespace}
-
-    # Parse user and orchestrator configuration
-    mq_services_config = _parse_services(mq_services, "mq-backend")
-    users_to_configure, neon_mq_user_auth, \
-        mq_docker_compose_configuration, mq_kubernetes_configuration = \
-        _parse_configuration(mq_services_config)
-    if http:
-        _, _, http_docker_configuration, http_kubernetes_configuration = \
-            _parse_configuration(_parse_services(valid_http_services(), "http-backend"))
-        docker_configuration = {**mq_docker_compose_configuration,
-                                **http_docker_configuration}
-        kubernetes_configuration = [*mq_kubernetes_configuration,
-                                    *http_kubernetes_configuration]
-    else:
-        docker_configuration = mq_docker_compose_configuration
-        kubernetes_configuration = mq_kubernetes_configuration
-
-    if not skip_config:
-        configure_mq_backend(admin_user=admin_user, admin_pass=admin_pass,
-                             services=mq_services_config,
-                             users_config=users_to_configure,
-                             neon_mq_user_auth=neon_mq_user_auth)
-
-    generate_backend_config(docker_compose_config=docker_configuration,
-                            kubernetes_config=kubernetes_configuration,
-                            config_path=config_path,
-                            volume_driver=volume_driver,
-                            volumes=volumes,
-                            namespaces=namespaces)
-
-
-def cli_start_backend(config_path: str, attach: bool,
-                      orchestrator: Orchestrator):
-    """
-    Start a backend specified at the passed config_path
-    :param config_path: path to backend spec files
-    :param attach: attach the calling thread to the command output
-    :param orchestrator: orchestrator to use to start backend services
-    """
-    # TODO: Validate the requested file is for a backend
-    if not config_path:
-        raise ValueError("Null config_path")
-
-    config_path = expanduser(config_path)
-    if not isfile(join(config_path, "docker-compose.yml")):
-        raise ValueError(f"docker-compose.yml not found in {config_path}")
-
-    if orchestrator == Orchestrator.DOCKER:
-        docker_compose_command = "docker-compose up"
-        if not attach:
-            docker_compose_command += " --detach"
-            subprocess.Popen(["/bin/bash", "-c", f"cd {config_path} && {docker_compose_command}"]).communicate()
-        else:
-            subprocess.Popen(["/bin/bash", "-c", f"cd {config_path} && {docker_compose_command}"]).communicate()
-    else:
-        raise ValueError("The requested orchestrator is not currently supported")
-
-
-def cli_stop_backend(config_path: str, orchestrator: Orchestrator):
-    """
-    Stop a backend specified at the passed config_path
-    :param config_path: path to backend spec files
-    :param orchestrator: orchestrator to use to start backend services
-    """
-    # TODO: Validate the requested file is for a backend
-    config_path = expanduser(config_path)
-    if not config_path:
-        raise ValueError("Null config_path")
-    if not isfile(join(config_path, "docker-compose.yml")):
-        raise ValueError(f"docker-compose.yml not found in {config_path}")
-
-    if orchestrator == Orchestrator.DOCKER:
-        docker_compose_command = "docker-compose down"
-        subprocess.Popen(["/bin/bash", "-c", f"cd {config_path} && {docker_compose_command}"]).communicate()
-    else:
-        raise ValueError("The requested orchestrator is not currently supported")
-
-
-def write_neon_mq_config(credentials: dict, config_file: Optional[str] = None):
-    """
-    Takes the passed credentials and exports an MQ credential config file
-    :param credentials: MQ User credentials
-    :param config_file: `mq_config.json` file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    config_file = config_file if config_file else \
-        join(getenv("NEON_CONFIG_PATH", "~/.config/neon"), "mq_config.json")
-    config_file = expanduser(config_file)
-    config_path = dirname(config_file)
-    if not exists(config_path):
-        os.makedirs(config_path)
-
-    configuration = {"server": "neon-rabbitmq",
-                     "users": credentials}
-    LOG.info(f"Writing Neon MQ configuration to {config_file}")
-    with open(config_file, 'w+') as new_config:
-        json.dump(configuration, new_config, indent=2)
-
-    # Generate k8s secret
-    generate_secret("mq-config", {"mq_config.json": json.dumps(configuration)},
-                    join(config_path, "config", "k8s_secret_mq-config.yml"))
-
-
-def write_rabbit_config(api: RabbitMQAPI, config_file: Optional[str] = None):
-    """
-    Writes out RabbitMQ config files for persistence on next run
-    :param api: Configured RabbitMQAPI object
-    :param config_file: Path to `rabbit_mq_config.json` file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    config_file = config_file if config_file else \
-        join(getenv("NEON_CONFIG_PATH", "~/.config/neon"),
-             "rabbit_mq_config.json")
-    config_file = expanduser(config_file)
-    config_path = dirname(config_file)
-    if not exists(config_path):
-        os.makedirs(config_path)
-
-    config = api.get_definitions()
-    LOG.info(f"Exporting Rabbit MQ configuration to {config_file}")
-    with open(config_file, "w+") as exported:
-        json.dump(config, exported, indent=2)
-
-    config_basename = basename(config_file)
-    rmq_conf_contents = f"load_definitions = /config/{config_basename}"
-    with open(join(config_path, "rabbitmq.conf"), 'w+') as rabbit:
-        rabbit.write(rmq_conf_contents)
-
-
-def _parse_services(requested_services: set,
-                    service_class: str = "mq-backend") -> dict:
-    """
-    Parse requested services and return a dict mapping of valid service names
-    to configurations read from service_mappings.yml
-    :param requested_services: set of service names requested to be configured
-    :param service_class: string class of services to parse (http, mq)
-    :returns: mapping of service name to parameters required for configuration
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    # Read configuration from templates
-    template_file = join(dirname(dirname(__file__)), "templates",
-                         "service_mappings.yml")
-    with open(template_file) as f:
-        template_data = yaml.safe_load(f)
-    if not requested_services:
-        return {}
-    services_to_configure = {name: dict(template_data[name])
-                             for name in requested_services if
-                             name in template_data and
-                             template_data[name]["service_class"] ==
-                             service_class}
-
-    # Warn for unknown requested services
-    if set(services_to_configure.keys()) != set(requested_services):
-        unhandled_services = [s for s in requested_services
-                              if s not in services_to_configure.keys()]
-        LOG.warning(f"Some requested services not handled: {unhandled_services}")
-    return services_to_configure
-
-
-def _parse_vhosts(services_to_configure: dict) -> set:
-    """
-    Parse MQ vhosts specified in the requested configuration
-    :param services_to_configure: service mapping parsed from service_mappings.yml
-    :returns: set of vhosts to be created
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    default_vhosts = ["/neon_testing"]
-    vhosts = [service.get("mq", service).get("mq_vhosts", [])
-              for service in services_to_configure.values()]
-    vhosts.append(default_vhosts)
-    return set(itertools.chain.from_iterable(vhosts))
-
-
-def _parse_configuration(services_to_configure: dict) -> tuple:
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    # Parse user and orchestrator configuration
-    user_permissions = {
-        "neon_api_utils": {"/neon_testing": {"read": "./*",
-                                             "write": "./*",
-                                             "configure": "./*"}}
-    }
-    neon_mq_auth = dict()
-    docker_compose_configuration = dict()
-    kubernetes_configuration = list()
-    for name, service in services_to_configure.items():
-        # Get service MQ Config
-        if service.get("mq"):
-            merge_dict(user_permissions,
-                       service.get("mq", service).get("mq_user_permissions",
-                                                      dict()))
-            if service["mq"].get("mq_username"):
-                # TODO: Update MQ services such that their service names match the container names DM
-                neon_mq_auth[service.get("mq",
-                                         service).get("mq_service_name", name)] = \
-                    {"user": service.get("mq", service)["mq_username"]}
-        docker_compose_configuration[name] = service["docker_compose"]
-        kubernetes_configuration.extend(service.get("kubernetes") or list())
-    return user_permissions, neon_mq_auth, docker_compose_configuration, kubernetes_configuration
-
-
-def configure_mq_backend(admin_user: str, admin_pass: str,
-                         config_path: str = None,
-                         url: str = "http://0.0.0.0:15672",
-                         services: dict = None,
-                         users_config: dict = None,
-                         neon_mq_user_auth: dict = None) -> dict:
-    """
-    Configure a new Diana RabbitMQ backend
-    :param url: URL of admin portal (default=http://0.0.0.0:15672)
-    :param admin_user: username to configure for RabbitMQ configuration
-    :param admin_pass: password associated with admin_user
-    :param config_path: local path to write configuration files
-    :param services: dict of services to configure on this backend
-    :param users_config: dict of user permissions to configure
-    :param neon_mq_user_auth: dict of MQ service names to credentials
-    :returns: dict MQ user configuration
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    api = RabbitMQAPI(url)
-
-    services = services or _parse_services({"neon-api-proxy",
-                                            "neon-brands-service",
-                                            "neon-email-proxy",
-                                            "neon-script-parser",
-                                            "neon-metrics-service"})
-    if not (users_config and neon_mq_user_auth):
-        users_config, neon_mq_user_auth, _, _ = \
-            _parse_configuration(services)
-
-    # Configure Administrator
-    api.login(admin_user, admin_pass)
-
-    # Configure vhosts
-    vhosts_to_configure = _parse_vhosts(services)
-    LOG.debug(f"vhosts={vhosts_to_configure}")
-    for vhost in vhosts_to_configure:
-        api.add_vhost(vhost)
-
-    # Configure users
-    LOG.debug(f"users={users_config}")
-    credentials = api.create_default_users(list(users_config.keys()))
-    api.add_user("neon_api_utils", "Klatchat2021")
-
-    # Configure user permissions
-    for user, vhost_config in users_config.items():
-        for vhost, permissions in vhost_config.items():
-            if not api.configure_vhost_user_permissions(vhost, user, **permissions):
-                raise RuntimeError(f"Error setting Permission! {user} {vhost}")
-
-    # Export and save rabbitMQ Config
-    config_path = expanduser(config_path or join(xdg_config_home(), "diana"))
-    rabbit_mq_config_file = join(config_path, "rabbit_mq_config.json")
-    write_rabbit_config(api, rabbit_mq_config_file)
-
-    # Write out MQ Connector config file
-    for service in neon_mq_user_auth.values():
-        service["password"] = credentials[service["user"]]
-    neon_mq_config_file = join(config_path, "mq_config.json")
-    write_neon_mq_config(neon_mq_user_auth, neon_mq_config_file)
-    return neon_mq_user_auth
-
-
-def generate_backend_config(docker_compose_config: dict,
-                            kubernetes_config: list,
-                            config_path: Optional[str] = None,
-                            volume_driver: str = "none",
-                            volumes: Optional[dict] = None,
-                            namespaces: dict = None):
-    """
-    Generate orchestrator configuration for the specified services
-    :param docker_compose_config: dict of Docker compose container specs
-    :param kubernetes_config: list of Kubernetes Service/Deployment specs
-    :param config_path: local path to write configuration files
-           (default=NEON_CONFIG_PATH)
-    :param volume_driver: Docker volume driver
-           (https://docs.docker.com/storage/volumes/#use-a-volume-driver)
-    :param volumes: Optional dict of volume names to directories
-           (including hostnames for nfs volumes)
-    :param namespaces: k8s namespaces to configure
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    # Generate docker-compose file
-    docker_compose_file = join(expanduser(config_path), "docker-compose.yml") if config_path else None
-    write_docker_compose(docker_compose_config, docker_compose_file,
-                         volume_driver, volumes)
-
-    # Generate Kubernetes spec file
-    write_kubernetes_spec(kubernetes_config, config_path, namespaces)
-
-
-def generate_rmq_config() -> dict:
-    """
-    Generate a default configuration for RabbitMQ backend services
-    """
-    base_config_file = join(dirname(dirname(__file__)), "templates",
-                            "rmq_backend_config.yml")
-    with open(base_config_file) as f:
-        base_config = yaml.safe_load(f)
-    for user in base_config['users']:
-        if user["password"]:
-            # Skip users with defined passwords
-            continue
-        user['password'] = secrets.token_urlsafe(32)
-    return base_config
-
-
-def generate_mq_auth_config(rmq_config: dict) -> dict:
-    """
-    Generate an MQ auth config from RabbitMQ config
-    :param rmq_config: RabbitMQ definitions, i.d. from `generate_rmq_config
-    :returns: Configuration for Neon MQ-Connector
-    """
-    mq_user_mapping_file = join(dirname(dirname(__file__)), "templates",
-                                "mq_user_mapping.yml")
-    with open(mq_user_mapping_file) as f:
-        mq_user_mapping = yaml.safe_load(f)
-
-    mq_config = dict()
-    LOG.debug(rmq_config.keys())
-    for user in rmq_config['users']:
-        username = user['name']
-        for service in mq_user_mapping.get(username, []):
-            mq_config[service] = {"user": username,
-                                  "password": user['password']}
-    return mq_config
diff --git a/neon_diana_utils/utils/docker_utils.py b/neon_diana_utils/utils/docker_utils.py
deleted file mode 100644
index dbc5088d..00000000
--- a/neon_diana_utils/utils/docker_utils.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import yaml
-import docker
-
-from os import getenv, makedirs
-from os.path import join, dirname, expanduser, isdir
-from typing import Optional
-
-from docker.errors import APIError
-from docker.models.containers import Container
-from ovos_utils.log import LOG
-
-
-def run_clean_rabbit_mq_docker(bind_existing: bool = False) -> Container:
-    """
-    Start a clean RabbitMQ Docker instance to generate configuration files
-    :param bind_existing: If true, allows for binding to a running container
-        WARNING: setting `bind_existing` can result in overwriting valid MQ configuration
-    """
-    docker_client = docker.from_env()
-    try:
-        container = docker_client.containers.run("rabbitmq:3-management",
-                                                 ports={"15672": "15672",
-                                                        "5672": "5672"},
-                                                 detach=True)
-    except APIError as e:
-        if e.status_code == 500:
-            LOG.warning("Is an instance of RabbitMQ already running?")
-            if bind_existing:
-                for c in docker_client.containers.list():
-                    if "rabbitmq:3-management" in c.image.tags:
-                        LOG.info(f"Found a running RabbitMQ instance to configure")
-                        return c
-        else:
-            LOG.error(e)
-        raise e
-    return container
-
-
-def cleanup_docker_container(container_to_remove: Container):
-    """
-    Stop and remove the specified Docker Container
-    :param container_to_remove: Docker.Container object to stop and remove
-    """
-    container_to_remove.stop()
-    container_to_remove.remove()
-
-
-def write_docker_compose(services_config: dict, compose_file: Optional[str] = None,
-                         volume_type: str = "none",
-                         volumes: Optional[dict] = None):
-    """
-    Generates and writes a docker-compose.yml file according to the specified services
-    :param services_config: dict services, usually read from service_mappings.yml
-    :param compose_file: path to docker-compose.yml file to write
-    :param volume_type: volume type to use for config (default "none" uses local fs)
-    :param volumes: Optional dict of volume names to directories (including hostnames for nfs volumes)
-    """
-    compose_file = compose_file if compose_file else \
-        join(getenv("NEON_CONFIG_PATH", "~/.config/neon"), "docker-compose.yml")
-    compose_file = expanduser(compose_file)
-
-    with open(join(dirname(dirname(__file__)), "templates", "docker-compose.yml")) as f:
-        compose_boilerplate = yaml.safe_load(f)
-    compose_contents = {**compose_boilerplate, **{"services": services_config}}
-
-    volumes = volumes or list()
-    for vol in volumes:
-        if volume_type == "nfs":
-            volume_host, volume_path = volumes[vol].split(':')
-            volume_opts = f"addr={volume_host},nolock,rw,soft,nfsvers=4"
-            volume_path = f"\":{volume_path}\""
-        else:
-            volume_opts = "bind"
-            volume_path = volumes[vol] or dirname(compose_file)
-            if not isdir(volume_path):
-                makedirs(volume_path)
-        compose_contents["volumes"][vol] = {
-            "driver_opts": {
-                "type": f"{volume_type}-{vol}" if volume_type else vol,
-                "o": volume_opts,
-                "device": volume_path
-            },
-            "labels": {
-                "kompose.volume.storage-class-name": f"nfs-{vol}"
-            }
-        }
-
-    with open(compose_file, "w+") as f:
-        yaml.dump(compose_contents, f)
diff --git a/neon_diana_utils/utils/kubernetes_utils.py b/neon_diana_utils/utils/kubernetes_utils.py
deleted file mode 100644
index d708f522..00000000
--- a/neon_diana_utils/utils/kubernetes_utils.py
+++ /dev/null
@@ -1,420 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import os.path
-import yaml
-
-from typing import Optional
-from os import getenv
-from os.path import dirname, join, expanduser, isdir, isfile
-from ovos_utils.log import LOG
-
-
-def cli_make_rmq_config_map(input_path: str, output_path: str) -> str:
-    """
-    Generate a ConfigMap object for RabbitMQ from general config files
-    :param input_path: path to directory containing RabbitMQ config files
-    :param output_path: file or dir to write Kubernetes spec file to
-    :returns: path to output config file
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    file_path = expanduser(input_path)
-    if not isdir(file_path):
-        raise FileNotFoundError(f"Could not find requested directory: "
-                                f"{input_path}")
-    output_path = expanduser(output_path)
-    if isdir(output_path):
-        output_file = join(output_path, f"k8s_config_rabbitmq.yml")
-    elif isfile(output_path):
-        output_file = output_path
-    else:
-        raise ValueError(f"Invalid output_path: {output_path}")
-
-    with open(join(file_path, "rabbitmq.conf"), 'r') as f:
-        rabbitmq_file_contents = f.read()
-    with open(join(file_path, "rabbit_mq_config.json")) as f:
-        rmq_config = f.read()
-    generate_config_map("rabbitmq", {"rabbitmq.conf": rabbitmq_file_contents,
-                                     "rabbit_mq_config.json": rmq_config}, output_file)
-    return output_file
-
-
-def cli_make_api_secret(input_path: str, output_path: str) -> str:
-    """
-    Generate a Secret object for ngi-auth from general config files
-    :param input_path: path to directory containing ngi_auth_vars.yml
-    :param output_path: file or dir to write Kubernetes spec file to
-    :returns: path to output config file
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    file_path = expanduser(input_path)
-    if not isdir(file_path):
-        raise FileNotFoundError(f"Could not find requested directory: {input_path}")
-    output_path = expanduser(output_path)
-    if isfile(output_path):
-        output_path = dirname(output_path)
-
-    with open(join(file_path, "ngi_auth_vars.yml")) as f:
-        ngi_auth = f.read()
-    generate_secret("ngi-auth", {"ngi_auth_vars.yml": ngi_auth},
-                    join(output_path, "k8s_secret_ngi-auth.yml"))
-    return output_path
-
-
-def cli_make_github_secret(username: str, token: str, config_dir: str) -> str:
-    """
-    Generate a Secret object for a GitHub image pull secret
-    :param username: Github username
-    :param token: Github PAT with package_read permissions
-    :param config_dir: Directory to write output spec file to
-    :returns: path to output config file
-    """
-    output_file = _create_github_secret(username, token,
-                                        join(config_dir,
-                                             "k8s_secret_github.yml"))
-    return output_file
-
-
-def write_kubernetes_spec(k8s_config: list, output_path: Optional[str] = None,
-                          namespaces: dict = None):
-    """
-    Generates and writes a kubernetes.yml spec file according to the passed services
-    :param k8s_config: list of k8s objects specified, usually read from service_mappings.yml
-    :param output_path: path to write spec files to
-    :param namespaces: dict of placeholders to namespaces
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    namespaces = namespaces or dict()
-    output_dir = expanduser(output_path) if output_path else \
-        expanduser(getenv("NEON_CONFIG_PATH", "~/.config/neon"))
-
-    diana_spec_file = join(output_dir, "services", "k8s_diana_backend.yml")
-    os.makedirs(dirname(diana_spec_file), exist_ok=True)
-
-    # Write Diana services spec file
-    with open(join(dirname(dirname(__file__)), "templates",
-                   "kubernetes.yml")) as f:
-        diana_spec_contents = yaml.safe_load(f)
-    diana_spec_contents["items"].extend(k8s_config)
-
-    with open(diana_spec_file, "w+") as f:
-        yaml.dump(diana_spec_contents, f)
-        f.seek(0)
-        string_contents = f.read()
-        for placeholder, replacement in namespaces.items():
-            string_contents = string_contents.replace(
-                '${' + placeholder + '}', replacement)
-        f.seek(0)
-        f.truncate(0)
-        f.write(string_contents)
-
-    # Write Ingress spec file
-    os.makedirs(join(output_path, "ingress"), exist_ok=True)
-
-    tcp_config = {'5672': f"{namespaces.get('MQ_NAMESPACE') or 'default'}"
-                          f"/neon-rabbitmq:5672"}
-    tcp_config = _update_tcp_config(tcp_config,
-                                    join(output_path, "ingress",
-                                         "k8s_config_tcp_services.yml"))
-    LOG.info(f"Wrote {tcp_config}")
-    ingress_config = _patch_ingress_nginx_controller_service(
-        "neon-rabbitmq", 5672,
-        output_path=join(output_path, "ingress",
-                         "k8s_patch_nginx_service.yml"))
-    LOG.info(f"Wrote {ingress_config}")
-
-
-def generate_config_map(name: str, config_data: dict, output_path: str):
-    """
-    Generate a Kubernetes ConfigMap yml definition
-    :param name: ConfigMap name
-    :param config_data: Dict data to store
-    :param output_path: output file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_path = output_path or join(getenv("NEON_CONFIG_PATH", "~/.config/neon"), f"k8s_config_{name}.yml")
-    output_path = expanduser(output_path)
-    if not isdir(dirname(output_path)):
-        os.makedirs(dirname(output_path), exist_ok=True)
-    config_template = join(dirname(dirname(__file__)),
-                           "templates", "k8s_config_map.yml")
-    with open(config_template) as f:
-        config_map = yaml.safe_load(f)
-
-    config_map["metadata"]["name"] = name
-    config_map["data"] = config_data
-
-    with open(output_path, 'w+') as f:
-        yaml.dump(config_map, f)
-
-
-def generate_secret(name: str, secret_data: dict,
-                    output_path: Optional[str] = None):
-    """
-    Generate a Kubernetes Secret yml definition
-    :param name: ConfigMap name
-    :param secret_data: Dict data to store
-    :param output_path: output file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_path = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"),
-                                      f"k8s_secret_{name}.yml")
-    output_path = expanduser(output_path)
-    if not isdir(dirname(output_path)):
-        os.makedirs(dirname(output_path), exist_ok=True)
-    config_template = join(dirname(dirname(__file__)),
-                           "templates", "k8s_secret.yml")
-    with open(config_template) as f:
-        config_map = yaml.safe_load(f)
-
-    config_map["metadata"]["name"] = name
-    config_map["stringData"] = secret_data
-
-    with open(output_path, 'w+') as f:
-        yaml.dump(config_map, f)
-
-
-def _create_github_secret(username: str, token: str,
-                          output_path: Optional[str] = None) -> str:
-    """
-    Generate a Kubernetes Secret to authenticate to GitHub for image pulls
-    :param username: GitHub username
-    :param token: GitHub token with read_packages permission
-    :param output_path: output file to write
-    :returns: path to written Kubernetes config file
-    """
-    import json
-    from base64 import b64encode
-    output_path = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"),
-                                      f"k8s_secret_github.yml")
-    output_path = expanduser(output_path)
-    encoded_auth = b64encode(f"{username}:{token}".encode())
-    auth_dict = {"auths": {"ghcr.io": {"auth": encoded_auth.decode()}}}
-    auth_str = json.dumps(auth_dict)
-    encoded_config = b64encode(auth_str.encode())
-    secret_spec = {
-        "kind": "Secret",
-        "type": "kubernetes.io/dockerconfigjson",
-        "apiVersion": "v1",
-        "metadata": {
-            "name": "github-auth"
-        },
-        "data": {
-            ".dockerconfigjson": encoded_config.decode()
-        }
-    }
-    with open(output_path, 'w+') as f:
-        yaml.dump(secret_spec, f)
-    return output_path
-
-
-def _update_tcp_config(port_config: dict,
-                       output_path: Optional[str] = None) -> str:
-    """
-    Generate (or update) a tcp-services config spec for ingress-nginx
-    :param port_config: dict of "{port}" to "{namespace}/{service}:{port}"
-    :param output_path: output file to write
-    :returns: path to `tcp-services` k8s config map spec to be applied
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_file = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"), "ingress",
-                                      f"k8s_config_tcp_services.yml")
-    output_file = expanduser(output_file)
-
-    if isfile(output_file):
-        with open(output_file) as f:
-            config = yaml.safe_load(f)
-    else:
-        config = {'apiVersion': 'v1',
-                  'kind': 'ConfigMap',
-                  'metadata': {
-                      'name': 'tcp-services',
-                      'namespace': 'ingress-nginx'
-                  },
-                  'data': {}
-                  }
-    config['data'] = {**config['data'], **port_config}
-    with open(output_file, 'w+') as f:
-        yaml.dump(config, f, default_flow_style=False)
-    return output_file
-
-
-def _patch_ingress_nginx_controller_service(name: str, port: int,
-                                            target_port: Optional[int] = None,
-                                            protocol: str = "TCP",
-                                            output_path: Optional[str] = None):
-    """
-    Generate an updated nginx controller service spec
-    :param name: name of TCP service to configure
-    :param port: port to forward
-    :param target_port: optional target port (else uses `port`)
-    :param protocol: transport protocol (default TCP)
-    :param output_path: path to output file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_file = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"), "ingress",
-                                      f"k8s_patch_nginx_service.yml")
-    output_file = expanduser(output_file)
-    if os.path.isfile(output_file):
-        LOG.info(f"Reading ingress config from {output_file}")
-        with open(output_file) as f:
-            config = yaml.safe_load(f)
-    else:
-        config = {"spec": {"ports": []}}
-
-    if any([i['name'] == name for i in config['spec']['ports']]):
-        LOG.info(f"Skipping already configured port: {name}")
-        return output_file
-    config['spec']['ports'].append({'name': name,
-                                    'port': port,
-                                    'targetPort': target_port or port,
-                                    'protocol': protocol})
-
-    with open(output_file, 'w+') as f:
-        yaml.dump(config, f, default_flow_style=False)
-    return output_file
-
-
-def _update_ingress_config(address: str, service: str, port: int,
-                           cert_issuer: str = "letsencrypt-prod",
-                           output_path: Optional[str] = None):
-    """
-    Update routing rules to route traffic to a service.
-    Note this assumes you are using ingress-nginx and tls
-    :param address: hostname rule applies to
-    :param service: service name
-    :param port: exposed container port service is running on
-    :param cert_issuer: Name of k8s Issuer to provide TLS certificates
-    :param output_path: path to output file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_file = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"), "ingress",
-                                      f"k8s_config_ingress.yml")
-    output_file = expanduser(output_file)
-    if os.path.isfile(output_file):
-        LOG.info(f"Reading ingress config from {output_file}")
-        with open(output_file) as f:
-            config = yaml.safe_load(f)
-        if config['metadata']['annotations']['cert-manager.io/issuer'] != cert_issuer:
-            raise RuntimeError("Config cert_issuer conflict, skipping update")
-    else:
-        config = {
-            "kind": "Ingress",
-            "apiVersion": "networking.k8s.io/v1",
-            "metadata": {
-                "name": "ingress-diana",
-                "annotations": {
-                    "cert-manager.io/issuer": cert_issuer
-                }
-            },
-            "spec": {
-                "ingressClassName": "nginx",
-                "tls": [
-                    {"hosts": [],
-                     "secretName": f"tls-{cert_issuer}"}
-                ],
-                "rules": []
-            }
-        }
-
-    if address in config['spec']['tls'][0]['hosts']:
-        LOG.warning(f"Skipping address already configured: {address}")
-        return output_file
-
-    config['spec']['tls'][0]['hosts'].append(address)
-    config['spec']['rules'].append({"host": address,
-                                    "http": {"paths": [
-                                        {"path": '/',
-                                         "pathType": "Prefix",
-                                         "backend": {
-                                             "service": {
-                                                 "name": service,
-                                                 "port": {
-                                                     "number": port
-                                                 }
-                                             }
-                                         }}
-                                    ]}})
-    with open(output_file, 'w+') as f:
-        yaml.dump(config, f, default_flow_style=False)
-    return output_file
-
-
-def _create_cert_issuer(name: str, email: str,
-                        output_path: Optional[str] = None) -> str:
-    """
-    Create a certificate Issuer k8s spec
-    :param name: name for Issuer
-    :param email: email to use for ACME registration
-    :param output_path: spec file to write
-    """
-    # TODO: Deprecate Method
-    LOG.warning("This function is deprecated")
-    output_file = output_path or join(getenv("NEON_CONFIG_PATH",
-                                             "~/.config/neon"), "ingress",
-                                      f"k8s_config_cert_issuer.yml")
-    output_file = expanduser(output_file)
-    spec = {
-        "apiVersion": "cert-manager.io/v1",
-        "kind": "Issuer",
-        "metadata": {
-            "name": name
-        },
-        "spec": {
-            "acme": {
-                "server": "https://acme-v02.api.letsencrypt.org/directory",
-                "email": email,
-                "privateKeySecretRef": {
-                    "name": name
-                },
-                "solvers": [
-                    {
-                        "http01": {
-                            "ingress": {
-                                "class": "nginx"
-                            }
-                        }
-                    }
-                ]
-            }
-        }
-    }
-    with open(output_file, 'w+') as f:
-        yaml.safe_dump(spec, f, default_flow_style=False)
-    return output_file


Test patch:
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index 7d4b9f44..987913c2 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -19,29 +19,13 @@ jobs:
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install -r requirements/requirements.txt
+          pip install -e .
           pip install -r requirements/test_requirements.txt
-      - name: Run Utils Tests
+      - name: Run Tests
         run: |
-          pytest tests/test_diana_utils.py --doctest-modules --junitxml=tests/utils-test-results.xml
-      - name: Upload Utils test results
+          pytest tests --doctest-modules --junitxml=tests/diana-test-results.xml
+      - name: Upload test results
         uses: actions/upload-artifact@v2
         with:
           name: utils-test-results
-          path: tests/utils-test-results.xml
-      - name: Run Constants Tests
-        run: |
-          pytest tests/test_constants.py --doctest-modules --junitxml=tests/constants-test-results.xml
-      - name: Upload Constants test results
-        uses: actions/upload-artifact@v2
-        with:
-          name: constants-test-results
-          path: tests/constants-test-results.xml
-      - name: Run Service Tests
-        run: |
-          pytest tests/test_diana_services.py --doctest-modules --junitxml=tests/services-test-results.xml
-      - name: Upload Services test results
-        uses: actions/upload-artifact@v2
-        with:
-          name: services-test-results
-          path: tests/services-test-results.xml
\ No newline at end of file
+          path: tests/diana-test-results.xml
diff --git a/tests/config/ngi_local_conf.yml b/tests/config/ngi_local_conf.yml
deleted file mode 100644
index e13b33ec..00000000
--- a/tests/config/ngi_local_conf.yml
+++ /dev/null
@@ -1,239 +0,0 @@
-prefFlags:
-  codeSource: git
-  devMode: false
-  autoStart: false
-  autoUpdate: false
-  notifyRelease: true
-  showDemo: true
-  optDiags: false
-  metrics: false
-  saveAudio: false
-  saveText: true
-  # TODO: These should go somewhere in HAL/Enclosure
-  localGui: true
-  guiEvents: true
-
-interface:
-# True, False
-  display_neon_brain: false
-  wake_word_enabled: true
-  clap_commands_enabled: false
-  blink_commands_enabled: false
-  random_dialog_enabled: false
-  confirm_listening: true
-  mute_on_listen: false
-  # True=Muted, False=lowered vol
-  use_hesitation: false
-
-devVars:
-  devName:
-  devType: generic
-  # generic, server, pi, neonK, neonX, neonPi, neonAlpha, neonU, mycroft_mark_2, ubuntu, linux
-  version:
-  touchDev:
-  micDev:
-  camDev: 0
-  soundDev:
-  defaultVolume: 60
-  defaultMicVolume: 100
-  installUser:
-  # TODO: Depreciate installUser
-
-gestures:
-  clapThreshold: 10.0e10
-
-audioService:
-  backends:
-    local: {type: simple, active: true}
-    vlc: {type: vlc, active: true, duck: true}
-  defaultBackend: vlc
-  debug: true
-
-padatious:
-  intent_cache: ~/.neon/intent_cache
-  train_delay: 4
-  single_thread: false
-
-websocket:
-  host: 0.0.0.0
-  port: 8181
-  route: /core
-  ssl: false
-  allow_self_signed: true
-  ssl_cert:
-  ssl_key:
-
-gui:
-  # TODO: This should be built so lang, enclosure are read from other places
-  # Host information for the gui server
-  lang: en-us
-  enclosure: generic
-  host: 0.0.0.0
-  port: 18181
-  route: /gui
-  ssl: false
-  resource_root: https://0000.us/klatchat/app/files/neon_qml
-
-hotwords:
-  Hey Neon: {module: ovos-ww-plugin-pocketsphinx, phonemes: HH EY . N IY AA N ., threshold: 1e-20,
-    lang: en-us, sample_rate: 16000, listen: true, sound: snd/start_listening.wav,
-    local_model_file: None}
-
-listener:
-  sample_rate: 16000
-  channels: 1
-  wake_word_upload: {disable: true, url: https://training.mycroft.ai/precise/upload}
-  mute_during_output: true
-  duck_while_listening: 0.3
-  dev_index:
-  phoneme_duration: 120
-  multiplier: 1.0
-  energy_ratio: 1.5
-  wake_word: hey neon
-  stand_up_word: wake up
-  recording_timeout: 10.0,
-  recording_timeout_with_silence: 3.0
-
-skills:
-  auto_update: true
-  install_essential: true
-  install_default: true
-  wait_for_internet: true
-  debug: true
-  blacklist: []
-  priority: [about.neon.neongeckocom, date-time.neon.neongeckocom, skill-weather.neongeckocom]
-  essential_skills: []
-  default_skills: https://raw.githubusercontent.com/NeonGeckoCom/neon-skills-submodules/master/DEFAULT-SKILLS
-  skill_manager: osm
-  # recommended osm, optional msm
-  appstore_sync_interval: 6.0
-  # time between server syncs in hours
-  auto_update_interval: 24.0
-  # time between automatic skill updates in hours
-  msm_ver: false
-  repo_url: https://github.com/MycroftAI/mycroft-skills
-  repo_branch: '18.08'
-  data_dir: ~/.neon/msm
-  neon_token:
-
-audio_parsers:
-  blacklist: [gender]
-
-session:
-  ttl: 180
-tts:
-  module: amazon
-  amazon: {region: us-west-2, aws_access_key_id: '', aws_secret_access_key: ''}
-  mozilla: {request_url: http://0.0.0.0:5002/api/tts?}
-  mimic: {voice: ap}
-  mimic2: {lang: en-us, url: https://mimic-api.mycroft.ai/synthesize?text=}
-stt:
-  module: google_cloud_streaming
-
-language:
-  boost: false
-  core_lang: en-us
-  translation_module: libretranslate_plug
-  detection_module: libretranslate_detection_plug
-  libretranslate: {libretranslate_host: https://translate.neon.ai:5000}
-
-logs:
-  blacklist: [enclosure.mouth.viseme, enclosure.mouth.display]
-
-device:
-  mac: 00:00:00:00:00:00
-  ip4: 127.0.0.1
-  ip6: ::1
-  ver: ''
-
-api:
-  url: https://api.mycroft.ai
-  version: v1
-  update: true
-  disabled: true
-  sync_skill_settings: false
-
-# Below are (mostly) depreciated DM
-
-remoteVars:
-  coreGit:
-  coreBranch:
-  skillsGit:
-  skillsBranch:
-  guiGit:
-  guiBranch:
-  url: https://api.mycroft.ai
-  ver: v1
-  remoteHost: 167.172.112.7
-  enableConnection: true
-
-gnome:
-  favApps:
-  appFolders: "['accessories', 'office', 'Neongecko']"
-
-dirVars:
-  coreDir: /opt/neon
-  # coreDir is depreciated for packaged Neon Installations
-  rootDir: ~/.local/share/neon
-  confDir: ~/.config/neon
-  cacheDir: ~/.cache/neon
-  skillsDir: ~/.local/share/neon/skills
-  ngiDir: /opt/neon/NGI
-  # ngiDir is depreciated for packaged Neon Installations
-  guiDir:
-  tempDir: /tmp/neon
-  docsDir: ~/Documents/NeonGecko
-  diagsDir: ~/Documents/NeonGecko/Diagnostics
-  ipcDir: /tmp/neon/ipc
-  # Changes to ipcDir must also be made in NGI/utilities/configHelper
-  musicDir: ~/Music
-  videoDir: ~/Videos/NeonGecko
-  picsDir: ~/Pictures/NeonGecko
-  logsDir: /var/log/mycroft
-  repoDir: ~/.neon/skills-repo
-  padatiousDir: ~/.neon/intent_cache
-
-# File paths should be absolute (or ~/ relative) or relative to /res/ (see resolve_resource_file)
-fileVars:
-  sshKey: ~/.ssh/id_rsa
-  notify: snd/loaded.wav
-
-sounds:
-  startListening: snd/start_listening.wav
-  endListening: snd/end_listening.wav
-  acknowledge: snd/acknowledge.mp3
-
-ttsOpts: {None: '', 'Chinese, Mandarin': zh-ZH, Danish: da-DK, Dutch: nl-NL, 'English, Australian': en-AU,
-  'English, British': en-GB, 'English, Indian': en-IN, 'English, US': en-US, 'English, Welsh': en-GB-WLS,
-  French: fr-FR, 'French, Canadian': fr-CA, Hindi: hi-IN, German: de-DE, Icelandic: is-IS,
-  Italian: it-IT, Japanese: ja-JP, Korean: ko-KR, Norwegian: nb-NO, Polish: pl-PL,
-  'Portuguese, Brazilian': pt-BR, 'Portuguese, European': pt-PT, Romanian: ro-RO,
-  Russian: ru-RU, 'Spanish, European': es-ES, 'Spanish, Mexican': es-MX, 'Spanish, US': es-US,
-  Swedish: sv-SE, Turkish: tr-TR, Welsh: cy-GB}
-
-sttOpts: {Deutsch (Deutschland): de-DE, English (United States): en-US, Español (España): es-ES,
-  Español (México): es-MX, Français (Canada): fr-CA, Français (France): fr-FR, Italiano (Italia): it-IT,
-  Português (Portugal): pt-PT}
-
-sttSpokenOpts: {Afrikaans: af-ZA, Amharic: am-ET, Armenian: hy-AM, Azerbaijani: az-AZ,
-  Indonesian: id-ID, Malay: ms-MY, Bengali: bn-BD, Catalan: ca-ES, Czech: cs-CZ, Danish: da-DK,
-  German: de-DE, 'English, British': en-GB, 'English, American': en-US, Spanish: es-ES,
-  'Spanish, American': es-US, 'Spanish, Mexican': es-MX, Basque: eu-ES, Filipino: fil-PH,
-  'French, Canadian': fr-CA, French: fr-FR, Galician: gl-ES, Georgian: ka-GE, Gujarati: gu-IN,
-  Croatian: hr-HR, Zulu: zu-ZA, Icelandic: is-IS, Italian: it-IT, Javanese: jv-ID,
-  Kannada: kn-IN, Khmer: km-KH, Lao: lo-LA, Latvian: lv-LV, Lithuanian: lt-LT, Hungarian: hu-HU,
-  Malayalam: ml-IN, Marathi: mr-IN, Dutch: nl-NL, Nepali: ne-NP, Norwegian Bokmål: nb-NO,
-  Polish: pl-PL, 'Portuguese, Brazilian': pt-BR, Portuguese: pt-PT, Romanian: ro-RO,
-  Sinhala: si-LK, Slovak: sk-SK, Slovenian: sl-SI, Sundanese: su-ID, Swahili: sw-TZ,
-  Finnish: fi-FI, Swedish: sv-SE, Tamil: ta-IN, Telugu: te-IN, Vietnamese: vi-VN,
-  Turkish: tr-TR, Urdu: ur-PK, Greek: el-GR, Bulgarian: bg-BG, Russian: ru-RU, Serbian: sr-RS,
-  Ukrainian: uk-UA, Hebrew: he-IL, Arabic: ar-IL, Persian: fa-IR, Hindi: hi-IN, Thai: th-TH,
-  Korean: ko-KR, Taiwanese: zh-TW, 'Chinese, Cantonese': yue-Hant-HK, Japanese: ja-JP,
-  'Chinese, Mandarin': zh}
-
-MQ:
-  server: 0.0.0.0
-  users:
-    mq_handler:
-      user: neon_api_utils
-      password: Klatchat2021
diff --git a/tests/config/valid_docker-compose-services.json b/tests/config/valid_docker-compose-services.json
deleted file mode 100644
index 857bc428..00000000
--- a/tests/config/valid_docker-compose-services.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
-  "neon-metrics-service": {
-    "container_name": "neon-metrics-service",
-    "hostname": "neon-metrics-service",
-    "image": "ghcr.io/neongeckocom/neon-metrics-service:dev",
-    "restart": "unless-stopped",
-    "links": [
-      "neon-rabbitmq"
-    ],
-    "volumes": [
-      "host_path:/config",
-      "host_path_metrics:/metrics"
-    ]
-  },
-  "neon-rabbitmq": {
-    "container_name": "neon-rabbitmq",
-    "hostname": "neon-rabbitmq",
-    "image": "rabbitmq:3-management",
-    "restart": "unless-stopped",
-    "ports": [
-      "15672:15672",
-      "5672:5672"
-    ],
-    "volumes": [
-      "host_path/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf",
-      "host_path:/config"
-    ]
-  },
-  "neon-api-proxy": {
-    "container_name": "neon-api-proxy",
-    "hostname": "neon-api-proxy",
-    "image": "ghcr.io/neongeckocom/neon-api-proxy:dev",
-    "restart": "unless-stopped",
-    "links": [
-      "neon-rabbitmq"
-    ],
-    "volumes": [
-      "host_path:/config"
-    ]
-  }
-}
\ No newline at end of file
diff --git a/tests/config/valid_k8s_config.json b/tests/config/valid_k8s_config.json
deleted file mode 100644
index 52862faf..00000000
--- a/tests/config/valid_k8s_config.json
+++ /dev/null
@@ -1,191 +0,0 @@
-[
-  {
-    "apiVersion": "v1",
-    "kind": "Service",
-    "metadata": {
-      "labels": {
-        "io.kompose.service": "neon-api-proxy"
-      },
-      "name": "neon-api-proxy"
-    },
-    "spec": {
-      "clusterIP": "None",
-      "ports": [
-        {
-          "name": "headless",
-          "port": 55555,
-          "targetPort": 0
-        }
-      ],
-      "selector": {
-        "io.kompose.service": "neon-api-proxy"
-      }
-    }
-  },
-  {
-    "apiVersion": "apps/v1",
-    "kind": "Deployment",
-    "metadata": {
-      "labels": {
-        "io.kompose.service": "neon-api-proxy"
-      },
-      "name": "neon-api-proxy"
-    },
-    "spec": {
-      "replicas": 1,
-      "selector": {
-        "matchLabels": {
-          "io.kompose.service": "neon-api-proxy"
-        }
-      },
-      "strategy": {
-        "type": "Recreate"
-      },
-      "template": {
-        "metadata": {
-          "labels": {
-            "io.kompose.network/mq-backend": "true",
-            "io.kompose.service": "neon-api-proxy"
-          }
-        },
-        "spec": {
-          "containers": [
-            {
-              "image": "ghcr.io/neongeckocom/neon_api_proxy:dev",
-              "name": "neon-api-proxy",
-              "resources": {},
-              "volumeMounts": [
-                {
-                  "mountPath": "/config/ngi_auth_vars.yml",
-                  "name": "ngi-auth"
-                },
-                {
-                  "mountPath": "/config/mq_config.json",
-                  "name": "mq-auth"
-                }
-              ]
-            }
-          ],
-          "restartPolicy": "Always",
-          "volumes": [
-            {
-              "name": "ngi-auth",
-              "secret": {
-                "secretName": "api-keys"
-              }
-            },
-            {
-              "name": "mq-auth",
-              "secret": {
-                "secretName": "mq-auth"
-              }
-            }
-          ]
-        }
-      }
-    }
-  },
-  {
-    "apiVersion": "v1",
-    "kind": "Service",
-    "metadata": {
-      "labels": {
-        "io.kompose.service": "neon-rabbitmq"
-      },
-      "name": "neon-rabbitmq"
-    },
-    "spec": {
-      "type": "LoadBalancer",
-      "ports": [
-        {
-          "name": "mq-admin",
-          "port": 15672,
-          "targetPort": 15672,
-          "protocol": "TCP"
-        },
-        {
-          "name": "mq-traffic",
-          "port": 5672,
-          "targetPort": 5672,
-          "protocol": "TCP"
-        }
-      ],
-      "selector": {
-        "io.kompose.service": "neon-rabbitmq"
-      }
-    }
-  },
-  {
-    "apiVersion": "apps/v1",
-    "kind": "Deployment",
-    "metadata": {
-      "name": "neon-rabbitmq"
-    },
-    "spec": {
-      "replicas": 1,
-      "selector": {
-        "matchLabels": {
-          "io.kompose.service": "neon-rabbitmq"
-        }
-      },
-      "strategy": {
-        "type": "Recreate"
-      },
-      "template": {
-        "metadata": {
-          "labels": {
-            "io.kompose.network/mq-backend": "true",
-            "io.kompose.service": "neon-rabbitmq"
-          }
-        },
-        "spec": {
-          "containers": [
-            {
-              "env": [
-                {
-                  "name": "RABBITMQ_CONFIG_FILE",
-                  "value": "/config/rabbitmq.conf"
-                }
-              ],
-              "image": "rabbitmq:3-management",
-              "name": "neon-rabbitmq",
-              "ports": [
-                {
-                  "containerPort": 15672
-                },
-                {
-                  "containerPort": 5672
-                }
-              ],
-              "volumeMounts": [
-                {
-                  "mountPath": "/config",
-                  "name": "config"
-                }
-              ]
-            }
-          ],
-          "restartPolicy": "Always",
-          "volumes": [
-            {
-              "name": "config",
-              "configMap": {
-                "name": "rabbitmq",
-                "items": [
-                  {
-                    "key": "rabbit_mq_config.json",
-                    "path": "rabbit_mq_config.json"
-                  },
-                  {
-                    "key": "rabbitmq.conf",
-                    "path": "rabbitmq.conf"
-                  }
-                ]
-              }
-            }
-          ]
-        }
-      }
-    }
-  }
-]
\ No newline at end of file
diff --git a/tests/config/valid_rabbitmq.json b/tests/config/valid_rabbitmq.json
deleted file mode 100644
index 35321db5..00000000
--- a/tests/config/valid_rabbitmq.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
-  "rabbit_version": "3.9.9",
-  "rabbitmq_version": "3.9.9",
-  "product_name": "RabbitMQ",
-  "product_version": "3.9.9",
-  "users": [
-    {
-      "name": "admin",
-      "password_hash": "wf7UYT6N8MZOD4LAQIs9CrUIG4qZ9bK0/OviUS2y7uHBrhPZ",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [
-        "administrator"
-      ],
-      "limits": {}
-    },
-    {
-      "name": "neon_api_utils",
-      "password_hash": "S8JsaNbG7436awNWb1XyehfVcueqOvJU5ecEimzbcVCQt+d3",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    },
-    {
-      "name": "neon_metrics",
-      "password_hash": "7SgPQoQRcOSMrRCMZotCUu9zeKBK+z3YtBcG7jZ+bS69GoFa",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    },
-    {
-      "name": "neon_api",
-      "password_hash": "Ua92n5KKsUtIY49mlEnZLSnUtM/9Is6McCCCcP2qMFXUk0os",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    }
-  ],
-  "vhosts": [
-    {
-      "name": "/"
-    },
-    {
-      "name": "/neon_metrics"
-    },
-    {
-      "name": "/neon_api"
-    }
-  ],
-  "permissions": [
-    {
-      "user": "admin",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "admin",
-      "vhost": "/neon_api",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "neon_api_utils",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ""
-    },
-    {
-      "user": "neon_metrics",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "neon_api_utils",
-      "vhost": "/neon_api",
-      "configure": "./*",
-      "write": "./*",
-      "read": "./*"
-    },
-    {
-      "user": "neon_api",
-      "vhost": "/neon_api",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    }
-  ],
-  "topic_permissions": [],
-  "parameters": [],
-  "global_parameters": [
-    {
-      "name": "internal_cluster_id",
-      "value": "rabbitmq-cluster-id-WZ_YB7W-2OfQZV_NAU1_kg"
-    }
-  ],
-  "policies": [],
-  "queues": [],
-  "exchanges": [],
-  "bindings": []
-}
\ No newline at end of file
diff --git a/tests/config/valid_services_to_configure.json b/tests/config/valid_services_to_configure.json
deleted file mode 100644
index a4c02dc1..00000000
--- a/tests/config/valid_services_to_configure.json
+++ /dev/null
@@ -1,430 +0,0 @@
-{
-  "neon-script-parser": {
-    "container_image": "ghcr.io/neongeckocom/neon-script-parser:dev",
-    "container_name": "neon-script-parser",
-    "mq": {
-      "mq_service_name": "neon_script_parser_service",
-      "mq_username": "neon_script_parser",
-      "mq_user_permissions": {
-        "neon_script_parser": {
-          "/neon_script_parser": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        },
-        "neon_api_utils": {
-          "/neon_script_parser": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        }
-      },
-      "mq_vhosts": [
-        "/neon_script_parser"
-      ]
-    },
-    "docker_compose": {
-      "container_name": "neon-script-parser",
-      "image": "ghcr.io/neongeckocom/neon-script-parser:dev",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "headless"
-      },
-      "volumes": [
-        "config:/config"
-      ]
-    },
-    "kubernetes": null
-  },
-  "neon-api-proxy": {
-    "container_image": "ghcr.io/neongeckocom/neon_api_proxy:dev",
-    "container_name": "neon-api-proxy",
-    "mq": {
-      "mq_service_name": "neon_api_connector",
-      "mq_username": "neon_api",
-      "mq_user_permissions": {
-        "neon_api": {
-          "/neon_api": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        },
-        "neon_api_utils": {
-          "/neon_api": {
-            "read": "./*",
-            "write": "./*",
-            "configure": "./*"
-          }
-        }
-      },
-      "mq_vhosts": [
-        "/neon_api"
-      ]
-    },
-    "docker_compose": {
-      "container_name": "neon-api-proxy",
-      "image": "ghcr.io/neongeckocom/neon_api_proxy:dev",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "headless"
-      },
-      "volumes": [
-        "config:/config"
-      ]
-    },
-    "kubernetes": [
-      {
-        "apiVersion": "v1",
-        "kind": "Service",
-        "metadata": {
-          "labels": {
-            "io.kompose.service": "neon-api-proxy"
-          },
-          "name": "neon-api-proxy"
-        },
-        "spec": {
-          "clusterIP": "None",
-          "ports": [
-            {
-              "name": "headless",
-              "port": 55555,
-              "targetPort": 0
-            }
-          ],
-          "selector": {
-            "io.kompose.service": "neon-api-proxy"
-          }
-        }
-      },
-      {
-        "apiVersion": "apps/v1",
-        "kind": "Deployment",
-        "metadata": {
-          "labels": {
-            "io.kompose.service": "neon-api-proxy"
-          },
-          "name": "neon-api-proxy"
-        },
-        "spec": {
-          "replicas": 1,
-          "selector": {
-            "matchLabels": {
-              "io.kompose.service": "neon-api-proxy"
-            }
-          },
-          "strategy": {
-            "type": "Recreate"
-          },
-          "template": {
-            "metadata": {
-              "labels": {
-                "io.kompose.network/mq-backend": "true",
-                "io.kompose.service": "neon-api-proxy"
-              }
-            },
-            "spec": {
-              "containers": [
-                {
-                  "image": "ghcr.io/neongeckocom/neon_api_proxy:dev",
-                  "name": "neon-api-proxy",
-                  "resources": {},
-                  "volumeMounts": [
-                    {
-                      "mountPath": "/config/ngi_auth_vars.yml",
-                      "name": "ngi-auth"
-                    },
-                    {
-                      "mountPath": "/config/mq_config.json",
-                      "name": "mq-auth"
-                    }
-                  ]
-                }
-              ],
-              "restartPolicy": "Always",
-              "volumes": [
-                {
-                  "name": "ngi-auth",
-                  "secret": {
-                    "secretName": "api-keys"
-                  }
-                },
-                {
-                  "name": "mq-auth",
-                  "secret": {
-                    "secretName": "mq-auth"
-                  }
-                }
-              ]
-            }
-          }
-        }
-      }
-    ]
-  },
-  "neon-rabbitmq": {
-    "container_image": "rabbitmq:3-management",
-    "container_name": "neon-rabbitmq",
-    "docker_compose": {
-      "container_name": "neon-rabbitmq",
-      "image": "rabbitmq:3-management",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "LoadBalancer"
-      },
-      "ports": [
-        "15672:15672",
-        "5672:5672"
-      ],
-      "environment": [
-        "RABBITMQ_CONFIG_FILE=/config/rabbitmq.conf"
-      ],
-      "volumes": [
-        "config:/config"
-      ]
-    },
-    "kubernetes": [
-      {
-        "apiVersion": "v1",
-        "kind": "Service",
-        "metadata": {
-          "labels": {
-            "io.kompose.service": "neon-rabbitmq"
-          },
-          "name": "neon-rabbitmq"
-        },
-        "spec": {
-          "type": "LoadBalancer",
-          "ports": [
-            {
-              "name": "mq-admin",
-              "port": 15672,
-              "targetPort": 15672,
-              "protocol": "TCP"
-            },
-            {
-              "name": "mq-traffic",
-              "port": 5672,
-              "targetPort": 5672,
-              "protocol": "TCP"
-            }
-          ],
-          "selector": {
-            "io.kompose.service": "neon-rabbitmq"
-          }
-        }
-      },
-      {
-        "apiVersion": "apps/v1",
-        "kind": "Deployment",
-        "metadata": {
-          "name": "neon-rabbitmq"
-        },
-        "spec": {
-          "replicas": 1,
-          "selector": {
-            "matchLabels": {
-              "io.kompose.service": "neon-rabbitmq"
-            }
-          },
-          "strategy": {
-            "type": "Recreate"
-          },
-          "template": {
-            "metadata": {
-              "labels": {
-                "io.kompose.network/mq-backend": "true",
-                "io.kompose.service": "neon-rabbitmq"
-              }
-            },
-            "spec": {
-              "containers": [
-                {
-                  "env": [
-                    {
-                      "name": "RABBITMQ_CONFIG_FILE",
-                      "value": "/config/rabbitmq.conf"
-                    }
-                  ],
-                  "image": "rabbitmq:3-management",
-                  "name": "neon-rabbitmq",
-                  "ports": [
-                    {
-                      "containerPort": 15672
-                    },
-                    {
-                      "containerPort": 5672
-                    }
-                  ],
-                  "volumeMounts": [
-                    {
-                      "mountPath": "/config",
-                      "name": "config"
-                    }
-                  ]
-                }
-              ],
-              "restartPolicy": "Always",
-              "volumes": [
-                {
-                  "name": "config",
-                  "configMap": {
-                    "name": "rabbitmq",
-                    "items": [
-                      {
-                        "key": "rabbit_mq_config.json",
-                        "path": "rabbit_mq_config.json"
-                      },
-                      {
-                        "key": "rabbitmq.conf",
-                        "path": "rabbitmq.conf"
-                      }
-                    ]
-                  }
-                }
-              ]
-            }
-          }
-        }
-      }
-    ]
-  },
-  "neon-email-proxy": {
-    "container_image": "neon-email-proxy",
-    "container_name": "ghcr.io/neongeckocom/neon_email_proxy:dev",
-    "mq": {
-      "mq_service_name": "neon_email_proxy",
-      "mq_username": "neon_email",
-      "mq_user_permissions": {
-        "neon_email": {
-          "/neon_emails": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        },
-        "neon_api_utils": {
-          "/neon_emails": {
-            "read": "^(?!neon_emails_input).*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        }
-      },
-      "mq_vhosts": [
-        "/neon_emails"
-      ]
-    },
-    "docker_compose": {
-      "container_name": "ghcr.io/neongeckocom/neon_email_proxy:dev",
-      "image": "neon-email-proxy",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "headless"
-      },
-      "volumes": [
-        "config:/config"
-      ]
-    },
-    "kubernetes": null
-  },
-  "neon-brands-service": {
-    "container_image": "ghcr.io/neongeckocom/neon_brands_service:dev",
-    "container_name": "neon-brands-service",
-    "mq": {
-      "mq_service_name": "neon_coupon_connector",
-      "mq_username": "neon_coupons",
-      "mq_user_permissions": {
-        "neon_coupons": {
-          "/neon_coupons": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        },
-        "neon_api_utils": {
-          "/neon_coupons": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        }
-      },
-      "mq_vhosts": [
-        "/neon_coupons"
-      ]
-    },
-    "docker_compose": {
-      "container_name": "neon-brands-service",
-      "image": "ghcr.io/neongeckocom/neon_brands_service:dev",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "headless"
-      },
-      "volumes": [
-        "config:/config"
-      ]
-    },
-    "kubernetes": null
-  },
-  "neon-metrics-service": {
-    "container_image": "ghcr.io/neongeckocom/neon_metrics_service:dev",
-    "container_name": "neon-metrics-service",
-    "mq": {
-      "mq_service_name": "neon_metrics_connector",
-      "mq_username": "neon_metrics",
-      "mq_user_permissions": {
-        "neon_metrics": {
-          "/neon_metrics": {
-            "read": ".*",
-            "write": ".*",
-            "configure": ".*"
-          }
-        },
-        "neon_api_utils": {
-          "/neon_metrics": {
-            "read": "",
-            "write": ".*",
-            "configure": ".*"
-          }
-        }
-      },
-      "mq_vhosts": [
-        "/neon_metrics"
-      ]
-    },
-    "docker_compose": {
-      "container_name": "neon-metrics-service",
-      "image": "ghcr.io/neongeckocom/neon_metrics_service:dev",
-      "restart": "always",
-      "networks": [
-        "mq-backend"
-      ],
-      "labels": {
-        "kompose.service.type": "headless"
-      },
-      "volumes": [
-        "config:/config",
-        "metrics:/metrics"
-      ]
-    },
-    "kubernetes": null
-  }
-}
\ No newline at end of file
diff --git a/tests/test_constants.py b/tests/test_constants.py
deleted file mode 100644
index 59ad06de..00000000
--- a/tests/test_constants.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import os
-import sys
-import unittest
-
-sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
-from neon_diana_utils.constants import *
-
-
-class TestConstants(unittest.TestCase):
-    def test_orchestrators(self):
-        self.assertIsInstance(Orchestrator.DOCKER.value, int)
-        self.assertIsInstance(Orchestrator.KUBERNETES.value, int)
-        self.assertIsInstance(Orchestrator.OPENSHIFT.value, int)
-
-    def test_service_classes(self):
-        self.assertEqual(ServiceClass.MQ.value, "mq-backend")
-        self.assertEqual(ServiceClass.HTTP.value, "http-backend")
-        self.assertEqual(ServiceClass.CORE.value, "neon-core")
-
-    def test_get_services_by_class(self):
-        from neon_diana_utils.constants import _get_services_by_class
-        services_by_class = _get_services_by_class()
-        self.assertIsInstance(services_by_class, dict)
-        self.assertIsInstance(services_by_class[ServiceClass.MQ.value], list)
-        self.assertIsInstance(services_by_class[ServiceClass.HTTP.value], list)
-        # self.assertIsInstance(services_by_class[ServiceClass.CORE.value], list)
-        self.assertFalse(set(services_by_class[ServiceClass.MQ.value])
-                         .intersection(set(services_by_class
-                                           [ServiceClass.HTTP.value])))
-
-    def test_valid_mq_services(self):
-        self.assertIsInstance(valid_mq_services(), set)
-
-    def test_valid_http_services(self):
-        self.assertIsInstance(valid_http_services(), set)
-
-    def test_valid_default_mq_services(self):
-        defaults = default_mq_services()
-        self.assertIsInstance(defaults, set)
-        self.assertTrue(all([svc in valid_mq_services() for svc in defaults]))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test_diana_services.py b/tests/test_diana_services.py
deleted file mode 100644
index 5adab7fa..00000000
--- a/tests/test_diana_services.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import os
-import subprocess
-import unittest
-import pytest
-from time import time
-from neon_mq_connector.utils.client_utils import send_mq_request
-from neon_mq_connector.utils import wait_for_mq_startup
-
-
-class TestDianaServices(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls) -> None:
-        cls.cached_config_path = os.environ.get("NEON_CONFIG_PATH")
-        os.environ["NEON_CONFIG_PATH"] = os.path.join(os.path.dirname(__file__), "config")
-        cls.docker_compose_path = os.path.join(os.path.dirname(__file__), "test_diana_services_config")
-        subprocess.Popen(["/bin/bash", "-c", f"cd {cls.docker_compose_path} && docker-compose up"])
-        wait_for_mq_startup("0.0.0.0", 5672)
-
-    @classmethod
-    def tearDownClass(cls) -> None:
-        os.environ.pop("NEON_CONFIG_PATH")
-        if cls.cached_config_path:
-            os.environ["NEON_CONFIG_PATH"] = cls.cached_config_path
-        subprocess.Popen(["/bin/bash", "-c", f"cd {cls.docker_compose_path} && docker-compose down"])
-
-    @pytest.mark.skip
-    def test_api_proxy(self):
-        resp = send_mq_request("/neon_api", {"test": True,
-                                             "service": "api_test_endpoint"}, "neon_api_input", "neon_api_output")
-        self.assertIsInstance(resp, dict)
-        self.assertEqual(resp['status_code'], 200)
-
-        resp = send_mq_request("/neon_api", {"test": True,
-                                             "service": "not_implemented"}, "neon_api_input", "neon_api_output")
-        self.assertIsInstance(resp, dict)
-        self.assertEqual(resp['status_code'], 401)
-
-    @pytest.mark.skip
-    def test_brands_service(self):
-        brand_coupon_data = send_mq_request("/neon_coupons", {"brands": True,
-                                                              "coupons": True}, "neon_coupons_input")
-        self.assertTrue(brand_coupon_data["success"])
-
-    @pytest.mark.skip
-    def test_email_proxy(self):
-        request_data = {"recipient": "test@neongecko.com",
-                        "subject": "Test Message",
-                        "body": "This is a test\ncalled from neon_skill_tests.py in neon-utils",
-                        "attachments": None}
-        data = send_mq_request("/neon_emails", request_data, "neon_emails_input")
-        self.assertTrue(data.get("success"))
-
-    def test_metrics_service(self):
-        t = time()
-        send_mq_request("/neon_metrics", {"name": "test",
-                                          "time": t}, "neon_metrics_input", expect_response=False)
-        # TODO: Check metrics dir for this entry
-
-    @pytest.mark.skip
-    def test_script_parser(self):
-        with open(os.path.join(os.path.dirname(__file__), "ccl", "test.nct")) as f:
-            text = f.read()
-        resp = send_mq_request("/neon_script_parser", {"text": text,
-                                                       "meta": {"test": "val"}}, "neon_script_parser_input", timeout=45)
-        self.assertIsInstance(resp, dict)
-        self.assertIsInstance(resp["parsed_file"], str)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test_diana_services_config/.env b/tests/test_diana_services_config/.env
deleted file mode 100644
index 064fe4b2..00000000
--- a/tests/test_diana_services_config/.env
+++ /dev/null
@@ -1,2 +0,0 @@
-NEON_CONFIG_PATH=/home/d_mcknight/PycharmProjects/_.API/neon-diana-backend/tests/test_diana_services_config
-NEON_METRIC_PATH=/home/d_mcknight/PycharmProjects/_.API/neon-diana-backend/tests/test_diana_services_config/metrics
\ No newline at end of file
diff --git a/tests/test_diana_services_config/docker-compose.yml b/tests/test_diana_services_config/docker-compose.yml
deleted file mode 100644
index dd0be43d..00000000
--- a/tests/test_diana_services_config/docker-compose.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-version: '3.1'
-networks:
-  mq_backend:
-services:
-  neon-rabbitmq:
-    container_name: neon-rabbitmq
-    image: rabbitmq:3-management
-    restart: always
-    networks:
-    - mq_backend
-    ports:
-    - 15672:15672
-    - 5672:5672
-    volumes:
-      - ${NEON_CONFIG_PATH}/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
-      - ${NEON_CONFIG_PATH}:/config
-  neon-metrics-service:
-    container_name: neon-metrics-service
-    image: ghcr.io/neongeckocom/neon_metrics_service:dev
-    restart: on-failure
-    networks:
-    - mq_backend
-    volumes:
-    - ${NEON_CONFIG_PATH}:/config
-    - ${NEON_CONFIG_PATH}/metrics:/metrics
-    labels:
-      kompose.service.type: headless
-  neon-api-proxy:
-    container_name: neon-api-proxy
-    image: ghcr.io/neongeckocom/neon_api_proxy:dev
-    restart: on-failure
-    networks:
-    - mq_backend
-    volumes:
-    - ${NEON_CONFIG_PATH}:/config
-    labels:
-      kompose.service.type: headless
diff --git a/tests/test_diana_services_config/mq_config.json b/tests/test_diana_services_config/mq_config.json
deleted file mode 100644
index a4392183..00000000
--- a/tests/test_diana_services_config/mq_config.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "server": "neon-rabbitmq",
-  "users": {
-    "neon_metrics_connector": {
-      "user": "neon_metrics",
-      "password": "0kUv28tVK0Omw3H4HOq7AqnDERoUqPmWx3-vdsLjmRQ"
-    },
-    "neon_api_connector": {
-      "user": "neon_api",
-      "password": "5akSUyWvSEV62ymdc6DvADUEnx46W5E8vZFmFjYbYGI"
-    }
-  }
-}
\ No newline at end of file
diff --git a/tests/test_diana_services_config/ngi_auth_vars.yml b/tests/test_diana_services_config/ngi_auth_vars.yml
deleted file mode 100644
index ec3e64e8..00000000
--- a/tests/test_diana_services_config/ngi_auth_vars.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-track_my_brands: {}
-emails: {}
-api_services: {}
\ No newline at end of file
diff --git a/tests/test_diana_services_config/rabbit_mq_config.json b/tests/test_diana_services_config/rabbit_mq_config.json
deleted file mode 100644
index c63938b3..00000000
--- a/tests/test_diana_services_config/rabbit_mq_config.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
-  "rabbit_version": "3.9.9",
-  "rabbitmq_version": "3.9.9",
-  "product_name": "RabbitMQ",
-  "product_version": "3.9.9",
-  "users": [
-    {
-      "name": "admin",
-      "password_hash": "zTCCKdLkkImVHINvBxosIMB7zgo+Ohvlfaoqz7iMQK7afX2o",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [
-        "administrator"
-      ],
-      "limits": {}
-    },
-    {
-      "name": "neon_api_utils",
-      "password_hash": "au3vx9SxJqT01D+kczKOCgWsotSLcPCZxFOipTgFGaapsFso",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    },
-    {
-      "name": "neon_metrics",
-      "password_hash": "6H2oEqaqeThINnGtRrruAqh69iih48iCnKTPe58sLl6hW7tZ",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    },
-    {
-      "name": "neon_api",
-      "password_hash": "t15jXuyThl06tgnO1zp9n+id5rCITh2xXoikmj6nJHYlGdwL",
-      "hashing_algorithm": "rabbit_password_hashing_sha256",
-      "tags": [],
-      "limits": {}
-    }
-  ],
-  "vhosts": [
-    {
-      "name": "/"
-    },
-    {
-      "name": "/neon_metrics"
-    },
-    {
-      "name": "/neon_api"
-    }
-  ],
-  "permissions": [
-    {
-      "user": "admin",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "admin",
-      "vhost": "/neon_api",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "neon_api_utils",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ""
-    },
-    {
-      "user": "neon_metrics",
-      "vhost": "/neon_metrics",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    },
-    {
-      "user": "neon_api_utils",
-      "vhost": "/neon_api",
-      "configure": "./*",
-      "write": "./*",
-      "read": "./*"
-    },
-    {
-      "user": "neon_api",
-      "vhost": "/neon_api",
-      "configure": ".*",
-      "write": ".*",
-      "read": ".*"
-    }
-  ],
-  "topic_permissions": [],
-  "parameters": [],
-  "global_parameters": [
-    {
-      "name": "internal_cluster_id",
-      "value": "rabbitmq-cluster-id-CTk1SeHQ9dgq7XGJpl-YLg"
-    }
-  ],
-  "policies": [],
-  "queues": [],
-  "exchanges": [],
-  "bindings": []
-}
\ No newline at end of file
diff --git a/tests/test_diana_services_config/rabbitmq.conf b/tests/test_diana_services_config/rabbitmq.conf
deleted file mode 100644
index f1682722..00000000
--- a/tests/test_diana_services_config/rabbitmq.conf
+++ /dev/null
@@ -1,1 +0,0 @@
-load_definitions = /config/rabbit_mq_config.json
\ No newline at end of file
diff --git a/tests/test_diana_utils.py b/tests/test_diana_utils.py
index 3b4684d3..253d1e80 100644
--- a/tests/test_diana_utils.py
+++ b/tests/test_diana_utils.py
@@ -27,503 +27,125 @@
 import json
 import os
 import shutil
-import sys
 import unittest
-import docker
-import docker.models.containers
 import yaml
 
-from docker.errors import APIError
-from mock import Mock
-
-sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
-from neon_diana_utils.constants import Orchestrator
-
-MOCK_RMQ_USERS = {"users": [
-    {"name": "neon_api_utils",
-     "password": "Klatchat2021"},
-    {"name": "neon_metrics",
-     "password": "neon_metrics_password"},
-    {"name": "neon_coupons",
-     "password": "neon_coupons_password"},
-    {"name": "neon_email",
-     "password": "neon_email_password"},
-    {"name": "neon_script_parser",
-     "password": "neon_scripts_password"},
-    {"name": "neon_api",
-     "password": "neon_api_password"}
-]}
-
-
-class TestBackendUtils(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls) -> None:
-        cls.cached_config_path = os.environ.get("NEON_CONFIG_PATH")
-        os.environ["NEON_CONFIG_PATH"] = os.path.join(os.path.dirname(__file__), "config")
-
-    @classmethod
-    def tearDownClass(cls) -> None:
-        os.environ.pop("NEON_CONFIG_PATH")
-        if cls.cached_config_path:
-            os.environ["NEON_CONFIG_PATH"] = cls.cached_config_path
-
-    def test_cli_configure_backend(self):
-        from neon_diana_utils.constants import default_mq_services
-        from neon_diana_utils.utils.backend import cli_configure_backend
-        test_config_dir = os.path.join(os.path.dirname(__file__), "test_config")
-        os.makedirs(test_config_dir, exist_ok=True)
-
-        with self.assertRaises(ValueError):
-            cli_configure_backend(None, default_mq_services(),
-                                  "admin", "pass", True, "", "", True,
-                                  "mq-backend",
-                                  "http-backend")
-        with self.assertRaises(FileExistsError):
-            cli_configure_backend(__file__, default_mq_services(),
-                                  "admin", "pass", True, "", "", True,
-                                  "mq-backend",
-                                  "http-backend")
-        with self.assertRaises(ValueError):
-            cli_configure_backend(test_config_dir, default_mq_services(),
-                                  "admin", "", True, "", "", False,
-                                  "mq-backend",
-                                  "http-backend")
-
-        with self.assertRaises(ValueError):
-            cli_configure_backend(test_config_dir, set(),
-                                  "admin", "pass", False, "", "", False,
-                                  "mq-backend",
-                                  "http-backend")
-
-        cli_configure_backend(test_config_dir, default_mq_services(),
-                              "", "", True, "", "", True,
-                              "mq-backend",
-                              "http-backend")
-        docker_compose = os.path.join(test_config_dir, "docker-compose.yml")
-        kubernetes_spec = os.path.join(test_config_dir, "services",
-                                       "k8s_diana_backend.yml")
-        kubernetes_ingress = os.path.join(test_config_dir, "ingress",
-                                          "k8s_patch_nginx_service.yml")
-        kubernetes_tcp = os.path.join(test_config_dir, "ingress",
-                                      "k8s_config_tcp_services.yml")
-        for output in {docker_compose, kubernetes_spec, kubernetes_ingress,
-                       kubernetes_tcp}:
-            self.assertTrue(os.path.isfile(output))
-        shutil.rmtree(test_config_dir)
-
-    def test_cli_start_backend(self):
-        from neon_diana_utils.utils.backend import cli_start_backend
-        test_dir = os.path.join(os.path.dirname(__file__), "test_diana_services_config")
-
-        with self.assertRaises(ValueError):
-            cli_start_backend("", False, Orchestrator.DOCKER)
-
-        with self.assertRaises(ValueError):
-            cli_start_backend(os.path.dirname(__file__), False, Orchestrator.DOCKER)
-
-        with self.assertRaises(ValueError):
-            cli_start_backend(test_dir, False, Orchestrator.OPENSHIFT)
-
-    def test_cli_stop_backend(self):
-        from neon_diana_utils.utils.backend import cli_stop_backend
-        test_dir = os.path.join(os.path.dirname(__file__), "test_diana_services_config")
-
-        with self.assertRaises(ValueError):
-            cli_stop_backend("", Orchestrator.DOCKER)
-
-        with self.assertRaises(ValueError):
-            cli_stop_backend(os.path.dirname(__file__), Orchestrator.DOCKER)
-
-        with self.assertRaises(ValueError):
-            cli_stop_backend(test_dir, Orchestrator.OPENSHIFT)
-
-    def test_write_neon_mq_config(self):
-        from neon_diana_utils.utils.backend import write_neon_mq_config
-        sample_creds = {
-            'neon_metrics_connector':
-                {'user': 'neon_metrics',
-                 'password': 'jWAPqP4a2oL8mOq2T5Lj8cxMo72KwXv6keh1WItNWSk'},
-            'neon_api_connector':
-                {'user': 'neon_api',
-                 'password': 'N3h-w4I561l20gZ_cSRvuMHdseYVYA-wHlw-6aJh0VQ'}}
-        write_neon_mq_config(sample_creds)
-        config_file = os.path.join(os.environ["NEON_CONFIG_PATH"], "mq_config.json")
-        self.assertTrue(os.path.isfile(config_file))
-        with open(config_file) as f:
-            config = json.load(f)
-        self.assertEqual(config["users"], sample_creds)
-        self.assertIsInstance(config["server"], str)
-        os.remove(config_file)
-
-    def test_write_rabbit_config(self):
-        from neon_diana_utils.utils.backend import write_rabbit_config
-        with open(os.path.join(os.path.dirname(__file__), "config", "valid_rabbitmq.json")) as f:
-            sample_config = json.load(f)
-        api = Mock()
-
-        def get_definitions():
-            return sample_config
-
-        api.get_definitions = get_definitions
-
-        write_rabbit_config(api)
-
-        config_file = os.path.join(os.environ["NEON_CONFIG_PATH"], "rabbit_mq_config.json")
-        self.assertTrue(os.path.isfile(config_file))
-        with open(config_file) as f:
-            config = json.load(f)
-        self.assertEqual(config, sample_config)
-        os.remove(config_file)
-
-        rabbitmq_conf = os.path.join(os.environ["NEON_CONFIG_PATH"], "rabbitmq.conf")
-        self.assertTrue(os.path.isfile(rabbitmq_conf))
-        with open(rabbitmq_conf) as f:
-            self.assertIn("load_definitions = /config/rabbit_mq_config.json", f.read())
-        os.remove(rabbitmq_conf)
-
-    def test_parse_services(self):
-        from neon_diana_utils.constants import valid_mq_services,\
-            valid_http_services
-        from neon_diana_utils.utils.backend import _parse_services
-        minimal_valid_services = {"neon-rabbitmq", "neon-api-proxy"}
-        mq_services = set(valid_mq_services())
-        invalid_services = {"neon-invalid", "neon_invalid"}
-        http_services = valid_http_services()
-        all_services = mq_services.union(http_services)
-
-        minimal = _parse_services(minimal_valid_services)
-        all_mq = _parse_services(all_services)
-        invalid = _parse_services(invalid_services)
-        http = _parse_services(http_services, "http-backend")
-        complete_http = _parse_services(all_services, "http-backend")
-        complete_mq = _parse_services(all_services)
-
-        self.assertIsInstance(minimal, dict)
-        self.assertEqual(set(minimal.keys()), minimal_valid_services)
-        for _, val in minimal.items():
-            self.assertIsInstance(val, dict)
-
-        self.assertIsInstance(all_mq, dict)
-        self.assertEqual(all_mq, complete_mq)
-        self.assertEqual(set(all_mq.keys()), mq_services)
-        for _, val in all_mq.items():
-            self.assertIsInstance(val, dict)
-
-        self.assertIsInstance(invalid, dict)
-        self.assertEqual(set(invalid.keys()), set())
-
-        self.assertIsInstance(http, dict)
-        self.assertEqual(http, complete_http)
-        self.assertEqual(set(http.keys()), http_services)
-        for _, val in http.items():
-            self.assertIsInstance(val, dict)
-
-    def test_parse_vhosts(self):
-        from neon_diana_utils.utils.backend import _parse_vhosts
-        with open(os.path.join(os.path.dirname(__file__), "config", "valid_services_to_configure.json")) as f:
-            valid_services = json.load(f)
-        vhosts = _parse_vhosts(valid_services)
-        self.assertIsInstance(vhosts, set)
-        self.assertTrue(all(v.startswith('/') for v in vhosts))
-
-    def test_parse_configuration(self):
-        from neon_diana_utils.utils.backend import _parse_configuration
-        with open(os.path.join(os.path.dirname(__file__), "config", "valid_services_to_configure.json")) as f:
-            valid_services = json.load(f)
-
-        mq_user_permissions, neon_mq_auth, docker_config, k8s_config = _parse_configuration(valid_services)
-
-        self.assertIsInstance(mq_user_permissions, dict)
-        for user, vhost_config in mq_user_permissions.items():
-            self.assertIsInstance(user, str)
-            self.assertIsInstance(vhost_config, dict)
-            for route, perms in vhost_config.items():
-                self.assertIsInstance(route, str)
-                self.assertTrue(route.startswith('/'))
-                self.assertEqual(set(perms.keys()), {"read", "write", "configure"})
-
-        self.assertIsInstance(neon_mq_auth, dict)
-        self.assertTrue(all([set(conf.keys()) == {"user"} for conf in neon_mq_auth.values()]))
-        self.assertTrue(all([conf['user'] in mq_user_permissions for conf in neon_mq_auth.values()]))
-
-        self.assertIsInstance(docker_config, dict)
-        self.assertTrue(all([isinstance(conf, dict) for conf in docker_config.values()]))
-
-        self.assertIsInstance(k8s_config, list)
-        for config in k8s_config:
-            self.assertIsInstance(config, dict)
-            self.assertIsInstance(config["apiVersion"], str)
-            self.assertIsInstance(config["kind"], str)
-            self.assertIsInstance(config.get("metadata", dict()), dict)
-            self.assertIsInstance(config["spec"], dict)
-
-    def test_generate_config(self):
-        import neon_diana_utils.utils.backend
-        docker_mock = Mock()
-        kubernetes_mock = Mock()
-        neon_diana_utils.utils.backend.write_docker_compose = docker_mock
-        neon_diana_utils.utils.backend.write_kubernetes_spec = \
-            kubernetes_mock
-        test_config_dir = os.path.dirname(__file__)
-        docker_config = {"docker": "test"}
-        kubernetes_config = ["k8s_test"]
-        namespace_config = {"MQ_NAMESPACE": "mq-ns",
-                            "HTTP_NAMESPACE": "http-ns"}
-        neon_diana_utils.utils.backend.generate_backend_config(
-            docker_config, kubernetes_config,
-            test_config_dir, namespaces=namespace_config)
-        docker_mock.assert_called_once()
-        docker_mock.assert_called_with(docker_config,
-                                       os.path.join(test_config_dir,
-                                                    "docker-compose.yml"),
-                                       "none", None
-                                       )
-        kubernetes_mock.assert_called_once()
-        kubernetes_mock.assert_called_with(kubernetes_config, test_config_dir,
-                                           namespace_config)
-
-    def test_configure_mq_backend(self):
-        pass
+from unittest.mock import patch
+from os.path import join, dirname, isdir, isfile
+
+
+class TestConfiguration(unittest.TestCase):
+    def test_validate_output_file(self):
+        from neon_diana_utils.configuration import validate_output_path
+        valid_output_path = join(dirname(__file__), 'test_output')
+        self.assertFalse(isdir(valid_output_path))
+        self.assertTrue(validate_output_path(valid_output_path))
+        self.assertTrue(validate_output_path(valid_output_path))
+        self.assertFalse(validate_output_path(dirname(valid_output_path)))
+        self.assertTrue(validate_output_path(join(valid_output_path,
+                                                  'test.txt')))
+        shutil.rmtree(valid_output_path)
+
+    @patch("click.confirm")
+    def test_make_keys_config(self, confirm):
+        from neon_diana_utils.configuration import make_keys_config
+        confirm.return_value = False
+        test_output_file = join(dirname(__file__), "test_keys.yaml")
+        # Test without file write
+        config = make_keys_config(False, test_output_file)
+        self.assertFalse(isfile(test_output_file))
+        self.assertIsInstance(config, dict)
+        self.assertIsInstance(config['keys'], dict)
+        self.assertIsInstance(config['ChatGPT'], dict)
+        self.assertIsInstance(config['FastChat'], dict)
+
+        # Test with file write
+        config2 = make_keys_config(True, test_output_file)
+        self.assertTrue(isfile(test_output_file))
+        self.assertEqual(config, config2)
+        with open(test_output_file, 'r') as f:
+            from_disk = yaml.safe_load(f)
+        self.assertEqual(from_disk, config2)
+
+        os.remove(test_output_file)
 
     def test_generate_rmq_config(self):
-        from neon_diana_utils.utils.backend import generate_rmq_config
-        config = generate_rmq_config()
-        self.assertIsInstance(config['users'], list)
+        from neon_diana_utils.configuration import generate_rmq_config
+        test_output_file = join(dirname(__file__), "test_rmq.json")
+        # Test without write
+        config = generate_rmq_config("test_admin1", "test_password1")
+        self.assertFalse(isfile(test_output_file))
+        self.assertIsInstance(config, dict)
+        self.assertEqual(config['users'][0], {'name': 'neon_api_utils',
+                                              'password': 'Klatchat2021',
+                                              'tags': ['backend', 'user']})
+        self.assertEqual(config['users'][-1], {'name': 'test_admin1',
+                                               'password': 'test_password1',
+                                               'tags': ['administrator']})
+        self.assertEqual(set(config.keys()), {'users', 'vhosts', 'permissions'})
         for user in config['users']:
-            self.assertIsInstance(user['name'], str)
-            self.assertIsInstance(user['password'], str)
-            self.assertNotEqual(user['password'], '')
-            self.assertIsInstance(user['tags'], list)
-            if user['name'] == "neon_api_utils":
-                self.assertEqual(user['password'], "Klatchat2021")
+            self.assertEqual(set(user.keys()), {'name', 'password', 'tags'})
         for vhost in config['vhosts']:
+            self.assertEqual(set(vhost.keys()), {'name'})
             self.assertTrue(vhost['name'].startswith('/'))
-        for perm in config['permissions']:
-            self.assertIsInstance(perm['user'], str)
-            self.assertIsInstance(perm['vhost'], str)
-            self.assertIsInstance(perm['configure'], str)
-            self.assertIsInstance(perm['write'], str)
-            self.assertIsInstance(perm['read'], str)
+        for permission in config['permissions']:
+            self.assertEqual(set(permission.keys()),
+                             {'user', 'vhost', 'configure', 'write', 'read'})
+
+        # Test with valid write
+        config2 = generate_rmq_config("test_admin2", "test_password2",
+                                      test_output_file)
+        self.assertTrue(isfile(test_output_file))
+        self.assertNotEqual(config, config2)
+        self.assertEqual(config2['users'][-1], {'name': 'test_admin2',
+                                                'password': 'test_password2',
+                                                'tags': ['administrator']})
+        with open(test_output_file, 'r') as f:
+            from_disk = json.load(f)
+        self.assertEqual(config2, from_disk)
+
+        # Test invalid write
+        config3 = generate_rmq_config("test_admin3", "test_password3",
+                                      test_output_file)
+        self.assertTrue(isfile(test_output_file))
+        self.assertNotEqual(config2, config3)
+        self.assertEqual(config3['users'][-1], {'name': 'test_admin3',
+                                                'password': 'test_password3',
+                                                'tags': ['administrator']})
+        with open(test_output_file, 'r') as f:
+            from_disk = json.load(f)
+        self.assertEqual(config2, from_disk)
+
+        os.remove(test_output_file)
 
     def test_generate_mq_auth_config(self):
-        from neon_diana_utils.utils.backend import generate_mq_auth_config
-        config = generate_mq_auth_config(MOCK_RMQ_USERS)
-        self.assertIsNotNone(config)
-        for key, value in config.items():
-            self.assertEqual(set(value.keys()), {'user', 'password'})
-            self.assertIsInstance(value['user'], str)
-            self.assertIsInstance(value['password'], str)
-
-
-class TestDockerUtils(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls) -> None:
-        cls.cached_config_path = os.environ.get("NEON_CONFIG_PATH")
-        os.environ["NEON_CONFIG_PATH"] = os.path.join(os.path.dirname(__file__), "config")
-
-    @classmethod
-    def tearDownClass(cls) -> None:
-        os.environ.pop("NEON_CONFIG_PATH")
-        if cls.cached_config_path:
-            os.environ["NEON_CONFIG_PATH"] = cls.cached_config_path
-
-    @unittest.skip
-    def test_run_and_cleanup_rabbit_mq_docker(self):
-        from neon_diana_utils.utils.docker_utils import \
-            run_clean_rabbit_mq_docker, cleanup_docker_container
-        container = run_clean_rabbit_mq_docker()
-        self.assertIsInstance(container, docker.models.containers.Container)
-        self.assertIn(container, docker.from_env().containers.list())
-        cleanup_docker_container(container)
-        self.assertNotIn(container, docker.from_env().containers.list(all=True))
-
-        run_clean_rabbit_mq_docker()
-        with self.assertRaises(APIError):
-            run_clean_rabbit_mq_docker()
-        container = run_clean_rabbit_mq_docker(True)
-        self.assertIsInstance(container, docker.models.containers.Container)
-        self.assertIn(container, docker.from_env().containers.list())
-        cleanup_docker_container(container)
-        self.assertNotIn(container, docker.from_env().containers.list(all=True))
-
-    def test_write_docker_compose(self):
-        from neon_diana_utils.utils.docker_utils import write_docker_compose
-        with open(os.path.join(os.path.dirname(__file__), "config", "valid_docker-compose-services.json")) as f:
-            sample_config = json.load(f)
-        write_docker_compose(sample_config)
-
-        docker_compose_file = os.path.join(os.environ["NEON_CONFIG_PATH"], "docker-compose.yml")
-        self.assertTrue(os.path.isfile(docker_compose_file))
-        with open(docker_compose_file) as f:
-            docker_compose = yaml.safe_load(f)
-        self.assertIn("version", docker_compose)
-        self.assertEqual(docker_compose["services"], sample_config)
-        os.remove(docker_compose_file)
+        from neon_diana_utils.configuration import generate_mq_auth_config, \
+            generate_rmq_config
+        mq_config = generate_rmq_config("test", "test")
+        auth = generate_mq_auth_config(mq_config)
+        self.assertIsInstance(auth, dict)
+        for service_auth in auth.values():
+            rmq_users = [user for user in mq_config['users']
+                         if user['name'] == service_auth['user']]
+            self.assertEqual(len(rmq_users), 1)
+            user = rmq_users[0]
+            self.assertEqual(user['name'], service_auth['user'])
+            self.assertEqual(user['password'], service_auth['password'])
+
+    def test_configure_backend(self):
+        from neon_diana_utils.configuration import configure_backend
+        # TODO
 
 
 class TestKubernetesUtils(unittest.TestCase):
-    def test_cli_make_rmq_config_map(self):
-        import neon_diana_utils.utils.kubernetes_utils
-        mock = Mock()
-        real_method = neon_diana_utils.utils.kubernetes_utils.generate_config_map
-        neon_diana_utils.utils.kubernetes_utils.generate_config_map = mock
-        input_path = os.path.join(os.path.dirname(__file__),
-                                  "test_diana_services_config")
-        output_path = os.path.dirname(__file__)
-        out = neon_diana_utils.utils.kubernetes_utils.cli_make_rmq_config_map(
-            input_path, output_path
-        )
-
-        self.assertEqual(os.path.join(output_path, "k8s_config_rabbitmq.yml"),
-                         out)
-
-        mock.assert_called_once()
-        args = mock.call_args[0]
-        self.assertEqual(args[0], "rabbitmq")
-        self.assertIsInstance(args[1], dict)
-        self.assertEqual(set(args[1].keys()), {"rabbitmq.conf",
-                                               "rabbit_mq_config.json"})
-
-        output_path = __file__
-        out = neon_diana_utils.utils.kubernetes_utils.cli_make_rmq_config_map(
-            input_path, output_path
-        )
-        self.assertEqual(output_path, out)
-
-        with self.assertRaises(FileNotFoundError):
-            neon_diana_utils.utils.kubernetes_utils.cli_make_rmq_config_map(
-                os.path.join(output_path, "__invalid"), output_path
-            )
-
-        with self.assertRaises(ValueError):
-            neon_diana_utils.utils.kubernetes_utils.cli_make_rmq_config_map(
-                input_path, "/tmp/__unlikely_path"
-            )
-
-        neon_diana_utils.utils.kubernetes_utils.generate_config_map = \
-            real_method
-
-    def test_cli_make_api_secret(self):
-        import neon_diana_utils.utils.kubernetes_utils
-        mock = Mock()
-        real_method = neon_diana_utils.utils.kubernetes_utils.generate_secret
-        neon_diana_utils.utils.kubernetes_utils.generate_secret = mock
-        input_path = os.path.join(os.path.dirname(__file__),
-                                  "test_diana_services_config")
-        output_path = os.path.dirname(__file__)
-        out = neon_diana_utils.utils.kubernetes_utils.cli_make_api_secret(
-            input_path, output_path
-        )
-        self.assertEqual(out, output_path)
-
-        mock.assert_called_once()
-        args = mock.call_args[0]
-        self.assertEqual(args[0], "ngi-auth")
-        self.assertIsInstance(args[1], dict)
-        self.assertEqual(set(args[1].keys()), {"ngi_auth_vars.yml"})
-        self.assertTrue(args[2].startswith(output_path))
-
-        with self.assertRaises(FileNotFoundError):
-            neon_diana_utils.utils.kubernetes_utils.cli_make_api_secret(
-                os.path.join(output_path, "__invalid"), output_path
-            )
-
-        neon_diana_utils.utils.kubernetes_utils.generate_secret = real_method
-
-    def test_write_kubernetes_spec(self):
-        from neon_diana_utils.utils.kubernetes_utils import \
-            write_kubernetes_spec
-        namespaces = {"MQ_NAMESPACE": "mq_namespace",
-                      "HTTP_NAMESPACE": "http_namespace"}
-        with open(os.path.join(os.path.dirname(__file__), "config",
-                               "valid_k8s_config.json")) as f:
-            k8s_config = json.load(f)
-        test_k8s_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(test_k8s_path, exist_ok=True)
-        write_kubernetes_spec(k8s_config, test_k8s_path, namespaces)
-        k8s_diana = os.path.join(test_k8s_path, "services",
-                                 "k8s_diana_backend.yml")
-        k8s_ingress = os.path.join(test_k8s_path, "ingress",
-                                   "k8s_patch_nginx_service.yml")
-
-        def _validate_k8s_spec(spec: dict):
-            self.assertIn("apiVersion", spec)
-            self.assertIn("kind", spec)
-            self.assertIn("metadata", spec)
-            self.assertIsInstance(spec["items"], list)
-            for item in spec["items"]:
-                self.assertIsInstance(item["kind"], str)
-                self.assertIsInstance(item["apiVersion"], str)
-                self.assertIsInstance(item["metadata"], dict)
-                self.assertIsInstance(item.get("spec", dict()), dict)
-
-        self.assertTrue(os.path.isfile(k8s_diana))
-        with open(k8s_diana) as f:
-            kubernetes_spec = yaml.safe_load(f)
-        _validate_k8s_spec(kubernetes_spec)
-
-        with open(k8s_ingress) as f:
-            nginx_ingress = yaml.safe_load(f)
-            f.seek(0)
-            string_contents = f.read()
-        self.assertIsInstance(nginx_ingress['spec']['ports'][0], dict)
-
-        # Validate namespace substitution
-        self.assertNotIn("${", string_contents)
-
-        shutil.rmtree(test_k8s_path)
-
-    def test_generate_config_map(self):
-        from neon_diana_utils.utils.kubernetes_utils import generate_config_map
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-        config_name = "test-config-map"
-        config_data = {"some_filename.test": "text file contents\n",
-                       "some_other_file.bak": b"byte contents"}
-
-        output_file = os.path.join(output_path, f"k8s_config_{config_name}.yml")
-        generate_config_map(config_name, config_data, output_file)
-        self.assertTrue(os.path.isfile(output_file))
-
-        with open(output_file) as f:
-            contents = yaml.safe_load(f)
-
-        self.assertEqual(contents["metadata"]["name"], config_name)
-        self.assertEqual(contents["data"], config_data)
-        self.assertEqual(contents["kind"], "ConfigMap")
-
-        shutil.rmtree(output_path)
-
-    def test_generate_secret(self):
-        from neon_diana_utils.utils.kubernetes_utils import generate_secret
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-        secret_name = "test-secret"
-        config_data = {"some_secret.test": "secret\ntext file contents\n",
-                       "some_other_secret.bak": b"secret byte contents"}
-
-        output_file = os.path.join(output_path, f"k8s_secret_{secret_name}.yml")
-        generate_secret(secret_name, config_data, output_file)
-        self.assertTrue(os.path.isfile(output_file))
-
-        with open(output_file) as f:
-            contents = yaml.safe_load(f)
-
-        self.assertEqual(contents["metadata"]["name"], secret_name)
-        self.assertEqual(contents["stringData"], config_data)
-        self.assertEqual(contents["kind"], "Secret")
-        self.assertEqual(contents["type"], "Opaque")
-
-        shutil.rmtree(output_path)
-
-    def test_generate_github_auth(self):
-        from neon_diana_utils.utils.kubernetes_utils import cli_make_github_secret
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-        output_file = cli_make_github_secret("username",
-                                             "123123adsfasdf123123",
-                                             output_path)
+    def test_generate_github_secret(self):
+        from neon_diana_utils.kubernetes_utils import create_github_secret
+        output_path = os.path.join(os.path.dirname(__file__), "test_gh.yaml")
+        output_file = create_github_secret("username",
+                                           "123123adsfasdf123123",
+                                           output_path)
         self.assertTrue(os.path.isfile(output_file))
+        self.assertEqual(output_file, output_path)
         with open(output_file) as f:
             contents = yaml.safe_load(f)
         self.assertEqual(contents['kind'], "Secret")
@@ -533,128 +155,16 @@ def test_generate_github_auth(self):
                          "eyJhdXRocyI6IHsiZ2hjci5pbyI6IHsiYXV0aCI6ICJkWE5sY201"
                          "aGJXVTZNVEl6TVRJellXUnpabUZ6WkdZeE1qTXhNak09In19fQ=="
                          )
-        shutil.rmtree(output_path)
-
-    def test_update_tcp_config(self):
-        from neon_diana_utils.utils.kubernetes_utils import _update_tcp_config
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-
-        output_file = os.path.join(output_path, "tcp_test.yml")
-
-        out_file = _update_tcp_config({'80': 'default/test:80'}, output_file)
-        self.assertTrue(os.path.isfile(out_file))
-        with open(out_file) as f:
-            config = yaml.safe_load(f)
-        self.assertEqual(config['apiVersion'], 'v1')
-        self.assertEqual(config['kind'], 'ConfigMap')
-        self.assertEqual(config['metadata'], {'name': 'tcp-services',
-                                              'namespace': 'ingress-nginx'})
-        self.assertEqual(config['data'], {'80': 'default/test:80'})
-
-        file = _update_tcp_config({'80': 'default/test:80'}, output_file)
-        self.assertEqual(file, out_file)
-        with open(file) as f:
-            config_2 = yaml.safe_load(f)
-
-        self.assertEqual(config, config_2)
 
-        file = _update_tcp_config({'443': 'test/test:443'}, output_file)
-        self.assertEqual(file, out_file)
-        with open(file) as f:
-            config = yaml.safe_load(f)
-        self.assertEqual(config['data'], {'80': 'default/test:80',
-                                          '443': 'test/test:443'})
-
-        shutil.rmtree(output_path)
-
-    def test_update_ingress_config(self):
-        from neon_diana_utils.utils.kubernetes_utils import _update_ingress_config
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-
-        output_file = os.path.join(output_path, "ingress_test.yml")
-        out_file = _update_ingress_config("test.neon.ai", "test_service", 80,
-                                          output_path=output_file)
-        self.assertTrue(os.path.isfile(out_file))
-        with open(out_file) as f:
-            config = yaml.safe_load(f)
-        self.assertEqual(config['apiVersion'], 'networking.k8s.io/v1')
-        self.assertEqual(config['kind'], "Ingress")
-        self.assertIsInstance(
-            config['metadata']['annotations']['cert-manager.io/issuer'], str)
-        self.assertIn("test.neon.ai", config['spec']['tls'][0]['hosts'])
-        self.assertTrue(any(x for x in config['spec']['rules'] if
-                            x['host'] == "test.neon.ai" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['name'] == "test_service" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['port']['number'] == 80
-                            ))
-
-        with self.assertRaises(RuntimeError):
-            _update_ingress_config("test.neon.ai", "test_service",
-                                   80, "other-cert-issuer",
-                                   output_path=output_file)
-
-        out_file_2 = _update_ingress_config("test.neon.ai", "test_service", 80,
-                                            output_path=output_file)
-        self.assertEqual(out_file, out_file_2)
-        with open(out_file_2) as f:
-            config_2 = yaml.safe_load(f)
-        self.assertEqual(config, config_2)
-
-        out_file = _update_ingress_config("valid.neon.ai", "valid_service",
-                                          8080, output_path=output_file)
-        with open(out_file) as f:
-            config = yaml.safe_load(f)
-
-        self.assertIn("test.neon.ai", config['spec']['tls'][0]['hosts'])
-        self.assertIn("valid.neon.ai", config['spec']['tls'][0]['hosts'])
-        self.assertTrue(any(x for x in config['spec']['rules'] if
-                            x['host'] == "test.neon.ai" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['name'] == "test_service" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['port']['number'] == 80
-                            ))
-        self.assertTrue(any(x for x in config['spec']['rules'] if
-                            x['host'] == "valid.neon.ai" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['name'] == "valid_service" and
-                            x['http']['paths'][0]['backend']['service']
-                            ['port']['number'] == 8080
-                            ))
-
-        shutil.rmtree(output_path)
-
-    def test_create_cert_issuer(self):
-        from neon_diana_utils.utils.kubernetes_utils import _create_cert_issuer
-        output_path = os.path.join(os.path.dirname(__file__), "outputs")
-        os.makedirs(output_path, exist_ok=True)
-
-        output_file = os.path.join(output_path, "ingress_test.yml")
-        file = _create_cert_issuer("letsencrypt-prod", "test@neon.ai",
-                                   output_file)
-        self.assertEqual(file, output_file)
-        with open(file) as f:
-            config = yaml.safe_load(f)
-
-        self.assertEqual(config['kind'], 'Issuer')
-        self.assertEqual(config['metadata']['name'], 'letsencrypt-prod')
-        self.assertEqual(config['spec']['acme']['email'], "test@neon.ai")
+        with self.assertRaises(FileExistsError):
+            create_github_secret("test", "test", output_path)
 
-        file = _create_cert_issuer("letsencrypt-test", "test1@neon.ai",
-                                   output_file)
-        self.assertEqual(file, output_file)
-        with open(file) as f:
-            config = yaml.safe_load(f)
+        os.remove(output_path)
 
-        self.assertEqual(config['kind'], 'Issuer')
-        self.assertEqual(config['metadata']['name'], 'letsencrypt-test')
-        self.assertEqual(config['spec']['acme']['email'], "test1@neon.ai")
 
-        shutil.rmtree(output_path)
+class TestRabbitMQAPI(unittest.TestCase):
+    from neon_diana_utils.rabbitmq_api import RabbitMQAPI
+    # TODO
 
 
 if __name__ == '__main__':
diff --git a/tests/test_rabbitmq_api.py b/tests/test_rabbitmq_api.py
deleted file mode 100644
index 2e295ef6..00000000
--- a/tests/test_rabbitmq_api.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
-# All trademark and other rights reserved by their respective owners
-# Copyright 2008-2021 Neongecko.com Inc.
-# BSD-3
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-# 1. Redistributions of source code must retain the above copyright notice,
-#    this list of conditions and the following disclaimer.
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-#    this list of conditions and the following disclaimer in the documentation
-#    and/or other materials provided with the distribution.
-# 3. Neither the name of the copyright holder nor the names of its
-#    contributors may be used to endorse or promote products derived from this
-#    software without specific prior written permission.
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-# CONTRIBUTORS  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-# OR PROFITS;  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE,  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import os
-import sys
-import unittest
-
-sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
-from neon_diana_utils.rabbitmq_api import RabbitMQAPI
-
-
-class TestRabbitMQAPI(unittest.TestCase):
-    pass
-
-
-if __name__ == '__main__':
-    unittest.main()