Merge ~simpoir/ubuntu/+source/landscape-client:disco_pulse into ubuntu/+source/landscape-client:ubuntu/disco-devel
- Git
- lp:~simpoir/ubuntu/+source/landscape-client
- disco_pulse
- Merge into ubuntu/disco-devel
Proposed by
Simon Poirier
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Merge reported by: | Simon Poirier | ||||||||
Merged at revision: | 9aa8c7a66731d26dc42db4ce184d0e63ab3d3bb3 | ||||||||
Proposed branch: | ~simpoir/ubuntu/+source/landscape-client:disco_pulse | ||||||||
Merge into: | ubuntu/+source/landscape-client:ubuntu/disco-devel | ||||||||
Diff against target: |
346 lines (+312/-0) 5 files modified
debian/changelog (+8/-0) debian/patches/apt-pulse-1758529.patch (+27/-0) debian/patches/reporter_logging.patch (+19/-0) debian/patches/security_pocket_1848828.patch (+255/-0) debian/patches/series (+3/-0) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Desrochers (community) | Approve | ||
git-ubuntu developers | Pending | ||
Review via email: mp+373537@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
- dc814d0... by Simon Poirier
-
d/p/apt-
pulse-1758529. patch: Override the apt progress pulse (LP: #1758529) - bbda002... by Simon Poirier
-
d/p/security_
pocket_ 1848828. patch
d/p/reporter_logging. patch - 9aa8c7a... by Simon Poirier
-
Updated changelog:
* d/p/apt-
pulse-1758529. patch: Override the apt progress pulse (LP: #1758529)
* d/p/security_pocket_ 1848828. patch (LP: #1848828)
* d/p/reporter_logging. patch (LP: #1848828)<Paste>
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/debian/changelog b/debian/changelog | |||
2 | index 48d61e1..a868976 100644 | |||
3 | --- a/debian/changelog | |||
4 | +++ b/debian/changelog | |||
5 | @@ -1,3 +1,11 @@ | |||
6 | 1 | landscape-client (18.01-0ubuntu7.2) disco; urgency=medium | ||
7 | 2 | |||
8 | 3 | * d/p/apt-pulse-1758529.patch: Override the apt progress pulse (LP: #1758529) | ||
9 | 4 | * d/p/security_pocket_1848828.patch (LP: #1848828) | ||
10 | 5 | * d/p/reporter_logging.patch (LP: #1848828) | ||
11 | 6 | |||
12 | 7 | -- Simon Poirier <simon.poirier@canonical.com> Tue, 22 Oct 2019 18:54:21 -0400 | ||
13 | 8 | |||
14 | 1 | landscape-client (18.01-0ubuntu7.1) disco; urgency=medium | 9 | landscape-client (18.01-0ubuntu7.1) disco; urgency=medium |
15 | 2 | 10 | ||
16 | 3 | * d/p/product-name-vminfo-1828217.patch: Add product_name to things scanned | 11 | * d/p/product-name-vminfo-1828217.patch: Add product_name to things scanned |
17 | diff --git a/debian/patches/apt-pulse-1758529.patch b/debian/patches/apt-pulse-1758529.patch | |||
18 | 4 | new file mode 100644 | 12 | new file mode 100644 |
19 | index 0000000..14db379 | |||
20 | --- /dev/null | |||
21 | +++ b/debian/patches/apt-pulse-1758529.patch | |||
22 | @@ -0,0 +1,27 @@ | |||
23 | 1 | Description: Override the apt progress pulse. | ||
24 | 2 | This should remove the tracebacks under python3 + bionic. | ||
25 | 3 | Author: Simon Poirier <simon.poirier@canonical.com> | ||
26 | 4 | Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/8e52e1d3418b3b042a2f305d82336b9866914e43 | ||
27 | 5 | Bug-Ubuntu: https://bugs.launchpad.net/bugs/1758529 | ||
28 | 6 | Last-Update: 2019-09-30 | ||
29 | 7 | |||
30 | 8 | diff --git a/landscape/lib/apt/package/facade.py b/landscape/lib/apt/package/facade.py | ||
31 | 9 | index 1c026beb..15c57ab5 100644 | ||
32 | 10 | --- a/landscape/lib/apt/package/facade.py | ||
33 | 11 | +++ b/landscape/lib/apt/package/facade.py | ||
34 | 12 | @@ -57,6 +57,15 @@ class LandscapeAcquireProgress(AcquireProgress): | ||
35 | 13 | fcntl.ioctl API differences for different Python versions. | ||
36 | 14 | """ | ||
37 | 15 | |||
38 | 16 | + def pulse(self, owner): | ||
39 | 17 | + """Override updating the acquire progress, which needs a tty. | ||
40 | 18 | + | ||
41 | 19 | + Under Python3, StringIO.fileno() raises UnsupportedOperation instead | ||
42 | 20 | + of an AttributeError. This would be uncaught by apt, thus we force a | ||
43 | 21 | + NOOP here. | ||
44 | 22 | + """ | ||
45 | 23 | + return True | ||
46 | 24 | + | ||
47 | 25 | |||
48 | 26 | class LandscapeInstallProgress(InstallProgress): | ||
49 | 27 | |||
50 | diff --git a/debian/patches/reporter_logging.patch b/debian/patches/reporter_logging.patch | |||
51 | 0 | new file mode 100644 | 28 | new file mode 100644 |
52 | index 0000000..42d8535 | |||
53 | --- /dev/null | |||
54 | +++ b/debian/patches/reporter_logging.patch | |||
55 | @@ -0,0 +1,19 @@ | |||
56 | 1 | Description: Remove extra keyword which breaks log format | ||
57 | 2 | Author: Simon Poirier <simon.poirier@canonical.com> | ||
58 | 3 | Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/ad37c28e80ebfb85f43bdabece9946d3a062c0e9 | ||
59 | 4 | Bug-Ubuntu: https://bugs.launchpad.net/bugs/1848828 | ||
60 | 5 | Last-Update: 2019-10-22 | ||
61 | 6 | |||
62 | 7 | diff --git a/landscape/client/package/reporter.py b/landscape/client/package/reporter.py | ||
63 | 8 | index 9f34f2a1..39b67df3 100644 | ||
64 | 9 | --- a/landscape/client/package/reporter.py | ||
65 | 10 | +++ b/landscape/client/package/reporter.py | ||
66 | 11 | @@ -755,7 +755,7 @@ class PackageReporter(PackageTaskHandler): | ||
67 | 12 | "%(not_locked)d not locked, " | ||
68 | 13 | "%(not_auto)d not autoremovable, " | ||
69 | 14 | "%(not_security)d not security.", | ||
70 | 15 | - extra=dict( | ||
71 | 16 | + dict( | ||
72 | 17 | installed=len(new_installed), available=len(new_available), | ||
73 | 18 | upgrades=len(new_upgrades), locked=len(new_locked), | ||
74 | 19 | auto=len(new_autoremovable), not_installed=len(not_installed), | ||
75 | diff --git a/debian/patches/security_pocket_1848828.patch b/debian/patches/security_pocket_1848828.patch | |||
76 | 0 | new file mode 100644 | 20 | new file mode 100644 |
77 | index 0000000..cbcd104 | |||
78 | --- /dev/null | |||
79 | +++ b/debian/patches/security_pocket_1848828.patch | |||
80 | @@ -0,0 +1,255 @@ | |||
81 | 1 | Description: report packages from security-pocket | ||
82 | 2 | Author: Simon Poirier <simon.poirier@canonical.com> | ||
83 | 3 | Origin: upstream, https://github.com/CanonicalLtd/landscape-client/commit/77c54c2e9c01255f8ed43935b47b65abcd62d021 | ||
84 | 4 | Bug-Ubuntu: https://bugs.launchpad.net/bugs/1848828 | ||
85 | 5 | Last-Update: 2019-10-22 | ||
86 | 6 | |||
87 | 7 | diff --git a/Makefile.travis b/Makefile.travis | ||
88 | 8 | index d82d473d..b532241a 100644 | ||
89 | 9 | --- a/Makefile.travis | ||
90 | 10 | +++ b/Makefile.travis | ||
91 | 11 | @@ -11,6 +11,7 @@ pipinstallpythonapt: pipinstallpythonapt_deps | ||
92 | 12 | pipinstallpythonapt_deps: | ||
93 | 13 | pip install pyopenssl | ||
94 | 14 | pip install service_identity | ||
95 | 15 | + sudo apt-get update | ||
96 | 16 | sudo apt-get -y build-dep python-apt python3-apt | ||
97 | 17 | sudo apt-get -y install libapt-pkg-dev | ||
98 | 18 | |||
99 | 19 | diff --git a/landscape/client/package/reporter.py b/landscape/client/package/reporter.py | ||
100 | 20 | index c48a4c04..6054ec3b 100644 | ||
101 | 21 | --- a/landscape/client/package/reporter.py | ||
102 | 22 | +++ b/landscape/client/package/reporter.py | ||
103 | 23 | @@ -609,6 +609,9 @@ class PackageReporter(PackageTaskHandler): | ||
104 | 24 | - are now set, and were not; | ||
105 | 25 | - were previously set but are not anymore; | ||
106 | 26 | |||
107 | 27 | + Also, packages coming from the security pocket will be | ||
108 | 28 | + reported as such. | ||
109 | 29 | + | ||
110 | 30 | In all cases, the server is notified of the new situation | ||
111 | 31 | with a "packages" message. | ||
112 | 32 | |||
113 | 33 | @@ -622,14 +625,17 @@ class PackageReporter(PackageTaskHandler): | ||
114 | 34 | old_upgrades = set(self._store.get_available_upgrades()) | ||
115 | 35 | old_locked = set(self._store.get_locked()) | ||
116 | 36 | old_autoremovable = set(self._store.get_autoremovable()) | ||
117 | 37 | + old_security = set(self._store.get_security()) | ||
118 | 38 | |||
119 | 39 | current_installed = set() | ||
120 | 40 | current_available = set() | ||
121 | 41 | current_upgrades = set() | ||
122 | 42 | current_locked = set() | ||
123 | 43 | current_autoremovable = set() | ||
124 | 44 | + current_security = set() | ||
125 | 45 | lsb = parse_lsb_release(LSB_RELEASE_FILENAME) | ||
126 | 46 | backports_archive = "{}-backports".format(lsb["code-name"]) | ||
127 | 47 | + security_archive = "{}-security".format(lsb["code-name"]) | ||
128 | 48 | |||
129 | 49 | for package in self._facade.get_packages(): | ||
130 | 50 | # Don't include package versions from the official backports | ||
131 | 51 | @@ -664,6 +670,13 @@ class PackageReporter(PackageTaskHandler): | ||
132 | 52 | if self._facade.is_package_upgrade(package): | ||
133 | 53 | current_upgrades.add(id) | ||
134 | 54 | |||
135 | 55 | + # Is this package present in the security pocket? | ||
136 | 56 | + security_origins = any( | ||
137 | 57 | + origin for origin in package.origins | ||
138 | 58 | + if origin.archive == security_archive) | ||
139 | 59 | + if security_origins: | ||
140 | 60 | + current_security.add(id) | ||
141 | 61 | + | ||
142 | 62 | for package in self._facade.get_locked_packages(): | ||
143 | 63 | hash = self._facade.get_package_hash(package) | ||
144 | 64 | id = self._store.get_hash_id(hash) | ||
145 | 65 | @@ -675,12 +688,14 @@ class PackageReporter(PackageTaskHandler): | ||
146 | 66 | new_upgrades = current_upgrades - old_upgrades | ||
147 | 67 | new_locked = current_locked - old_locked | ||
148 | 68 | new_autoremovable = current_autoremovable - old_autoremovable | ||
149 | 69 | + new_security = current_security - old_security | ||
150 | 70 | |||
151 | 71 | not_installed = old_installed - current_installed | ||
152 | 72 | not_available = old_available - current_available | ||
153 | 73 | not_upgrades = old_upgrades - current_upgrades | ||
154 | 74 | not_locked = old_locked - current_locked | ||
155 | 75 | not_autoremovable = old_autoremovable - current_autoremovable | ||
156 | 76 | + not_security = old_security - current_security | ||
157 | 77 | |||
158 | 78 | message = {} | ||
159 | 79 | if new_installed: | ||
160 | 80 | @@ -703,6 +718,13 @@ class PackageReporter(PackageTaskHandler): | ||
161 | 81 | message["not-autoremovable"] = list( | ||
162 | 82 | sequence_to_ranges(sorted(not_autoremovable))) | ||
163 | 83 | |||
164 | 84 | + if new_security: | ||
165 | 85 | + message["security"] = list( | ||
166 | 86 | + sequence_to_ranges(sorted(new_security))) | ||
167 | 87 | + if not_security: | ||
168 | 88 | + message["not-security"] = list( | ||
169 | 89 | + sequence_to_ranges(sorted(not_security))) | ||
170 | 90 | + | ||
171 | 91 | if not_installed: | ||
172 | 92 | message["not-installed"] = \ | ||
173 | 93 | list(sequence_to_ranges(sorted(not_installed))) | ||
174 | 94 | @@ -722,17 +744,25 @@ class PackageReporter(PackageTaskHandler): | ||
175 | 95 | message["type"] = "packages" | ||
176 | 96 | result = self.send_message(message) | ||
177 | 97 | |||
178 | 98 | - logging.info("Queuing message with changes in known packages: " | ||
179 | 99 | - "%d installed, %d available, %d available upgrades, " | ||
180 | 100 | - "%d locked, %d autoremovable, %d not installed, " | ||
181 | 101 | - "%d not available, %d not available upgrades, " | ||
182 | 102 | - "%d not locked, %d not autoremovable. " | ||
183 | 103 | - % (len(new_installed), len(new_available), | ||
184 | 104 | - len(new_upgrades), len(new_locked), | ||
185 | 105 | - len(new_autoremovable), | ||
186 | 106 | - len(not_installed), len(not_available), | ||
187 | 107 | - len(not_upgrades), len(not_locked), | ||
188 | 108 | - len(not_autoremovable))) | ||
189 | 109 | + logging.info( | ||
190 | 110 | + "Queuing message with changes in known packages: " | ||
191 | 111 | + "%(installed)d installed, %(available)d available, " | ||
192 | 112 | + "%(upgrades)d available upgrades, %(locked)d locked, " | ||
193 | 113 | + "%(auto)d autoremovable, %(security)d security, " | ||
194 | 114 | + "%(not_installed)d not installed, " | ||
195 | 115 | + "%(not_available)d not available, " | ||
196 | 116 | + "%(not_upgrades)d not available upgrades, " | ||
197 | 117 | + "%(not_locked)d not locked, " | ||
198 | 118 | + "%(not_auto)d not autoremovable, " | ||
199 | 119 | + "%(not_security)d not security.", | ||
200 | 120 | + extra=dict( | ||
201 | 121 | + installed=len(new_installed), available=len(new_available), | ||
202 | 122 | + upgrades=len(new_upgrades), locked=len(new_locked), | ||
203 | 123 | + auto=len(new_autoremovable), not_installed=len(not_installed), | ||
204 | 124 | + not_available=len(not_available), | ||
205 | 125 | + not_upgrades=len(not_upgrades), not_locked=len(not_locked), | ||
206 | 126 | + not_auto=len(not_autoremovable), security=len(new_security), | ||
207 | 127 | + not_security=len(not_security))) | ||
208 | 128 | |||
209 | 129 | def update_currently_known(result): | ||
210 | 130 | if new_installed: | ||
211 | 131 | @@ -755,6 +785,10 @@ class PackageReporter(PackageTaskHandler): | ||
212 | 132 | self._store.remove_locked(not_locked) | ||
213 | 133 | if not_autoremovable: | ||
214 | 134 | self._store.remove_autoremovable(not_autoremovable) | ||
215 | 135 | + if new_security: | ||
216 | 136 | + self._store.add_security(new_security) | ||
217 | 137 | + if not_security: | ||
218 | 138 | + self._store.remove_security(not_security) | ||
219 | 139 | # Something has changed wrt the former run, let's update the | ||
220 | 140 | # timestamp and return True. | ||
221 | 141 | stamp_file = self._config.detect_package_changes_stamp | ||
222 | 142 | diff --git a/landscape/client/package/tests/test_reporter.py b/landscape/client/package/tests/test_reporter.py | ||
223 | 143 | index 851c8eb9..6500affb 100644 | ||
224 | 144 | --- a/landscape/client/package/tests/test_reporter.py | ||
225 | 145 | +++ b/landscape/client/package/tests/test_reporter.py | ||
226 | 146 | @@ -999,6 +999,47 @@ class PackageReporterAptTest(LandscapeTest): | ||
227 | 147 | self.assertFalse(result) | ||
228 | 148 | self.assertEqual([1], self.store.get_autoremovable()) | ||
229 | 149 | |||
230 | 150 | + @inlineCallbacks | ||
231 | 151 | + def test_detect_packages_from_security_pocket(self): | ||
232 | 152 | + """Packages versions coming from security are reported as such.""" | ||
233 | 153 | + message_store = self.broker_service.message_store | ||
234 | 154 | + message_store.set_accepted_types(["packages"]) | ||
235 | 155 | + lsb = parse_lsb_release(LSB_RELEASE_FILENAME) | ||
236 | 156 | + release_path = os.path.join(self.repository_dir, "Release") | ||
237 | 157 | + with open(release_path, "w") as release: | ||
238 | 158 | + release.write("Suite: {}-security".format(lsb["code-name"])) | ||
239 | 159 | + | ||
240 | 160 | + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) | ||
241 | 161 | + | ||
242 | 162 | + yield self.reporter.detect_packages_changes() | ||
243 | 163 | + | ||
244 | 164 | + self.assertMessages(message_store.get_pending_messages(), [{ | ||
245 | 165 | + "type": "packages", | ||
246 | 166 | + "available": [(1, 3)], | ||
247 | 167 | + "security": [(1, 3)], | ||
248 | 168 | + }]) | ||
249 | 169 | + self.assertEqual(sorted(self.store.get_available()), [1, 2, 3]) | ||
250 | 170 | + self.assertEqual(sorted(self.store.get_security()), [1, 2, 3]) | ||
251 | 171 | + | ||
252 | 172 | + @inlineCallbacks | ||
253 | 173 | + def test_detect_packages_not_from_security_pocket(self): | ||
254 | 174 | + """Packages versions removed from security are reported as such.""" | ||
255 | 175 | + message_store = self.broker_service.message_store | ||
256 | 176 | + message_store.set_accepted_types(["packages"]) | ||
257 | 177 | + | ||
258 | 178 | + self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3}) | ||
259 | 179 | + self.store.add_available([1, 2, 3]) | ||
260 | 180 | + self.store.add_security([1, 2]) | ||
261 | 181 | + | ||
262 | 182 | + yield self.reporter.detect_packages_changes() | ||
263 | 183 | + | ||
264 | 184 | + self.assertMessages(message_store.get_pending_messages(), [{ | ||
265 | 185 | + "type": "packages", | ||
266 | 186 | + "not-security": [1, 2], | ||
267 | 187 | + }]) | ||
268 | 188 | + self.assertEqual(sorted(self.store.get_available()), [1, 2, 3]) | ||
269 | 189 | + self.assertEqual(self.store.get_security(), []) | ||
270 | 190 | + | ||
271 | 191 | def test_detect_packages_changes_with_backports(self): | ||
272 | 192 | """ | ||
273 | 193 | Package versions coming from backports aren't considered to be | ||
274 | 194 | diff --git a/landscape/lib/apt/package/store.py b/landscape/lib/apt/package/store.py | ||
275 | 195 | index 2764f7cd..59d5ca4f 100644 | ||
276 | 196 | --- a/landscape/lib/apt/package/store.py | ||
277 | 197 | +++ b/landscape/lib/apt/package/store.py | ||
278 | 198 | @@ -232,6 +232,25 @@ class PackageStore(HashIdStore): | ||
279 | 199 | cursor.execute("SELECT id FROM autoremovable") | ||
280 | 200 | return [row[0] for row in cursor.fetchall()] | ||
281 | 201 | |||
282 | 202 | + @with_cursor | ||
283 | 203 | + def add_security(self, cursor, ids): | ||
284 | 204 | + for id in ids: | ||
285 | 205 | + cursor.execute("REPLACE INTO security VALUES (?)", (id,)) | ||
286 | 206 | + | ||
287 | 207 | + @with_cursor | ||
288 | 208 | + def remove_security(self, cursor, ids): | ||
289 | 209 | + id_list = ",".join(str(int(id)) for id in ids) | ||
290 | 210 | + cursor.execute("DELETE FROM security WHERE id IN (%s)" % id_list) | ||
291 | 211 | + | ||
292 | 212 | + @with_cursor | ||
293 | 213 | + def clear_security(self, cursor): | ||
294 | 214 | + cursor.execute("DELETE FROM security") | ||
295 | 215 | + | ||
296 | 216 | + @with_cursor | ||
297 | 217 | + def get_security(self, cursor): | ||
298 | 218 | + cursor.execute("SELECT id FROM security") | ||
299 | 219 | + return [row[0] for row in cursor.fetchall()] | ||
300 | 220 | + | ||
301 | 221 | @with_cursor | ||
302 | 222 | def add_installed(self, cursor, ids): | ||
303 | 223 | for id in ids: | ||
304 | 224 | @@ -450,6 +469,8 @@ def ensure_package_schema(db): | ||
305 | 225 | # try block. | ||
306 | 226 | cursor = db.cursor() | ||
307 | 227 | try: | ||
308 | 228 | + cursor.execute("CREATE TABLE security" | ||
309 | 229 | + " (id INTEGER PRIMARY KEY)") | ||
310 | 230 | cursor.execute("CREATE TABLE autoremovable" | ||
311 | 231 | " (id INTEGER PRIMARY KEY)") | ||
312 | 232 | cursor.execute("CREATE TABLE locked" | ||
313 | 233 | diff --git a/landscape/message_schemas/server_bound.py b/landscape/message_schemas/server_bound.py | ||
314 | 234 | index be06c8d6..a03d76c3 100644 | ||
315 | 235 | --- a/landscape/message_schemas/server_bound.py | ||
316 | 236 | +++ b/landscape/message_schemas/server_bound.py | ||
317 | 237 | @@ -367,13 +367,16 @@ PACKAGES = Message( | ||
318 | 238 | "locked": package_ids_or_ranges, | ||
319 | 239 | "autoremovable": package_ids_or_ranges, | ||
320 | 240 | "not-autoremovable": package_ids_or_ranges, | ||
321 | 241 | + "security": package_ids_or_ranges, | ||
322 | 242 | "not-installed": package_ids_or_ranges, | ||
323 | 243 | "not-available": package_ids_or_ranges, | ||
324 | 244 | "not-available-upgrades": package_ids_or_ranges, | ||
325 | 245 | - "not-locked": package_ids_or_ranges}, | ||
326 | 246 | + "not-locked": package_ids_or_ranges, | ||
327 | 247 | + "not-security": package_ids_or_ranges}, | ||
328 | 248 | optional=["installed", "available", "available-upgrades", "locked", | ||
329 | 249 | "not-available", "not-installed", "not-available-upgrades", | ||
330 | 250 | - "not-locked", "autoremovable", "not-autoremovable"]) | ||
331 | 251 | + "not-locked", "autoremovable", "not-autoremovable", "security", | ||
332 | 252 | + "not-security"]) | ||
333 | 253 | |||
334 | 254 | package_locks = List(Tuple(Unicode(), Unicode(), Unicode())) | ||
335 | 255 | PACKAGE_LOCKS = Message( | ||
336 | diff --git a/debian/patches/series b/debian/patches/series | |||
337 | index ebfbf13..7dd8fac 100644 | |||
338 | --- a/debian/patches/series | |||
339 | +++ b/debian/patches/series | |||
340 | @@ -9,3 +9,6 @@ unicode-tags-script.patch | |||
341 | 9 | mem-usage-1685885.patch | 9 | mem-usage-1685885.patch |
342 | 10 | product-name-vminfo-1828217.patch | 10 | product-name-vminfo-1828217.patch |
343 | 11 | stagger-launch-1788518.patch | 11 | stagger-launch-1788518.patch |
344 | 12 | apt-pulse-1758529.patch | ||
345 | 13 | security_pocket_1848828.patch | ||
346 | 14 | reporter_logging.patch |
lgtm