mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Improve parsing of 'systemctl show' output
This commit is contained in:
parent
fc0bf87c20
commit
2d9d1762ba
2 changed files with 86 additions and 20 deletions
|
@ -259,6 +259,40 @@ def is_running_service(service_status):
|
|||
def request_was_ignored(out):
|
||||
return '=' not in out and 'ignoring request' in out
|
||||
|
||||
def parse_systemctl_show(lines):
|
||||
# The output of 'systemctl show' can contain values that span multiple lines. At first glance it
|
||||
# appears that such values are always surrounded by {}, so the previous version of this code
|
||||
# assumed that any value starting with { was a multi-line value; it would then consume lines
|
||||
# until it saw a line that ended with }. However, it is possible to have a single-line value
|
||||
# that starts with { but does not end with } (this could happen in the value for Description=,
|
||||
# for example), and the previous version of this code would then consume all remaining lines as
|
||||
# part of that value. Cryptically, this would lead to Ansible reporting that the service file
|
||||
# couldn't be found.
|
||||
#
|
||||
# To avoid this issue, the following code only accepts multi-line values for keys whose names
|
||||
# start with Exec (e.g., ExecStart=), since these are the only keys whose values are known to
|
||||
# span multiple lines.
|
||||
parsed = {}
|
||||
multival = []
|
||||
k = None
|
||||
for line in lines:
|
||||
if k is None:
|
||||
if '=' in line:
|
||||
k, v = line.split('=', 1)
|
||||
if k.startswith('Exec') and v.lstrip().startswith('{'):
|
||||
if not v.rstrip().endswith('}'):
|
||||
multival.append(v)
|
||||
continue
|
||||
parsed[k] = v.strip()
|
||||
k = None
|
||||
else:
|
||||
multival.append(line)
|
||||
if line.rstrip().endswith('}'):
|
||||
parsed[k] = '\n'.join(multival).strip()
|
||||
multival = []
|
||||
k = None
|
||||
return parsed
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Main control flow
|
||||
|
@ -320,27 +354,8 @@ def main():
|
|||
|
||||
elif rc == 0:
|
||||
# load return of systemctl show into dictionary for easy access and return
|
||||
multival = []
|
||||
if out:
|
||||
k = None
|
||||
for line in to_native(out).split('\n'): # systemd can have multiline values delimited with {}
|
||||
if line.strip():
|
||||
if k is None:
|
||||
if '=' in line:
|
||||
k,v = line.split('=', 1)
|
||||
if v.lstrip().startswith('{'):
|
||||
if not v.rstrip().endswith('}'):
|
||||
multival.append(line)
|
||||
continue
|
||||
result['status'][k] = v.strip()
|
||||
k = None
|
||||
else:
|
||||
if line.rstrip().endswith('}'):
|
||||
result['status'][k] = '\n'.join(multival).strip()
|
||||
multival = []
|
||||
k = None
|
||||
else:
|
||||
multival.append(line)
|
||||
result['status'] = parse_systemctl_show(to_native(out).split('\n'))
|
||||
|
||||
is_systemd = 'LoadState' in result['status'] and result['status']['LoadState'] != 'not-found'
|
||||
|
||||
|
|
51
test/units/modules/system/test_systemd.py
Normal file
51
test/units/modules/system/test_systemd.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
import os
|
||||
import tempfile
|
||||
|
||||
from ansible.compat.tests import unittest
|
||||
from ansible.modules.system.systemd import parse_systemctl_show
|
||||
|
||||
|
||||
class ParseSystemctlShowTestCase(unittest.TestCase):
|
||||
|
||||
def test_simple(self):
|
||||
lines = [
|
||||
'Type=simple',
|
||||
'Restart=no',
|
||||
'Requires=system.slice sysinit.target',
|
||||
'Description=Blah blah blah',
|
||||
]
|
||||
parsed = parse_systemctl_show(lines)
|
||||
self.assertEqual(parsed, {
|
||||
'Type': 'simple',
|
||||
'Restart': 'no',
|
||||
'Requires': 'system.slice sysinit.target',
|
||||
'Description': 'Blah blah blah',
|
||||
})
|
||||
|
||||
def test_multiline_exec(self):
|
||||
# This was taken from a real service that specified "ExecStart=/bin/echo foo\nbar"
|
||||
lines = [
|
||||
'Type=simple',
|
||||
'ExecStart={ path=/bin/echo ; argv[]=/bin/echo foo',
|
||||
'bar ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }',
|
||||
'Description=blah',
|
||||
]
|
||||
parsed = parse_systemctl_show(lines)
|
||||
self.assertEqual(parsed, {
|
||||
'Type': 'simple',
|
||||
'ExecStart': '{ path=/bin/echo ; argv[]=/bin/echo foo\nbar ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }',
|
||||
'Description': 'blah',
|
||||
})
|
||||
|
||||
def test_single_line_with_brace(self):
|
||||
lines = [
|
||||
'Type=simple',
|
||||
'Description={ this is confusing',
|
||||
'Restart=no',
|
||||
]
|
||||
parsed = parse_systemctl_show(lines)
|
||||
self.assertEqual(parsed, {
|
||||
'Type': 'simple',
|
||||
'Description': '{ this is confusing',
|
||||
'Restart': 'no',
|
||||
})
|
Loading…
Reference in a new issue