complete rewrite
This commit is contained in:
530
alakazam.py
530
alakazam.py
@ -6,10 +6,16 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from tabulate import tabulate
|
||||||
import click
|
import click
|
||||||
import dotenv
|
import dotenv
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
import yaml
|
import yaml
|
||||||
|
import re
|
||||||
|
|
||||||
|
COMBINE_PATH = os.path.dirname(os.path.realpath(__file__)) + "/combine.yml"
|
||||||
|
CONFIG_FILE_NAME = 'alaka.yaml'
|
||||||
|
CONFIGS = {}
|
||||||
|
|
||||||
|
|
||||||
def read_config(filepath):
|
def read_config(filepath):
|
||||||
@ -29,10 +35,115 @@ def read_config(filepath):
|
|||||||
return yaml.safe_load(template.render())
|
return yaml.safe_load(template.render())
|
||||||
|
|
||||||
|
|
||||||
INSTANCE = read_config("config.yml")
|
""" Get value from nested dicts, return None if one of the keys does not exists """
|
||||||
DEFAULTS = read_config("~/.abra/defaults.yml")
|
def get_value(dict, *keys):
|
||||||
COMBINE = read_config(os.path.dirname(
|
_element = dict
|
||||||
os.path.realpath(__file__)) + "/combine.yml")
|
for key in keys:
|
||||||
|
try:
|
||||||
|
_element = _element[key]
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
|
return _element
|
||||||
|
|
||||||
|
|
||||||
|
def merge_dict(dict1, dict2):
|
||||||
|
""" Merge two nested dicts recursively, the second overwrites the first one"""
|
||||||
|
merged_dict = dict1.copy()
|
||||||
|
for key, value in dict2.items():
|
||||||
|
if key in merged_dict and isinstance(value, dict) and isinstance(merged_dict[key], dict):
|
||||||
|
merged_dict[key] = merge_dict(merged_dict[key], value)
|
||||||
|
elif key in merged_dict and isinstance(value, list) and isinstance(merged_dict[key], list):
|
||||||
|
merged_dict[key] = list(set(merged_dict[key] + value))
|
||||||
|
else:
|
||||||
|
merged_dict[key] = value
|
||||||
|
return merged_dict
|
||||||
|
|
||||||
|
|
||||||
|
def merge_pool_configs(dir_path):
|
||||||
|
dir_path = Path(dir_path).absolute()
|
||||||
|
merged_configs = {}
|
||||||
|
for root, _, files in os.walk(dir_path):
|
||||||
|
no_config = True
|
||||||
|
for file in files:
|
||||||
|
if file == 'alaka.yaml':
|
||||||
|
file_path = os.path.join(root, file)
|
||||||
|
config = read_config(file_path)
|
||||||
|
# Merge the config with the merged config from the parent dir
|
||||||
|
if par_config := merged_configs.get(os.path.dirname(root)):
|
||||||
|
merged_configs[root] = merge_dict(par_config, config)
|
||||||
|
else:
|
||||||
|
merged_configs[root] = config
|
||||||
|
no_config = False
|
||||||
|
if no_config:
|
||||||
|
merged_configs[root] = merged_configs.get(os.path.dirname(root))
|
||||||
|
return merged_configs
|
||||||
|
|
||||||
|
def merge_instance_configs(pool_config, instance_domain, instance_config):
|
||||||
|
merged_config = {}
|
||||||
|
for app, app_config in instance_config.items():
|
||||||
|
if not (server:= get_value(pool_config, 'GLOBALS', 'server')):
|
||||||
|
server = instance_domain
|
||||||
|
if app_config and pool_config.get(app):
|
||||||
|
merged_config[app] = merge_dict(pool_config[app], app_config)
|
||||||
|
elif app_config:
|
||||||
|
merged_config[app] = app_config
|
||||||
|
elif pool_config.get(app):
|
||||||
|
merged_config[app] = pool_config[app]
|
||||||
|
else:
|
||||||
|
merged_config[app] = {}
|
||||||
|
merged_config[app]['app_domain'] = map_subdomain(app, instance_domain, merged_config[app])
|
||||||
|
if not merged_config[app].get('server'):
|
||||||
|
merged_config[app]['server'] = server
|
||||||
|
return merged_config
|
||||||
|
|
||||||
|
|
||||||
|
def map_subdomain(recipe, instance_domain, app_config):
|
||||||
|
if subdomain:= app_config.get('subdomain'):
|
||||||
|
domain = subdomain.replace("example.com", instance_domain)
|
||||||
|
else:
|
||||||
|
domain = f"{recipe}.{instance_domain}"
|
||||||
|
return domain
|
||||||
|
|
||||||
|
|
||||||
|
def get_merged_instance_configs(pool_path, pool_configs):
|
||||||
|
pool_path = Path(pool_path).absolute()
|
||||||
|
if pool_path.is_file():
|
||||||
|
parent_path = os.path.dirname(pool_path)
|
||||||
|
instance_config = read_config(pool_path)
|
||||||
|
domain = pool_path.name.removesuffix('.yml').removesuffix('.yaml')
|
||||||
|
merged_config = merge_instance_configs(pool_configs[parent_path], domain, instance_config)
|
||||||
|
return {pool_path.name: merged_config}
|
||||||
|
instances = {}
|
||||||
|
for root, _, files in os.walk(Path(pool_path)):
|
||||||
|
for file in files:
|
||||||
|
# This pattern matches for files of the format "<domain>.yml" or "<domain>.yaml"
|
||||||
|
pattern = r"^(?:[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)+[A-Za-z]{2,6}(?:\.yaml|\.yml)$"
|
||||||
|
if re.match(pattern, file):
|
||||||
|
instance_config = read_config(f'{root}/{file}')
|
||||||
|
domain = file.removesuffix('.yml').removesuffix('.yaml')
|
||||||
|
merged_config = merge_instance_configs(pool_configs[root], domain, instance_config)
|
||||||
|
instances[domain] = merged_config
|
||||||
|
return instances
|
||||||
|
|
||||||
|
|
||||||
|
def merge_connection_configs(configs):
|
||||||
|
connection_config = read_config(COMBINE_PATH)
|
||||||
|
extend_shared_secrets(connection_config)
|
||||||
|
merged_configs = configs.copy()
|
||||||
|
for _, instance_config in merged_configs.items():
|
||||||
|
for target_app, source_apps in connection_config.items():
|
||||||
|
for source_app, target_conf in source_apps.items():
|
||||||
|
if target_app in instance_config and source_app in instance_config:
|
||||||
|
instance_config[target_app] = merge_dict(target_conf, instance_config[target_app])
|
||||||
|
return merged_configs
|
||||||
|
|
||||||
|
|
||||||
|
"""Add a layer containing the source app"""
|
||||||
|
def extend_shared_secrets(connection_config):
|
||||||
|
for _, source_apps in connection_config.items():
|
||||||
|
for source_app, target_conf in source_apps.items():
|
||||||
|
if shared_secrets:= target_conf.get('shared_secrets'):
|
||||||
|
target_conf['shared_secrets'] = {source_app: shared_secrets}
|
||||||
|
|
||||||
|
|
||||||
def abra(*args, machine_output=False, ignore_error=False):
|
def abra(*args, machine_output=False, ignore_error=False):
|
||||||
@ -46,6 +157,7 @@ def abra(*args, machine_output=False, ignore_error=False):
|
|||||||
if process.stdout:
|
if process.stdout:
|
||||||
logging.debug(process.stdout.decode())
|
logging.debug(process.stdout.decode())
|
||||||
if process.returncode and not ignore_error:
|
if process.returncode and not ignore_error:
|
||||||
|
breakpoint()
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f'{" ".join(command)} \n STDOUT: \n {process.stdout.decode()} \n STDERR: {process.stderr.decode()}')
|
f'{" ".join(command)} \n STDOUT: \n {process.stdout.decode()} \n STDERR: {process.stderr.decode()}')
|
||||||
if machine_output:
|
if machine_output:
|
||||||
@ -66,12 +178,12 @@ def write_env_header(path):
|
|||||||
file.write(header + old_content)
|
file.write(header + old_content)
|
||||||
|
|
||||||
|
|
||||||
def new_app(recipe):
|
def new_app(recipe, domain, server):
|
||||||
domain = map_subdomain(recipe)
|
path = get_env_path(server, domain)
|
||||||
server = INSTANCE["server"] if INSTANCE.get(
|
if path.exists():
|
||||||
"server") else INSTANCE["domain"]
|
print(f'remove {path}')
|
||||||
print(f'create {recipe} config on {server} at {domain}')
|
path.unlink()
|
||||||
path = get_env_path(recipe)
|
logging.info(f'create {recipe} config on {server} at {domain}')
|
||||||
out = abra("app", "new", recipe, "-n", "-s", server, "-D", domain)
|
out = abra("app", "new", recipe, "-n", "-s", server, "-D", domain)
|
||||||
if not "app has been created" in out:
|
if not "app has been created" in out:
|
||||||
raise RuntimeError(f'App "{recipe}" creation failed')
|
raise RuntimeError(f'App "{recipe}" creation failed')
|
||||||
@ -80,71 +192,106 @@ def new_app(recipe):
|
|||||||
logging.info(f'{recipe} created on {server} at {domain}')
|
logging.info(f'{recipe} created on {server} at {domain}')
|
||||||
|
|
||||||
|
|
||||||
def map_subdomain(recipe):
|
def update_configs(path, config):
|
||||||
if ((INSTANCE.get('apps') and (app := INSTANCE['apps'].get(recipe)) and (subdomain := app.get('subdomain'))) or
|
if uncomment_keys := config.get("uncomment"):
|
||||||
((app := DEFAULTS.get(recipe)) and (subdomain := app.get('subdomain')))):
|
|
||||||
domain = subdomain.replace("example.com", INSTANCE["domain"])
|
|
||||||
else:
|
|
||||||
domain = f"{recipe}.{INSTANCE['domain']}"
|
|
||||||
return domain
|
|
||||||
|
|
||||||
|
|
||||||
def generate_secrets(recipe):
|
|
||||||
domain = map_subdomain(recipe)
|
|
||||||
stored_secrets = abra("app", "secret", "ls", domain).splitlines()
|
|
||||||
if any("false" in line for line in stored_secrets):
|
|
||||||
abra("app", "secret", "generate", "-a", domain)
|
|
||||||
print(f"secrets for {domain} generated")
|
|
||||||
|
|
||||||
|
|
||||||
def get_env_path(recipe):
|
|
||||||
domain = map_subdomain(recipe)
|
|
||||||
server = INSTANCE["server"] if INSTANCE.get(
|
|
||||||
"server") else INSTANCE["domain"]
|
|
||||||
return Path(f"~/.abra/servers/{server}/{domain}.env").expanduser()
|
|
||||||
|
|
||||||
|
|
||||||
def connect_apps(target_app):
|
|
||||||
path = get_env_path(target_app)
|
|
||||||
target_conf = COMBINE.get(target_app)
|
|
||||||
for source_app in INSTANCE["apps"]:
|
|
||||||
if target_conf and (configs := target_conf.get(source_app)):
|
|
||||||
logging.info(f'connect {target_app} with {source_app}')
|
|
||||||
set_configs(target_app, configs)
|
|
||||||
if shared_secrets := configs.get("shared_secrets"):
|
|
||||||
logging.info(
|
|
||||||
f'share secrets between {target_app} and {source_app}')
|
|
||||||
share_secrets(target_app, source_app, shared_secrets)
|
|
||||||
insert_domains(path, source_app)
|
|
||||||
|
|
||||||
|
|
||||||
def set_configs(recipe, configs):
|
|
||||||
path = get_env_path(recipe)
|
|
||||||
if uncomment_keys := configs.get("uncomment"):
|
|
||||||
uncomment(uncomment_keys, path, True)
|
uncomment(uncomment_keys, path, True)
|
||||||
if envs := configs.get("env"):
|
if envs := config.get("env"):
|
||||||
uncomment(envs.keys(), path)
|
uncomment(envs.keys(), path)
|
||||||
for key, value in envs.items():
|
for key, value in envs.items():
|
||||||
logging.debug(f'set {key}={value} in {path}')
|
logging.debug(f'set {key}={value} in {path}')
|
||||||
dotenv.set_key(path, key, value, quote_mode="never")
|
dotenv.set_key(path, key, value, quote_mode="never")
|
||||||
if secrets := configs.get("secrets"):
|
|
||||||
domain = map_subdomain(recipe)
|
|
||||||
|
def generate_all_secrets(domain):
|
||||||
|
stored_secrets = abra("app", "secret", "ls", domain).splitlines()
|
||||||
|
if any("false" in line for line in stored_secrets):
|
||||||
|
logging.info(f"Generate all secrets for {domain}")
|
||||||
|
abra("app", "secret", "generate", "-a", domain)
|
||||||
|
print(f"secrets for {domain} generated")
|
||||||
|
|
||||||
|
|
||||||
|
def get_env_path(server, domain):
|
||||||
|
return Path(f"~/.abra/servers/{server}/{domain}.env").expanduser()
|
||||||
|
|
||||||
|
|
||||||
|
# def connect_apps(target_app):
|
||||||
|
# path = get_env_path(target_app)
|
||||||
|
# target_conf = COMBINE.get(target_app)
|
||||||
|
# for source_app in INSTANCE["apps"]:
|
||||||
|
# if target_conf and (configs := target_conf.get(source_app)):
|
||||||
|
# logging.info(f'connect {target_app} with {source_app}')
|
||||||
|
# update_configs(target_app, configs)
|
||||||
|
# if shared_secrets := configs.get("shared_secrets"):
|
||||||
|
# logging.info(
|
||||||
|
# f'share secrets between {target_app} and {source_app}')
|
||||||
|
# share_secrets(target_app, source_app, shared_secrets)
|
||||||
|
# insert_domains(path, source_app)
|
||||||
|
|
||||||
|
|
||||||
|
def exchange_secrets(app1, instance_config, apps):
|
||||||
|
#TODO: check this function
|
||||||
|
app1_config = instance_config[app1]
|
||||||
|
app1_domain = app1_config['app_domain']
|
||||||
|
for app2 in apps:
|
||||||
|
app2_config = instance_config[app2]
|
||||||
|
app2_domain = app2_config['app_domain']
|
||||||
|
if app1_shared_secrets := get_value(app1_config, "shared_secrets", app2):
|
||||||
|
logging.info(f'share secrets between {app1_domain} and {app2_domain}')
|
||||||
|
share_secrets(app1_domain, app2_domain, app1_shared_secrets)
|
||||||
|
if app2_shared_secrets := get_value(app2_config, "shared_secrets", app1):
|
||||||
|
logging.info(f'share secrets between {app1_domain} and {app2_domain}')
|
||||||
|
share_secrets(app2_domain, app1_domain, app2_shared_secrets)
|
||||||
|
|
||||||
|
|
||||||
|
def str2bool(value):
|
||||||
|
return value.lower() in ("yes", "true", "t", "1")
|
||||||
|
|
||||||
|
def share_secrets(app1_domain, app2_domain, secrets):
|
||||||
|
app1_stored_secrets = abra("app", "secret", "ls", app1_domain, machine_output=True)
|
||||||
|
app1_stored_secrets = {x['name']: str2bool(x['created-on-server']) for x in app1_stored_secrets}
|
||||||
|
app2_stored_secrets = abra("app", "secret", "ls", app2_domain, machine_output=True)
|
||||||
|
app2_stored_secrets = {x['name']: str2bool(x['created-on-server']) for x in app2_stored_secrets}
|
||||||
|
for app2_secret in secrets:
|
||||||
|
app1_secret = secrets[app2_secret]
|
||||||
|
# TODO: test if both apps have the secret available
|
||||||
|
try:
|
||||||
|
app1_secret_is_stored = app1_stored_secrets[app1_secret]
|
||||||
|
except KeyError:
|
||||||
|
logging.error(f"{app1_domain} does not contain secret {app1_secret}")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
app2_secret_is_stored = app2_stored_secrets[app2_secret]
|
||||||
|
except KeyError:
|
||||||
|
logging.error(f"{app2_domain} does not contain secret {app2_secret}")
|
||||||
|
continue
|
||||||
|
if app1_secret_is_stored and not app2_secret_is_stored:
|
||||||
|
secret = get_secret(app1_domain, app1_secret)
|
||||||
|
insert_secret(app2_domain, app2_secret, secret)
|
||||||
|
elif app2_secret_is_stored and not app1_secret_is_stored:
|
||||||
|
secret = get_secret(app2_domain, app2_secret)
|
||||||
|
insert_secret(app1_domain, app1_secret, secret)
|
||||||
|
elif not any([app1_secret_is_stored, app2_secret_is_stored]):
|
||||||
|
secret = generate_secret(app1_domain, app1_secret)
|
||||||
|
insert_secret(app2_domain, app2_secret, secret)
|
||||||
|
|
||||||
|
|
||||||
|
def get_secret(domain, secret_name):
|
||||||
|
# TODO: use "abra secret get <secret_name>"
|
||||||
|
secret = abra("app", "run", domain, "worker", "cat", f"/var/run/secrets/{secret_name}")
|
||||||
|
return secret
|
||||||
|
|
||||||
|
def generate_secret(domain, secret_name):
|
||||||
|
secret = abra("app", "secret", "generate", domain, secret_name, "v1", machine_output=True)
|
||||||
|
return secret[0]['value']
|
||||||
|
|
||||||
|
|
||||||
|
def insert_secrets_from_conf(domain, config):
|
||||||
|
logging.info(f"Insert secrets for {domain}")
|
||||||
|
if secrets := config.get("secrets"):
|
||||||
for secret_name, secret in secrets.items():
|
for secret_name, secret in secrets.items():
|
||||||
insert_secret(domain, secret_name, secret)
|
insert_secret(domain, secret_name, secret)
|
||||||
|
|
||||||
|
|
||||||
def share_secrets(target, source, secrets):
|
|
||||||
target_domain = map_subdomain(target)
|
|
||||||
source_domain = map_subdomain(source)
|
|
||||||
stored_secrets = abra("app", "secret", "ls", target_domain).splitlines()
|
|
||||||
for source_secret in secrets:
|
|
||||||
target_secret = secrets[source_secret]
|
|
||||||
if not any(target_secret in line and "true" in line for line in stored_secrets):
|
|
||||||
secret = get_generated_secret(source_domain, source_secret)
|
|
||||||
target_secret = secrets[source_secret]
|
|
||||||
insert_secret(target_domain, target_secret, secret)
|
|
||||||
|
|
||||||
|
|
||||||
def insert_secret(domain, secret_name, secret):
|
def insert_secret(domain, secret_name, secret):
|
||||||
# TODO parse json
|
# TODO parse json
|
||||||
stored_secrets = abra("app", "secret", "ls", domain).splitlines()
|
stored_secrets = abra("app", "secret", "ls", domain).splitlines()
|
||||||
@ -152,19 +299,6 @@ def insert_secret(domain, secret_name, secret):
|
|||||||
abra("app", "secret", "insert", domain, secret_name, "v1", secret)
|
abra("app", "secret", "insert", domain, secret_name, "v1", secret)
|
||||||
|
|
||||||
|
|
||||||
def get_generated_secret(domain, secret_name):
|
|
||||||
# TODO: use "abra secret get <secret_name>"
|
|
||||||
stored_secrets = abra("app", "secret", "ls", domain).splitlines()
|
|
||||||
if not any(secret_name in line and "true" in line for line in stored_secrets):
|
|
||||||
secret = abra("app", "secret", "generate", domain, secret_name, "v1")
|
|
||||||
secret = secret.splitlines()[3].split("|")[2].strip()
|
|
||||||
else:
|
|
||||||
secret = abra(
|
|
||||||
"app", "run", domain, "worker", "cat", f"/var/run/secrets/{secret_name}"
|
|
||||||
)
|
|
||||||
return secret
|
|
||||||
|
|
||||||
|
|
||||||
def uncomment(keys, path, match_all=False):
|
def uncomment(keys, path, match_all=False):
|
||||||
logging.debug(f'Uncomment {keys} in {path}')
|
logging.debug(f'Uncomment {keys} in {path}')
|
||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
@ -179,8 +313,13 @@ def uncomment(keys, path, match_all=False):
|
|||||||
file.write(line)
|
file.write(line)
|
||||||
|
|
||||||
|
|
||||||
def insert_domains(path, app):
|
def exchange_domains(instance_config, apps, path):
|
||||||
domain = map_subdomain(app)
|
for app in apps:
|
||||||
|
domain = instance_config[app]['app_domain']
|
||||||
|
insert_domains(path, app, domain)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_domains(path, app, domain):
|
||||||
logging.debug(f'replace all {app}.example.com with {domain} in {path}')
|
logging.debug(f'replace all {app}.example.com with {domain} in {path}')
|
||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
content = file.read()
|
content = file.read()
|
||||||
@ -189,21 +328,15 @@ def insert_domains(path, app):
|
|||||||
file.write(content)
|
file.write(content)
|
||||||
|
|
||||||
|
|
||||||
def execute_cmds(app):
|
def execute_cmds(app_config):
|
||||||
domain = map_subdomain(app)
|
domain = app_config['app_domain']
|
||||||
all_cmds = []
|
if not (all_cmds:= app_config.get('execute')):
|
||||||
if (app_conf := DEFAULTS.get(app)) and (cmds := app_conf.get("execute")):
|
logging.info(f"No post deploy cmds for {domain}")
|
||||||
all_cmds += cmds
|
return
|
||||||
if COMBINE.get(app):
|
|
||||||
for target_app, target_conf in COMBINE[app].items():
|
|
||||||
if target_app in INSTANCE["apps"] and (cmds := target_conf.get("execute")):
|
|
||||||
all_cmds += cmds
|
|
||||||
if (app_conf := INSTANCE["apps"][app]) and (cmds := app_conf.get("execute")):
|
|
||||||
all_cmds += cmds
|
|
||||||
for cmd in all_cmds:
|
for cmd in all_cmds:
|
||||||
container = cmd.split()[0]
|
container = cmd.split()[0]
|
||||||
cmd = cmd.split()[1]
|
cmd = cmd.split()[1]
|
||||||
logging.info(f"Run '{cmd}' in {domain}:{container}")
|
print(f"Run '{cmd}' in {domain}:{container}")
|
||||||
if container == "local":
|
if container == "local":
|
||||||
print(abra("app", "cmd", "--local", domain, cmd, ignore_error=True))
|
print(abra("app", "cmd", "--local", domain, cmd, ignore_error=True))
|
||||||
else:
|
else:
|
||||||
@ -212,81 +345,138 @@ def execute_cmds(app):
|
|||||||
|
|
||||||
@click.group()
|
@click.group()
|
||||||
@click.option('-l', '--log', 'loglevel')
|
@click.option('-l', '--log', 'loglevel')
|
||||||
@click.option('-c', '--config', 'config')
|
@click.option('-p', '--pool_path', 'pool_path')
|
||||||
def cli(loglevel, config):
|
@click.option('-c', '--config_path', 'config_path', default=".")
|
||||||
global INSTANCE
|
def cli(loglevel, pool_path, config_path):
|
||||||
|
global CONFIGS
|
||||||
|
#combine_config = read_config(COMBINE_PATH)
|
||||||
|
pool_configs = merge_pool_configs(config_path)
|
||||||
|
instance_configs = get_merged_instance_configs(pool_path, pool_configs)
|
||||||
|
CONFIGS = merge_connection_configs(instance_configs)
|
||||||
if loglevel:
|
if loglevel:
|
||||||
numeric_level = getattr(logging, loglevel.upper(), None)
|
numeric_level = getattr(logging, loglevel.upper(), None)
|
||||||
if not isinstance(numeric_level, int):
|
if not isinstance(numeric_level, int):
|
||||||
raise ValueError('Invalid log level: %s' % loglevel)
|
raise ValueError('Invalid log level: %s' % loglevel)
|
||||||
logging.basicConfig(level=numeric_level)
|
logging.basicConfig(level=numeric_level)
|
||||||
if config:
|
|
||||||
INSTANCE = read_config(config)
|
|
||||||
|
|
||||||
|
|
||||||
|
#@cli.command()
|
||||||
|
#def init_server():
|
||||||
|
# """ Initialize the server """
|
||||||
|
# new_app("traefik")
|
||||||
|
# new_app("backup-bot-two")
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
def init_server():
|
@click.option('-a', '--apps', multiple=True)
|
||||||
""" Initialize the server """
|
def setup(apps):
|
||||||
new_app("traefik")
|
pass
|
||||||
new_app("backup-bot-two")
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def setup_apps(apps):
|
def config(apps):
|
||||||
""" Configure and connect the apps """
|
""" Configure the apps """
|
||||||
|
for instance, instance_config in CONFIGS.items():
|
||||||
if not apps:
|
if not apps:
|
||||||
apps = INSTANCE["apps"]
|
apps = instance_config.keys()
|
||||||
for app in apps:
|
for app in apps:
|
||||||
new_app(app)
|
if app not in instance_config:
|
||||||
if configs := DEFAULTS.get(app):
|
logging.error(f"Could not find any '{app}' configuration for instance {instance}")
|
||||||
logging.info(f'set defaults for {app}')
|
exit(1)
|
||||||
set_configs(app, configs)
|
app_config = instance_config[app]
|
||||||
if configs := INSTANCE["apps"].get(app):
|
domain = app_config['app_domain']
|
||||||
logging.info(f'set extra configs for {app}')
|
server = app_config["server"]
|
||||||
set_configs(app, configs)
|
path = get_env_path(server, domain)
|
||||||
|
print(f'Setup {app} config on {server} at {domain}')
|
||||||
|
new_app(app, domain, server)
|
||||||
|
logging.info(f'set configs for {app} at {instance}')
|
||||||
|
update_configs(path, app_config)
|
||||||
|
exchange_domains(instance_config, instance_config.keys(), path)
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('-a', '--apps', multiple=True)
|
||||||
|
def secrets(apps):
|
||||||
|
""" Configure the apps """
|
||||||
|
for instance, instance_config in CONFIGS.items():
|
||||||
|
if not apps:
|
||||||
|
apps = instance_config.keys()
|
||||||
for app in apps:
|
for app in apps:
|
||||||
print(f'create connections for {app}')
|
if app not in instance_config:
|
||||||
connect_apps(app)
|
logging.error(f"Could not find any '{app}' configuration for instance {instance}")
|
||||||
for app in apps:
|
exit(1)
|
||||||
logging.info(f'generate secrets for {app}')
|
app_config = instance_config[app]
|
||||||
generate_secrets(app)
|
domain = app_config['app_domain']
|
||||||
|
print(f"Create secrets for {domain}")
|
||||||
|
insert_secrets_from_conf(domain, app_config)
|
||||||
|
exchange_secrets(app, instance_config, apps)
|
||||||
|
generate_all_secrets(domain)
|
||||||
|
|
||||||
|
|
||||||
def get_deployed_apps():
|
def get_deployed_apps(apps):
|
||||||
server = INSTANCE["server"] if INSTANCE.get("server") else INSTANCE["domain"]
|
deployed_apps = []
|
||||||
|
processed_server = []
|
||||||
|
for _, instance_config in CONFIGS.items():
|
||||||
|
if not apps:
|
||||||
|
apps = instance_config.keys()
|
||||||
|
for app in apps:
|
||||||
|
server = instance_config[app]['server']
|
||||||
|
if server in processed_server:
|
||||||
|
continue
|
||||||
|
processed_server.append(server)
|
||||||
deployed = abra("app", "ls", "-S", "-s", server, "-m", machine_output=True)
|
deployed = abra("app", "ls", "-S", "-s", server, "-m", machine_output=True)
|
||||||
deployed_apps = filter(
|
deployed_domains = [app["domain"] for app in deployed[server]["apps"] if app["status"] == "deployed"]
|
||||||
lambda x: x["status"] == "deployed", deployed[server]["apps"])
|
deployed_apps.extend(deployed_domains)
|
||||||
deployed_domains = [app["domain"] for app in deployed_apps]
|
return deployed_apps
|
||||||
return deployed_domains
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def deploy_apps(apps):
|
@click.option('-r', '--run-cmds', is_flag=True)
|
||||||
|
def deploy(apps, run_cmds):
|
||||||
""" Deploy all the apps """
|
""" Deploy all the apps """
|
||||||
deployed_domains = get_deployed_apps()
|
deployed_domains = get_deployed_apps(apps)
|
||||||
for app in INSTANCE["apps"]:
|
for _, instance_config in CONFIGS.items():
|
||||||
domain = map_subdomain(app)
|
if not apps:
|
||||||
if (apps and app in apps) or (not apps and domain not in deployed_domains):
|
apps = instance_config.keys()
|
||||||
print(f'deploy {domain}')
|
for app in apps:
|
||||||
print(abra("app", "deploy", "-C", "-n", domain))
|
app_config = instance_config[app]
|
||||||
|
domain = app_config['app_domain']
|
||||||
|
if domain in deployed_domains:
|
||||||
|
print(f"{domain} is already deployed")
|
||||||
|
continue
|
||||||
|
version = app_config.get('version')
|
||||||
|
if not version:
|
||||||
|
version = 'latest'
|
||||||
|
cmd = ["deploy", "-n"]
|
||||||
|
if version == 'chaos':
|
||||||
|
cmd.append("-chaos")
|
||||||
|
if not run_cmds:
|
||||||
|
cmd.append("--no-converge-checks")
|
||||||
|
cmd.append(domain)
|
||||||
|
if version not in ['latest', 'chaos']:
|
||||||
|
cmd.append(version)
|
||||||
|
print(f'deploy {domain} with version "{version}"')
|
||||||
|
print(abra("app", *cmd))
|
||||||
|
if run_cmds:
|
||||||
logging.info(f'execute commands for {domain}')
|
logging.info(f'execute commands for {domain}')
|
||||||
execute_cmds(app)
|
execute_cmds(app_config)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def undeploy_apps(apps):
|
def undeploy(apps):
|
||||||
""" Undeploy all the apps """
|
""" Undeploy all the apps """
|
||||||
deployed_domains = get_deployed_apps()
|
deployed_domains = get_deployed_apps(apps)
|
||||||
|
for _, instance_config in CONFIGS.items():
|
||||||
if not apps:
|
if not apps:
|
||||||
apps = INSTANCE["apps"]
|
apps = instance_config.keys()
|
||||||
for app in apps:
|
for app in apps:
|
||||||
domain = map_subdomain(app)
|
app_config = instance_config[app]
|
||||||
|
domain = app_config['app_domain']
|
||||||
|
if domain not in deployed_domains:
|
||||||
|
print(f"{domain} is not deployed")
|
||||||
|
continue
|
||||||
print(f'undeploy {domain}')
|
print(f'undeploy {domain}')
|
||||||
if domain in deployed_domains:
|
|
||||||
print(abra("app", "undeploy", "-n", domain))
|
print(abra("app", "undeploy", "-n", domain))
|
||||||
|
|
||||||
|
|
||||||
@ -294,42 +484,74 @@ def undeploy_apps(apps):
|
|||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def cmds(apps):
|
def cmds(apps):
|
||||||
""" execute all post deploy cmds """
|
""" execute all post deploy cmds """
|
||||||
deployed_domains = get_deployed_apps()
|
deployed_domains = get_deployed_apps(apps)
|
||||||
|
for _, instance_config in CONFIGS.items():
|
||||||
if not apps:
|
if not apps:
|
||||||
apps = INSTANCE["apps"]
|
apps = instance_config.keys()
|
||||||
for app in apps:
|
for app in apps:
|
||||||
domain = map_subdomain(app)
|
app_config = instance_config[app]
|
||||||
if domain in deployed_domains:
|
domain = app_config['app_domain']
|
||||||
|
if domain not in deployed_domains:
|
||||||
|
print(f"{domain} is not deployed")
|
||||||
|
continue
|
||||||
logging.info(f'execute commands for {domain}')
|
logging.info(f'execute commands for {domain}')
|
||||||
execute_cmds(app)
|
execute_cmds(app_config)
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def purge_apps(apps):
|
def purge(apps):
|
||||||
""" Completely remove all the apps """
|
""" Completely remove all the apps """
|
||||||
if not apps:
|
# TODO: check for deployed apps
|
||||||
apps = INSTANCE["apps"]
|
pool_apps = print_all_apps(apps)
|
||||||
for app in apps:
|
domains = list(zip(*sum(pool_apps.values(), [])))[1]
|
||||||
domain = map_subdomain(app)
|
if input(f"Do you really want to purge these apps? Type YES: ") == "YES":
|
||||||
|
for domain in domains:
|
||||||
logging.info(f'purge {domain}')
|
logging.info(f'purge {domain}')
|
||||||
abra("app", "rm", "-n", domain)
|
abra("app", "rm", "-n", domain)
|
||||||
print(f"{domain} purged")
|
print(f"{domain} purged")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.option('-a', '--apps', multiple=True)
|
||||||
|
def ls(apps):
|
||||||
|
""" List all the apps """
|
||||||
|
print_all_apps(apps)
|
||||||
|
|
||||||
|
|
||||||
|
def print_all_apps(apps):
|
||||||
|
pool_apps = list_apps(apps)
|
||||||
|
for instance, instance_apps in pool_apps.items():
|
||||||
|
print(instance)
|
||||||
|
print(tabulate(instance_apps))
|
||||||
|
print()
|
||||||
|
return pool_apps
|
||||||
|
|
||||||
|
def list_apps(apps):
|
||||||
|
pool_apps = {}
|
||||||
|
for instance, instance_config in CONFIGS.items():
|
||||||
|
pool_apps[instance] = []
|
||||||
|
if not apps:
|
||||||
|
apps = instance_config.keys()
|
||||||
|
for app in apps:
|
||||||
|
domain = instance_config[app]['app_domain']
|
||||||
|
pool_apps[instance].append((app, domain))
|
||||||
|
return pool_apps
|
||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
@click.option('-a', '--apps', multiple=True)
|
@click.option('-a', '--apps', multiple=True)
|
||||||
def purge_secrets(apps):
|
def purge_secrets(apps):
|
||||||
""" Remove all the apps secrets """
|
""" Remove all the apps secrets """
|
||||||
if not apps:
|
# TODO: check for deployed apps
|
||||||
apps = INSTANCE["apps"]
|
pool_apps = print_all_apps(apps)
|
||||||
for app in apps:
|
domains = list(zip(*sum(pool_apps.values(), [])))[1]
|
||||||
domain = map_subdomain(app)
|
if input(f"Do you really want to purge the secrets for these apps? Type YES: ") == "YES":
|
||||||
logging.info(f'purge secrets from {domain}')
|
for domain in domains:
|
||||||
stored_secrets = abra("app", "secret", "ls", domain)
|
logging.info(f'purge {domain}')
|
||||||
if "true" in stored_secrets:
|
|
||||||
abra("app","secret" ,"rm", "-a", domain)
|
abra("app","secret" ,"rm", "-a", domain)
|
||||||
print(f"secrets removed for {domain}")
|
print(f"Secrets for {domain} purged")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
BIN
config_file_structure.png
Normal file
BIN
config_file_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
320
config_structure.drawio
Normal file
320
config_structure.drawio
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
<mxfile host="Electron" modified="2023-10-09T15:41:25.929Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="TpoyeJYr0o4faOI_h5Ok" version="22.0.2" type="device">
|
||||||
|
<diagram name="Page-1" id="UFZ3J2dV5uHkDdLlwz5H">
|
||||||
|
<mxGraphModel dx="3050" dy="1328" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0" />
|
||||||
|
<mxCell id="1" parent="0" />
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-7" target="EmT_t4AcRQ52VDmL_bbM-68">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="515" y="590.0000000000005" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-7" target="EmT_t4AcRQ52VDmL_bbM-66">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="675" y="570" as="targetPoint" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-28" value="Abritrary Layer of inhereting Global Configurations" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-30" y="80" width="790" height="450" as="geometry">
|
||||||
|
<mxRectangle x="-30" y="80" width="330" height="30" as="alternateBounds" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-2" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Global Org/Coop Config</b></p><div><br></div>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="310" y="30" width="150" height="130" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-1" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">smtp from address<br></span><span style="background-color: initial;">app subdomain<br></span><span style="background-color: initial;">install commands<br></span><span style="background-color: initial;">language</span></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="327.5" y="70" width="115" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-96" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="EmT_t4AcRQ52VDmL_bbM-28" source="EmT_t4AcRQ52VDmL_bbM-5" target="EmT_t4AcRQ52VDmL_bbM-55">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="253" y="310" />
|
||||||
|
<mxPoint x="435" y="310" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-5" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color);"><b style="border-color: var(--border-color);">Stable Pool Config</b></p><div style="border-color: var(--border-color);"><br style="border-color: var(--border-color);"></div>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="177.5" y="200" width="150" height="90" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="EmT_t4AcRQ52VDmL_bbM-28" source="EmT_t4AcRQ52VDmL_bbM-2" target="EmT_t4AcRQ52VDmL_bbM-5">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-95" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="EmT_t4AcRQ52VDmL_bbM-28" source="EmT_t4AcRQ52VDmL_bbM-6" target="EmT_t4AcRQ52VDmL_bbM-53">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="253" y="310" />
|
||||||
|
<mxPoint x="275" y="310" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-6" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">app versions<br></span><span style="background-color: initial;">app configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="195" y="240" width="115" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-7" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color);"><b style="border-color: var(--border-color);">Testing Pool Config</b></p><div style="border-color: var(--border-color);"><br style="border-color: var(--border-color);"></div>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="550" y="200" width="150" height="110" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="EmT_t4AcRQ52VDmL_bbM-28" source="EmT_t4AcRQ52VDmL_bbM-2" target="EmT_t4AcRQ52VDmL_bbM-7">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="385" y="180" />
|
||||||
|
<mxPoint x="625" y="180" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-15" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color);"><b>Sub-Org Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="40" y="340" width="150" height="90" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="EmT_t4AcRQ52VDmL_bbM-28" source="EmT_t4AcRQ52VDmL_bbM-5" target="EmT_t4AcRQ52VDmL_bbM-15">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="253" y="310" />
|
||||||
|
<mxPoint x="115" y="310" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-16" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">org branding/theme<br></span><span style="background-color: initial;">server: sub_org</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="57.5" y="380" width="115" height="40" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-51" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">app versions<br></span><span style="background-color: initial;">app configs<br></span><span style="background-color: initial;">server: </span>test <br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="567.5" y="240" width="115" height="60" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-53" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color);"><b style="border-color: var(--border-color);">Stable Server 1</b></p><div style="border-color: var(--border-color);"><br style="border-color: var(--border-color);"></div>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="200" y="340" width="150" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-54" value="<pre style="text-align: left; font-size: 10px;">server: server1</pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="217.5" y="380" width="115" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-55" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color);"><b style="border-color: var(--border-color);">Stable Server 2</b></p><div style="border-color: var(--border-color);"><br style="border-color: var(--border-color);"></div>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="360" y="340" width="150" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-56" value="<pre style="text-align: left; font-size: 10px;">server:server2</pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-28">
|
||||||
|
<mxGeometry x="377.5" y="380" width="115" height="30" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-42" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-33" target="EmT_t4AcRQ52VDmL_bbM-38">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="-620" y="320" as="targetPoint" />
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-685" y="322" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-91" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-33" target="EmT_t4AcRQ52VDmL_bbM-39">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-685" y="1150" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-33" value="<b>my_it_org</b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-710" y="160" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-36" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-585" y="210" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-38" target="EmT_t4AcRQ52VDmL_bbM-45">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-560" y="470" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-85" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-38" target="EmT_t4AcRQ52VDmL_bbM-73">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-560" y="708" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-88" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-38" target="EmT_t4AcRQ52VDmL_bbM-74">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-560" y="938" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-38" value="<b>stable<br></b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-585" y="300" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-39" value="<b>testing<br></b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-580" y="1120" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-41" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-33" target="EmT_t4AcRQ52VDmL_bbM-36">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-685" y="235" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-45" value="<b>sub_org<br></b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-495" y="440" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-46" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-490" y="360" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-47" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-38" target="EmT_t4AcRQ52VDmL_bbM-46">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-560" y="385" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-49" value="instance4.example.com.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#dae8fc;strokeColor=#6c8ebf;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-485" y="1280" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-58" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-53">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="215" y="590.0000000000005" as="targetPoint" />
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="245" y="570" />
|
||||||
|
<mxPoint x="215" y="570" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-59" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-15" target="EmT_t4AcRQ52VDmL_bbM-13">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="85" y="570" />
|
||||||
|
<mxPoint x="45" y="570" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-70" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-55" target="EmT_t4AcRQ52VDmL_bbM-64">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="405" y="570" />
|
||||||
|
<mxPoint x="360" y="570" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-71" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-485" y="1200" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-73" value="<b>stable_server_1<br></b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-495" y="685" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-74" value="<b>stable_server_2<br></b>" style="sketch=0;pointerEvents=1;shadow=0;dashed=0;html=1;strokeColor=#82b366;labelPosition=center;verticalLabelPosition=top;verticalAlign=bottom;outlineConnect=0;align=center;shape=mxgraph.office.concepts.folder;fillColor=#d5e8d4;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-495" y="915" width="50" height="45" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-75" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="510" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-76" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="750" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-77" value="instance2.example.com.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#dae8fc;strokeColor=#6c8ebf;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="825" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-78" value="instance1.example.com.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#dae8fc;strokeColor=#6c8ebf;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="590" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-79" value="alaka.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#fff2cc;strokeColor=#d6b656;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="990" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-80" value="instance3.example.com.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#dae8fc;strokeColor=#6c8ebf;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-400" y="1070" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-81" value="instance5.example.com.yaml" style="whiteSpace=wrap;html=1;shape=mxgraph.basic.document;fillColor=#dae8fc;strokeColor=#6c8ebf;labelPosition=center;verticalLabelPosition=top;align=center;verticalAlign=bottom;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-485" y="1390" width="40" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-82" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-45" target="EmT_t4AcRQ52VDmL_bbM-75">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="535" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-83" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-45" target="EmT_t4AcRQ52VDmL_bbM-78">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="615" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-86" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-73" target="EmT_t4AcRQ52VDmL_bbM-76">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="775" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-87" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-73" target="EmT_t4AcRQ52VDmL_bbM-77">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="850" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-89" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-74" target="EmT_t4AcRQ52VDmL_bbM-79">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="1015" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-90" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-74" target="EmT_t4AcRQ52VDmL_bbM-80">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-470" y="1095" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-92" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-39" target="EmT_t4AcRQ52VDmL_bbM-71">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-555" y="1225" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-93" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-39" target="EmT_t4AcRQ52VDmL_bbM-49">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-555" y="1305" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-94" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="EmT_t4AcRQ52VDmL_bbM-39" target="EmT_t4AcRQ52VDmL_bbM-81">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="-555" y="1415" />
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-29" value="Configuration per Instance of interconnected Apps&nbsp;" style="swimlane;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="-30" y="530" width="790" height="180" as="geometry">
|
||||||
|
<mxRectangle x="-30" y="530" width="330" height="30" as="alternateBounds" />
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-13" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Instance 1 Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="10" y="60" width="130" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-14" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">domain<br></span><span style="background-color: initial;">apps<br></span><span style="background-color: initial;">instance configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="17.5" y="100" width="115" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-62" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Instance 2 Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="170" y="60" width="130" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-63" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">domain<br></span><span style="background-color: initial;">apps<br></span><span style="background-color: initial;">instance configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="177.5" y="100" width="115" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-64" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Instance 3 Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="325" y="60" width="130" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-65" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">domain<br></span><span style="background-color: initial;">apps<br></span><span style="background-color: initial;">instance configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="332.5" y="100" width="115" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-66" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Instance 5 Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="640" y="60" width="130" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-67" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">domain<br></span><span style="background-color: initial;">apps<br></span><span style="background-color: initial;">instance configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="647.5" y="100" width="115" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-68" value="<p style="border-color: var(--border-color); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"></p><p style="border-color: var(--border-color); font-size: 12px;"><b>Instance 4 Config</b></p>" style="rounded=1;whiteSpace=wrap;html=1;verticalAlign=top;horizontal=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="490" y="60" width="130" height="100" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="EmT_t4AcRQ52VDmL_bbM-69" value="<pre style="text-align: left; font-size: 10px;"><span style="background-color: initial; text-align: left;">domain<br></span><span style="background-color: initial;">apps<br></span><span style="background-color: initial;">instance configs</span><br></pre>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="EmT_t4AcRQ52VDmL_bbM-29">
|
||||||
|
<mxGeometry x="497.5" y="100" width="115" height="50" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
BIN
config_structure.png
Normal file
BIN
config_structure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
Reference in New Issue
Block a user