Example #1
0
    def test_config_sections_from_home_base_config(self, mock_Cli):
        cli = Mock()
        cli.get_global_args.return_value = {}
        mock_Cli.return_value = cli
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/ok'}):
            runtime_config = RuntimeConfig(reread=True)

        assert runtime_config.get_xz_options() == ['-a', '-b', 'xxx']
        assert runtime_config.is_obs_public() is True
        assert runtime_config.get_bundle_compression() is True
        assert runtime_config.get_obs_download_server_url() == \
            'http://example.com'
        assert runtime_config.get_obs_api_server_url() == \
            'https://api.example.com'
        assert runtime_config.get_container_compression() is None
        assert runtime_config.get_iso_tool_category() == 'cdrtools'
        assert runtime_config.get_oci_archive_tool() == 'umoci'
        assert runtime_config.get_package_changes() is True
        assert runtime_config.get_disabled_runtime_checks() == [
            'check_dracut_module_for_oem_install_in_package_list',
            'check_container_tool_chain_installed'
        ]
        assert runtime_config.get_obs_api_credentials() == [{
            'user_name':
            'user_credentials'
        }]
Example #2
0
    def test_config_sections_other_settings(self, mock_Cli):
        cli = Mock()
        cli.get_global_args.return_value = {}
        mock_Cli.return_value = cli
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/other'}):
            runtime_config = RuntimeConfig(reread=True)

        assert runtime_config.get_container_compression() == 'xz'
        assert runtime_config.get_package_changes() is True
Example #3
0
    def test_config_sections_invalid(self):
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/invalid'}):
            runtime_config = RuntimeConfig(reread=True)

        with self._caplog.at_level(logging.WARNING):
            assert runtime_config.get_container_compression() is True
            assert 'Skipping invalid container compression: foo' in \
                self._caplog.text
        with self._caplog.at_level(logging.WARNING):
            assert runtime_config.get_iso_tool_category() == 'xorriso'
            assert 'Skipping invalid iso tool category: foo' in \
                self._caplog.text
Example #4
0
    def test_config_sections_invalid(self, mock_Cli):
        cli = Mock()
        cli.get_global_args.return_value = {}
        mock_Cli.return_value = cli
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/invalid'}):
            runtime_config = RuntimeConfig(reread=True)

        with self._caplog.at_level(logging.WARNING):
            assert runtime_config.get_container_compression() == 'xz'
            assert 'Skipping invalid container compression: foo' in \
                self._caplog.text
        with self._caplog.at_level(logging.WARNING):
            assert runtime_config.get_iso_tool_category() == 'xorriso'
            assert 'Skipping invalid iso tool category: foo' in \
                self._caplog.text
Example #5
0
    def test_config_sections_defaults(self, mock_is_buildservice_worker):
        mock_is_buildservice_worker.return_value = True
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/defaults'}):
            runtime_config = RuntimeConfig(reread=True)

        assert runtime_config.get_bundle_compression(default=True) is True
        assert runtime_config.get_bundle_compression(default=False) is False
        assert runtime_config.is_obs_public() is True
        assert runtime_config.get_obs_download_server_url() == \
            Defaults.get_obs_download_server_url()
        assert runtime_config.get_obs_api_server_url() == \
            Defaults.get_obs_api_server_url()
        assert runtime_config.get_container_compression() is True
        assert runtime_config.get_iso_tool_category() == 'xorriso'
        assert runtime_config.get_oci_archive_tool() == 'umoci'
        assert runtime_config.get_package_changes() is False
Example #6
0
class TestRuntimeConfig(object):
    def setup(self):
        with patch.dict('os.environ', {'HOME': '../data'}):
            self.runtime_config = RuntimeConfig()

    @raises(KiwiRuntimeConfigFormatError)
    def test_invalid_yaml_format(self):
        self.runtime_config.config_data = {'xz': None}
        self.runtime_config.get_xz_options()

    def test_get_xz_options(self):
        assert self.runtime_config.get_xz_options() == ['-a', '-b', 'xxx']

    def test_is_obs_public(self):
        assert self.runtime_config.is_obs_public() is True

    def test_is_obs_public_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.is_obs_public() is True

    def test_get_obs_download_server_url(self):
        assert self.runtime_config.get_obs_download_server_url() == \
            'http://example.com'

    def test_get_obs_download_server_url_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_obs_download_server_url() == \
                Defaults.get_obs_download_server_url()

    def test_get_container_compression(self):
        assert self.runtime_config.get_container_compression() is None

    def test_get_container_compression_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_container_compression() == 'xz'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_container_compression_invalid(self, mock_warning,
                                               mock_get_attribute):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_container_compression() == 'xz'
        mock_warning.assert_called_once_with(
            'Skipping invalid container compression: foo')

    @patch.object(RuntimeConfig, '_get_attribute')
    def test_get_container_compression_xz(self, mock_get_attribute):
        mock_get_attribute.return_value = 'xz'
        assert self.runtime_config.get_container_compression() == 'xz'

    def test_get_iso_tool_category(self):
        assert self.runtime_config.get_iso_tool_category() == 'cdrtools'

    def test_get_iso_tool_category_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_iso_tool_category() == 'xorriso'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_iso_tool_category_invalid(self, mock_warning,
                                           mock_get_attribute):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_iso_tool_category() == 'xorriso'
        mock_warning.assert_called_once_with(
            'Skipping invalid iso tool category: foo')
