224 lines
7.9 KiB
Python
224 lines
7.9 KiB
Python
# Copyright 2013, Red Hat Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
|
|
from distutils import dir_util
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
from diskimage_builder import element_dependencies
|
|
|
|
from instack import element
|
|
|
|
# dib-run-parts has moved in dibv2 to an internal call. We don't
|
|
# really want to introduce a dependency on dib-utils here. For v1
|
|
# just call it from path.
|
|
# Note this is fragile as dib considers this internal
|
|
_DIB_RUN_PARTS = 'dib-run-parts'
|
|
try:
|
|
import diskimage_builder.paths
|
|
_DIB_RUN_PARTS = os.path.join(diskimage_builder.paths.get_path('lib'),
|
|
'dib-run-parts')
|
|
except ImportError:
|
|
pass
|
|
|
|
|
|
LOG = logging.getLogger()
|
|
|
|
|
|
class ElementRunner(object):
|
|
|
|
def __init__(self, elements, hooks, element_paths=None, blacklist=None,
|
|
exclude_elements=None, dry_run=False, interactive=False,
|
|
no_cleanup=False):
|
|
"""Element Runner initialization.
|
|
|
|
:param elements: Element names to apply.
|
|
:type elements: list.
|
|
:param hooks: Hooks to run for each element.
|
|
:type hooks: list.
|
|
:param element_paths: File system paths to search for elements.
|
|
:type element_paths: list of strings.
|
|
:param dry_run: If True, do not actually run the hooks.
|
|
:type dry_run: bool
|
|
"""
|
|
self.elements = elements
|
|
self.dry_run = dry_run
|
|
self.hooks = hooks
|
|
self.blacklist = blacklist or []
|
|
self.exclude_elements = exclude_elements or []
|
|
self.interactive = interactive
|
|
self.no_cleanup = no_cleanup
|
|
self.loaded_elements = {}
|
|
self.tmp_hook_dir = tempfile.mkdtemp()
|
|
self.environment_file = os.path.join(self.tmp_hook_dir,
|
|
'environment.d',
|
|
'00-dib-v2-env')
|
|
|
|
# the environment variable should override anything passed in
|
|
if 'ELEMENTS_PATH' in os.environ:
|
|
self.element_paths = os.environ['ELEMENTS_PATH'].split(':')
|
|
else:
|
|
self.element_paths = element_paths
|
|
|
|
if self.element_paths is None:
|
|
raise Exception("No element paths specified")
|
|
|
|
LOG.info('Initialized with elements path: %s' %
|
|
' '.join(list(self.element_paths)))
|
|
|
|
self.load_elements()
|
|
self.load_dependencies()
|
|
self.process_exclude_elements()
|
|
self.copy_elements()
|
|
self.generate_environment()
|
|
|
|
def generate_environment(self):
|
|
"""Generate a dib v2 environment."""
|
|
# NOTE(bnemec): Older versions of dib don't need this. We can tell
|
|
# by looking for the --env parameter to element-info.
|
|
check_output = subprocess.check_output(['element-info', '-h'])
|
|
if '--env' not in check_output:
|
|
return
|
|
|
|
command = ['element-info', '--env'] + list(self.elements)
|
|
env_output = subprocess.check_output(command)
|
|
with open(self.environment_file, 'w') as f:
|
|
f.write(env_output)
|
|
|
|
def run(self):
|
|
"""Apply the elements by running each specified hook."""
|
|
for hook in self.hooks:
|
|
self.run_hook(hook)
|
|
|
|
self.cleanup()
|
|
|
|
def cleanup(self):
|
|
"""Clean up after a run."""
|
|
if not self.no_cleanup:
|
|
shutil.rmtree(self.tmp_hook_dir)
|
|
|
|
def load_elements(self):
|
|
"""Load all elements from self.element_paths.
|
|
|
|
This populates self.loaded_elements.
|
|
"""
|
|
for path in self.element_paths:
|
|
self.process_path(path)
|
|
|
|
def copy_elements(self):
|
|
"""Copy elements to apply to a temporary directory."""
|
|
# self.tmp_hook_dir may exist from a previous run, delete it if so.
|
|
if os.path.exists(self.tmp_hook_dir):
|
|
shutil.rmtree(self.tmp_hook_dir)
|
|
|
|
os.makedirs(self.tmp_hook_dir)
|
|
|
|
for elem in self.elements:
|
|
element_dir = self.loaded_elements[elem].directory
|
|
dir_util.copy_tree(element_dir, self.tmp_hook_dir)
|
|
|
|
# elements expect this environment variable to be set
|
|
os.environ['TMP_HOOKS_PATH'] = self.tmp_hook_dir
|
|
tmp_path = '%s/bin' % self.tmp_hook_dir
|
|
if 'PATH' in os.environ:
|
|
tmp_path = os.environ["PATH"] + os.pathsep + tmp_path
|
|
os.environ["PATH"] = tmp_path
|
|
|
|
def process_path(self, path):
|
|
"""Load elements from a given filesystem path.
|
|
|
|
:param path: Filesystem path from which to load elements.
|
|
:type path: str.
|
|
"""
|
|
if not os.access(path, os.R_OK):
|
|
raise Exception("Can't read from elements path at %s." % path)
|
|
|
|
for elem in os.listdir(path):
|
|
if not os.path.isdir(os.path.join(path, elem)):
|
|
continue
|
|
self.loaded_elements[elem] = element.Element(
|
|
os.path.join(path, elem))
|
|
|
|
def load_dependencies(self):
|
|
"""Load and add all element dependencies to self.elements."""
|
|
all_elements = element_dependencies.expand_dependencies(
|
|
self.elements, ':'.join(self.element_paths))
|
|
self.elements = all_elements
|
|
os.environ['IMAGE_ELEMENT'] = ' '.join(
|
|
[x for x in sorted(self.elements)])
|
|
LOG.info("List of all elements and dependencies: %s" %
|
|
' '.join(list(self.elements)))
|
|
|
|
def process_exclude_elements(self):
|
|
"""Remove any elements that have been specified as excluded."""
|
|
for elem in self.exclude_elements:
|
|
if elem in self.elements:
|
|
LOG.info("Excluding element %s" % elem)
|
|
self.elements.remove(elem)
|
|
# Need to redefine OS.environ['IMAGE_ELEMENT'] after removing excludes
|
|
os.environ['IMAGE_ELEMENT'] = ' '.join(
|
|
[x for x in sorted(self.elements)])
|
|
LOG.info("List of all elements and dependencies after excludes: %s" %
|
|
' '.join(list(self.elements)))
|
|
|
|
def run_hook(self, hook):
|
|
"""Run a hook on the current system.
|
|
|
|
:param hook: name of hook to run
|
|
:type hook: str
|
|
"""
|
|
LOG.info(" Running hook %s" % hook)
|
|
|
|
hook_dir = os.path.join(self.tmp_hook_dir, '%s.d' % hook)
|
|
if not os.path.exists(hook_dir):
|
|
LOG.info(" Skipping hook %s, the hook directory doesn't "
|
|
"exist at %s" % (hook, hook_dir))
|
|
return
|
|
|
|
for blacklisted_script in self.blacklist:
|
|
if blacklisted_script in os.listdir(hook_dir):
|
|
LOG.debug(" Blacklisting %s" % blacklisted_script)
|
|
os.unlink(os.path.join(hook_dir, blacklisted_script))
|
|
|
|
command = [_DIB_RUN_PARTS, hook_dir]
|
|
if self.dry_run:
|
|
LOG.info(" Dry Run specified, not running hook")
|
|
else:
|
|
rc = call(command, env=os.environ)
|
|
if rc != 0:
|
|
LOG.error(" Hook FAILED.")
|
|
raise Exception("Failed running command %s" % command)
|
|
|
|
|
|
def call(command, **kwargs):
|
|
"""Call out to run a command via subprocess."""
|
|
|
|
LOG.debug(' executing command: %s' % command)
|
|
|
|
LOG.info('############### Begin stdout/stderr logging ###############')
|
|
rc = subprocess.call(command,
|
|
stdout=sys.stdout,
|
|
stderr=sys.stderr,
|
|
**kwargs)
|
|
LOG.info('############### End stdout/stderr logging ###############')
|
|
|
|
LOG.debug(' exited with code: %s' % rc)
|
|
return rc
|