# Copyright 2014-2016 Canonical Limited. # # This file is part of layer-basic, the reactive base layer for Juju. # # charm-helpers is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License version 3 as # published by the Free Software Foundation. # # charm-helpers is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with charm-helpers. If not, see . # This module may only import from the Python standard library. import os import sys import subprocess import time ''' execd/preinstall Read the layer-basic docs for more info on how to use this feature. https://charmsreactive.readthedocs.io/en/latest/layer-basic.html#exec-d-support ''' def default_execd_dir(): return os.path.join(os.environ['JUJU_CHARM_DIR'], 'exec.d') def execd_module_paths(execd_dir=None): """Generate a list of full paths to modules within execd_dir.""" if not execd_dir: execd_dir = default_execd_dir() if not os.path.exists(execd_dir): return for subpath in os.listdir(execd_dir): module = os.path.join(execd_dir, subpath) if os.path.isdir(module): yield module def execd_submodule_paths(command, execd_dir=None): """Generate a list of full paths to the specified command within exec_dir. """ for module_path in execd_module_paths(execd_dir): path = os.path.join(module_path, command) if os.access(path, os.X_OK) and os.path.isfile(path): yield path def execd_sentinel_path(submodule_path): module_path = os.path.dirname(submodule_path) execd_path = os.path.dirname(module_path) module_name = os.path.basename(module_path) submodule_name = os.path.basename(submodule_path) return os.path.join(execd_path, '.{}_{}.done'.format(module_name, submodule_name)) def execd_run(command, execd_dir=None, stop_on_error=True, stderr=None): """Run command for each module within execd_dir which defines it.""" if stderr is None: stderr = sys.stdout for submodule_path in execd_submodule_paths(command, execd_dir): # Only run each execd once. We cannot simply run them in the # install hook, as potentially storage hooks are run before that. # We cannot rely on them being idempotent. sentinel = execd_sentinel_path(submodule_path) if os.path.exists(sentinel): continue try: subprocess.check_call([submodule_path], stderr=stderr, universal_newlines=True) with open(sentinel, 'w') as f: f.write('{} ran successfully {}\n'.format(submodule_path, time.ctime())) f.write('Removing this file will cause it to be run again\n') except subprocess.CalledProcessError as e: # Logs get the details. We can't use juju-log, as the # output may be substantial and exceed command line # length limits. print("ERROR ({}) running {}".format(e.returncode, e.cmd), file=stderr) print("STDOUT<