Example #7
0
 def test_get_container_compression_default(self):
     with patch.dict('os.environ', {'HOME': './'}):
         runtime_config = RuntimeConfig()
         assert runtime_config.get_container_compression() == 'xz'
Example #8
0
class TestRuntimeConfig(object):
    def setup(self):
        with patch.dict('os.environ', {'HOME': '../data'}):
            self.runtime_config = RuntimeConfig()

    @patch('os.path.exists')
    @patch('yaml.load')
    def test_reading_system_wide_config_file(self, mock_yaml, mock_exists):
        exists_call_results = [True, False]

        def os_path_exists(config):
            return exists_call_results.pop()

        mock_exists.side_effect = os_path_exists
        with patch_open as mock_open:
            self.runtime_config = RuntimeConfig()
            mock_open.assert_called_once_with('/etc/kiwi.yml', 'r')

    @raises(KiwiRuntimeConfigFormatError)
    def test_invalid_yaml_format(self):
        self.runtime_config.config_data = {'xz': None}
        self.runtime_config.get_xz_options()

    def test_get_xz_options(self):
        assert self.runtime_config.get_xz_options() == ['-a', '-b', 'xxx']

    def test_is_obs_public(self):
        assert self.runtime_config.is_obs_public() is True

    def test_is_obs_public_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.is_obs_public() is True

    def test_get_obs_download_server_url(self):
        assert self.runtime_config.get_obs_download_server_url() == \
            'http://example.com'

    def test_get_obs_download_server_url_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_obs_download_server_url() == \
                Defaults.get_obs_download_server_url()

    def test_get_container_compression(self):
        assert self.runtime_config.get_container_compression() is None

    def test_get_container_compression_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_container_compression() == 'xz'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_container_compression_invalid(
        self, mock_warning, mock_get_attribute
    ):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_container_compression() == 'xz'
        mock_warning.assert_called_once_with(
            'Skipping invalid container compression: foo'
        )

    @patch.object(RuntimeConfig, '_get_attribute')
    def test_get_container_compression_xz(self, mock_get_attribute):
        mock_get_attribute.return_value = 'xz'
        assert self.runtime_config.get_container_compression() == 'xz'

    def test_get_iso_tool_category(self):
        assert self.runtime_config.get_iso_tool_category() == 'cdrtools'

    def test_get_iso_tool_category_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_iso_tool_category() == 'xorriso'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_iso_tool_category_invalid(
        self, mock_warning, mock_get_attribute
    ):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_iso_tool_category() == 'xorriso'
        mock_warning.assert_called_once_with(
            'Skipping invalid iso tool category: foo'
        )

    def test_get_oci_archive_tool(self):
        assert self.runtime_config.get_oci_archive_tool() == 'umoci'

    def test_get_oci_archive_tool_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_oci_archive_tool() == 'umoci'
