Support custom partitions
In addition to the volume volume management settings also
allow to setup low level table entries like in the following
example:
<partitions>
<partition name="var" size="100" mountpoint="/var" filesystem="ext3"/>
</partitions>
This commit is contained in:
parent
bf5c9d0d55
commit
94de1336d8
@ -707,8 +707,24 @@ that is being used as a vagrant box. For details see: :ref:`setup_vagrant`
|
||||
|
||||
<preferences><type><systemdisk>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Used to describe the geometry, partitions and volumes, in a
|
||||
disk image. For details see: :ref:`custom_volumes`
|
||||
Used to describe the volumes of the disk area which
|
||||
contains the root filesystem. Volumes are either a feature
|
||||
of the used filesystem or LVM is used for this purpose.
|
||||
For details see: :ref:`custom_volumes`
|
||||
|
||||
.. note::
|
||||
|
||||
When both `<partitions>` and `<systemdisk>` are used, `<partitions>`
|
||||
are evaluated first and mount points defined in `<partitions>` cannot
|
||||
be redefined as `<systemdisk>` volumes. The two types define a
|
||||
complete disk setup, so there cannot be any overlapping volumes
|
||||
or mount points. As a result, whatever is written in `<partitions>`
|
||||
cannot be expressed in the same way in `<volumes>`.
|
||||
|
||||
<preferences><type><partitions>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Used to describe the geometry of the disk on the level of the
|
||||
partition table. For details see: :ref:`custom_partitions`
|
||||
|
||||
<preferences><type><oemconfig>
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@ -5,132 +5,107 @@ Custom Disk Partitions
|
||||
|
||||
.. sidebar:: Abstract
|
||||
|
||||
This page provides some details about what {kiwi} supports and does
|
||||
not support regarding customization over the partition scheme. It also
|
||||
provides some guidance in case the user requires some custom layout
|
||||
beyond {kiwi} supported features.
|
||||
|
||||
By design, {kiwi} does not support a customized partition table. Alternatively,
|
||||
{kiwi} supports the definition of user-defined volumes which covers most of
|
||||
common use cases. See :ref:`Custom Disk Volumes <custom_volumes>` for
|
||||
further details about that.
|
||||
This page provides details about the opportunities and limitations
|
||||
to customize the partition table in addition to the volume management
|
||||
settings from :ref:`custom_volumes`.
|
||||
|
||||
{kiwi} has its own partitioning schema which is defined according to several
|
||||
different user configurations: boot firmware, boot partition,
|
||||
expandable layouts, etc. Those supported features have an impact on the
|
||||
partitioning schema. MBR or GUID partition tables are not flexible,
|
||||
carry limitations and are tied to some specific disk geometry. Because
|
||||
of that the preferred alternative to disk layouts based on traditional
|
||||
partition tables is using flexible approaches like logic volumes.
|
||||
partitioning schema.
|
||||
|
||||
As an example, expandable OEM images is a relevant {kiwi} feature that
|
||||
is incompatible with the idea of adding user defined partitions on the
|
||||
system area.
|
||||
MBR or GUID partition tables are not flexible, carry limitations and are
|
||||
tied to some specific disk geometry. Because of that the preferred alternative
|
||||
to disk layouts based on traditional partition tables is using flexible
|
||||
approaches like logic volumes.
|
||||
|
||||
Despite no full customization is supported, some aspects of the partition
|
||||
schema can be customized. {kiwi} supports:
|
||||
However, on certain conditions additional entries to the low level
|
||||
partition table are needed. For this purpose the `<partitions>` section
|
||||
exists and allows to add custom entries like shown in the following
|
||||
example:
|
||||
|
||||
1. Adding a spare partition *before* the root (`/`) partition.
|
||||
.. code:: xml
|
||||
|
||||
It can be achieved by using the `spare_part` type attribute.
|
||||
<partitions>
|
||||
<partition name="var" size="100" mountpoint="/var" filesystem="ext3"/>
|
||||
</partitions>
|
||||
|
||||
2. Leaving some unpartitioned area at the *end* of the disk.
|
||||
Each `<partition>` entry puts a partition of the configured size in the
|
||||
low level partition table, creates a filesystem on it and includes
|
||||
it to the system's fstab file. If parts of the root filesystem are
|
||||
moved into its own partition like it's the case in the above example,
|
||||
this partition will also contain the data that gets installed during
|
||||
the image creation process to that area.
|
||||
|
||||
The following attributes must/can be set to configured a partition entry:
|
||||
|
||||
name="identifier"
|
||||
Mandatory name of the partition as handled by {kiwi}.
|
||||
|
||||
.. note::
|
||||
|
||||
There are the following reserved names which cannot be used
|
||||
because they are already represented by existing attributes:
|
||||
`root`, `readonly`, `boot`, `prep`, `spare`, `swap`, `efi_csm`
|
||||
and `efi`.
|
||||
|
||||
partition_name="name"
|
||||
Optional name of the partition as it appears when listing the
|
||||
table contents with tools like `gdisk`. If no name is set
|
||||
{kiwi} constructs a name of the form `p.lx(identifier_from_name_attr)`
|
||||
|
||||
partition_type="type_identifier"
|
||||
Optional partition type identifier as handled by {kiwi}.
|
||||
Allowed values are `t.linux` and `t.raid`. If not specified
|
||||
`t.linux` is the default.
|
||||
|
||||
size="size_string"
|
||||
Mandatory size of the partition. A size string can end with `M` or
|
||||
`G` to indicate a mega-Byte or giga-Byte value. Without a unit
|
||||
specification mega-Byte is used.
|
||||
|
||||
mountpoint="path"
|
||||
Mandatory mountpoint to mount the partition in the system.
|
||||
|
||||
filesystem="btrfs|ext2|ext3|ext4|squashfs|xfs
|
||||
Mandatory filesystem configuration to create one of the supported
|
||||
filesystems on the partition.
|
||||
|
||||
Despite the customization options of the partition table shown above
|
||||
there are the following limitations:
|
||||
|
||||
1. The root partition is always the last one
|
||||
|
||||
Disk imags build with {kiwi} are designed to be expandable.
|
||||
For this feature to work the partition containing the system
|
||||
rootfs must always be the last one. If this is unwanted for
|
||||
some reason {kiwi} offers an opportunity for one extra/spare
|
||||
partition with the option to be also placed at the end of the
|
||||
table. For details lookup `spare_part` in :ref:`image-description-elements`
|
||||
|
||||
2. There can be no gaps in the partition table
|
||||
|
||||
The way partitions are configured does not allow for gaps in the
|
||||
table. As of today there was no use case were it made sense to
|
||||
leave a gap between table entries. However, leaving some space
|
||||
free at the **end** of the partition geometry is possible in the
|
||||
following ways:
|
||||
|
||||
* **Deploy with unpartitioned free space.**
|
||||
|
||||
To leave space unpartitioned on first boot of a disk image
|
||||
it is possible to configured an `<oem-systemsize>` which is
|
||||
smaller than the disk the image gets deployed to. Details
|
||||
about this setting can be found in :ref:`image-description-elements`
|
||||
|
||||
* **Build with unpartitioned free space.**
|
||||
|
||||
Setting some unpartitioned free space on the disk can be done using
|
||||
the `unpartitioned` attribute of `size` element in type's section. [LINK]
|
||||
the `unpartitioned` attribute of `size` element in type's section.
|
||||
For details see :ref:`disk-the-size-element`
|
||||
|
||||
3. Expand built disks to a new size adding unpartitioned free space at
|
||||
the *end* of the disk.
|
||||
* **Resize built image adding unpartitioned free space.**
|
||||
|
||||
A built image can be resized by using the `kiwi-ng image resize` command
|
||||
and set a new extended size for the disk. See {kiwi} commands docs
|
||||
:ref:`here <db_kiwi_image_resize>`.
|
||||
|
||||
Custom Partitioning at Boot Time
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Adding additional partitions at boot time of {kiwi} images is also possible,
|
||||
however, setting the tools and scripts for doing so needs to be handled by
|
||||
the user. A possible strategy to add partitions on system area are described
|
||||
below.
|
||||
|
||||
The main idea consists on running a first boot service that creates the
|
||||
partitions that are needed. Adding custom services is simple, use the
|
||||
following steps:
|
||||
|
||||
1. Create a unit file for a systemd service:
|
||||
|
||||
.. code:: shell
|
||||
|
||||
[Unit]
|
||||
Description=Add a data partition
|
||||
After=basic.target
|
||||
Wants=basic.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/bash /usr/local/bin/create_part
|
||||
|
||||
|
||||
This systemd unit file will run at boot time once systemd reaches the basic
|
||||
target. At this stage all basic services are up an running (devices mounted,
|
||||
network interfaces up, etc.). In case the service is required to run on
|
||||
earlier stages for some reason, default dependencies need to be disabled,
|
||||
see `systemd man pages <https://www.freedesktop.org/software/systemd/man/systemd.service.html>`_.
|
||||
|
||||
2. Create partitioner shell script matching your specific needs
|
||||
|
||||
Consider the following steps for a partitioner shell script that
|
||||
creates a new partition. Following the above unit file example
|
||||
the `/usr/local/bin/create_part` script should cover the following
|
||||
steps:
|
||||
|
||||
a. Verify partition exists
|
||||
|
||||
Verify the required partition is not mounted neither exists. Exit
|
||||
zero (0) if is already there.
|
||||
|
||||
Use tools such `findmnt` to find the root device and `blkid`
|
||||
or `lsblk` to find a partition with certain label or similar
|
||||
criteria.
|
||||
|
||||
b. Create a new partition
|
||||
|
||||
Create a new partition. On error, exit with non zero.
|
||||
|
||||
Use partitioner tools such as `sgdisk` that can be easily used
|
||||
in non interactive scripts. Using `partprobe` to reload partition
|
||||
table to make OS aware of the changes is handy.
|
||||
|
||||
c. Make filesystem
|
||||
|
||||
Add the desired filesystem to the new partitions. On error, exit
|
||||
with non zero.
|
||||
|
||||
Regular filesystem formatting tools (`mkfs.ext4` just to mention one)
|
||||
can be used to apply the desired filesystem to the just created
|
||||
new partition. At this stage it is handy to add a label to the
|
||||
filesystem for easy recognition on later stages or script reruns.
|
||||
|
||||
d. Update fstab file
|
||||
|
||||
Just echo and append the desired entry in /etc/fstab.
|
||||
|
||||
e. Mount partition
|
||||
|
||||
`mount --all` will try to mount all fstab volumes, it just omits
|
||||
any already mounted device.
|
||||
|
||||
|
||||
3. Add additional files into the root overlay tree.
|
||||
|
||||
The above described unit files and partition creation shell script
|
||||
need to be included into the overlay tree of the image, thus they should
|
||||
be placed into the expected paths in root folder (or in
|
||||
:file:`root.tar.gz` tarball).
|
||||
|
||||
4. Activate the service in :file:`config.sh`
|
||||
|
||||
The service needs to be enabled during image built time to be
|
||||
run during the very first boot. In can be done by adding the following
|
||||
snipped inside the :file:`config.sh`.
|
||||
|
||||
@ -25,6 +25,7 @@ from typing import (
|
||||
import kiwi.defaults as defaults
|
||||
|
||||
from kiwi.utils.temporary import Temporary
|
||||
from kiwi.storage.disk import ptable_entry_type
|
||||
from kiwi.defaults import Defaults
|
||||
from kiwi.filesystem.base import FileSystemBase
|
||||
from kiwi.bootloader.config import BootLoaderConfig
|
||||
@ -98,6 +99,7 @@ class DiskBuilder:
|
||||
self.blocksize = xml_state.build_type.get_target_blocksize()
|
||||
self.volume_manager_name = xml_state.get_volume_management()
|
||||
self.volumes = xml_state.get_volumes()
|
||||
self.custom_partitions = xml_state.get_partitions()
|
||||
self.volume_group_name = xml_state.get_volume_group_name()
|
||||
self.mdraid = xml_state.build_type.get_mdraid()
|
||||
self.hybrid_mbr = xml_state.build_type.get_gpt_hybrid_mbr()
|
||||
@ -208,6 +210,10 @@ class DiskBuilder:
|
||||
# representing the spare_part_mountpoint area of the disk
|
||||
system_spare: Optional[FileSystemBase] = None
|
||||
|
||||
# a list of instances with the sync_data capability
|
||||
# representing the custom partitions area of the disk
|
||||
system_custom_parts: List[FileSystemBase] = []
|
||||
|
||||
if self.install_media and self.build_type_name != 'oem':
|
||||
raise KiwiInstallMediaError(
|
||||
'Install media requires oem type setup, got {0}'.format(
|
||||
@ -316,6 +322,10 @@ class DiskBuilder:
|
||||
# create spare filesystem on spare partition if present
|
||||
system_spare = self._build_spare_filesystem(device_map)
|
||||
|
||||
system_custom_parts = self._build_custom_parts_filesystem(
|
||||
device_map, self.custom_partitions
|
||||
)
|
||||
|
||||
# create filesystems on boot partition(s) if any
|
||||
system_boot, system_efi = self._build_boot_filesystems(device_map)
|
||||
|
||||
@ -439,7 +449,8 @@ class DiskBuilder:
|
||||
|
||||
# syncing system data to disk image
|
||||
self._sync_system_to_image(
|
||||
device_map, system, system_boot, system_efi, system_spare
|
||||
device_map, system, system_boot, system_efi, system_spare,
|
||||
system_custom_parts
|
||||
)
|
||||
|
||||
# run post sync script hook
|
||||
@ -630,12 +641,40 @@ class DiskBuilder:
|
||||
if 'efi' in device_map:
|
||||
exclude_list.append('boot/efi/*')
|
||||
exclude_list.append('boot/efi/.*')
|
||||
if self.custom_partitions:
|
||||
for map_name in sorted(self.custom_partitions.keys()):
|
||||
if map_name in device_map:
|
||||
mountpoint = os.path.normpath(
|
||||
self.custom_partitions[map_name].mountpoint
|
||||
).lstrip(os.sep)
|
||||
exclude_list.append(f'{mountpoint}/*')
|
||||
exclude_list.append(f'{mountpoint}/.*')
|
||||
return exclude_list
|
||||
|
||||
@staticmethod
|
||||
def _get_exclude_list_for_boot_data_sync() -> list:
|
||||
return ['efi/*']
|
||||
|
||||
def _build_custom_parts_filesystem(
|
||||
self, device_map: Dict,
|
||||
custom_partitions: Dict['str', ptable_entry_type]
|
||||
) -> List[FileSystemBase]:
|
||||
filesystem_list = []
|
||||
if custom_partitions:
|
||||
for map_name in sorted(custom_partitions.keys()):
|
||||
if map_name in device_map:
|
||||
ptable_entry = custom_partitions[map_name]
|
||||
filesystem = FileSystem.new(
|
||||
ptable_entry.filesystem,
|
||||
device_map[map_name],
|
||||
f'{self.root_dir}{ptable_entry.mountpoint}/'
|
||||
)
|
||||
filesystem.create_on_device(
|
||||
label=map_name.upper()
|
||||
)
|
||||
filesystem_list.append(filesystem)
|
||||
return filesystem_list
|
||||
|
||||
def _build_spare_filesystem(self, device_map: Dict) -> Optional[FileSystemBase]:
|
||||
if 'spare' in device_map and self.spare_part_fs:
|
||||
spare_part_data_path = None
|
||||
@ -697,7 +736,9 @@ class DiskBuilder:
|
||||
system_boot = filesystem
|
||||
return system_boot, system_efi
|
||||
|
||||
def _build_and_map_disk_partitions(self, disk: Disk, disksize_mbytes: float) -> Dict:
|
||||
def _build_and_map_disk_partitions(
|
||||
self, disk: Disk, disksize_mbytes: float
|
||||
) -> Dict:
|
||||
disk.wipe()
|
||||
disksize_used_mbytes = 0
|
||||
if self.firmware.legacy_bios_mode():
|
||||
@ -740,6 +781,14 @@ class DiskBuilder:
|
||||
)
|
||||
disksize_used_mbytes += self.swap_mbytes
|
||||
|
||||
if self.custom_partitions:
|
||||
log.info(
|
||||
'--> creating custom partition(s): {0}'.format(
|
||||
sorted(self.custom_partitions.keys())
|
||||
)
|
||||
)
|
||||
disk.create_custom_partitions(self.custom_partitions)
|
||||
|
||||
if self.spare_part_mbsize and not self.spare_part_is_last:
|
||||
log.info('--> creating spare partition')
|
||||
disk.create_spare_partition(
|
||||
@ -914,6 +963,13 @@ class DiskBuilder:
|
||||
self._add_fstab_entry(
|
||||
device_map['swap'].get_device(), 'swap'
|
||||
)
|
||||
if self.custom_partitions:
|
||||
for map_name in sorted(self.custom_partitions.keys()):
|
||||
if device_map.get(map_name):
|
||||
self._add_fstab_entry(
|
||||
device_map[map_name].get_device(),
|
||||
self.custom_partitions[map_name].mountpoint
|
||||
)
|
||||
setup.create_fstab(
|
||||
self.fstab
|
||||
)
|
||||
@ -1040,12 +1096,18 @@ class DiskBuilder:
|
||||
self, device_map: Dict, system: Any,
|
||||
system_boot: Optional[FileSystemBase],
|
||||
system_efi: Optional[FileSystemBase],
|
||||
system_spare: Optional[FileSystemBase]
|
||||
system_spare: Optional[FileSystemBase],
|
||||
system_custom_parts: List[FileSystemBase]
|
||||
) -> None:
|
||||
log.info('Syncing system to image')
|
||||
if system_spare:
|
||||
log.info('--> Syncing spare partition data')
|
||||
system_spare.sync_data()
|
||||
|
||||
for system_custom_part in system_custom_parts:
|
||||
log.info('--> Syncing custom partition(s) data')
|
||||
system_custom_part.sync_data()
|
||||
|
||||
if system_efi:
|
||||
log.info('--> Syncing EFI boot data to EFI partition')
|
||||
system_efi.sync_data()
|
||||
|
||||
@ -814,3 +814,10 @@ class KiwiUmountBusyError(KiwiError):
|
||||
"""
|
||||
Exception raised if the attempt to umount a resource has failed
|
||||
"""
|
||||
|
||||
|
||||
class KiwiCustomPartitionConflictError(KiwiError):
|
||||
"""
|
||||
Exception raised if the entry in a custom partition setup
|
||||
conflicts with an existing partition table layout setting
|
||||
"""
|
||||
|
||||
@ -847,38 +847,6 @@ div {
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# common element <partition>
|
||||
#
|
||||
div {
|
||||
k.partition.type.attribute =
|
||||
## Partition Type identifier, see parted for details
|
||||
attribute type { text }
|
||||
k.partition.number.attribute =
|
||||
## Partition ID
|
||||
attribute number { text }
|
||||
k.partition.size.attribute = k.size.attribute
|
||||
k.partition.mountpoint.attribute =
|
||||
## Mount path for this partition
|
||||
attribute mountpoint { text }
|
||||
k.partition.target.attribute =
|
||||
## Is a real target or not which means is part of
|
||||
## the /etc/fstab file or not
|
||||
attribute target { xsd:boolean }
|
||||
k.partition.attlist =
|
||||
k.partition.type.attribute &
|
||||
k.partition.number.attribute &
|
||||
k.partition.size.attribute? &
|
||||
k.partition.mountpoint.attribute? &
|
||||
k.partition.target.attribute?
|
||||
k.partition =
|
||||
## A Partition
|
||||
element partition {
|
||||
k.partition.attlist,
|
||||
empty
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# common element <profile>
|
||||
#
|
||||
@ -1914,6 +1882,7 @@ div {
|
||||
k.oemconfig? &
|
||||
k.size? &
|
||||
k.systemdisk? &
|
||||
k.partitions? &
|
||||
k.vagrantconfig* &
|
||||
k.installmedia?
|
||||
}
|
||||
@ -2087,6 +2056,69 @@ div {
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# common element <partition>
|
||||
#
|
||||
div {
|
||||
sch:pattern [
|
||||
abstract = "true"
|
||||
id = "partition_name_type"
|
||||
sch:rule [
|
||||
context = "partition[@name]"
|
||||
sch:assert [
|
||||
test = "not(contains('$reserved', @name))"
|
||||
"partition(name) is reserved "
|
||||
"Reserved names are '$reserved'"
|
||||
]
|
||||
]
|
||||
]
|
||||
k.partition.name.attribute =
|
||||
## Partition map name. The name of the partition as handled
|
||||
## by KIWI. Note, that there are the following reserved
|
||||
## names which cannot be used because they are already
|
||||
## represented by existing KIWI attributes: root, readonly,
|
||||
## boot, prep, spare, swap, efi_csm and efi. The filesystem
|
||||
## created on the partition will also use this name in
|
||||
## uppercase as its label
|
||||
attribute name { text }
|
||||
>> sch:pattern [ id = "partition_name" is-a = "partition_name_type"
|
||||
sch:param [ name = "reserved" value = "root readonly boot prep spare swap efi_csm efi" ]
|
||||
]
|
||||
k.partition.size.attribute =
|
||||
## Absolute size of the partition.
|
||||
## The value is used as MB by default but you can
|
||||
## add "M" and/or "G" as postfix
|
||||
attribute size { partition-size-type }
|
||||
k.partition.partition_type.attribute =
|
||||
## Partition type name in the context of kiwi
|
||||
## Allowed values are: t.linux
|
||||
attribute partition_type { "t.linux" | "t.raid" }
|
||||
k.partition.partition_name.attribute =
|
||||
## Partition name as it appears in the table
|
||||
attribute partition_name { safe-posix-short-name }
|
||||
k.partition.mountpoint.attribute =
|
||||
## Mountpoint below which this partition should be mounted to
|
||||
attribute mountpoint { text }
|
||||
k.partition.filesystem.attribute =
|
||||
## Filesystem which should be created on the partition
|
||||
attribute filesystem {
|
||||
"btrfs" | "ext2" | "ext3" | "ext4" | "squashfs" | "xfs"
|
||||
}
|
||||
k.partition.attlist =
|
||||
k.partition.name.attribute &
|
||||
k.partition.size.attribute &
|
||||
k.partition.partition_name.attribute? &
|
||||
k.partition.partition_type.attribute? &
|
||||
k.partition.mountpoint.attribute &
|
||||
k.partition.filesystem.attribute
|
||||
k.partition =
|
||||
## Specify custom partition in the partition table
|
||||
element partition {
|
||||
k.partition.attlist,
|
||||
empty
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# common element <volume>
|
||||
#
|
||||
@ -2535,6 +2567,20 @@ div {
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# main block: <partitions>
|
||||
#
|
||||
div {
|
||||
k.partitions.attlist = empty
|
||||
k.partitions =
|
||||
## Partition table entries within the custom area
|
||||
## of the storage device
|
||||
element partitions {
|
||||
k.partitions.attlist &
|
||||
k.partition+
|
||||
}
|
||||
}
|
||||
|
||||
#==========================================
|
||||
# main block: <environment>
|
||||
#
|
||||
|
||||
@ -1300,60 +1300,6 @@ the device is looked up in /dev/disk/by-* and /dev/mapper/*</a:documentation>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
common element <partition>
|
||||
|
||||
-->
|
||||
<div>
|
||||
<define name="k.partition.type.attribute">
|
||||
<attribute name="type">
|
||||
<a:documentation>Partition Type identifier, see parted for details</a:documentation>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.number.attribute">
|
||||
<attribute name="number">
|
||||
<a:documentation>Partition ID</a:documentation>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.size.attribute">
|
||||
<ref name="k.size.attribute"/>
|
||||
</define>
|
||||
<define name="k.partition.mountpoint.attribute">
|
||||
<attribute name="mountpoint">
|
||||
<a:documentation>Mount path for this partition</a:documentation>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.target.attribute">
|
||||
<attribute name="target">
|
||||
<a:documentation>Is a real target or not which means is part of
|
||||
the /etc/fstab file or not</a:documentation>
|
||||
<data type="boolean"/>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.attlist">
|
||||
<interleave>
|
||||
<ref name="k.partition.type.attribute"/>
|
||||
<ref name="k.partition.number.attribute"/>
|
||||
<optional>
|
||||
<ref name="k.partition.size.attribute"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<ref name="k.partition.mountpoint.attribute"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<ref name="k.partition.target.attribute"/>
|
||||
</optional>
|
||||
</interleave>
|
||||
</define>
|
||||
<define name="k.partition">
|
||||
<element name="partition">
|
||||
<a:documentation>A Partition</a:documentation>
|
||||
<ref name="k.partition.attlist"/>
|
||||
<empty/>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
common element <profile>
|
||||
@ -2887,6 +2833,9 @@ kiwi-ng result bundle ...</a:documentation>
|
||||
<optional>
|
||||
<ref name="k.systemdisk"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<ref name="k.partitions"/>
|
||||
</optional>
|
||||
<zeroOrMore>
|
||||
<ref name="k.vagrantconfig"/>
|
||||
</zeroOrMore>
|
||||
@ -3186,6 +3135,95 @@ scsi CD or an ide CD drive</a:documentation>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
common element <partition>
|
||||
|
||||
-->
|
||||
<div>
|
||||
<sch:pattern abstract="true" id="partition_name_type">
|
||||
<sch:rule context="partition[@name]">
|
||||
<sch:assert test="not(contains('$reserved', @name))">partition(name) is reserved Reserved names are '$reserved'</sch:assert>
|
||||
</sch:rule>
|
||||
</sch:pattern>
|
||||
<define name="k.partition.name.attribute">
|
||||
<attribute name="name">
|
||||
<a:documentation>Partition map name. The name of the partition as handled
|
||||
by KIWI. Note, that there are the following reserved
|
||||
names which cannot be used because they are already
|
||||
represented by existing KIWI attributes: root, readonly,
|
||||
boot, prep, spare, swap, efi_csm and efi. The filesystem
|
||||
created on the partition will also use this name in
|
||||
uppercase as its label</a:documentation>
|
||||
</attribute>
|
||||
<sch:pattern id="partition_name" is-a="partition_name_type">
|
||||
<sch:param name="reserved" value="root readonly boot prep spare swap efi_csm efi"/>
|
||||
</sch:pattern>
|
||||
</define>
|
||||
<define name="k.partition.size.attribute">
|
||||
<attribute name="size">
|
||||
<a:documentation>Absolute size of the partition.
|
||||
The value is used as MB by default but you can
|
||||
add "M" and/or "G" as postfix</a:documentation>
|
||||
<ref name="partition-size-type"/>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.partition_type.attribute">
|
||||
<attribute name="partition_type">
|
||||
<a:documentation>Partition type name in the context of kiwi
|
||||
Allowed values are: t.linux</a:documentation>
|
||||
<choice>
|
||||
<value>t.linux</value>
|
||||
<value>t.raid</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.partition_name.attribute">
|
||||
<attribute name="partition_name">
|
||||
<a:documentation>Partition name as it appears in the table</a:documentation>
|
||||
<ref name="safe-posix-short-name"/>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.mountpoint.attribute">
|
||||
<attribute name="mountpoint">
|
||||
<a:documentation>Mountpoint below which this partition should be mounted to</a:documentation>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.filesystem.attribute">
|
||||
<attribute name="filesystem">
|
||||
<a:documentation>Filesystem which should be created on the partition</a:documentation>
|
||||
<choice>
|
||||
<value>btrfs</value>
|
||||
<value>ext2</value>
|
||||
<value>ext3</value>
|
||||
<value>ext4</value>
|
||||
<value>squashfs</value>
|
||||
<value>xfs</value>
|
||||
</choice>
|
||||
</attribute>
|
||||
</define>
|
||||
<define name="k.partition.attlist">
|
||||
<interleave>
|
||||
<ref name="k.partition.name.attribute"/>
|
||||
<ref name="k.partition.size.attribute"/>
|
||||
<optional>
|
||||
<ref name="k.partition.partition_name.attribute"/>
|
||||
</optional>
|
||||
<optional>
|
||||
<ref name="k.partition.partition_type.attribute"/>
|
||||
</optional>
|
||||
<ref name="k.partition.mountpoint.attribute"/>
|
||||
<ref name="k.partition.filesystem.attribute"/>
|
||||
</interleave>
|
||||
</define>
|
||||
<define name="k.partition">
|
||||
<element name="partition">
|
||||
<a:documentation>Specify custom partition in the partition table</a:documentation>
|
||||
<ref name="k.partition.attlist"/>
|
||||
<empty/>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
common element <volume>
|
||||
@ -3828,6 +3866,28 @@ At least one volume must be configured</a:documentation>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
main block: <partitions>
|
||||
|
||||
-->
|
||||
<div>
|
||||
<define name="k.partitions.attlist">
|
||||
<empty/>
|
||||
</define>
|
||||
<define name="k.partitions">
|
||||
<element name="partitions">
|
||||
<a:documentation>Partition table entries within the custom area
|
||||
of the storage device</a:documentation>
|
||||
<interleave>
|
||||
<ref name="k.partitions.attlist"/>
|
||||
<oneOrMore>
|
||||
<ref name="k.partition"/>
|
||||
</oneOrMore>
|
||||
</interleave>
|
||||
</element>
|
||||
</define>
|
||||
</div>
|
||||
<!--
|
||||
==========================================
|
||||
main block: <environment>
|
||||
|
||||
@ -18,7 +18,9 @@
|
||||
import os
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from typing import Dict
|
||||
from typing import (
|
||||
Dict, NamedTuple
|
||||
)
|
||||
|
||||
# project
|
||||
from kiwi.utils.temporary import Temporary
|
||||
@ -26,6 +28,17 @@ from kiwi.command import Command
|
||||
from kiwi.storage.device_provider import DeviceProvider
|
||||
from kiwi.storage.mapped_device import MappedDevice
|
||||
from kiwi.partitioner import Partitioner
|
||||
from kiwi.exceptions import KiwiCustomPartitionConflictError
|
||||
|
||||
ptable_entry_type = NamedTuple(
|
||||
'ptable_entry_type', [
|
||||
('mbsize', int),
|
||||
('partition_name', str),
|
||||
('partition_type', str),
|
||||
('mountpoint', str),
|
||||
('filesystem', str)
|
||||
]
|
||||
)
|
||||
|
||||
log = logging.getLogger('kiwi')
|
||||
|
||||
@ -47,6 +60,21 @@ class Disk(DeviceProvider):
|
||||
# the correct destructor order when the device should be released.
|
||||
self.storage_provider = storage_provider
|
||||
|
||||
# list of protected map ids. If used in a custom partitions
|
||||
# setup this will lead to a raise conditition in order to
|
||||
# avoid conflicts with the existing partition layout and its
|
||||
# customizaton capabilities
|
||||
self.protected_map_ids = [
|
||||
'root',
|
||||
'readonly',
|
||||
'boot',
|
||||
'prep',
|
||||
'spare',
|
||||
'swap',
|
||||
'efi_csm',
|
||||
'efi'
|
||||
]
|
||||
|
||||
self.partition_map: Dict[str, str] = {}
|
||||
self.public_partition_id_map: Dict[str, str] = {}
|
||||
self.partition_id_map: Dict[str, str] = {}
|
||||
@ -88,6 +116,33 @@ class Disk(DeviceProvider):
|
||||
"""
|
||||
return self.storage_provider.is_loop()
|
||||
|
||||
def create_custom_partitions(
|
||||
self, table_entries: Dict[str, ptable_entry_type]
|
||||
) -> None:
|
||||
"""
|
||||
Create partitions from custom data set
|
||||
|
||||
.. code:: python
|
||||
|
||||
table_entries = {
|
||||
map_name: ptable_entry_type
|
||||
}
|
||||
|
||||
:param dict table: partition table spec
|
||||
"""
|
||||
for map_name in table_entries:
|
||||
if map_name in self.protected_map_ids:
|
||||
raise KiwiCustomPartitionConflictError(
|
||||
f'Cannot use reserved table entry name: {map_name!r}'
|
||||
)
|
||||
id_name = f'kiwi_{map_name.title()}Part'
|
||||
entry = table_entries[map_name]
|
||||
self.partitioner.create(
|
||||
entry.partition_name, entry.mbsize, entry.partition_type
|
||||
)
|
||||
self._add_to_map(map_name)
|
||||
self._add_to_public_id_map(id_name)
|
||||
|
||||
def create_root_partition(self, mbsize: str):
|
||||
"""
|
||||
Create root partition
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#
|
||||
# Generated by generateDS.py version 2.29.24.
|
||||
# Python 3.6.12 (default, Dec 02 2020, 09:44:23) [GCC]
|
||||
# Python 3.6.13 (default, Mar 10 2021, 18:30:35) [GCC]
|
||||
#
|
||||
# Command line options:
|
||||
# ('-f', '')
|
||||
@ -1757,130 +1757,6 @@ class package(GeneratedsSuper):
|
||||
# end class package
|
||||
|
||||
|
||||
class partition(GeneratedsSuper):
|
||||
"""A Partition"""
|
||||
subclass = None
|
||||
superclass = None
|
||||
def __init__(self, type_=None, number=None, size=None, mountpoint=None, target=None):
|
||||
self.original_tagname_ = None
|
||||
self.type_ = _cast(None, type_)
|
||||
self.number = _cast(None, number)
|
||||
self.size = _cast(None, size)
|
||||
self.mountpoint = _cast(None, mountpoint)
|
||||
self.target = _cast(bool, target)
|
||||
def factory(*args_, **kwargs_):
|
||||
if CurrentSubclassModule_ is not None:
|
||||
subclass = getSubclassFromModule_(
|
||||
CurrentSubclassModule_, partition)
|
||||
if subclass is not None:
|
||||
return subclass(*args_, **kwargs_)
|
||||
if partition.subclass:
|
||||
return partition.subclass(*args_, **kwargs_)
|
||||
else:
|
||||
return partition(*args_, **kwargs_)
|
||||
factory = staticmethod(factory)
|
||||
def get_type(self): return self.type_
|
||||
def set_type(self, type_): self.type_ = type_
|
||||
def get_number(self): return self.number
|
||||
def set_number(self, number): self.number = number
|
||||
def get_size(self): return self.size
|
||||
def set_size(self, size): self.size = size
|
||||
def get_mountpoint(self): return self.mountpoint
|
||||
def set_mountpoint(self, mountpoint): self.mountpoint = mountpoint
|
||||
def get_target(self): return self.target
|
||||
def set_target(self, target): self.target = target
|
||||
def validate_size_type(self, value):
|
||||
# Validate type size-type, a restriction on xs:token.
|
||||
if value is not None and Validate_simpletypes_:
|
||||
if not self.gds_validate_simple_patterns(
|
||||
self.validate_size_type_patterns_, value):
|
||||
warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_size_type_patterns_, ))
|
||||
validate_size_type_patterns_ = [['^\\d*|image$']]
|
||||
def hasContent_(self):
|
||||
if (
|
||||
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
def export(self, outfile, level, namespaceprefix_='', name_='partition', namespacedef_='', pretty_print=True):
|
||||
imported_ns_def_ = GenerateDSNamespaceDefs_.get('partition')
|
||||
if imported_ns_def_ is not None:
|
||||
namespacedef_ = imported_ns_def_
|
||||
if pretty_print:
|
||||
eol_ = '\n'
|
||||
else:
|
||||
eol_ = ''
|
||||
if self.original_tagname_ is not None:
|
||||
name_ = self.original_tagname_
|
||||
showIndent(outfile, level, pretty_print)
|
||||
outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
|
||||
already_processed = set()
|
||||
self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='partition')
|
||||
if self.hasContent_():
|
||||
outfile.write('>%s' % (eol_, ))
|
||||
self.exportChildren(outfile, level + 1, namespaceprefix_='', name_='partition', pretty_print=pretty_print)
|
||||
outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
|
||||
else:
|
||||
outfile.write('/>%s' % (eol_, ))
|
||||
def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='partition'):
|
||||
if self.type_ is not None and 'type_' not in already_processed:
|
||||
already_processed.add('type_')
|
||||
outfile.write(' type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.type_), input_name='type')), ))
|
||||
if self.number is not None and 'number' not in already_processed:
|
||||
already_processed.add('number')
|
||||
outfile.write(' number=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.number), input_name='number')), ))
|
||||
if self.size is not None and 'size' not in already_processed:
|
||||
already_processed.add('size')
|
||||
outfile.write(' size=%s' % (quote_attrib(self.size), ))
|
||||
if self.mountpoint is not None and 'mountpoint' not in already_processed:
|
||||
already_processed.add('mountpoint')
|
||||
outfile.write(' mountpoint=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.mountpoint), input_name='mountpoint')), ))
|
||||
if self.target is not None and 'target' not in already_processed:
|
||||
already_processed.add('target')
|
||||
outfile.write(' target="%s"' % self.gds_format_boolean(self.target, input_name='target'))
|
||||
def exportChildren(self, outfile, level, namespaceprefix_='', name_='partition', fromsubclass_=False, pretty_print=True):
|
||||
pass
|
||||
def build(self, node):
|
||||
already_processed = set()
|
||||
self.buildAttributes(node, node.attrib, already_processed)
|
||||
for child in node:
|
||||
nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
|
||||
self.buildChildren(child, node, nodeName_)
|
||||
return self
|
||||
def buildAttributes(self, node, attrs, already_processed):
|
||||
value = find_attr_value_('type', node)
|
||||
if value is not None and 'type' not in already_processed:
|
||||
already_processed.add('type')
|
||||
self.type_ = value
|
||||
value = find_attr_value_('number', node)
|
||||
if value is not None and 'number' not in already_processed:
|
||||
already_processed.add('number')
|
||||
self.number = value
|
||||
value = find_attr_value_('size', node)
|
||||
if value is not None and 'size' not in already_processed:
|
||||
already_processed.add('size')
|
||||
self.size = value
|
||||
self.size = ' '.join(self.size.split())
|
||||
self.validate_size_type(self.size) # validate type size-type
|
||||
value = find_attr_value_('mountpoint', node)
|
||||
if value is not None and 'mountpoint' not in already_processed:
|
||||
already_processed.add('mountpoint')
|
||||
self.mountpoint = value
|
||||
value = find_attr_value_('target', node)
|
||||
if value is not None and 'target' not in already_processed:
|
||||
already_processed.add('target')
|
||||
if value in ('true', '1'):
|
||||
self.target = True
|
||||
elif value in ('false', '0'):
|
||||
self.target = False
|
||||
else:
|
||||
raise_parse_error(node, 'Bad boolean attribute')
|
||||
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
|
||||
pass
|
||||
# end class partition
|
||||
|
||||
|
||||
class profile(GeneratedsSuper):
|
||||
"""Profiles creates a namespace on an image description and thus can be
|
||||
used to have one description with different profiles for example
|
||||
@ -2619,7 +2495,7 @@ class type_(GeneratedsSuper):
|
||||
"""The Image Type of the Logical Extend"""
|
||||
subclass = None
|
||||
superclass = None
|
||||
def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efiparttable=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luksOS=None, mdraid=None, overlayroot=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, vga=None, vhdfixedtag=None, volid=None, wwid_wait_timeout=None, derived_from=None, xen_server=None, publisher=None, disk_start_sector=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, vagrantconfig=None, installmedia=None):
|
||||
def __init__(self, boot=None, bootfilesystem=None, firmware=None, bootkernel=None, bootpartition=None, bootpartsize=None, efipartsize=None, efiparttable=None, bootprofile=None, btrfs_quota_groups=None, btrfs_root_is_snapshot=None, btrfs_root_is_readonly_snapshot=None, compressed=None, devicepersistency=None, editbootconfig=None, editbootinstall=None, filesystem=None, flags=None, format=None, formatoptions=None, fsmountoptions=None, fscreateoptions=None, squashfscompression=None, gcelicense=None, hybridpersistent=None, hybridpersistent_filesystem=None, gpt_hybrid_mbr=None, force_mbr=None, initrd_system=None, image=None, metadata_path=None, installboot=None, install_continue_on_timeout=None, installprovidefailsafe=None, installiso=None, installstick=None, installpxe=None, mediacheck=None, kernelcmdline=None, luks=None, luksOS=None, mdraid=None, overlayroot=None, primary=None, ramonly=None, rootfs_label=None, spare_part=None, spare_part_mountpoint=None, spare_part_fs=None, spare_part_fs_attributes=None, spare_part_is_last=None, target_blocksize=None, target_removable=None, vga=None, vhdfixedtag=None, volid=None, wwid_wait_timeout=None, derived_from=None, xen_server=None, publisher=None, disk_start_sector=None, bundle_format=None, bootloader=None, containerconfig=None, machine=None, oemconfig=None, size=None, systemdisk=None, partitions=None, vagrantconfig=None, installmedia=None):
|
||||
self.original_tagname_ = None
|
||||
self.boot = _cast(None, boot)
|
||||
self.bootfilesystem = _cast(None, bootfilesystem)
|
||||
@ -2707,6 +2583,10 @@ class type_(GeneratedsSuper):
|
||||
self.systemdisk = []
|
||||
else:
|
||||
self.systemdisk = systemdisk
|
||||
if partitions is None:
|
||||
self.partitions = []
|
||||
else:
|
||||
self.partitions = partitions
|
||||
if vagrantconfig is None:
|
||||
self.vagrantconfig = []
|
||||
else:
|
||||
@ -2756,6 +2636,11 @@ class type_(GeneratedsSuper):
|
||||
def add_systemdisk(self, value): self.systemdisk.append(value)
|
||||
def insert_systemdisk_at(self, index, value): self.systemdisk.insert(index, value)
|
||||
def replace_systemdisk_at(self, index, value): self.systemdisk[index] = value
|
||||
def get_partitions(self): return self.partitions
|
||||
def set_partitions(self, partitions): self.partitions = partitions
|
||||
def add_partitions(self, value): self.partitions.append(value)
|
||||
def insert_partitions_at(self, index, value): self.partitions.insert(index, value)
|
||||
def replace_partitions_at(self, index, value): self.partitions[index] = value
|
||||
def get_vagrantconfig(self): return self.vagrantconfig
|
||||
def set_vagrantconfig(self, vagrantconfig): self.vagrantconfig = vagrantconfig
|
||||
def add_vagrantconfig(self, value): self.vagrantconfig.append(value)
|
||||
@ -2926,6 +2811,7 @@ class type_(GeneratedsSuper):
|
||||
self.oemconfig or
|
||||
self.size or
|
||||
self.systemdisk or
|
||||
self.partitions or
|
||||
self.vagrantconfig or
|
||||
self.installmedia
|
||||
):
|
||||
@ -3157,6 +3043,8 @@ class type_(GeneratedsSuper):
|
||||
size_.export(outfile, level, namespaceprefix_, name_='size', pretty_print=pretty_print)
|
||||
for systemdisk_ in self.systemdisk:
|
||||
systemdisk_.export(outfile, level, namespaceprefix_, name_='systemdisk', pretty_print=pretty_print)
|
||||
for partitions_ in self.partitions:
|
||||
partitions_.export(outfile, level, namespaceprefix_, name_='partitions', pretty_print=pretty_print)
|
||||
for vagrantconfig_ in self.vagrantconfig:
|
||||
vagrantconfig_.export(outfile, level, namespaceprefix_, name_='vagrantconfig', pretty_print=pretty_print)
|
||||
for installmedia_ in self.installmedia:
|
||||
@ -3594,6 +3482,11 @@ class type_(GeneratedsSuper):
|
||||
obj_.build(child_)
|
||||
self.systemdisk.append(obj_)
|
||||
obj_.original_tagname_ = 'systemdisk'
|
||||
elif nodeName_ == 'partitions':
|
||||
obj_ = partitions.factory()
|
||||
obj_.build(child_)
|
||||
self.partitions.append(obj_)
|
||||
obj_.original_tagname_ = 'partitions'
|
||||
elif nodeName_ == 'vagrantconfig':
|
||||
obj_ = vagrantconfig.factory()
|
||||
obj_.build(child_)
|
||||
@ -4075,6 +3968,146 @@ class vmnic(GeneratedsSuper):
|
||||
# end class vmnic
|
||||
|
||||
|
||||
class partition(GeneratedsSuper):
|
||||
"""Specify custom partition in the partition table"""
|
||||
subclass = None
|
||||
superclass = None
|
||||
def __init__(self, name=None, size=None, partition_name=None, partition_type=None, mountpoint=None, filesystem=None):
|
||||
self.original_tagname_ = None
|
||||
self.name = _cast(None, name)
|
||||
self.size = _cast(None, size)
|
||||
self.partition_name = _cast(None, partition_name)
|
||||
self.partition_type = _cast(None, partition_type)
|
||||
self.mountpoint = _cast(None, mountpoint)
|
||||
self.filesystem = _cast(None, filesystem)
|
||||
def factory(*args_, **kwargs_):
|
||||
if CurrentSubclassModule_ is not None:
|
||||
subclass = getSubclassFromModule_(
|
||||
CurrentSubclassModule_, partition)
|
||||
if subclass is not None:
|
||||
return subclass(*args_, **kwargs_)
|
||||
if partition.subclass:
|
||||
return partition.subclass(*args_, **kwargs_)
|
||||
else:
|
||||
return partition(*args_, **kwargs_)
|
||||
factory = staticmethod(factory)
|
||||
def get_name(self): return self.name
|
||||
def set_name(self, name): self.name = name
|
||||
def get_size(self): return self.size
|
||||
def set_size(self, size): self.size = size
|
||||
def get_partition_name(self): return self.partition_name
|
||||
def set_partition_name(self, partition_name): self.partition_name = partition_name
|
||||
def get_partition_type(self): return self.partition_type
|
||||
def set_partition_type(self, partition_type): self.partition_type = partition_type
|
||||
def get_mountpoint(self): return self.mountpoint
|
||||
def set_mountpoint(self, mountpoint): self.mountpoint = mountpoint
|
||||
def get_filesystem(self): return self.filesystem
|
||||
def set_filesystem(self, filesystem): self.filesystem = filesystem
|
||||
def validate_partition_size_type(self, value):
|
||||
# Validate type partition-size-type, a restriction on xs:token.
|
||||
if value is not None and Validate_simpletypes_:
|
||||
if not self.gds_validate_simple_patterns(
|
||||
self.validate_partition_size_type_patterns_, value):
|
||||
warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_partition_size_type_patterns_, ))
|
||||
validate_partition_size_type_patterns_ = [['^(\\d+|\\d+M|\\d+G)$']]
|
||||
def validate_safe_posix_short_name(self, value):
|
||||
# Validate type safe-posix-short-name, a restriction on xs:token.
|
||||
if value is not None and Validate_simpletypes_:
|
||||
if not self.gds_validate_simple_patterns(
|
||||
self.validate_safe_posix_short_name_patterns_, value):
|
||||
warnings_.warn('Value "%s" does not match xsd pattern restrictions: %s' % (value.encode('utf-8'), self.validate_safe_posix_short_name_patterns_, ))
|
||||
validate_safe_posix_short_name_patterns_ = [['^[a-zA-Z0-9_\\-\\.]{1,32}$']]
|
||||
def hasContent_(self):
|
||||
if (
|
||||
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
def export(self, outfile, level, namespaceprefix_='', name_='partition', namespacedef_='', pretty_print=True):
|
||||
imported_ns_def_ = GenerateDSNamespaceDefs_.get('partition')
|
||||
if imported_ns_def_ is not None:
|
||||
namespacedef_ = imported_ns_def_
|
||||
if pretty_print:
|
||||
eol_ = '\n'
|
||||
else:
|
||||
eol_ = ''
|
||||
if self.original_tagname_ is not None:
|
||||
name_ = self.original_tagname_
|
||||
showIndent(outfile, level, pretty_print)
|
||||
outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
|
||||
already_processed = set()
|
||||
self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='partition')
|
||||
if self.hasContent_():
|
||||
outfile.write('>%s' % (eol_, ))
|
||||
self.exportChildren(outfile, level + 1, namespaceprefix_='', name_='partition', pretty_print=pretty_print)
|
||||
outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
|
||||
else:
|
||||
outfile.write('/>%s' % (eol_, ))
|
||||
def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='partition'):
|
||||
if self.name is not None and 'name' not in already_processed:
|
||||
already_processed.add('name')
|
||||
outfile.write(' name=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.name), input_name='name')), ))
|
||||
if self.size is not None and 'size' not in already_processed:
|
||||
already_processed.add('size')
|
||||
outfile.write(' size=%s' % (quote_attrib(self.size), ))
|
||||
if self.partition_name is not None and 'partition_name' not in already_processed:
|
||||
already_processed.add('partition_name')
|
||||
outfile.write(' partition_name=%s' % (quote_attrib(self.partition_name), ))
|
||||
if self.partition_type is not None and 'partition_type' not in already_processed:
|
||||
already_processed.add('partition_type')
|
||||
outfile.write(' partition_type=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.partition_type), input_name='partition_type')), ))
|
||||
if self.mountpoint is not None and 'mountpoint' not in already_processed:
|
||||
already_processed.add('mountpoint')
|
||||
outfile.write(' mountpoint=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.mountpoint), input_name='mountpoint')), ))
|
||||
if self.filesystem is not None and 'filesystem' not in already_processed:
|
||||
already_processed.add('filesystem')
|
||||
outfile.write(' filesystem=%s' % (self.gds_encode(self.gds_format_string(quote_attrib(self.filesystem), input_name='filesystem')), ))
|
||||
def exportChildren(self, outfile, level, namespaceprefix_='', name_='partition', fromsubclass_=False, pretty_print=True):
|
||||
pass
|
||||
def build(self, node):
|
||||
already_processed = set()
|
||||
self.buildAttributes(node, node.attrib, already_processed)
|
||||
for child in node:
|
||||
nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
|
||||
self.buildChildren(child, node, nodeName_)
|
||||
return self
|
||||
def buildAttributes(self, node, attrs, already_processed):
|
||||
value = find_attr_value_('name', node)
|
||||
if value is not None and 'name' not in already_processed:
|
||||
already_processed.add('name')
|
||||
self.name = value
|
||||
value = find_attr_value_('size', node)
|
||||
if value is not None and 'size' not in already_processed:
|
||||
already_processed.add('size')
|
||||
self.size = value
|
||||
self.size = ' '.join(self.size.split())
|
||||
self.validate_partition_size_type(self.size) # validate type partition-size-type
|
||||
value = find_attr_value_('partition_name', node)
|
||||
if value is not None and 'partition_name' not in already_processed:
|
||||
already_processed.add('partition_name')
|
||||
self.partition_name = value
|
||||
self.partition_name = ' '.join(self.partition_name.split())
|
||||
self.validate_safe_posix_short_name(self.partition_name) # validate type safe-posix-short-name
|
||||
value = find_attr_value_('partition_type', node)
|
||||
if value is not None and 'partition_type' not in already_processed:
|
||||
already_processed.add('partition_type')
|
||||
self.partition_type = value
|
||||
self.partition_type = ' '.join(self.partition_type.split())
|
||||
value = find_attr_value_('mountpoint', node)
|
||||
if value is not None and 'mountpoint' not in already_processed:
|
||||
already_processed.add('mountpoint')
|
||||
self.mountpoint = value
|
||||
value = find_attr_value_('filesystem', node)
|
||||
if value is not None and 'filesystem' not in already_processed:
|
||||
already_processed.add('filesystem')
|
||||
self.filesystem = value
|
||||
self.filesystem = ' '.join(self.filesystem.split())
|
||||
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
|
||||
pass
|
||||
# end class partition
|
||||
|
||||
|
||||
class volume(GeneratedsSuper):
|
||||
"""Specify which parts of the filesystem should be on an extra volume."""
|
||||
subclass = None
|
||||
@ -5536,6 +5569,87 @@ class volumes(GeneratedsSuper):
|
||||
# end class volumes
|
||||
|
||||
|
||||
class partitions(GeneratedsSuper):
|
||||
"""Partition table entries within the custom area of the storage device"""
|
||||
subclass = None
|
||||
superclass = None
|
||||
def __init__(self, partition=None):
|
||||
self.original_tagname_ = None
|
||||
if partition is None:
|
||||
self.partition = []
|
||||
else:
|
||||
self.partition = partition
|
||||
def factory(*args_, **kwargs_):
|
||||
if CurrentSubclassModule_ is not None:
|
||||
subclass = getSubclassFromModule_(
|
||||
CurrentSubclassModule_, partitions)
|
||||
if subclass is not None:
|
||||
return subclass(*args_, **kwargs_)
|
||||
if partitions.subclass:
|
||||
return partitions.subclass(*args_, **kwargs_)
|
||||
else:
|
||||
return partitions(*args_, **kwargs_)
|
||||
factory = staticmethod(factory)
|
||||
def get_partition(self): return self.partition
|
||||
def set_partition(self, partition): self.partition = partition
|
||||
def add_partition(self, value): self.partition.append(value)
|
||||
def insert_partition_at(self, index, value): self.partition.insert(index, value)
|
||||
def replace_partition_at(self, index, value): self.partition[index] = value
|
||||
def hasContent_(self):
|
||||
if (
|
||||
self.partition
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
def export(self, outfile, level, namespaceprefix_='', name_='partitions', namespacedef_='', pretty_print=True):
|
||||
imported_ns_def_ = GenerateDSNamespaceDefs_.get('partitions')
|
||||
if imported_ns_def_ is not None:
|
||||
namespacedef_ = imported_ns_def_
|
||||
if pretty_print:
|
||||
eol_ = '\n'
|
||||
else:
|
||||
eol_ = ''
|
||||
if self.original_tagname_ is not None:
|
||||
name_ = self.original_tagname_
|
||||
showIndent(outfile, level, pretty_print)
|
||||
outfile.write('<%s%s%s' % (namespaceprefix_, name_, namespacedef_ and ' ' + namespacedef_ or '', ))
|
||||
already_processed = set()
|
||||
self.exportAttributes(outfile, level, already_processed, namespaceprefix_, name_='partitions')
|
||||
if self.hasContent_():
|
||||
outfile.write('>%s' % (eol_, ))
|
||||
self.exportChildren(outfile, level + 1, namespaceprefix_='', name_='partitions', pretty_print=pretty_print)
|
||||
showIndent(outfile, level, pretty_print)
|
||||
outfile.write('</%s%s>%s' % (namespaceprefix_, name_, eol_))
|
||||
else:
|
||||
outfile.write('/>%s' % (eol_, ))
|
||||
def exportAttributes(self, outfile, level, already_processed, namespaceprefix_='', name_='partitions'):
|
||||
pass
|
||||
def exportChildren(self, outfile, level, namespaceprefix_='', name_='partitions', fromsubclass_=False, pretty_print=True):
|
||||
if pretty_print:
|
||||
eol_ = '\n'
|
||||
else:
|
||||
eol_ = ''
|
||||
for partition_ in self.partition:
|
||||
partition_.export(outfile, level, namespaceprefix_, name_='partition', pretty_print=pretty_print)
|
||||
def build(self, node):
|
||||
already_processed = set()
|
||||
self.buildAttributes(node, node.attrib, already_processed)
|
||||
for child in node:
|
||||
nodeName_ = Tag_pattern_.match(child.tag).groups()[-1]
|
||||
self.buildChildren(child, node, nodeName_)
|
||||
return self
|
||||
def buildAttributes(self, node, attrs, already_processed):
|
||||
pass
|
||||
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
|
||||
if nodeName_ == 'partition':
|
||||
obj_ = partition.factory()
|
||||
obj_.build(child_)
|
||||
self.partition.append(obj_)
|
||||
obj_.original_tagname_ = 'partition'
|
||||
# end class partitions
|
||||
|
||||
|
||||
class environment(GeneratedsSuper):
|
||||
"""Provides details about the container environment variables At least
|
||||
one environment variable must be configured"""
|
||||
@ -8144,6 +8258,7 @@ __all__ = [
|
||||
"package",
|
||||
"packages",
|
||||
"partition",
|
||||
"partitions",
|
||||
"port",
|
||||
"preferences",
|
||||
"product",
|
||||
|
||||
@ -27,6 +27,7 @@ from textwrap import dedent
|
||||
import kiwi.defaults as defaults
|
||||
|
||||
from kiwi import xml_parse
|
||||
from kiwi.storage.disk import ptable_entry_type
|
||||
from kiwi.system.uri import Uri
|
||||
from kiwi.defaults import Defaults
|
||||
from kiwi.utils.size import StringToSize
|
||||
@ -727,6 +728,19 @@ class XMLState:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_build_type_partitions_section(self) -> Any:
|
||||
"""
|
||||
First partitions section from the build type section
|
||||
|
||||
:return: <partitions> section reference
|
||||
|
||||
:rtype: xml_parse::partitions
|
||||
"""
|
||||
partitions_sections = self.build_type.get_partitions()
|
||||
if partitions_sections:
|
||||
return partitions_sections[0]
|
||||
return None
|
||||
|
||||
def get_build_type_system_disk_section(self) -> Any:
|
||||
"""
|
||||
First system disk section from the build type section
|
||||
@ -1332,6 +1346,47 @@ class XMLState:
|
||||
|
||||
container_config_section.set_labels(labels)
|
||||
|
||||
def get_partitions(self) -> Dict[str, ptable_entry_type]:
|
||||
"""
|
||||
Dictionary of configured partitions.
|
||||
|
||||
Each entry in the dict references a ptable_entry_type
|
||||
Each key in the dict references the name of the
|
||||
partition entry as handled by KIWI
|
||||
|
||||
:return:
|
||||
Contains dict of ptable_entry_type tuples
|
||||
|
||||
.. code:: python
|
||||
|
||||
{
|
||||
'NAME': ptable_entry_type(
|
||||
mbsize=int,
|
||||
partition_name=str,
|
||||
partition_type=str,
|
||||
mountpoint=str,
|
||||
filesystem=str
|
||||
)
|
||||
}
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
partitions: Dict[str, ptable_entry_type] = {}
|
||||
partitions_section = self.get_build_type_partitions_section()
|
||||
if not partitions_section:
|
||||
return partitions
|
||||
for partition in partitions_section.get_partition():
|
||||
name = partition.get_name()
|
||||
partition_name = partition.get_partition_name() or f'p.lx{name}'
|
||||
partitions[name] = ptable_entry_type(
|
||||
mbsize=self._to_mega_byte(partition.get_size()),
|
||||
partition_name=partition_name,
|
||||
partition_type=partition.get_partition_type() or 't.linux',
|
||||
mountpoint=partition.get_mountpoint(),
|
||||
filesystem=partition.get_filesystem()
|
||||
)
|
||||
return partitions
|
||||
|
||||
def get_volumes(self) -> List[volume_type]:
|
||||
"""
|
||||
List of configured systemdisk volumes.
|
||||
|
||||
@ -17,7 +17,7 @@ exclude=xml_parse.py
|
||||
# we ignore warnings about quoting of escape sequences (W605)
|
||||
ignore = E501, W605
|
||||
# we allow a custom complexity level
|
||||
max-complexity = 18
|
||||
max-complexity = 19
|
||||
|
||||
[doc8]
|
||||
max-line-length = 90
|
||||
|
||||
31
test/data/example_partitions_config.xml
Normal file
31
test/data/example_partitions_config.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<image schemaversion="7.4" name="TestPartitions">
|
||||
<description type="system">
|
||||
<author>Marcus Schäfer</author>
|
||||
<contact>ms@suse.com</contact>
|
||||
<specification>
|
||||
test partitions setup
|
||||
</specification>
|
||||
</description>
|
||||
<preferences>
|
||||
<version>1.1.1</version>
|
||||
<packagemanager>zypper</packagemanager>
|
||||
<type image="oem" filesystem="ext3" installiso="true" kernelcmdline="splash" firmware="efi">
|
||||
<partitions>
|
||||
<partition name="var" size="100" mountpoint="/var" filesystem="ext3"/>
|
||||
</partitions>
|
||||
</type>
|
||||
</preferences>
|
||||
<repository>
|
||||
<source path="obs://13.2/repo/oss"/>
|
||||
</repository>
|
||||
<packages type="image">
|
||||
<package name="patterns-openSUSE-base"/>
|
||||
</packages>
|
||||
<packages type="bootstrap">
|
||||
<package name="udev"/>
|
||||
<package name="filesystem"/>
|
||||
<package name="glibc-locale"/>
|
||||
</packages>
|
||||
</image>
|
||||
@ -18,6 +18,7 @@ from kiwi.defaults import Defaults
|
||||
from kiwi.xml_description import XMLDescription
|
||||
from kiwi.xml_state import XMLState
|
||||
from kiwi.builder.disk import DiskBuilder
|
||||
from kiwi.storage.disk import ptable_entry_type
|
||||
from kiwi.storage.mapped_device import MappedDevice
|
||||
|
||||
from kiwi.exceptions import (
|
||||
@ -55,7 +56,8 @@ class TestDiskBuilder:
|
||||
'boot': MappedDevice('/dev/boot-device', Mock()),
|
||||
'prep': MappedDevice('/dev/prep-device', Mock()),
|
||||
'efi': MappedDevice('/dev/efi-device', Mock()),
|
||||
'spare': MappedDevice('/dev/spare-device', Mock())
|
||||
'spare': MappedDevice('/dev/spare-device', Mock()),
|
||||
'var': MappedDevice('/dev/spare-device', Mock())
|
||||
}
|
||||
self.id_map = {
|
||||
'kiwi_RootPart': 1,
|
||||
@ -739,6 +741,38 @@ class TestDiskBuilder:
|
||||
config_file='root_dir/etc/dracut.conf.d/99-luks-boot.conf'
|
||||
)
|
||||
|
||||
@patch('kiwi.builder.disk.FileSystem.new')
|
||||
@patch('kiwi.builder.disk.Command.run')
|
||||
@patch('kiwi.builder.disk.Defaults.get_grub_boot_directory_name')
|
||||
@patch('os.path.exists')
|
||||
def test_create_disk_uses_custom_partitions(
|
||||
self, mock_exists, mock_grub_dir, mock_command, mock_fs
|
||||
):
|
||||
mock_exists.return_value = True
|
||||
self.disk_builder.custom_partitions = {
|
||||
'var': ptable_entry_type(
|
||||
mbsize=100,
|
||||
partition_name='p.lxvar',
|
||||
partition_type='t.linux',
|
||||
mountpoint='/var',
|
||||
filesystem='ext3'
|
||||
)
|
||||
}
|
||||
self.disk_builder.volume_manager_name = None
|
||||
filesystem = Mock()
|
||||
mock_fs.return_value = filesystem
|
||||
|
||||
with patch('builtins.open'):
|
||||
self.disk_builder.create_disk()
|
||||
|
||||
self.disk.create_custom_partitions.assert_called_once_with(
|
||||
self.disk_builder.custom_partitions
|
||||
)
|
||||
|
||||
assert [
|
||||
call('UUID=blkid_result /var blkid_result_fs defaults 0 0')
|
||||
] in self.disk_builder.fstab.add_entry.call_args_list
|
||||
|
||||
@patch('kiwi.builder.disk.FileSystem.new')
|
||||
@patch('kiwi.builder.disk.VolumeManager.new')
|
||||
@patch('kiwi.builder.disk.Command.run')
|
||||
|
||||
@ -2,11 +2,15 @@ import logging
|
||||
from mock import (
|
||||
patch, mock_open
|
||||
)
|
||||
from pytest import fixture
|
||||
from pytest import (
|
||||
fixture, raises
|
||||
)
|
||||
|
||||
import mock
|
||||
|
||||
from kiwi.storage.disk import ptable_entry_type
|
||||
from kiwi.storage.disk import Disk
|
||||
from kiwi.exceptions import KiwiCustomPartitionConflictError
|
||||
|
||||
|
||||
class TestDisk:
|
||||
@ -132,6 +136,36 @@ class TestDisk:
|
||||
)
|
||||
assert self.disk.public_partition_id_map['kiwi_PrepPart'] == 1
|
||||
|
||||
@patch('kiwi.storage.disk.Command.run')
|
||||
def test_create_custom_partitions(self, mock_command):
|
||||
table_entries = {
|
||||
'var': ptable_entry_type(
|
||||
mbsize=100,
|
||||
partition_name='p.lxvar',
|
||||
partition_type='t.linux',
|
||||
mountpoint='/var',
|
||||
filesystem='ext3'
|
||||
)
|
||||
}
|
||||
self.disk.create_custom_partitions(table_entries)
|
||||
self.partitioner.create.assert_called_once_with(
|
||||
'p.lxvar', 100, 't.linux'
|
||||
)
|
||||
assert self.disk.public_partition_id_map['kiwi_VarPart'] == 1
|
||||
|
||||
def test_create_custom_partitions_reserved_name(self):
|
||||
table_entries = {
|
||||
'root': ptable_entry_type(
|
||||
mbsize=100,
|
||||
partition_name='p.lxroot',
|
||||
partition_type='t.linux',
|
||||
mountpoint='/',
|
||||
filesystem='ext3'
|
||||
)
|
||||
}
|
||||
with raises(KiwiCustomPartitionConflictError):
|
||||
self.disk.create_custom_partitions(table_entries)
|
||||
|
||||
@patch('kiwi.storage.disk.Command.run')
|
||||
def test_device_map_efi_partition(self, mock_command):
|
||||
self.disk.create_efi_partition(100)
|
||||
|
||||
@ -9,6 +9,7 @@ from pytest import (
|
||||
|
||||
from kiwi.defaults import Defaults
|
||||
from kiwi.xml_state import XMLState
|
||||
from kiwi.storage.disk import ptable_entry_type
|
||||
from kiwi.xml_description import XMLDescription
|
||||
|
||||
from kiwi.exceptions import (
|
||||
@ -311,6 +312,22 @@ class TestXMLState:
|
||||
'composedProfile', 'vmxSimpleFlavour', 'xenDomUFlavour'
|
||||
]
|
||||
|
||||
def test_get_partitions(self):
|
||||
description = XMLDescription(
|
||||
'../data/example_partitions_config.xml'
|
||||
)
|
||||
xml_data = description.load()
|
||||
state = XMLState(xml_data)
|
||||
assert state.get_partitions() == {
|
||||
'var': ptable_entry_type(
|
||||
mbsize=100,
|
||||
partition_name='p.lxvar',
|
||||
partition_type='t.linux',
|
||||
mountpoint='/var',
|
||||
filesystem='ext3'
|
||||
)
|
||||
}
|
||||
|
||||
def test_get_volumes_custom_root_volume_name(self):
|
||||
description = XMLDescription(
|
||||
'../data/example_lvm_custom_rootvol_config.xml'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user