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' }]
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
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
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
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
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')
def test_get_container_compression_default(self): with patch.dict('os.environ', {'HOME': './'}): runtime_config = RuntimeConfig() assert runtime_config.get_container_compression() == 'xz'
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'
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)
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
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' ]
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')
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)
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')
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' )
def test_get_container_compression_default(self): with patch.dict('os.environ', {'HOME': './'}): runtime_config = RuntimeConfig() assert runtime_config.get_container_compression() == 'xz'
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