Example #9
0
File: oci.py Project: eduardoj/kiwi
class ContainerImageOCI(object):
    """
    Create oci container from a root directory

    :param string root_dir: root directory path name
    :param dict custom_args:
        Custom processing arguments defined as hash keys:

        Example

        .. code:: python

            {
                'container_name': 'name',
                'container_tag': '1.0',
                'additional_tags': ['current', 'foobar'],
                'entry_command': [
                    '--config.entrypoint=/bin/bash',
                    '--config.entrypoint=-x'
                ],
                'entry_subcommand': [
                    '--config.cmd=ls',
                    '--config.cmd=-l'
                ],
                'maintainer': ['--author=tux'],
                'user': ['--config.user=root'],
                'workingdir': ['--config.workingdir=/root'],
                'expose_ports': [
                    '--config.exposedports=80',
                    '--config.exposedports=42'
                ],
                'volumes': [
                    '--config.volume=/var/log',
                    '--config.volume=/tmp'
                ],
                'environment': ['--config.env=PATH=/bin'],
                'labels': ['--config.label=name=value']
            }
    """
    def __init__(self, root_dir, custom_args=None):  # noqa: C901
        self.root_dir = root_dir
        self.oci_dir = None
        self.oci_root_dir = None

        self.container_name = Defaults.get_default_container_name()
        self.container_tag = Defaults.get_default_container_tag()
        self.additional_tags = []
        self.entry_command = []
        self.entry_subcommand = []
        self.maintainer = []
        self.user = []
        self.workingdir = []
        self.expose_ports = []
        self.volumes = []
        self.environment = []
        self.labels = []

        self.runtime_config = RuntimeConfig()

        if custom_args:
            if 'container_name' in custom_args:
                self.container_name = custom_args['container_name']

            if 'container_tag' in custom_args:
                self.container_tag = custom_args['container_tag']

            if 'additional_tags' in custom_args:
                self.additional_tags = custom_args['additional_tags']

            if 'entry_command' in custom_args:
                self.entry_command = custom_args['entry_command']

            if 'entry_subcommand' in custom_args:
                self.entry_subcommand = custom_args['entry_subcommand']

            if 'maintainer' in custom_args:
                self.maintainer = custom_args['maintainer']

            if 'user' in custom_args:
                self.user = custom_args['user']

            if 'workingdir' in custom_args:
                self.workingdir = custom_args['workingdir']

            if 'expose_ports' in custom_args:
                self.expose_ports = custom_args['expose_ports']

            if 'volumes' in custom_args:
                self.volumes = custom_args['volumes']

            if 'environment' in custom_args:
                self.environment = custom_args['environment']

            if 'labels' in custom_args:
                self.labels = custom_args['labels']

        # for builds inside the buildservice we include a reference to the
        # specific build. Thus disturl label only exists inside the
        # buildservice.
        if Defaults.is_buildservice_worker():
            bs_label = 'org.openbuildservice.disturl'
            # Do not label anything if any build service label is
            # already present
            if not [label for label in self.labels if bs_label in label]:
                self._append_buildservice_disturl_label()

        if not custom_args or 'container_name' not in custom_args:
            log.info('No container configuration provided, '
                     'using default container name "kiwi-container:latest"')

        if not self.entry_command and not self.entry_subcommand:
            self.entry_subcommand = ['--config.cmd=/bin/bash']

    def create(self, filename, base_image):
        """
        Create compressed oci system container tar archive

        :param string filename: archive file name
        :param string base_image: archive used as a base image
        """
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        exclude_list.append('boot')
        exclude_list.append('dev')
        exclude_list.append('sys')
        exclude_list.append('proc')

        self.oci_dir = mkdtemp(prefix='kiwi_oci_dir.')
        self.oci_root_dir = mkdtemp(prefix='kiwi_oci_root_dir.')

        container_dir = os.sep.join([self.oci_dir, 'umoci_layout'])
        container_name = ':'.join([container_dir, self.container_tag])

        if base_image:
            Path.create(container_dir)
            image_tar = ArchiveTar(base_image)
            image_tar.extract(container_dir)
            Command.run([
                'umoci', 'config', '--image',
                '{0}:base_layer'.format(container_dir), '--tag',
                self.container_tag
            ])
        else:
            Command.run(['umoci', 'init', '--layout', container_dir])
            Command.run(['umoci', 'new', '--image', container_name])

        Command.run(
            ['umoci', 'unpack', '--image', container_name, self.oci_root_dir])
        oci_root = DataSync(''.join([self.root_dir, os.sep]),
                            os.sep.join([self.oci_root_dir, 'rootfs']))
        oci_root.sync_data(options=['-a', '-H', '-X', '-A', '--delete'],
                           exclude=exclude_list)
        Command.run(
            ['umoci', 'repack', '--image', container_name, self.oci_root_dir])
        for tag in self.additional_tags:
            Command.run(
                ['umoci', 'config', '--image', container_name, '--tag', tag])
        Command.run(['umoci', 'config'] + self.maintainer + self.user +
                    self.workingdir + self.entry_command +
                    self.entry_subcommand + self.expose_ports + self.volumes +
                    self.environment + self.labels + [
                        '--image', container_name, '--created',
                        datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')
                    ])
        Command.run(['umoci', 'gc', '--layout', container_dir])

        return self.pack_image_to_file(filename)

    def pack_image_to_file(self, filename):
        """
        Packs the oci image into the given filename.

        :param string filename: file name of the resulting packed image
        """
        image_dir = os.sep.join([self.oci_dir, 'umoci_layout'])
        oci_tarfile = ArchiveTar(filename)
        container_compressor = self.runtime_config.get_container_compression()
        if container_compressor:
            return oci_tarfile.create_xz_compressed(
                image_dir, xz_options=self.runtime_config.get_xz_options())
        else:
            return oci_tarfile.create(image_dir)

    def _append_buildservice_disturl_label(self):
        with open(os.sep + Defaults.get_buildservice_env_name()) as env:
            for line in env:
                if line.startswith('BUILD_DISTURL') and '=' in line:
                    disturl = line.split('=')[1].lstrip('\'\"').rstrip(
                        '\n\'\"')
                    if disturl:
                        self.labels.append(''.join([
                            '--config.label=org.openbuildservice.disturl=',
                            disturl
                        ]))
                        return
            log.warning('Could not find BUILD_DISTURL inside .buildenv')

    def __del__(self):
        if self.oci_dir:
            Path.wipe(self.oci_dir)
        if self.oci_root_dir:
            Path.wipe(self.oci_root_dir)
