File: developing_elements.rst

package info (click to toggle)
python-diskimage-builder 3.37.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 5,572 kB
  • sloc: sh: 7,380; python: 6,444; makefile: 37
file content (507 lines) | stat: -rw-r--r-- 17,967 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
.. _developing-elements:

Developing Elements
===================

Conform to the following conventions:

* Use the environment for overridable defaults, prefixing environment variable
  names with ``DIB_``. For example:

  .. sourcecode:: sh

      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 :ref:`listed below
    <phase-subdirectories>`) 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.

* If caching is required, elements should use a location under
  ``$DIB_IMAGE_CACHE``.

* Elements should allow for remote data to be cached. When ``$DIB_OFFLINE`` is
  set, this cached data should be used if possible.
  See the :ref:`dev-global-image-build-variables` section of this document for
  more information.

* Elements in the upstream diskimage-builder elements should not create
  executables which run before 10- or after 90- in any of the phases if
  possible. This is to give downstream elements the ability to easily make
  executables which run after our upstream ones.

.. _phase-subdirectories:

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.

The phases are:

#. ``root.d``
#. ``extra-data.d``
#. ``pre-install.d``
#. ``install.d``
#. ``post-install.d``
#. ``post-root.d``
#. ``block-device.d``
#. ``pre-finalise.d``
#. ``finalise.d``
#. ``cleanup.d``

``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=amd64|armhf|arm64``
    * ``$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

  Contents placed under ``$TMP_HOOKS_PATH`` will be available at
  ``/tmp/in_target.d`` inside the chroot.

``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
    * Clean the cache left by the package manager to reduce the size
      of the image.

  * runs: **in chroot**

``post-root.d``
  Run code outside the chroot. This is a good place to perform tasks that
  cannot run inside the chroot and must run after installing things. The
  root filesystem content is rooted at ``$TMP_BUILD_DIR/mnt``.

  * runs: **outside chroot**

``block-device.d``
  Customise the block device that the image will be made on (for example to
  make partitions). Runs after the target tree has been fully populated but
  before the ``cleanup.d`` phase runs.

  * runs: **outside chroot**
  * inputs:

    * ``$IMAGE_BLOCK_DEVICE={path}``
    * ``$TARGET_ROOT={path}``

  * outputs: ``$IMAGE_BLOCK_DEVICE={path}``

``pre-finalise.d``

  Final tuning of the root filesystem, outside the chroot.  Filesystem
  content has been copied into the final file system which is rooted
  at ``$TMP_BUILD_DIR/mnt``.  You might do things like re-mount a
  cache directory that was used during the build in this phase (with
  subsequent unmount in ``cleanup.d``).

  * runs: **outside chroot**

``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=amd64|armhf|arm64``
    * ``$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`,
:doc:`../elements/dpkg/README` and :doc:`../elements/opensuse/README` 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 your
element ``environment.d``.  This directory contains bash script
snippets that are sourced before running scripts in each phase.  Note
that because environment includes are sourced together, they should
not set global flags like ``set -x`` because they will affect all
preceeding imports.


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.

.. _dev-global-image-build-variables:

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_CACHE``
  Path to where cached inputs to the build process are stored. Defaults to
  ``~/.cache/image_create``.

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
:doc:`../elements/source-repositories/README` for more information.

Finding other elements
----------------------

DIB exposes an internal ``$IMAGE_ELEMENT_YAML`` variable which
provides elements access to the full set of included elements and
their paths.  This can be used to process local in-element files
across all the elements (``pkg-map`` for example).

.. code-block:: python

   import os
   import yaml

   elements = yaml.load(os.getenv('IMAGE_ELEMENT_YAML'))
   for element, path in elements:
      ...

For elements written in Bash, there is a function
``get_image_element_array`` that can be used to instantiate an
associative-array of elements and paths (note arrays can not be
exported in bash).

.. code-block:: bash

   # note eval to expand the result of the get function
   eval declare -A image_elements=($(get_image_element_array))
   for i in ${!image_elements[$i]}; do
     element=$i
     path=${image_elements[$i]}
   done

Debugging elements
------------------

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 an in target hookpoint.

The :doc:`../elements/manifests/README` element will make a range of
manifest information generated by other elements available for
inspection inside and outside the built image.  Environment and
command line arguments are captured as described in the documentation
and can be useful for debugging.

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 typically 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. The
tests can be written either as shell or python unit tests.

shell
"""""

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. For example::

    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.

Tests are run with ``tools/run_functests.sh``.  Running
``run_functests.sh -l`` will show available tests (the example above
would be called ``apt-sources/test-case-1``, for example).  Specify
your test (or a series of tests as separate arguments) on the command
line to run it.  If it should not be run as part of the default CI
run, you can submit a change with it added to ``DEFAULT_SKIP_TESTS``
in that file.

Running the functional tests is time consuming.  Multiple parallel
jobs can be started by specifying ``-j <job count>``.  Each of the
jobs uses a lot resources (CPU, disk space, RAM) - therefore the job
count must carefully be chosen.

python
""""""

To run functional tests locally, install and start docker, then use
the following tox command::

    tox -efunc

Note that running functional tests requires *sudo* rights, thus you may be
asked for your password.

To run functional tests for one element, append its name to the command::

    tox -efunc ironic-agent

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
--------------------

Additional elements can be incorporated by setting ``ELEMENTS_PATH``, for
example if one were building tripleo-images, the variable would be set like:

  .. sourcecode:: sh

      export ELEMENTS_PATH=tripleo-image-elements/elements
      disk-image-create rhel cinder-api

Linting
-------

You should always run ``bin/dib-lint`` over your elements.  It will
warn you of common issues.

sudo
""""

Using ``sudo`` outside the chroot environment can cause breakout
issues where you accidentally modify parts of the host
system. ``dib-lint`` will warn if it sees ``sudo`` calls that do not
use the path arguments given to elements running outside the chroot.

To disable the error for a call you know is safe, add

::

   # dib-lint: safe_sudo

to the end of the ``sudo`` command line.  To disable the check for an
entire file, add

::

   # dib-lint: disable=safe_sudo