Skip to content

Commit

Permalink
Add new containers section
Browse files Browse the repository at this point in the history
Allow to specify references to OCI containers in the
image description like in the following example:

<containers source="registry.suse.com" backend="podman">
    <container name="some" tag="some" path="/some/path"/>
</containers>

During the kiwi process the containers are fetched into a
temporary location and a systemd service is configured to
one time load the containers into the local registry at
first boot of the system. This Fixes #2663
  • Loading branch information
schaefi committed Oct 19, 2024
1 parent eca8201 commit a349c05
Show file tree
Hide file tree
Showing 15 changed files with 816 additions and 2 deletions.
70 changes: 70 additions & 0 deletions doc/source/image_description/elements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,76 @@ Used to customize the installation media images created for oem images
deployment.
For details see: :ref:`installmedia_customize`

.. _sec.registry:

<containers>
------------

Setup containers to fetch from a registry assigned to one
of the supported container backends

.. code:: xml
<containers source="registry.opensuse.org" backend="podman">
<container name="some"/>
</containers>
The optional containers element specifies the location of one ore
more containers on a registry `source` server. {kiwi} will take
this information and fetch the containers as OCI archives to
the image. On first boot those container archives will be loaded
into the local container backend store for the selected
backend and the archive files get deleted.

Supported `backend` values are `docker` and `podman`.
The `backend` attribute is mandatory and specifies for which
container backend the image should be available in the system.
The `containers` element has the following optional attributes:

arch="arch"
The containers section can be configured to apply only for a certain
architecture. In this case specify the `arch` attribute with a
value as it is reported by :command:`uname -m`.

profiles="name[,name]"
A list of profiles to which this containers selection applies
(see :ref:`image-profiles`).

<containers><container>
-----------------------

Details about the container

.. code:: xml
<containers source="registry.opensuse.org" backend="podman">
<container name="some"/>
</containers>
The `name` attributes is mandatory and specifies
the name of the container as it exists in the registry.
The `container` element has the following optional attributes:

path="some/path"
The path to the container in the registry. If not specified
the value defaults to `/`

fetch_only="true|false"
If set to `true` kiwi will only fetch the container but does not
setup the systemd unit for loading the container into
the local registry. In this mode the container archive file stays
in the system and can be handled in a custom way. By default
`fetch_only` is set to `false`.

tag="tagname"
Specifies the container tag to fetch. If not set the tag name
defaults to `latest`

arch="arch"
The container section can be configured to apply only for a certain
architecture. In this case specify the `arch` attribute with a
value as it is reported by :command:`uname -m`.

.. _sec.repository:

<repository>
Expand Down
68 changes: 68 additions & 0 deletions kiwi/builder/template/container_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) 2024 SUSE LLC. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with kiwi. If not, see <http://www.gnu.org/licenses/>
#
import os
from typing import List
from string import Template
from textwrap import dedent


class BuilderTemplateSystemdUnit:
"""
**systemd unit file templates**
"""
def __init__(self):
self.unit = dedent('''
# kiwi generated unit file
[Unit]
Description=Import Local Container(s)
''').strip() + os.linesep

self.service = dedent('''
[Service]
Type=oneshot
''').strip() + os.linesep

self.install = dedent('''
[Install]
WantedBy=multi-user.target
''').strip() + os.linesep

def get_container_import_template(
self, container_files: List[str], load_commands: List[List[str]],
after: List[str]
):
template_data = self.unit
for container_file in container_files:
template_data += 'ConditionPathExists={0}{1}'.format(
container_file, os.linesep
)
if after:
template_data += 'After={0}{1}'.format(
' '.join(after), os.linesep
)
template_data += self.service
for load_command in load_commands:
template_data += 'ExecStart={0}{1}'.format(
' '.join(load_command), os.linesep
)
for container_file in container_files:
template_data += 'ExecStartPost=/bin/rm -f {0}{1}'.format(
container_file, os.linesep
)
template_data += self.install
return Template(template_data)
1 change: 1 addition & 0 deletions kiwi/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
) if MODULE_SPEC else 'unknown'

TEMP_DIR = '/var/tmp'
LOCAL_CONTAINERS = '/var/tmp/kiwi_containers'
CUSTOM_RUNTIME_CONFIG_FILE = None
PLATFORM_MACHINE = platform.machine()
EFI_FAT_IMAGE_SIZE = 20
Expand Down
56 changes: 56 additions & 0 deletions kiwi/schema/kiwi.rnc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ div {
k.drivers* &
k.strip* &
k.repository* &
k.containers* &
k.packages* &
k.extension?
}
Expand Down Expand Up @@ -1047,6 +1048,61 @@ div {
}
}

#==========================================
# common element <containers>
#
div {
k.containers.profiles.attribute = k.profiles.attribute
k.containers.arch.attribute = k.arch.attribute
k.containers.source.attribute =
## Name of registry source server
attribute source { text }
k.containers.backend.attribute =
## Use container with specified container backend
attribute backend { "podman" | "docker" }
k.containers.attlist =
k.containers.profiles.attribute? &
k.containers.arch.attribute? &
k.containers.source.attribute &
k.containers.backend.attribute
k.containers =
element containers {
k.containers.attlist,
k.container+
}
}

