diskimage-builder/doc/source/developer/developing_elements.rst
Gregory Haynes 9a166b9df6 Document only exectuables in phase dirs policy
Having data files in the phase subdirs is an easy source of confusion
in reviews (especially when the data file is a script) and theres really
no reason to be putting data files there at this point. Lets make a
convention out of not doing this.

Change-Id: I99571a2a49e14e8c709af20f6d13d662ac745eb4
2015-06-16 17:03:17 +00:00

321 lines
13 KiB
ReStructuredText

Developing Elements
===================
Conform to the following conventions:
* Use the environment for overridable defaults, prefixing environment variable
names with "DIB\_". For example: DIB\_MYDEFAULT=${DIB\_MYDEFAULT:-default}
If you do not use the DIB\_ prefix you may find that your overrides are
discarded as the build environment is sanitised.
* Consider that your element co-exists with many others and try to guard
against undefined behaviours. Some examples:
* Two elements use the source-repositories element, but use the same filename
for the source-repositories config file. Files such as these (and indeed the
scripts in the various .d directories listed below) should be named such
that they are unique. If they are not unique, when the combined tree is
created by disk-image-builder for injecting into the build environment, one
of the files will be overwritten.
* Two elements copy different scripts into /usr/local/bin with the same name.
If they both use set -e and cp -n then the conflict will be caught and cause
the build to fail.
* If your element mounts anything into the image build tree ($TMP\_BUILD\_DIR)
then it will be automatically unmounted when the build tree is unmounted -
and not remounted into the filesystem image - if the mount point is needed
again, your element will need to remount it at that point.
Phase Subdirectories
^^^^^^^^^^^^^^^^^^^^
Make as many of the following subdirectories as you need, depending on what
part of the process you need to customise. The subdirectories are executed in
the order given here. Scripts within the subdirectories should be named with a
two-digit numeric prefix, and are executed in numeric order. Only files which
are marked executable (+x) will be run, so other files can be stored in these
directories if needed. As a convention, we try to only store executable scripts
in the phase subdirectories and store data files elsewhere in the element.
* root.d: Create or adapt the initial root filesystem content. This is where
alternative distribution support is added, or customisations such as
building on an existing image.
Only one element can use this at a time unless particular care is taken not
to blindly overwrite but instead to adapt the context extracted by other
elements.
* runs: outside chroot
* inputs: $ARCH=i386|amd64|armhf $TARGET\_ROOT=/path/to/target/workarea
* extra-data.d: pull in extra data from the host environment that hooks may
need during image creation. This should copy any data (such as SSH keys,
http proxy settings and the like) somewhere under $TMP\_HOOKS\_PATH.
* runs: outside chroot
* inputs: $TMP\_HOOKS\_PATH
* outputs: None
* pre-install.d: Run code in the chroot before customisation or packages are
installed. A good place to add apt repositories.
* runs: in chroot
* install.d: Runs after pre-install.d in the chroot. This is a good place to
install packages, chain into configuration management tools or do other
image specific operations.
* runs: in chroot
* post-install.d: Run code in the chroot. This is a good place to perform
tasks you want to handle after the OS/application install but before the
first boot of the image. Some examples of use would be: Run chkconfig
to disable unneeded services and clean the cache left by the package
manager to reduce the size of the image.
* runs: in chroot
* block-device.d: customise the block device that the image will be made on
(e.g. to make partitions). Runs after the target tree has been fully
populated but before the cleanup hook runs.
* runs: outside chroot
* inputs: $IMAGE\_BLOCK\_DEVICE={path} $TARGET\_ROOT={path}
* outputs: $IMAGE\_BLOCK\_DEVICE={path}
* finalise.d: Perform final tuning of the root filesystem. Runs in a chroot
after the root filesystem content has been copied into the mounted
filesystem: this is an appropriate place to reset SELinux metadata, install
grub bootloaders and so on. Because this happens inside the final image, it
is important to limit operations here to only those necessary to affect the
filesystem metadata and image itself. For most operations, post-install.d
is preferred.
* runs: in chroot
* cleanup.d: Perform cleanup of the root filesystem content. For
instance, temporary settings to use the image build environment HTTP proxy
are removed here in the dpkg element.
* runs: outside chroot
* inputs: $ARCH=i386|amd64|armhf $TARGET\_ROOT=/path/to/target/workarea
Other Subdirectories
^^^^^^^^^^^^^^^^^^^^
Elements may have other subdirectories that are processed by specific elements
rather than the diskimage-builder tools themselves.
One example of this is the ``bin`` directory. The ``rpm-distro``, ``dpkg`` and
``opensuse`` elements install all files found in the ``bin`` directory into
``/usr/local/bin`` within the image as executable files.
Environment Variables
^^^^^^^^^^^^^^^^^^^^^
To set environment variables for other hooks, add a file to environment.d.
This directory contains bash script snippets that are sourced before running
scripts in each phase.
DIB exposes an internal IMAGE\_ELEMENT variable which provides elements access
to the full set of elements that are included in the image build. This can
be used to process local in-element files across all the elements
(pkg-map for example).
Dependencies
^^^^^^^^^^^^
Each element can use the following files to define or affect dependencies:
* 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.
* element-provides: A plain text, newline separated list of elements which
are provided by this element. These elements will be excluded from elements
built into the image at image creation time. For example if element A depends
on element B and element C includes element B in its "element-provides"
file and A and C are included when building an image, then B is not used.
Operating system elements
^^^^^^^^^^^^^^^^^^^^^^^^^
Some elements define the base structure for an operating system -- for example,
the ``opensuse`` element builds a base openSUSE system. Such elements have
more requirements than the other elements:
* they must have ``operating-system`` in their element-provides, so this
indicates they are an "operating system".
* they must export the ``DISTRO_NAME`` environment variable with the name
of the distribution built, using an environment.d script. For example,
the ``opensuse`` element exports ``DISTRO_NAME=opensuse``.
Ramdisk Elements
^^^^^^^^^^^^^^^^
Ramdisk elements support the following files in their element directories:
* binary-deps.d : text files listing executables required to be fed into the
ramdisk. These need to be present in $PATH in the build chroot (i.e. need to
be installed by your elements as described above).
* init.d : POSIX shell script fragments that will be appended to the default
script executed as the ramdisk is booted (/init).
* ramdisk-install.d : called to copy files into the ramdisk. The variable
TMP\_MOUNT\_PATH points to the root of the tree that will be packed into
the ramdisk.
* udev.d : udev rules files that will be copied into the ramdisk.
Element coding standard
^^^^^^^^^^^^^^^^^^^^^^^
- lines should not include trailing whitespace.
- there should be no hard tabs in the file.
- indents are 4 spaces, and all indentation should be some multiple of
them.
- `do` and `then` keywords should be on the same line as the if, while or
for conditions.
Global image-build variables
----------------------------
* DIB\_OFFLINE : this is always set. When not empty, any operations that
perform remote data access should avoid it if possible. If not possible
the operation should still be attempted as the user may have an external
cache able to keep the operation functional.
* DIB\_IMAGE\_ROOT\_FS\_UUID : this contains the UUID of the root fs, when
diskimage-builder is building a disk image. This works only for ext
filesystems.
Structure of an element
-----------------------
The above-mentioned global content can be further broken down in a way that
encourages composition of elements and reusability of their components. One
possible approach to this would be to label elements as either a "driver",
"service", or "config" element. Below are some examples.
- Driver-specific elements should only contain the necessary bits for that
driver::
elements/
driver-mellanox/
init - modprobe line
install.d/
10-mlx - package installation
- An element that installs and configures Nova might be a bit more complex,
containing several scripts across several phases::
elements/
service-nova/
source-repository-nova - register a source repository
pre-install.d/
50-my-ppa - add a PPA
install.d/
10-user - common Nova user accts
50-my-pack - install packages from my PPA
60-nova - install nova and some dependencies
- In the general case, configuration should probably be handled either by the
meta-data service (eg, o-r-c) or via normal CM tools
(eg, salt). That being said, it may occasionally be desirable to create a
set of elements which express a distinct configuration of the same software
components.
In this way, depending on the hardware and in which availability zone it is
to be deployed, an image would be composed of:
* zero or more driver-elements
* one or more service-elements
* zero or more config-elements
It should be noted that this is merely a naming convention to assist in
managing elements. Diskimage-builder is not, and should not be, functionally
dependent upon specific element names.
diskimage-builder has the ability to retrieve source code for an element and
place it into a directory on the target image during the extra-data phase. The
default location/branch can then be overridden by the process running
diskimage-builder, making it possible to use the same element to track more
then one branch of a git repository or to get source for a local cache. See
elements/source-repositories/README.md for more information.
Debugging elements
------------------
The build-time environment and command line arguments are captured by the
'base' element and written to /etc/dib\_environment and /etc/dib\_arguments
inside the image.
Export 'break' to drop to a shell during the image build. Break points can be
set either before or after any of the hook points by exporting
"break=[before|after]-hook-name". Multiple break points can be specified as a
comma-delimited string. Some examples:
* break=before-block-device-size will break before the block device size hooks
are called.
* break=before-pre-install will break before the pre-install hooks.
* break=after-error will break after an error during a in target hookpoint.
Images are built such that the Linux kernel is instructed not to switch into
graphical consoles (i.e. it will not activate KMS). This maximises
compatibility with remote console interception hardware, such as HP's iLO.
However, you will typicallly only see kernel messages on the console - init
daemons (e.g. upstart) will usually be instructed to output to a serial
console so nova's console-log command can function. There is an element in the
tripleo-image-elements repository called "remove-serial-console" which will
force all boot messages to appear on the main console.
Ramdisk images can be debugged at run-time by passing "troubleshoot" as a
kernel command line argument, or by pressing "t" when an error is reached. This
will spawn a shell on the console (this can be extremely useful when network
interfaces or disks are not detected correctly).
Testing Elements
----------------
An element can have functional tests encapsulated inside the element itself. In
order to create a test case, follow these steps:
* Create a directory called 'test-elements' inside your element.
* Inside the test-elements directory, create a directory with the name of your
test case. The test case directory should have the same structure as an
element.
i.e. elements/apt-sources/test-elements/test-case-1
* Assert state during each of the element build phases you would like to test.
You can exit 1 to indicate a failure.
* To exit early and indicate a success, touch a file /tmp/dib-test-should-fail
in the image chroot, then exit 1.
Additionally, elements can be tested using python unittests. To create a
a python test:
* Create a directory called 'tests' in the element directory.
* Create an empty file called '\_\_init\_\_.py' to make it into a python
package.
* Create your test files as 'test\_whatever.py', using regular python test
code.
To run all the tests use testr - `testr run`. To run just some tests provide
one or more regex filters - tests matching any of them are run -
`testr run apt-proxy`.
Third party elements
--------------------
Pending implementation. The idea is to have a search path for elements.