diff --git a/README.md b/README.md index 03db4b16d..1f4b41480 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ What tools are there? * elements can be found in the top level elements directory. +* element-info : Extract information about elements. + Why? ---- @@ -118,6 +120,9 @@ part of the process you need to customise: * first-boot.d: Runs inside the image before rc.local. Scripts from here are good for doing per-instance configuration based on cloud metadata. +* element-deps : A plain text, newline separated list of elements which will + be added to the list of elements built into the image at image creation time. + Ramdisk elements support the following files in their element directories: * binary-deps : executables required to be fed into the ramdisk. These need diff --git a/bin/disk-image-create b/bin/disk-image-create index 1c49d0b56..100c2f46c 100755 --- a/bin/disk-image-create +++ b/bin/disk-image-create @@ -18,6 +18,7 @@ set -e SCRIPTNAME=$(basename $0) +SCRIPT_HOME=$(dirname $0) export _LIB=$(dirname $0)/../lib source $_LIB/die @@ -58,6 +59,8 @@ source $_LIB/img-defaults source $_LIB/common-functions source $_LIB/img-functions +IMAGE_ELEMENT=$($SCRIPT_HOME/element-info --expand-dependencies $IMAGE_ELEMENT) + echo "Building elements: $IMAGE_ELEMENT" echo "If prompted for sudo, install sudoers.d/img-build-sudoers into /etc/sudoers.d and restart the build." mkdir -p $IMG_PATH diff --git a/bin/diskimage_builder b/bin/diskimage_builder new file mode 120000 index 000000000..ac949dc60 --- /dev/null +++ b/bin/diskimage_builder @@ -0,0 +1 @@ +../diskimage_builder \ No newline at end of file diff --git a/bin/element-info b/bin/element-info new file mode 100755 index 000000000..44f2f3e84 --- /dev/null +++ b/bin/element-info @@ -0,0 +1,23 @@ +#!/usr/bin/python +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. + +import os +import sys + +from diskimage_builder.elements import main + + +sys.exit(main(sys.argv)) diff --git a/bin/ramdisk-image-create b/bin/ramdisk-image-create index e7ec9f158..67a1aab79 100755 --- a/bin/ramdisk-image-create +++ b/bin/ramdisk-image-create @@ -20,6 +20,7 @@ set -e SCRIPTNAME=$(basename $0) +SCRIPT_HOME=$(dirname $0) export _DIR=$(dirname $0) export _LIB=${_DIR}/../lib source $_LIB/die @@ -57,6 +58,8 @@ source $_LIB/ramdisk-defaults source $_LIB/common-functions source $_LIB/ramdisk-functions +RAMDISK_ELEMENT=$($SCRIPT_HOME/element-info --expand-dependencies $RAMDISK_ELEMENT) + echo "Building element(s): ${RAMDISK_ELEMENT}" echo "Discovering binary dependencies" diff --git a/diskimage_builder/elements.py b/diskimage_builder/elements.py new file mode 100644 index 000000000..1029854b8 --- /dev/null +++ b/diskimage_builder/elements.py @@ -0,0 +1,91 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. + +import argparse +from errno import ENOENT +import os +import sys + + +def get_elements_dir(): + if 'ELEMENTS_DIR' not in os.environ: + return os.path.abspath( + os.path.join(os.path.dirname(__file__), '..', 'elements')) + return os.environ['ELEMENTS_DIR'] + + +def dependencies(element, elements_dir=None): + """ Return the non-transitive list of dependencies for a single element + :param user_elements: iterable enumerating elements a user has requested + :param elements_dir: the elements dir to read from. If not supplied, + inferred by calling get_elements_dir(). + + :return: a set just containing all elements that the specified element + depends on. + """ + if elements_dir is None: + elements_dir = get_elements_dir() + + element_deps_path = os.path.join(elements_dir, element, 'element-deps') + try: + with open(element_deps_path) as element_deps: + return set([line.strip() for line in element_deps]) + except IOError as e: + if e.errno == 2: + return set() + else: + raise + + +def expand_dependencies(user_elements, elements_dir=None): + """ Expand user requested elements using element-deps files. + + Arguments: + :param user_elements: iterable enumerating the elements a user requested + :param elements_dir: the elements dir to read from. Passed directly to + dependencies() + + :return: a set containing user_elements and all dependent elements + including any transitive dependencies. + """ + final_elements = set(user_elements) + check_queue = list(user_elements) + + while check_queue: + element = check_queue.pop() + deps = dependencies(element, elements_dir) + check_queue.extend(deps - final_elements) + final_elements.update(deps) + + return final_elements + + +def main(argv): + parser = argparse.ArgumentParser() + parser.add_argument('elements', nargs='+', + help='elements to inspect') + parser.add_argument('--expand-dependencies', '-d', action='store_true', + default=False, + help='Print expanded dependencies of all args') + + args = parser.parse_args(argv[1:]) + + if args.expand_dependencies: + print(' '.join(expand_dependencies(args.elements))) + return 0 + + + sys.stderr.write("ERROR: please choose an option.\n") + return -1 diff --git a/diskimage_builder/test_elementdeps.py b/diskimage_builder/test_elementdeps.py new file mode 100644 index 000000000..6cb4a7e59 --- /dev/null +++ b/diskimage_builder/test_elementdeps.py @@ -0,0 +1,71 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# 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. + +import os + +from testtools import TestCase +from fixtures import Fixture, TempDir + +from diskimage_builder.elements import expand_dependencies + +data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test-elements')) + +def _populate_element(element_dir, element_name, element_deps=[]): + element_home = os.path.join(element_dir, element_name) + os.mkdir(element_home) + deps_path = os.path.join(element_home, 'element-deps') + with open(deps_path, 'w') as deps_file: + deps_file.write("\n".join(element_deps)) + +class TestElementDeps(TestCase): + + def setUp(self): + super(TestElementDeps, self).setUp() + self.element_dir = self.useFixture(TempDir()).path + _populate_element(self.element_dir, 'requires-foo', ['foo']) + _populate_element(self.element_dir, 'foo') + _populate_element(self.element_dir, + 'requires-requires-foo', + ['requires-foo']) + _populate_element(self.element_dir, 'self', ['self']) + _populate_element(self.element_dir, 'circular1', ['circular2']) + _populate_element(self.element_dir, 'circular2', ['circular1']) + + def test_non_transitive_deps(self): + result = expand_dependencies(['requires-foo'], + elements_dir=self.element_dir) + self.assertEquals(set(['requires-foo', 'foo']), result) + + def test_transitive_deps(self): + result = expand_dependencies(['requires-requires-foo'], + elements_dir=self.element_dir) + self.assertEquals(set(['requires-requires-foo', + 'requires-foo', + 'foo']), result) + + def test_no_deps(self): + result = expand_dependencies(['foo'], + elements_dir=self.element_dir) + self.assertEquals(set(['foo']), result) + + def test_self(self): + result = expand_dependencies(['self'], + elements_dir=self.element_dir) + self.assertEquals(set(['self']), result) + + def test_circular(self): + result = expand_dependencies(['circular1'], + elements_dir=self.element_dir) + self.assertEquals(set(['circular1', 'circular2']), result)