Merge ~ogayot/curtin:system-install-log-stderr into curtin:master

Proposed by Olivier Gayot
Status: Work in progress
Proposed branch: ~ogayot/curtin:system-install-log-stderr
Merge into: curtin:master
Diff against target: 115 lines (+23/-17)
2 files modified
curtin/distro.py (+5/-4)
tests/unittests/test_distro.py (+18/-13)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
curtin developers Pending
Review via email: mp+462051@code.launchpad.net

Commit message

system-install: log output of commands if they fail

Signed-off-by: Olivier Gayot <email address hidden>

Description of the change

If a subcommand fails as part of a `curtin system-install` invocation, curtin tries to show its output. Sadly, the output of the subcommand is not captured, so there is nothing to show.

Example:

Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Running command ['umount', '/target/dev'] with allowed return codes [0] (capture=False)
Mar 08 14:23:46 ubuntu-server systemd[1]: target-proc.mount: Deactivated successfully.
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: system install failed for ['openssh-server']: Unexpected error while running command.
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Command: ['unshare', '--fork', '--pid', '--mount-proc=/target/proc', '--', 'chroot', '/target', 'apt-get', '--quiet', '--assume-yes', '--option=Dpkg::options::=--force-unsafe-io', '--option=Dpkg::Options::=--force-confold', 'install', 'openssh-server']
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Exit code: 100
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Reason: -
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Stdout: ''
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: Stderr: ''
Mar 08 14:23:46 ubuntu-server subiquity_log.1543[10940]: finish: cmd-system-install: FAIL: curtin command system-install
Mar 08 14:23:46 ubuntu-server curtin_event.1543.10[10940]: finish: cmd-system-install: FAIL: curtin command system-install

NOTE that in the example above, the output of curtin system-install was redirected to the journal.

This change captures the output of subcommands so that there is something to show if a subcommand fails:

system install failed for ['openssh-server']: Unexpected error while running command.
Command: ['unshare', '--fork', '--pid', '--mount-proc=/target/proc', '--', 'chroot', '/target', 'apt-get', '--quiet', '--assume-yes', '--option=Dpkg::options::=--force-unsafe-io', '--option=Dpkg::Options::=--force-confold', 'install', 'openssh-server']
Exit code: 100
Reason: -
Stdout: Reading package lists...
        Building dependency tree...
        Reading state information...
        openssh-server is already the newest version (1:9.6p1-3ubuntu2).
        0 upgraded, 0 newly installed, 0 to remove and 130 not upgraded.
        1 not fully installed or removed.
        After this operation, 0 B of additional disk space will be used.
        Setting up openssh-server (1:9.6p1-3ubuntu2) ...
        needrestart is being skipped since dpkg has failed

Stderr: E: Can not write log (Is /dev/pts mounted?) - posix_openpt (19: No such device)
        Failed to connect to bus: No data available
        dpkg: error processing package openssh-server (--configure):
         installed openssh-server package post-installation script subprocess returned error exit status 1
        Errors were encountered while processing:
         openssh-server
        E: Sub-process /usr/bin/dpkg returned an error code (1)

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Olivier Gayot (ogayot) wrote :

I'm moving back this MP to "in progress". I think the change might hide existing logs when commands exit successfully. Would have to double check.

Unmerged commits

f322a95... by Olivier Gayot

system-install: log output of commands if they fail