Example #10
0
class ContainerBuilder:
    """
    **Container image builder**

    :param object xml_state: Instance of :class:`XMLState`
    :param str target_dir: target directory path name
    :param str root_dir: root directory path name
    :param dict custom_args: Custom processing arguments defined as hash keys:
        * xz_options: string of XZ compression parameters
    """
    def __init__(self,
                 xml_state: XMLState,
                 target_dir: str,
                 root_dir: str,
                 custom_args: Dict = None):
        self.custom_args = custom_args or {}
        self.root_dir = root_dir
        self.target_dir = target_dir
        self.bundle_format = xml_state.get_build_type_bundle_format()
        self.container_config = xml_state.get_container_config()
        self.requested_container_type = xml_state.get_build_type_name()
        self.base_image = None
        self.base_image_md5 = None

        self.container_config['xz_options'] = \
            self.custom_args.get('xz_options')

        self.container_config['metadata_path'] = \
            xml_state.build_type.get_metadata_path()

        if xml_state.get_derived_from_image_uri():
            # The base image is expected to be unpacked by the kiwi
            # prepare step and stored inside of the root_dir/image directory.
            # In addition a md5 file of the image is expected too
            self.base_image = Defaults.get_imported_root_image(self.root_dir)
            self.base_image_md5 = ''.join([self.base_image, '.md5'])

            if not os.path.exists(self.base_image):
                raise KiwiContainerBuilderError(
                    'Unpacked Base image {0} not found'.format(
                        self.base_image))
            if not os.path.exists(self.base_image_md5):
                raise KiwiContainerBuilderError(
                    'Base image MD5 sum {0} not found at'.format(
                        self.base_image_md5))

        self.system_setup = SystemSetup(xml_state=xml_state,
                                        root_dir=self.root_dir)
        self.filename = ''.join([
            target_dir, '/',
            xml_state.xml_data.get_name(), '.' + Defaults.get_platform_name(),
            '-' + xml_state.get_image_version(), '.',
            self.requested_container_type,
            '.tar' if self.requested_container_type != 'appx' else ''
        ])
        self.result = Result(xml_state)
        self.runtime_config = RuntimeConfig()

    def create(self) -> Result:
        """
        Builds a container image which is usually a data archive
        including container specific metadata.

        Image types which triggers this builder are:

        * image="docker"
        * image="oci"
        * image="appx"

        :return: result

        :rtype: instance of :class:`Result`
        """
        if not self.base_image:
            log.info('Setting up %s container', self.requested_container_type)
            container_setup = ContainerSetup.new(self.requested_container_type,
                                                 self.root_dir,
                                                 self.container_config)
            container_setup.setup()
        else:
            checksum = Checksum(self.base_image)
            if not checksum.matches(checksum.md5(), self.base_image_md5):
                raise KiwiContainerBuilderError(
                    'base image file {0} checksum validation failed'.format(
                        self.base_image))

        log.info('--> Creating container image')
        container_image = ContainerImage.new(self.requested_container_type,
                                             self.root_dir,
                                             self.container_config)
        self.filename = container_image.create(self.filename, self.base_image)
        Result.verify_image_size(self.runtime_config.get_max_size_constraint(),
                                 self.filename)
        if self.bundle_format:
            self.result.add_bundle_format(self.bundle_format)
        self.result.add(
            key='container',
            filename=self.filename,
            use_for_bundle=True,
            compress=self.runtime_config.get_container_compression(),
            shasum=True)
        self.result.add(key='image_packages',
                        filename=self.system_setup.export_package_list(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        self.result.add(key='image_changes',
                        filename=self.system_setup.export_package_changes(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=True,
                        shasum=False)
        self.result.add(key='image_verified',
                        filename=self.system_setup.export_package_verification(
                            self.target_dir),
                        use_for_bundle=True,
                        compress=False,
                        shasum=False)
        return self.result
Example #11
0
class TestRuntimeConfig:
    def setup(self):
        with patch.dict('os.environ', {'HOME': '../data'}):
            self.runtime_config = RuntimeConfig()

        # pretend that none of the runtime config files exist, even if they do
        # (e.g. the system wide config file in /etc/kiwi.yml)
        # => this will give us the defaults
        with patch('os.path.exists', return_value=False):
            self.default_runtime_config = RuntimeConfig()

    @patch('os.path.exists')
    @patch('yaml.safe_load')
    def test_reading_system_wide_config_file(self, mock_yaml, mock_exists):
        exists_call_results = [True, False]

        def os_path_exists(config):
            return exists_call_results.pop()

        mock_exists.side_effect = os_path_exists
        with patch('builtins.open') as m_open:
            self.runtime_config = RuntimeConfig()
            m_open.assert_called_once_with('/etc/kiwi.yml', 'r')

    def test_invalid_yaml_format(self):
        self.runtime_config.config_data = {'xz': None}
        with raises(KiwiRuntimeConfigFormatError):
            self.runtime_config.get_xz_options()

    def test_get_xz_options(self):
        assert self.runtime_config.get_xz_options() == ['-a', '-b', 'xxx']

    def test_is_obs_public(self):
        assert self.runtime_config.is_obs_public() is True

    def test_get_bundle_compression(self):
        assert self.runtime_config.get_bundle_compression() is True

    def test_get_bundle_compression_default(self):
        assert self.default_runtime_config.get_bundle_compression(
            default=True) is True
        assert self.default_runtime_config.get_bundle_compression(
            default=False) is False

    def test_is_obs_public_default(self):
        assert self.default_runtime_config.is_obs_public() is True

    def test_get_obs_download_server_url(self):
        assert self.runtime_config.get_obs_download_server_url() == \
            'http://example.com'

    def test_get_obs_download_server_url_default(self):
        assert self.default_runtime_config.get_obs_download_server_url() == \
            Defaults.get_obs_download_server_url()

    def test_get_container_compression(self):
        assert self.runtime_config.get_container_compression() is None

    def test_get_container_compression_default(self):
        assert self.default_runtime_config.get_container_compression() == 'xz'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_container_compression_invalid(self, mock_warning,
                                               mock_get_attribute):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_container_compression() == 'xz'
        mock_warning.assert_called_once_with(
            'Skipping invalid container compression: foo')

    @patch.object(RuntimeConfig, '_get_attribute')
    def test_get_container_compression_xz(self, mock_get_attribute):
        mock_get_attribute.return_value = 'xz'
        assert self.runtime_config.get_container_compression() == 'xz'

    def test_get_iso_tool_category(self):
        assert self.runtime_config.get_iso_tool_category() == 'cdrtools'

    def test_get_iso_tool_category_default(self):
        assert self.default_runtime_config.get_iso_tool_category() == 'xorriso'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_iso_tool_category_invalid(self, mock_warning,
                                           mock_get_attribute):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_iso_tool_category() == 'xorriso'
        mock_warning.assert_called_once_with(
            'Skipping invalid iso tool category: foo')

    def test_get_oci_archive_tool(self):
        assert self.runtime_config.get_oci_archive_tool() == 'umoci'

    def test_get_oci_archive_tool_default(self):
        assert self.default_runtime_config.get_oci_archive_tool() == 'umoci'

    def test_get_disabled_runtime_checks(self):
        assert self.runtime_config.get_disabled_runtime_checks() == [
            'check_dracut_module_for_oem_install_in_package_list',
            'check_container_tool_chain_installed'
        ]
Example #12
0
class ContainerImageOCI(object):
    """
    Create oci container from a root directory

    :param string root_dir: root directory path name
    :param dict custom_args:
        Custom processing arguments defined as hash keys:

        Example

        .. code:: python

            {
                'container_name': 'name',
                'container_tag': '1.0',
                'additional_tags': ['current', 'foobar'],
                'entry_command': ['/bin/bash', '-x'],
                'entry_subcommand': ['ls', '-l'],
                'maintainer': 'tux',
                'user': '******',
                'workingdir': '/root',
                'expose_ports': ['80', '42'],
                'volumes': ['/var/log', '/tmp'],
                'environment': {'PATH': '/bin'},
                'labels': {'name': 'value'},
                'history': {
                    'created_by': 'some explanation here',
                    'comment': 'some comment here',
                    'author': 'tux'
                }
            }
    """
    def __init__(self, root_dir, transport, custom_args=None):
        self.root_dir = root_dir
        self.archive_transport = transport
        if custom_args:
            self.oci_config = custom_args
        else:
            self.oci_config = {}

        self.runtime_config = RuntimeConfig()

        # for builds inside the buildservice we include a reference to the
        # specific build. Thus disturl label only exists inside the
        # buildservice.
        if Defaults.is_buildservice_worker():
            bs_label = 'org.openbuildservice.disturl'
            # Do not label anything if the build service label is
            # already present
            if 'labels' not in self.oci_config or \
                    bs_label not in self.oci_config['labels']:
                self._append_buildservice_disturl_label()

        if 'container_name' not in self.oci_config:
            log.info('No container configuration provided, '
                     'using default container name "kiwi-container:latest"')
            self.oci_config['container_name'] = \
                Defaults.get_default_container_name()
            self.oci_config['container_tag'] = \
                Defaults.get_default_container_tag()

        if 'container_tag' not in self.oci_config:
            self.oci_config['container_tag'] = \
                Defaults.get_default_container_tag()

        if 'entry_command' not in self.oci_config and \
                'entry_subcommand' not in self.oci_config:
            self.oci_config['entry_subcommand'] = \
                Defaults.get_default_container_subcommand()

        if 'history' not in self.oci_config:
            self.oci_config['history'] = {}
        if 'created_by' not in self.oci_config['history']:
            self.oci_config['history']['created_by'] = \
                Defaults.get_default_container_created_by()

    def create(self, filename, base_image):
        """
        Create compressed oci system container tar archive

        :param string filename: archive file name
        :param string base_image: archive used as a base image
        """
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        exclude_list.append('boot')
        exclude_list.append('dev')
        exclude_list.append('sys')
        exclude_list.append('proc')

        oci = OCI()
        if base_image:
            oci.import_container_image('oci-archive:{0}:{1}'.format(
                base_image, Defaults.get_container_base_image_tag()))
        else:
            oci.init_container()

        image_ref = '{0}:{1}'.format(self.oci_config['container_name'],
                                     self.oci_config['container_tag'])

        oci.unpack()
        oci.sync_rootfs(self.root_dir, exclude_list)
        oci.repack(self.oci_config)
        oci.set_config(self.oci_config)
        oci.post_process()

        if self.archive_transport == 'docker-archive':
            image_ref = '{0}:{1}'.format(self.oci_config['container_name'],
                                         self.oci_config['container_tag'])
            additional_refs = []
            if 'additional_tags' in self.oci_config:
                additional_refs = []
                for tag in self.oci_config['additional_tags']:
                    additional_refs.append('{0}:{1}'.format(
                        self.oci_config['container_name'], tag))
        else:
            image_ref = self.oci_config['container_tag']
            additional_refs = []

        oci.export_container_image(filename, self.archive_transport, image_ref,
                                   additional_refs)

        if self.runtime_config.get_container_compression():
            compressor = Compress(filename)
            return compressor.xz(self.runtime_config.get_xz_options())
        else:
            return filename

    def _append_buildservice_disturl_label(self):
        with open(os.sep + Defaults.get_buildservice_env_name()) as env:
            for line in env:
                if line.startswith('BUILD_DISTURL') and '=' in line:
                    disturl = line.split('=')[1].lstrip('\'\"').rstrip(
                        '\n\'\"')
                    if disturl:
                        self.oci_config['labels'] = {
                            'org.openbuildservice.disturl': disturl
                        }
                        return
            log.warning('Could not find BUILD_DISTURL inside .buildenv')
Example #13
0
class ContainerImageOCI(object):
    """
    Create oci container from a root directory

    :param string root_dir: root directory path name
    :param dict custom_args:
        Custom processing arguments defined as hash keys:

        Example

        .. code:: python

            {
                'container_name': 'name',
                'container_tag': '1.0',
                'additional_tags': ['current', 'foobar'],
                'entry_command': [
                    '--config.entrypoint=/bin/bash',
                    '--config.entrypoint=-x'
                ],
                'entry_subcommand': [
                    '--config.cmd=ls',
                    '--config.cmd=-l'
                ],
                'maintainer': ['--author=tux'],
                'user': ['--config.user=root'],
                'workingdir': ['--config.workingdir=/root'],
                'expose_ports': [
                    '--config.exposedports=80',
                    '--config.exposedports=42'
                ],
                'volumes': [
                    '--config.volume=/var/log',
                    '--config.volume=/tmp'
                ],
                'environment': ['--config.env=PATH=/bin'],
                'labels': ['--config.label=name=value']
            }
    """
    def __init__(self, root_dir, custom_args=None):         # noqa: C901
        self.root_dir = root_dir
        self.oci_dir = None
        self.oci_root_dir = None

        self.container_name = 'kiwi-container'
        self.container_tag = 'latest'
        self.additional_tags = []
        self.entry_command = []
        self.entry_subcommand = []
        self.maintainer = []
        self.user = []
        self.workingdir = []
        self.expose_ports = []
        self.volumes = []
        self.environment = []
        self.labels = []

        self.runtime_config = RuntimeConfig()

        if custom_args:
            if 'container_name' in custom_args:
                self.container_name = custom_args['container_name']

            if 'container_tag' in custom_args:
                self.container_tag = custom_args['container_tag']

            if 'additional_tags' in custom_args:
                self.additional_tags = custom_args['additional_tags']

            if 'entry_command' in custom_args:
                self.entry_command = custom_args['entry_command']

            if 'entry_subcommand' in custom_args:
                self.entry_subcommand = custom_args['entry_subcommand']

            if 'maintainer' in custom_args:
                self.maintainer = custom_args['maintainer']

            if 'user' in custom_args:
                self.user = custom_args['user']

            if 'workingdir' in custom_args:
                self.workingdir = custom_args['workingdir']

            if 'expose_ports' in custom_args:
                self.expose_ports = custom_args['expose_ports']

            if 'volumes' in custom_args:
                self.volumes = custom_args['volumes']

            if 'environment' in custom_args:
                self.environment = custom_args['environment']

            if 'labels' in custom_args:
                self.labels = custom_args['labels']

        # for builds inside the buildservice we include a reference to the
        # specific build. Thus disturl label only exists inside the
        # buildservice.
        if Defaults.is_buildservice_worker():
            self._append_buildservice_disturl_label()

        if not custom_args or 'container_name' not in custom_args:
            log.info(
                'No container configuration provided, '
                'using default container name "kiwi-container:latest"'
            )

        if not self.entry_command and not self.entry_subcommand:
            self.entry_subcommand = ['--config.cmd=/bin/bash']

    def create(self, filename, base_image):
        """
        Create compressed oci system container tar archive

        :param string filename: archive file name
        :param string base_image: archive used as a base image
        """
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        exclude_list.append('boot')
        exclude_list.append('dev')
        exclude_list.append('sys')
        exclude_list.append('proc')

        self.oci_dir = mkdtemp(prefix='kiwi_oci_dir.')
        self.oci_root_dir = mkdtemp(prefix='kiwi_oci_root_dir.')

        container_dir = os.sep.join(
            [self.oci_dir, 'umoci_layout']
        )
        container_name = ':'.join(
            [container_dir, self.container_tag]
        )

        if base_image:
            Path.create(container_dir)
            image_tar = ArchiveTar(base_image)
            image_tar.extract(container_dir)
            Command.run([
                'umoci', 'config', '--image',
                '{0}:base_layer'.format(container_dir),
                '--tag', self.container_tag
            ])
        else:
            Command.run(
                ['umoci', 'init', '--layout', container_dir]
            )
            Command.run(
                ['umoci', 'new', '--image', container_name]
            )

        Command.run(
            ['umoci', 'unpack', '--image', container_name, self.oci_root_dir]
        )
        oci_root = DataSync(
            ''.join([self.root_dir, os.sep]),
            os.sep.join([self.oci_root_dir, 'rootfs'])
        )
        oci_root.sync_data(
            options=['-a', '-H', '-X', '-A', '--delete'], exclude=exclude_list
        )
        Command.run(
            ['umoci', 'repack', '--image', container_name, self.oci_root_dir]
        )
        for tag in self.additional_tags:
            Command.run(
                ['umoci', 'config', '--image', container_name, '--tag', tag]
            )
        Command.run(
            [
                'umoci', 'config'
            ] +
            self.maintainer +
            self.user +
            self.workingdir +
            self.entry_command +
            self.entry_subcommand +
            self.expose_ports +
            self.volumes +
            self.environment +
            self.labels +
            [
                '--image', container_name,
                '--created', datetime.utcnow().strftime(
                    '%Y-%m-%dT%H:%M:%S+00:00'
                )
            ]
        )
        Command.run(
            ['umoci', 'gc', '--layout', container_dir]
        )

        return self.pack_image_to_file(filename)

    def pack_image_to_file(self, filename):
        """
        Packs the oci image into the given filename.

        :param string filename: file name of the resulting packed image
        """
        image_dir = os.sep.join([self.oci_dir, 'umoci_layout'])
        oci_tarfile = ArchiveTar(filename)
        container_compressor = self.runtime_config.get_container_compression()
        if container_compressor:
            return oci_tarfile.create_xz_compressed(
                image_dir, xz_options=self.runtime_config.get_xz_options()
            )
        else:
            return oci_tarfile.create(image_dir)

    def _append_buildservice_disturl_label(self):
        with open(os.sep + Defaults.get_buildservice_env_name()) as env:
            for line in env:
                if line.startswith('BUILD_DISTURL') and '=' in line:
                    disturl = line.split('=')[1].strip()
                    if disturl:
                        self.labels.append(
                            ''.join([
                                '--config.label='
                                'org.openbuildservice.disturl=',
                                line.split('=')[1].strip()
                            ])
                        )
            log.warning('Could not find BUILD_DISTURL inside .buildenv')

    def __del__(self):
        if self.oci_dir:
            Path.wipe(self.oci_dir)
        if self.oci_root_dir:
            Path.wipe(self.oci_root_dir)
Example #14
0
class ContainerImageOCI(object):
    """
    Create oci container from a root directory

    :param string root_dir: root directory path name
    :param dict custom_args:
        Custom processing arguments defined as hash keys:

        Example

        .. code:: python

            {
                'container_name': 'name',
                'container_tag': '1.0',
                'additional_tags': ['current', 'foobar'],
                'entry_command': ['/bin/bash', '-x'],
                'entry_subcommand': ['ls', '-l'],
                'maintainer': 'tux',
                'user': '******',
                'workingdir': '/root',
                'expose_ports': ['80', '42'],
                'volumes': ['/var/log', '/tmp'],
                'environment': {'PATH': '/bin'},
                'labels': {'name': 'value'},
                'history': {
                    'created_by': 'some explanation here',
                    'comment': 'some comment here',
                    'author': 'tux'
                }
            }
    """
    def __init__(self, root_dir, custom_args=None):
        self.root_dir = root_dir
        self.oci_root_dir = None
        if custom_args:
            self.oci_config = custom_args
        else:
            self.oci_config = {}

        self.runtime_config = RuntimeConfig()

        # for builds inside the buildservice we include a reference to the
        # specific build. Thus disturl label only exists inside the
        # buildservice.
        if Defaults.is_buildservice_worker():
            bs_label = 'org.openbuildservice.disturl'
            # Do not label anything if any build service label is
            # already present
            if 'labels' not in self.oci_config or \
                    bs_label not in self.oci_config['labels']:
                self._append_buildservice_disturl_label()

        if 'container_name' not in self.oci_config:
            log.info('No container configuration provided, '
                     'using default container name "kiwi-container:latest"')
            self.oci_config['container_name'] = \
                Defaults.get_default_container_name()
            self.oci_config['container_tag'] = \
                Defaults.get_default_container_tag()

        if 'container_tag' not in self.oci_config:
            self.oci_config['container_tag'] = \
                Defaults.get_default_container_tag()

        if 'entry_command' not in self.oci_config and \
                'entry_subcommand' not in self.oci_config:
            self.oci_config['entry_subcommand'] = \
                Defaults.get_default_container_subcommand()

        if 'history' not in self.oci_config:
            self.oci_config['history'] = {}
        if 'created_by' not in self.oci_config['history']:
            self.oci_config['history']['created_by'] = \
                Defaults.get_default_container_created_by()

        self.oci = OCI(self.oci_config['container_tag'])

    def create(self, filename, base_image):
        """
        Create compressed oci system container tar archive

        :param string filename: archive file name
        :param string base_image: archive used as a base image
        """
        exclude_list = Defaults.get_exclude_list_for_root_data_sync()
        exclude_list.append('boot')
        exclude_list.append('dev')
        exclude_list.append('sys')
        exclude_list.append('proc')

        if base_image:
            Path.create(self.oci.container_dir)
            image_tar = ArchiveTar(base_image)
            image_tar.extract(self.oci.container_dir)

        self.oci.init_layout(bool(base_image))

        self.oci.unpack()
        self.oci.sync_rootfs(''.join([self.root_dir, os.sep]), exclude_list)
        self.oci.repack(self.oci_config)

        if 'additional_tags' in self.oci_config:
            for tag in self.oci_config['additional_tags']:
                self.oci.add_tag(tag)

        self.oci.set_config(self.oci_config, bool(base_image))

        self.oci.garbage_collect()

        return self.pack_image_to_file(filename)

    def pack_image_to_file(self, filename):
        """
        Packs the oci image into the given filename.

        :param string filename: file name of the resulting packed image
        """
        oci_tarfile = ArchiveTar(filename)
        container_compressor = self.runtime_config.get_container_compression()
        if container_compressor:
            return oci_tarfile.create_xz_compressed(
                self.oci.container_dir,
                xz_options=self.runtime_config.get_xz_options())
        else:
            return oci_tarfile.create(self.oci.container_dir)

    def _append_buildservice_disturl_label(self):
        with open(os.sep + Defaults.get_buildservice_env_name()) as env:
            for line in env:
                if line.startswith('BUILD_DISTURL') and '=' in line:
                    disturl = line.split('=')[1].lstrip('\'\"').rstrip(
                        '\n\'\"')
                    if disturl:
                        self.oci_config['labels'] = {
                            'org.openbuildservice.disturl': disturl
                        }
                        return
            log.warning('Could not find BUILD_DISTURL inside .buildenv')
Example #15
0
class TestRuntimeConfig(object):
    def setup(self):
        with patch.dict('os.environ', {'HOME': '../data'}):
            self.runtime_config = RuntimeConfig()

    @raises(KiwiRuntimeConfigFormatError)
    def test_invalid_yaml_format(self):
        self.runtime_config.config_data = {'xz': None}
        self.runtime_config.get_xz_options()

    def test_get_xz_options(self):
        assert self.runtime_config.get_xz_options() == ['-a', '-b', 'xxx']

    def test_is_obs_public(self):
        assert self.runtime_config.is_obs_public() is True

    def test_is_obs_public_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.is_obs_public() is True

    def test_get_obs_download_server_url(self):
        assert self.runtime_config.get_obs_download_server_url() == \
            'http://example.com'

    def test_get_obs_download_server_url_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_obs_download_server_url() == \
                Defaults.get_obs_download_server_url()

    def test_get_container_compression(self):
        assert self.runtime_config.get_container_compression() is None

    def test_get_container_compression_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_container_compression() == 'xz'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_container_compression_invalid(
        self, mock_warning, mock_get_attribute
    ):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_container_compression() == 'xz'
        mock_warning.assert_called_once_with(
            'Skipping invalid container compression: foo'
        )

    @patch.object(RuntimeConfig, '_get_attribute')
    def test_get_container_compression_xz(self, mock_get_attribute):
        mock_get_attribute.return_value = 'xz'
        assert self.runtime_config.get_container_compression() == 'xz'

    def test_get_iso_tool_category(self):
        assert self.runtime_config.get_iso_tool_category() == 'cdrtools'

    def test_get_iso_tool_category_default(self):
        with patch.dict('os.environ', {'HOME': './'}):
            runtime_config = RuntimeConfig()
            assert runtime_config.get_iso_tool_category() == 'xorriso'

    @patch.object(RuntimeConfig, '_get_attribute')
    @patch('kiwi.logger.log.warning')
    def test_get_iso_tool_category_invalid(
        self, mock_warning, mock_get_attribute
    ):
        mock_get_attribute.return_value = 'foo'
        assert self.runtime_config.get_iso_tool_category() == 'xorriso'
        mock_warning.assert_called_once_with(
            'Skipping invalid iso tool category: foo'
        )
Example #16
0
 def test_get_container_compression_default(self):
     with patch.dict('os.environ', {'HOME': './'}):
         runtime_config = RuntimeConfig()
         assert runtime_config.get_container_compression() == 'xz'
Example #17
0
    def test_config_sections_other_settings(self):
        with patch.dict('os.environ', {'HOME': '../data/kiwi_config/other'}):
            runtime_config = RuntimeConfig(reread=True)

        assert runtime_config.get_container_compression() is True
        assert runtime_config.get_package_changes() is True