#!/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 shutil import subprocess import sys DEPLOY_KEYS = ("deploy_server_id", "deploy_action", "deploy_stack_id", "deploy_resource_name", "deploy_signal_transport", "deploy_signal_id", "deploy_signal_verb") WORKING_DIR = os.environ.get('HEAT_CHEF_WORKING', '/var/lib/heat-config/heat-config-chef') OUTPUTS_DIR = os.environ.get('HEAT_CHEF_OUTPUTS', '/var/run/heat-config/heat-config-chef') def prepare_dir(path): if not os.path.isdir(path): os.makedirs(path, 0o700) def run_subproc(fn, **kwargs): env = os.environ.copy() for k, v in kwargs.items(): env[str(k)] = v try: subproc = subprocess.Popen(fn, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) stdout, stderr = subproc.communicate() except OSError as exc: ret = -1 stderr = str(exc) stdout = "" else: ret = subproc.returncode if not ret: ret = 0 return ret, stdout, stderr def main(argv=sys.argv): 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') prepare_dir(OUTPUTS_DIR) prepare_dir(WORKING_DIR) os.chdir(WORKING_DIR) c = json.load(sys.stdin) client_config = ("log_level :debug\n" "log_location STDOUT\n" "local_mode true\n" "chef_zero.enabled true") # configure/set up the kitchen kitchen = c['options'].get('kitchen') kitchen_path = c['options'].get('kitchen_path', os.path.join(WORKING_DIR, "kitchen")) cookbook_path = os.path.join(kitchen_path, "cookbooks") role_path = os.path.join(kitchen_path, "roles") environment_path = os.path.join(kitchen_path, "environments") client_config += "\ncookbook_path '%s'" % cookbook_path client_config += "\nrole_path '%s'" % role_path client_config += "\nenvironment_path '%s'" % environment_path if kitchen: log.debug("Cloning kitchen from %s", kitchen) # remove the existing kitchen on update so we get a fresh clone dep_action = next((input['value'] for input in c['inputs'] if input['name'] == "deploy_action"), None) if dep_action == "UPDATE": shutil.rmtree(kitchen_path, ignore_errors=True) cmd = ["git", "clone", kitchen, kitchen_path] ret, out, err = run_subproc(cmd) if ret != 0: log.error("Error cloning kitchen from %s into %s: %s", kitchen, kitchen_path, err) json.dump({'deploy_status_code': ret, 'deploy_stdout': out.decode('utf-8', 'replace'), 'deploy_stderr': err.decode('utf-8', 'replace')}, sys.stdout) return 0 # write the json attributes ret, out, err = run_subproc(['hostname', '-f']) if ret == 0: fqdn = out.decode('utf-8').strip() else: err = "Could not determine hostname with hostname -f" json.dump({'deploy_status_code': ret, 'deploy_stdout': "", 'deploy_stderr': err.decode('utf-8', 'replace')}, sys.stdout) return 0 node_config = {} for input in c['inputs']: if input['name'] == 'environment': client_config += "\nenvironment '%s'" % input['value'] elif input['name'] not in DEPLOY_KEYS: node_config.update({input['name']: input['value']}) node_config.update({"run_list": json.loads(c['config'])}) node_path = os.path.join(WORKING_DIR, "node") prepare_dir(node_path) node_file = os.path.join(node_path, "%s.json" % fqdn) with os.fdopen(os.open(node_file, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: f.write(json.dumps(node_config, indent=4, separators=(',', ': '))) client_config += "\nnode_path '%s'" % node_path # write out the completed client config config_path = os.path.join(WORKING_DIR, "client.rb") with os.fdopen(os.open(config_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: f.write(client_config) # run chef heat_outputs_path = os.path.join(OUTPUTS_DIR, c['id']) cmd = ['chef-client', '-z', '--config', config_path, "-j", node_file] ret, out, err = run_subproc(cmd, heat_outputs_path=heat_outputs_path) resp = {'deploy_status_code': ret, 'deploy_stdout': out.decode('utf-8', 'replace'), 'deploy_stderr': err.decode('utf-8', 'replace')} log.debug("Chef output: %s", out) if ret != 0: log.error("Chef return code %s:\n%s", ret, err) for output in c.get('outputs', []): output_name = output['name'] try: with open('%s.%s' % (heat_outputs_path, output_name)) as out: resp[output_name] = out.read() except IOError: pass json.dump(resp, sys.stdout) if __name__ == '__main__': sys.exit(main(sys.argv))