Signed-off-by: Olivier Gayot <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/distro.py b/curtin/distro.py
2index 3284b69..5f9ad90 100644
3--- a/curtin/distro.py
4+++ b/curtin/distro.py
5@@ -259,11 +259,11 @@ def run_apt_command(mode, args=None, opts=None, env=None, target=None,
6 if clean and not download_only:
7 with ChrootableTarget(
8 target, allow_daemons=allow_daemons) as inchroot:
9- inchroot.subp(['apt-get', 'clean'])
10+ inchroot.subp(['apt-get', 'clean'], capture=True)
11 return cmd_rv
12
13 with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot:
14- return inchroot.subp(cmd, env=env)
15+ return inchroot.subp(cmd, env=env, capture=True)
16
17
18 def apt_install(mode, packages=None, opts=None, env=None, target=None,
19@@ -300,9 +300,10 @@ def apt_install(mode, packages=None, opts=None, env=None, target=None,
20 with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot:
21 if not assume_downloaded:
22 cmd_rv = inchroot.subp(cmd + dl_opts + packages, env=env,
23- retries=download_retries)
24+ retries=download_retries, capture=True)
25 if not download_only:
26- cmd_rv = inchroot.subp(cmd + inst_opts + packages, env=env)
27+ cmd_rv = inchroot.subp(cmd + inst_opts + packages, env=env,
28+ capture=True)
29
30 return cmd_rv
31
32diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
33index a28a741..b226e35 100644
34--- a/tests/unittests/test_distro.py
35+++ b/tests/unittests/test_distro.py
36@@ -313,7 +313,8 @@ class TestAptInstall(CiTestCase):
37 distro.run_apt_command('install', ['foobar', 'wark'])
38 m_apt_update.assert_called_once()
39 m_apt_install.assert_has_calls(expected_calls)
40- m_subp.assert_called_once_with(['apt-get', 'clean'], target='/')
41+ m_subp.assert_called_once_with(['apt-get', 'clean'], target='/',
42+ capture=True)
43
44 m_subp.reset_mock()
45 m_apt_install.reset_mock()
46@@ -336,10 +337,10 @@ class TestAptInstall(CiTestCase):
47 expected_calls = [
48 mock.call(cmd_prefix + ['install', '--download-only']
49 + ['foobar', 'wark'],
50- env=None, target='/', retries=None),
51+ env=None, target='/', retries=None, capture=True),
52 mock.call(cmd_prefix + ['install']
53 + ['foobar', 'wark'],
54- env=None, target='/'),
55+ env=None, target='/', capture=True),
56 ]
57
58 distro.apt_install('install', packages=['foobar', 'wark'])
59@@ -347,9 +348,9 @@ class TestAptInstall(CiTestCase):
60
61 expected_calls = [
62 mock.call(cmd_prefix + ['upgrade', '--download-only'],
63- env=None, target='/', retries=None),
64+ env=None, target='/', retries=None, capture=True),
65 mock.call(cmd_prefix + ['upgrade'],
66- env=None, target='/'),
67+ env=None, target='/', capture=True),
68 ]
69
70 m_subp.reset_mock()
71@@ -358,9 +359,9 @@ class TestAptInstall(CiTestCase):
72
73 expected_calls = [
74 mock.call(cmd_prefix + ['dist-upgrade', '--download-only'],
75- env=None, target='/', retries=None),
76+ env=None, target='/', retries=None, capture=True),
77 mock.call(cmd_prefix + ['dist-upgrade'],
78- env=None, target='/'),
79+ env=None, target='/', capture=True),
80 ]
81
82 m_subp.reset_mock()
83@@ -373,11 +374,12 @@ class TestAptInstall(CiTestCase):
84 m_subp.reset_mock()
85 distro.apt_install('install', ['git'], download_only=True)
86 m_subp.assert_called_once_with(expected_dl_cmd, env=None, target='/',
87- retries=None)
88+ retries=None, capture=True)
89
90 m_subp.reset_mock()
91 distro.apt_install('install', ['git'], assume_downloaded=True)
92- m_subp.assert_called_once_with(expected_inst_cmd, env=None, target='/')
93+ m_subp.assert_called_once_with(expected_inst_cmd, env=None, target='/',
94+ capture=True)
95
96 @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
97 @mock.patch('curtin.util.subp')
98@@ -588,10 +590,13 @@ class TestSystemUpgrade(CiTestCase):
99 auto_remove = apt_base + ['autoremove']
100 expected_calls = [
101 mock.call(dl_apt_cmd, env=env, retries=None,
102- target=paths.target_path(target)),
103- mock.call(inst_apt_cmd, env=env, target=paths.target_path(target)),
104- mock.call(['apt-get', 'clean'], target=paths.target_path(target)),
105- mock.call(auto_remove, env=env, target=paths.target_path(target)),
106+ target=paths.target_path(target), capture=True),
107+ mock.call(inst_apt_cmd, env=env, target=paths.target_path(target),
108+ capture=True),
109+ mock.call(['apt-get', 'clean'], target=paths.target_path(target),
110+ capture=True),
111+ mock.call(auto_remove, env=env, target=paths.target_path(target),
112+ capture=True),
113 ]
114 which_calls = [mock.call('eatmydata', target=target)]
115 apt_update_calls = [mock.call(target, env=env)]

Subscribers

People subscribed via source and target branches