#!/usr/bin/env python # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import json import logging import os import subprocess import sys import yaml CONF_FILE = os.environ.get('HEAT_SHELL_CONFIG', '/var/run/heat-config/heat-config') WORKING_DIR = os.environ.get( 'HEAT_DOCKER_CMD_WORKING', '/var/lib/heat-config/heat-config-docker-cmd') DOCKER_CMD = os.environ.get('HEAT_DOCKER_CMD', 'docker') log = None def main(argv=sys.argv): global log log = logging.getLogger('heat-config') handler = logging.StreamHandler(sys.stderr) handler.setFormatter( logging.Formatter( '[%(asctime)s] (%(name)s) [%(levelname)s] %(message)s')) log.addHandler(handler) log.setLevel('DEBUG') if not os.path.exists(CONF_FILE): log.warning('No config file %s' % CONF_FILE) return 1 if not os.path.isdir(WORKING_DIR): os.makedirs(WORKING_DIR, 0o700) try: configs = json.load(open(CONF_FILE)) except ValueError as e: log.warning('Could not load config json: %s' % e) return 1 cmd_configs = list(build_configs(configs)) try: delete_missing_projects(cmd_configs) for c in cmd_configs: delete_changed_project(c) write_project(c) except Exception as e: log.exception(e) def build_configs(configs): for c in configs: if c['group'] != 'docker-cmd': continue if not isinstance(c['config'], dict): # convert config to dict c['config'] = yaml.safe_load(c['config']) yield c def current_projects(): for proj_file in os.listdir(WORKING_DIR): if proj_file.endswith('.json'): proj = proj_file[:-5] yield proj def remove_project(proj): proj_file = os.path.join(WORKING_DIR, '%s.json' % proj) with open(proj_file, 'r') as f: proj_data = json.load(f) for name in extract_container_names(proj, proj_data): remove_container(name) os.remove(proj_file) def remove_container(name): cmd = [DOCKER_CMD, 'rm', '-f', name] log.debug(' '.join(cmd)) subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = subproc.communicate() log.info(stdout) log.debug(stderr) def delete_missing_projects(configs): config_names = [c['name'] for c in configs] for proj in current_projects(): if proj not in config_names: log.debug('%s no longer exists, deleting containers' % proj) remove_project(proj) def extract_container_names(proj, proj_data): # For now, assume a docker-compose v1 format where the # root keys are service names for name in sorted(proj_data): yield '%s__%s' % (proj, name) def delete_changed_project(c): proj = c['name'] proj_file = os.path.join(WORKING_DIR, '%s.json' % proj) proj_data = c.get('config', {}) if os.path.isfile(proj_file): with open(proj_file, 'r') as f: prev_proj_data = json.load(f) if proj_data != prev_proj_data: log.debug('%s has changed, deleting containers' % proj) remove_project(proj) def write_project(c): proj = c['name'] proj_file = os.path.join(WORKING_DIR, '%s.json' % proj) proj_data = c.get('config', {}) with os.fdopen(os.open( proj_file, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600), 'w') as f: json.dump(proj_data, f, indent=2) if __name__ == '__main__': sys.exit(main(sys.argv))