#==========================================
# common element <container>
#
div {
k.container.arch.attribute = k.arch.attribute
k.container.name.attribute =
## Container name
attribute name { text }
k.container.path.attribute =
## Container path, default to '/' if not specified
attribute path { text }
k.container.tag.attribute =
## Container tag, defaults to 'latest' if not specified
attribute tag { text }
k.container.fetch_only.attribute =
## Only fetch the container but do not activate the
## loading of the container at first boot
attribute fetch_only { xsd:boolean }
k.container.attlist =
k.container.name.attribute &
k.container.arch.attribute? &
k.container.path.attribute? &
k.container.tag.attribute? &
k.container.fetch_only.attribute?
k.container =
element container {
k.container.attlist,
empty
}
}

#==========================================
# common element <repository>
#
Expand Down
105 changes: 105 additions & 0 deletions kiwi/schema/kiwi.rng
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ named /etc/ImageID</a:documentation>
<zeroOrMore>
<ref name="k.repository"/>
</zeroOrMore>
<zeroOrMore>
<ref name="k.containers"/>
</zeroOrMore>
<zeroOrMore>
<ref name="k.packages"/>
</zeroOrMore>
Expand Down Expand Up @@ -1601,6 +1604,108 @@ definition can be composed by other existing profiles.</a:documentation>
</element>
</define>
</div>
<!--
==========================================
common element <containers>
-->
<div>
<define name="k.containers.profiles.attribute">
<ref name="k.profiles.attribute"/>
</define>
<define name="k.containers.arch.attribute">
<ref name="k.arch.attribute"/>
</define>
<define name="k.containers.source.attribute">
<attribute name="source">
<a:documentation>Name of registry source server</a:documentation>
</attribute>
</define>
<define name="k.containers.backend.attribute">
<attribute name="backend">
<a:documentation>Use container with specified container backend</a:documentation>
<choice>
<value>podman</value>
<value>docker</value>
</choice>
</attribute>
</define>
<define name="k.containers.attlist">
<interleave>
<optional>
<ref name="k.containers.profiles.attribute"/>
</optional>
<optional>
<ref name="k.containers.arch.attribute"/>
</optional>
<ref name="k.containers.source.attribute"/>
<ref name="k.containers.backend.attribute"/>
</interleave>
</define>
<define name="k.containers">
<element name="containers">
<ref name="k.containers.attlist"/>
<oneOrMore>
<ref name="k.container"/>
</oneOrMore>
</element>
</define>
</div>
<!--
==========================================
common element <container>
-->
<div>
<define name="k.container.arch.attribute">
<ref name="k.arch.attribute"/>
</define>
<define name="k.container.name.attribute">
<attribute name="name">
<a:documentation>Container name</a:documentation>
</attribute>
</define>
<define name="k.container.path.attribute">
<attribute name="path">
<a:documentation>Container path, default to '/' if not specified</a:documentation>
</attribute>
</define>
<define name="k.container.tag.attribute">
<attribute name="tag">
<a:documentation>Container tag, defaults to 'latest' if not specified</a:documentation>
</attribute>
</define>
<define name="k.container.fetch_only.attribute">
<attribute name="fetch_only">
<a:documentation>Only fetch the container but do not activate the
loading of the container at first boot</a:documentation>
<data type="boolean"/>
</attribute>
</define>
<define name="k.container.attlist">
<interleave>
<ref name="k.container.name.attribute"/>
<optional>
<ref name="k.container.arch.attribute"/>
</optional>
<optional>
<ref name="k.container.path.attribute"/>
</optional>
<optional>
<ref name="k.container.tag.attribute"/>
</optional>
<optional>
<ref name="k.container.fetch_only.attribute"/>
</optional>
</interleave>
</define>
<define name="k.container">
<element name="container">
<ref name="k.container.attlist"/>
<empty/>
</element>
</define>
</div>
<!--
==========================================
common element <repository>
Expand Down
43 changes: 43 additions & 0 deletions kiwi/system/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from kiwi.utils.compress import Compress
from kiwi.utils.command_capabilities import CommandCapabilities
from kiwi.utils.rpm_database import RpmDataBase
from kiwi.builder.template.container_import import BuilderTemplateSystemdUnit
from kiwi.system.profile import Profile

from kiwi.exceptions import (
Expand Down Expand Up @@ -87,6 +88,48 @@ def __init__(self, xml_state: XMLState, root_dir: str):
self._preferences_lookup()
self._oemconfig_lookup()

def setup_registry_import(self) -> None:
"""
Fetch container(s) and activate systemd unit to load
containers from local oci-archive file during boot
"""
container_files_to_load = []
container_execs_to_load = []
after_services = set()
for container in self.xml_state.get_containers():
log.info(f'Fetching container: {container.name}')
pathlib.Path(f'{self.root_dir}/{defaults.LOCAL_CONTAINERS}').mkdir(
parents=True, exist_ok=True
)
Command.run(
['chroot', self.root_dir] + container.fetch_command
)
if container.load_command:
container_files_to_load.append(container.container_file)
container_execs_to_load.append(container.load_command)
if container.backend == 'docker':
after_services.add('docker.service')

if container_files_to_load:
log.info('--> Setup kiwi_containers.service import unit')
service = BuilderTemplateSystemdUnit()
unit_template = service.get_container_import_template(
container_files_to_load, container_execs_to_load,
list(after_services)
)
unit = unit_template.substitute()
unit_file = '{0}/etc/systemd/system/{1}.service'.format(
self.root_dir, 'kiwi_containers'
)
with open(unit_file, 'w') as systemd:
systemd.write(unit)
Command.run(
[
'chroot', self.root_dir,
'systemctl', 'enable', 'kiwi_containers'
]
)

def import_description(self) -> None:
"""
Import XML descriptions, custom scripts, archives and
Expand Down
Loading

0 comments on commit a349c05

Please sign in to comment.