diff --git a/building_manager.py b/building_manager.py index e6da6117bad7a7dcc198b9b5dd6e79a8514f0a9f..b7a9adc96614a68a20e61347ea20791cd57a2299 100755 --- a/building_manager.py +++ b/building_manager.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -import bcrypt +""" Python module to assist creating and maintaining docker openHab stacks.""" import crypt -import docker import logging import os -# import yaml - from shutil import copy2 -from subprocess import run, PIPE +from subprocess import PIPE, run + +import bcrypt +import docker from PyInquirer import prompt from ruamel.yaml import YAML @@ -85,25 +85,16 @@ def add_sftp_service(base_dir, hostname, number=0): base_path = base_dir + '/' + CUSTOM_DIR # compose file compose_path = base_path + '/' + COMPOSE_NAME - # template - template = get_service_template(base_dir, SERVICES['sftp']) # service name service_name = f'sftp_{hostname}' + # template + template = get_service_template(base_dir, SERVICES['sftp']) + # only label contraint is building + template['deploy']['placement']['constraints'][0] = ( + f"{CONSTRAINTS['building']} == {hostname}") + template['ports'] = [f'{2222 + number}:22'] - with open(compose_path, 'r+') as compose_f: - # load compose file - compose = yaml.load(compose_f) - # only label contraint is building - template['deploy']['placement']['constraints'][0] = ( - f"{CONSTRAINTS['building']} == {hostname}") - template['ports'] = [f'{2222 + number}:22'] - compose['services'][service_name] = template - # write content starting from first line - compose_f.seek(0) - # write new compose content - yaml.dump(compose, compose_f) - # reduce file to new size - compose_f.truncate() + add_or_update_compose_service(compose_path, service_name, template) def add_openhab_service(base_dir, hostname): @@ -115,38 +106,27 @@ def add_openhab_service(base_dir, hostname): base_path = base_dir + '/' + CUSTOM_DIR # compose file compose_path = base_path + '/' + COMPOSE_NAME - # template - template = get_service_template(base_dir, SERVICES['openhab']) # service name service_name = f'openhab_{hostname}' - - with open(compose_path, 'r+') as compose_f: - # load compose file - compose = yaml.load(compose_f) - # only label contraint is building - template['deploy']['placement']['constraints'][0] = ( - f"{CONSTRAINTS['building']} == {hostname}") - # include in backups of this building - template['deploy']['labels'].append(f'backup={hostname}') - # traefik backend - template['deploy']['labels'].append(f'traefik.backend={service_name}') - # traefik frontend domain->openhab - template['deploy']['labels'].append( - f'traefik.main.frontend.rule=HostRegexp:{{domain:{hostname}}}') - template['deploy']['labels'].append('traefik.main.frontend.priority=1') - # traefik frontend subdomain openhab_hostname.* -> openhab - template['deploy']['labels'].append( - f'traefik.sub.frontend.rule=HostRegexp:' - f'{service_name}.{{domain:[a-zA-z0-9-]+}}') - template['deploy']['labels'].append('traefik.sub.frontend.priority=2') - - compose['services'][service_name] = template - # write content starting from first line - compose_f.seek(0) - # write new compose content - yaml.dump(compose, compose_f) - # reduce file to new size - compose_f.truncate() + # template + template = get_service_template(base_dir, SERVICES['openhab']) + # only label contraint is building + template['deploy']['placement']['constraints'][0] = ( + f"{CONSTRAINTS['building']} == {hostname}") + # include in backups of this building + template['deploy']['labels'].append(f'backup={hostname}') + # traefik backend + template['deploy']['labels'].append(f'traefik.backend={service_name}') + # traefik frontend domain->openhab + template['deploy']['labels'].extend( + generate_traefik_host_labels(hostname, segment='main')) + # traefik frontend subdomain openhab_hostname.* -> openhab + template['deploy']['labels'].append( + f'traefik.sub.frontend.rule=HostRegexp:' + f'{service_name}.{{domain:[a-zA-z0-9-]+}}') + template['deploy']['labels'].append('traefik.sub.frontend.priority=2') + + add_or_update_compose_service(compose_path, service_name, template) def add_nodered_service(base_dir, hostname): @@ -158,30 +138,19 @@ def add_nodered_service(base_dir, hostname): base_path = base_dir + '/' + CUSTOM_DIR # compose file compose_path = base_path + '/' + COMPOSE_NAME - # template - template = get_service_template(base_dir, SERVICES['nodered']) # service name service_name = f'nodered_{hostname}' + # template + template = get_service_template(base_dir, SERVICES['nodered']) + # only label contraint is building + template['deploy']['placement']['constraints'][0] = ( + f"{CONSTRAINTS['building']} == {hostname}") + template['deploy']['labels'].append(f'traefik.backend={service_name}') + template['deploy']['labels'].append(f'backup={hostname}') + template['deploy']['labels'].extend( + generate_traefik_path_labels(service_name, segment='main')) - with open(compose_path, 'r+') as compose_f: - # load compose file - compose = yaml.load(compose_f) - # only label contraint is building - template['deploy']['placement']['constraints'][0] = ( - f"{CONSTRAINTS['building']} == {hostname}") - template['deploy']['labels'].append(f'traefik.backend={service_name}') - template['deploy']['labels'].append(f'backup={hostname}') - template['deploy']['labels'].append( - f'traefik.frontend.rule=HostRegexp:' - f'{service_name}.{{domain:[a-zA-z0-9-]+}}') - template['deploy']['labels'].append('traefik.frontend.priority=2') - compose['services'][service_name] = template - # write content starting from first line - compose_f.seek(0) - # write new compose content - yaml.dump(compose, compose_f) - # reduce file to new size - compose_f.truncate() + add_or_update_compose_service(compose_path, service_name, template) def add_mqtt_service(base_dir, hostname, number=0): @@ -194,29 +163,20 @@ def add_mqtt_service(base_dir, hostname, number=0): base_path = base_dir + '/' + CUSTOM_DIR # compose file compose_path = base_path + '/' + COMPOSE_NAME - # template - template = get_service_template(base_dir, SERVICES['mqtt']) # service name service_name = f'mqtt_{hostname}' + # template + template = get_service_template(base_dir, SERVICES['mqtt']) + # only label contraint is building + template['deploy']['placement']['constraints'][0] = ( + f"{CONSTRAINTS['building']} == {hostname}") + # ports incremented by number of services + template['ports'] = [f'{1883 + number}:1883', f'{9001 + number}:9001'] - with open(compose_path, 'r+') as compose_f: - # load compose file - compose = yaml.load(compose_f) - # only label contraint is building - template['deploy']['placement']['constraints'][0] = ( - f"{CONSTRAINTS['building']} == {hostname}") - # ports incremented by number of services - template['ports'] = [f'{1883 + number}:1883', f'{9001 + number}:9001'] - # write template as service - compose['services'][service_name] = template - # write content starting from first line - compose_f.seek(0) - # write new compose content - yaml.dump(compose, compose_f) - # reduce file to new size - compose_f.truncate() + add_or_update_compose_service(compose_path, service_name, template) +# Helper functions def get_service_template(base_dir, service_name): """Gets a service template entry from the template yaml @@ -229,6 +189,67 @@ def get_service_template(base_dir, service_name): template_content = yaml.load(templates_file) return template_content['services'][service_name] + + +def generate_traefik_host_labels(hostname, segment=None, priority=1): + """Generates a traefik path url with necessary redirects + + :hostname: Hostname that gets assigned by the label + :segment: Optional traefik segment when using multiple rules + :priority: Priority of frontend rule + :returns: list of labels for traefik + """ + label_list = [] + # check segment + segment = f'.{segment}' if segment is not None else '' + # fill list + label_list.append( + f'traefik{segment}.frontend.rule=HostRegexp:{{domain:{hostname}}}') + label_list.append(f'traefik{segment}.frontend.priority={priority}') + return label_list + + +def generate_traefik_path_labels(url_path, segment=None, priority=2): + """Generates a traefik path url with necessary redirects + + :url_path: path that should be used for the site + :segment: Optional traefik segment when using multiple rules + :priority: Priority of frontend rule + :returns: list of labels for traefik + """ + label_list = [] + # check segment + segment = f'.{segment}' if segment is not None else '' + # fill list + label_list.append(f'traefik{segment}.frontend.priority={priority}') + label_list.append( + f'traefik{segment}.frontend.redirect.regex=^(.*)/{url_path}$$') + label_list.append( + f'traefik{segment}.frontend.redirect.replacement=$$1/{url_path}/') + label_list.append( + f'traefik{segment}.frontend.rule=PathPrefix:/{url_path};' + f'ReplacePathRegex:^/{url_path}/(.*) /$$1') + return label_list + + +def add_or_update_compose_service(compose_path, service_name, service_content): + """Adds or replaces a service in a compose file + + :compose_path: path of the compose file to change + :service_name: name of the service to add/replace + :service_content: service definition to add + """ + with open(compose_path, 'r+') as compose_f: + # load compose file + compose = yaml.load(compose_f) + # add / update service with template + compose['services'][service_name] = service_content + # write content starting from first line + compose_f.seek(0) + # write new compose content + yaml.dump(compose, compose_f) + # reduce file to new size + compose_f.truncate() # }}}