You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.6 KiB
198 lines
6.6 KiB
#!/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 stat |
|
import subprocess |
|
import six |
|
import sys |
|
|
|
import requests |
|
|
|
HOOKS_DIR_PATHS = ( |
|
os.environ.get('HEAT_CONFIG_HOOKS'), |
|
'/usr/libexec/heat-config/hooks', |
|
'/var/lib/heat-config/hooks', |
|
) |
|
CONF_FILE = os.environ.get('HEAT_SHELL_CONFIG', |
|
'/var/run/heat-config/heat-config') |
|
DEPLOYED_DIR = os.environ.get('HEAT_CONFIG_DEPLOYED', |
|
'/var/lib/heat-config/deployed') |
|
OLD_DEPLOYED_DIR = os.environ.get('HEAT_CONFIG_DEPLOYED_OLD', |
|
'/var/run/heat-config/deployed') |
|
HEAT_CONFIG_NOTIFY = os.environ.get('HEAT_CONFIG_NOTIFY', |
|
'heat-config-notify') |
|
|
|
|
|
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') |
|
|
|
if not os.path.exists(CONF_FILE): |
|
log.error('No config file %s' % CONF_FILE) |
|
return 1 |
|
|
|
conf_mode = stat.S_IMODE(os.lstat(CONF_FILE).st_mode) |
|
if conf_mode != 0o600: |
|
os.chmod(CONF_FILE, 0o600) |
|
|
|
if not os.path.isdir(DEPLOYED_DIR): |
|
if DEPLOYED_DIR != OLD_DEPLOYED_DIR and os.path.isdir(OLD_DEPLOYED_DIR): |
|
log.debug('Migrating deployed state from %s to %s' % |
|
(OLD_DEPLOYED_DIR, DEPLOYED_DIR)) |
|
shutil.move(OLD_DEPLOYED_DIR, DEPLOYED_DIR) |
|
else: |
|
os.makedirs(DEPLOYED_DIR, 0o700) |
|
|
|
try: |
|
configs = json.load(open(CONF_FILE)) |
|
except ValueError: |
|
pass |
|
else: |
|
for c in configs: |
|
try: |
|
invoke_hook(c, log) |
|
except Exception as e: |
|
log.exception(e) |
|
|
|
|
|
def find_hook_path(group): |
|
# sanitise the group to get an alphanumeric hook file name |
|
hook = "".join( |
|
x for x in group if x == '-' or x == '_' or x.isalnum()) |
|
|
|
for h in HOOKS_DIR_PATHS: |
|
if not h or not os.path.exists(h): |
|
continue |
|
hook_path = os.path.join(h, hook) |
|
if os.path.exists(hook_path): |
|
return hook_path |
|
|
|
|
|
def invoke_hook(c, log): |
|
# Sanitize input values (bug 1333992). Convert all String |
|
# inputs to strings if they're not already |
|
hot_inputs = c.get('inputs', []) |
|
for hot_input in hot_inputs: |
|
if hot_input.get('type', None) == 'String' and \ |
|
not isinstance(hot_input['value'], six.string_types): |
|
hot_input['value'] = str(hot_input['value']) |
|
iv = dict((i['name'], i['value']) for i in c['inputs']) |
|
# The group property indicates whether it is softwarecomponent or |
|
# plain softwareconfig |
|
# If it is softwarecomponent, pick up a property config to invoke |
|
# according to deploy_action |
|
group = c.get('group') |
|
if group == 'component': |
|
found = False |
|
action = iv.get('deploy_action') |
|
config = c.get('config') |
|
configs = config.get('configs') |
|
if configs: |
|
for cfg in configs: |
|
if action in cfg['actions']: |
|
c['config'] = cfg['config'] |
|
c['group'] = cfg['tool'] |
|
found = True |
|
break |
|
if not found: |
|
log.warn('Skipping group %s, no valid script is defined' |
|
' for deploy action %s' % (group, action)) |
|
return |
|
|
|
# check to see if this config is already deployed |
|
deployed_path = os.path.join(DEPLOYED_DIR, '%s.json' % c['id']) |
|
|
|
if os.path.exists(deployed_path): |
|
log.warn('Skipping config %s, already deployed' % c['id']) |
|
log.warn('To force-deploy, rm %s' % deployed_path) |
|
return |
|
|
|
signal_data = {} |
|
hook_path = find_hook_path(c['group']) |
|
|
|
if not hook_path: |
|
log.warn('Skipping group %s with no hook script %s' % ( |
|
c['group'], hook_path)) |
|
return |
|
|
|
# write out config, which indicates it is deployed regardless of |
|
# subsequent hook success |
|
with os.fdopen(os.open( |
|
deployed_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: |
|
json.dump(c, f, indent=2) |
|
|
|
log.debug('Running %s < %s' % (hook_path, deployed_path)) |
|
subproc = subprocess.Popen([hook_path], |
|
stdin=subprocess.PIPE, |
|
stdout=subprocess.PIPE, |
|
stderr=subprocess.PIPE) |
|
stdout, stderr = subproc.communicate( |
|
input=json.dumps(c).encode('utf-8', 'replace')) |
|
|
|
if subproc.returncode: |
|
log.error("Error running %s. [%s]\n" % ( |
|
hook_path, subproc.returncode)) |
|
else: |
|
log.info('Completed %s' % hook_path) |
|
|
|
try: |
|
if stdout: |
|
signal_data = json.loads(stdout.decode('utf-8', 'replace')) |
|
except ValueError: |
|
signal_data = { |
|
'deploy_stdout': stdout, |
|
'deploy_stderr': stderr, |
|
'deploy_status_code': subproc.returncode, |
|
} |
|
|
|
for i in signal_data.items(): |
|
log.info('%s\n%s' % i) |
|
log.debug(stderr.decode('utf-8', 'replace')) |
|
|
|
signal_data_path = os.path.join(DEPLOYED_DIR, '%s.notify.json' % c['id']) |
|
# write out notify data for debugging |
|
with os.fdopen(os.open( |
|
signal_data_path, os.O_CREAT | os.O_WRONLY, 0o600), 'w') as f: |
|
json.dump(signal_data, f, indent=2) |
|
|
|
log.debug('Running %s %s < %s' % ( |
|
HEAT_CONFIG_NOTIFY, deployed_path, signal_data_path)) |
|
subproc = subprocess.Popen([HEAT_CONFIG_NOTIFY, deployed_path], |
|
stdin=subprocess.PIPE, |
|
stdout=subprocess.PIPE, |
|
stderr=subprocess.PIPE) |
|
stdout, stderr = subproc.communicate( |
|
input=json.dumps(signal_data).encode('utf-8', 'replace')) |
|
|
|
log.info(stdout) |
|
|
|
if subproc.returncode: |
|
log.error( |
|
"Error running heat-config-notify. [%s]\n" % subproc.returncode) |
|
log.error(stderr) |
|
else: |
|
log.debug(stderr) |
|
|
|
|
|
if __name__ == '__main__': |
|
sys.exit(main(sys.argv))
|
|
|