diff --git a/doc/source/concept_and_workflow/shell_scripts.rst b/doc/source/concept_and_workflow/shell_scripts.rst index db1b8374b81..4d2d16f81f3 100644 --- a/doc/source/concept_and_workflow/shell_scripts.rst +++ b/doc/source/concept_and_workflow/shell_scripts.rst @@ -53,6 +53,53 @@ bit is set (in that case a shebang is mandatory) otherwise they will be invoked via the BASH. If a script exits with a non-zero exit code then {kiwi} will report the failure and abort the image creation. +Developing/Debugging Scripts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When creating a custom script it usually takes some iterations of +try and testing until a final stable state is reached. To support +developers with this task {kiwi} calls scripts associated with a +`screen` session. The connection to `screen` is only done if {kiwi} +is called with the `--debug` option. + +In this mode a script can start like the following template: + +.. code:: bash + + # The magic bits are still not set + + echo "break" + /bin/bash + +At call time of the script a `screen` session executes and you get +access to the break in shell. From this environment the needed script +code can be implemented. Once the shell is closed the {kiwi} process +continues. + +Apart from providing a full featured terminal throughout the +execution of the script code, there is also the advantage to +have control on the session during the process of the image +creation. Listing the active sessions for script execution +can be done as follows: + +.. code:: bash + + $ sudo screen -list + + There is a screen on: + 19699.pts-4.asterix (Attached) + 1 Socket in /run/screens/S-root. + +.. note:: + + As shown above the screen session(s) to execute script code + provides extended control which could also be considered a + security risk. Because of that {kiwi} only runs scripts through + `screen` when explicitly enabled via the `--debug` switch. + For production processes all scripts should run in their + native way and should not require a terminal to operate + correctly ! + Script Template for config.sh / images.sh ----------------------------------------- diff --git a/kiwi/system/setup.py b/kiwi/system/setup.py index e86cc0841f4..a7da9687912 100644 --- a/kiwi/system/setup.py +++ b/kiwi/system/setup.py @@ -998,7 +998,14 @@ def _call_script(self, name, option_list=None): script_path = os.path.join(self.root_dir, 'image', name) if os.path.exists(script_path): options = option_list or [] - command = ['chroot', self.root_dir] + if log.getLogLevel() == logging.DEBUG: + # In debug mode run scripts in a screen session to + # allow attaching and debugging + command = ['screen', '-t', '-X', 'chroot', self.root_dir] + else: + # In standard mode run scripts without a terminal + # associated to them + command = ['chroot', self.root_dir] if not Path.access(script_path, os.X_OK): command.append('bash') command.append( @@ -1031,9 +1038,17 @@ def _call_script_no_chroot( 'cd', working_directory, '&&', 'bash', '--norc', script_path, ' '.join(option_list) ] - config_script = Command.call( - ['bash', '-c', ' '.join(bash_command)] - ) + if log.getLogLevel() == logging.DEBUG: + # In debug mode run scripts in a screen session to + # allow attaching and debugging + config_script = Command.call( + ['screen', '-t', '-X', 'bash', '-c', ' '.join(bash_command)] + ) + else: + # In standard mode run script through bash + config_script = Command.call( + ['bash', '-c', ' '.join(bash_command)] + ) process = CommandProcess( command=config_script, log_topic='Calling ' + name + ' script' ) diff --git a/package/python-kiwi-pkgbuild-template b/package/python-kiwi-pkgbuild-template index 454d39832f5..6a70409d92e 100644 --- a/package/python-kiwi-pkgbuild-template +++ b/package/python-kiwi-pkgbuild-template @@ -21,7 +21,7 @@ build() { } package_python-kiwi(){ - depends=(python-docopt python-future python-lxml python-requests python-setuptools python-six python-pyxattr python-yaml grub qemu squashfs-tools gptfdisk pacman e2fsprogs xfsprogs btrfs-progs libisoburn lvm2 mtools parted multipath-tools rsync tar shadow kiwi-man-pages) + depends=(python-docopt python-future python-lxml python-requests python-setuptools python-six python-pyxattr python-yaml grub qemu squashfs-tools gptfdisk pacman e2fsprogs xfsprogs btrfs-progs libisoburn lvm2 mtools parted multipath-tools rsync tar shadow screen kiwi-man-pages) optdepends=('gnupg: keyring creation for APT package manager') cd kiwi-${pkgver} python setup.py install --root="${pkgdir}/" --optimize=1 --skip-build diff --git a/package/python-kiwi-spec-template b/package/python-kiwi-spec-template index 6ebe23a31e0..5064dc03d4c 100644 --- a/package/python-kiwi-spec-template +++ b/package/python-kiwi-spec-template @@ -324,6 +324,7 @@ Group: %{pygroup} Obsoletes: python2-kiwi Conflicts: python2-kiwi Conflicts: kiwi-man-pages < %{version} +Requires: screen Requires: python%{python3_pkgversion} >= 3.6 %if 0%{?ubuntu} || 0%{?debian} Requires: python%{python3_pkgversion}-yaml diff --git a/test/unit/system/setup_test.py b/test/unit/system/setup_test.py index 24ea0e3ec3a..c41f996ae09 100644 --- a/test/unit/system/setup_test.py +++ b/test/unit/system/setup_test.py @@ -705,6 +705,7 @@ def test_call_excutable_post_bootstrap_script( ['chroot', 'root_dir', 'image/post_bootstrap.sh'], {} ) + @patch('kiwi.logger.Logger.getLogLevel') @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @@ -714,8 +715,9 @@ def test_call_excutable_post_bootstrap_script( @patch('copy.deepcopy') def test_call_disk_script( self, mock_copy_deepcopy, mock_access, mock_stat, mock_os_path, - mock_watch, mock_command, mock_Profile + mock_watch, mock_command, mock_Profile, mock_getLogLevel ): + mock_getLogLevel.return_value = logging.DEBUG mock_copy_deepcopy.return_value = {} profile = Mock() mock_Profile.return_value = profile @@ -731,7 +733,10 @@ def test_call_disk_script( self.setup.call_disk_script() mock_copy_deepcopy.assert_called_once_with(os.environ) mock_command.assert_called_once_with( - ['chroot', 'root_dir', 'bash', 'image/disk.sh'], {} + [ + 'screen', '-t', '-X', + 'chroot', 'root_dir', 'bash', 'image/disk.sh' + ], {} ) @patch('kiwi.system.setup.Profile') @@ -787,13 +792,16 @@ def test_call_edit_boot_config_script( 'ext4 1' ]) + @patch('kiwi.logger.Logger.getLogLevel') @patch('kiwi.command.Command.call') @patch('kiwi.command_process.CommandProcess.poll_and_watch') @patch('os.path.exists') @patch('os.path.abspath') def test_call_edit_boot_install_script( - self, mock_abspath, mock_exists, mock_watch, mock_command + self, mock_abspath, mock_exists, mock_watch, mock_command, + mock_getLogLevel ): + mock_getLogLevel.return_value = logging.DEBUG result_type = namedtuple( 'result_type', ['stderr', 'returncode'] ) @@ -807,11 +815,15 @@ def test_call_edit_boot_install_script( mock_abspath.assert_called_once_with( 'root_dir/image/edit_boot_install.sh' ) - mock_command.assert_called_once_with([ - 'bash', '-c', - 'cd root_dir && bash --norc /root_dir/image/edit_boot_install.sh ' - 'my_image.raw /dev/mapper/loop0p1' - ]) + mock_command.assert_called_once_with( + [ + 'screen', '-t', '-X', + 'bash', '-c', + 'cd root_dir && bash --norc ' + '/root_dir/image/edit_boot_install.sh ' + 'my_image.raw /dev/mapper/loop0p1' + ] + ) @patch('kiwi.system.setup.Profile') @patch('kiwi.command.Command.call')