126
128
return subprocess.call(cmd) == 0
131
_UPSTART_CONF = "/etc/init/{}.conf"
132
_INIT_D_CONF = "/etc/init.d/{}"
129
135
def service_running(service_name):
130
136
"""Determine whether a system service is running"""
131
137
if init_is_systemd():
132
138
return service('is-active', service_name)
135
output = subprocess.check_output(
136
['service', service_name, 'status'],
137
stderr=subprocess.STDOUT).decode('UTF-8')
138
except subprocess.CalledProcessError:
141
if ("start/running" in output or "is running" in output):
140
if os.path.exists(_UPSTART_CONF.format(service_name)):
142
output = subprocess.check_output(
143
['status', service_name],
144
stderr=subprocess.STDOUT).decode('UTF-8')
145
except subprocess.CalledProcessError:
148
# This works for upstart scripts where the 'service' command
149
# returns a consistent string to represent running 'start/running'
150
if "start/running" in output:
152
elif os.path.exists(_INIT_D_CONF.format(service_name)):
153
# Check System V scripts init script return codes
154
return service('status', service_name)
147
158
def service_available(service_name):
162
173
def init_is_systemd():
174
"""Return True if the host system uses systemd, False otherwise."""
163
175
return os.path.isdir(SYSTEMD_SYSTEM)
166
178
def adduser(username, password=None, shell='/bin/bash', system_user=False,
167
primary_group=None, secondary_groups=None):
169
Add a user to the system.
179
primary_group=None, secondary_groups=None, uid=None):
180
"""Add a user to the system.
171
182
Will log but otherwise succeed if the user already exists.
174
185
:param str password: Password for user; if ``None``, create a system user
175
186
:param str shell: The default shell for the user
176
187
:param bool system_user: Whether to create a login or system user
177
:param str primary_group: Primary group for user; defaults to their username
188
:param str primary_group: Primary group for user; defaults to username
178
189
:param list secondary_groups: Optional list of additional groups
190
:param int uid: UID for user being created
180
192
:returns: The password database entry struct, as returned by `pwd.getpwnam`
183
195
user_info = pwd.getpwnam(username)
184
196
log('user {0} already exists!'.format(username))
198
user_info = pwd.getpwuid(int(uid))
199
log('user with uid {0} already exists!'.format(uid))
186
201
log('creating user {0}'.format(username))
187
202
cmd = ['useradd']
204
cmd.extend(['--uid', str(uid)])
188
205
if system_user or password is None:
189
206
cmd.append('--system')
219
236
return user_exists
222
def add_group(group_name, system_group=False):
223
"""Add a group to the system"""
240
"""Check if a uid exists"""
249
def group_exists(groupname):
250
"""Check if a group exists"""
252
grp.getgrnam(groupname)
260
"""Check if a gid exists"""
269
def add_group(group_name, system_group=False, gid=None):
270
"""Add a group to the system
272
Will log but otherwise succeed if the group already exists.
274
:param str group_name: group to create
275
:param bool system_group: Create system group
276
:param int gid: GID for user being created
278
:returns: The password database entry struct, as returned by `grp.getgrnam`
225
281
group_info = grp.getgrnam(group_name)
226
282
log('group {0} already exists!'.format(group_name))
284
group_info = grp.getgrgid(gid)
285
log('group with gid {0} already exists!'.format(gid))
228
287
log('creating group {0}'.format(group_name))
229
288
cmd = ['addgroup']
290
cmd.extend(['--gid', str(gid)])
231
292
cmd.append('--system')
302
363
def fstab_remove(mp):
303
"""Remove the given mountpoint entry from /etc/fstab
364
"""Remove the given mountpoint entry from /etc/fstab"""
305
365
return Fstab.remove_by_mountpoint(mp)
308
368
def fstab_add(dev, mp, fs, options=None):
309
"""Adds the given device entry to the /etc/fstab file
369
"""Adds the given device entry to the /etc/fstab file"""
311
370
return Fstab.add(dev, mp, fs, options=options)
365
424
def file_hash(path, hash_type='md5'):
367
Generate a hash checksum of the contents of 'path' or None if not found.
425
"""Generate a hash checksum of the contents of 'path' or None if not found.
369
427
:param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
370
428
such as md5, sha1, sha256, sha512, etc.
381
439
def path_hash(path):
383
Generate a hash checksum of all files matching 'path'. Standard wildcards
384
like '*' and '?' are supported, see documentation for the 'glob' module for
440
"""Generate a hash checksum of all files matching 'path'. Standard
441
wildcards like '*' and '?' are supported, see documentation for the 'glob'
442
module for more information.
387
444
:return: dict: A { filename: hash } dictionary for all matched files.
388
445
Empty if none found.
412
468
class ChecksumError(ValueError):
469
"""A class derived from Value error to indicate the checksum failed."""
416
def restart_on_change(restart_map, stopstart=False):
473
def restart_on_change(restart_map, stopstart=False, restart_functions=None):
417
474
"""Restart services based on configuration files changing
419
476
This function is used a decorator, for example::
431
488
restarted if any file matching the pattern got changed, created
432
489
or removed. Standard wildcards are supported, see documentation
433
490
for the 'glob' module for more information.
492
@param restart_map: {path_file_name: [service_name, ...]
493
@param stopstart: DEFAULT false; whether to stop, start OR restart
494
@param restart_functions: nonstandard functions to use to restart services
496
@returns result from decorated function
436
500
def wrapped_f(*args, **kwargs):
437
checksums = {path: path_hash(path) for path in restart_map}
440
for path in restart_map:
441
if path_hash(path) != checksums[path]:
442
restarts += restart_map[path]
443
services_list = list(OrderedDict.fromkeys(restarts))
445
for service_name in services_list:
446
service('restart', service_name)
448
for action in ['stop', 'start']:
449
for service_name in services_list:
450
service(action, service_name)
501
return restart_on_change_helper(
502
(lambda: f(*args, **kwargs)), restart_map, stopstart,
508
def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
509
restart_functions=None):
510
"""Helper function to perform the restart_on_change function.
512
This is provided for decorators to restart services if files described
513
in the restart_map have changed after an invocation of lambda_f().
515
@param lambda_f: function to call.
516
@param restart_map: {file: [service, ...]}
517
@param stopstart: whether to stop, start or restart a service
518
@param restart_functions: nonstandard functions to use to restart services
520
@returns result of lambda_f()
522
if restart_functions is None:
523
restart_functions = {}
524
checksums = {path: path_hash(path) for path in restart_map}
526
# create a list of lists of the services to restart
527
restarts = [restart_map[path]
528
for path in restart_map
529
if path_hash(path) != checksums[path]]
530
# create a flat list of ordered services without duplicates from lists
531
services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
533
actions = ('stop', 'start') if stopstart else ('restart',)
534
for service_name in services_list:
535
if service_name in restart_functions:
536
restart_functions[service_name](service_name)
538
for action in actions:
539
service(action, service_name)
455
543
def lsb_release():
456
544
"""Return /etc/lsb-release in a dict"""
559
647
def set_nic_mtu(nic, mtu):
560
'''Set MTU on a network interface'''
648
"""Set the Maximum Transmission Unit (MTU) on a network interface."""
561
649
cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
562
650
subprocess.check_call(cmd)
565
653
def get_nic_mtu(nic):
654
"""Return the Maximum Transmission Unit (MTU) for a network interface."""
566
655
cmd = ['ip', 'addr', 'show', nic]
567
656
ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
696
def chdir(directory):
697
"""Change the current working directory to a different directory for a code
698
block and return the previous directory after the block exits. Useful to
699
run commands from a specificed directory.
701
:param str directory: The directory path to change to for this context.
607
703
cur = os.getcwd()
705
yield os.chdir(directory)
614
710
def chownr(path, owner, group, follow_links=True, chowntopdir=False):
616
Recursively change user and group ownership of files and directories
711
"""Recursively change user and group ownership of files and directories
617
712
in given path. Doesn't chown path itself by default, only its children.
714
:param str path: The string path to start changing ownership.
715
:param str owner: The owner string to use when looking up the uid.
716
:param str group: The group string to use when looking up the gid.
619
717
:param bool follow_links: Also Chown links if True
620
718
:param bool chowntopdir: Also chown path itself if True
641
739
def lchownr(path, owner, group):
740
"""Recursively change user and group ownership of files and directories
741
in a given path, not following symbolic links. See the documentation for
742
'os.lchown' for more information.
744
:param str path: The string path to start changing ownership.
745
:param str owner: The owner string to use when looking up the uid.
746
:param str group: The group string to use when looking up the gid.
642
748
chownr(path, owner, group, follow_links=False)
645
751
def get_total_ram():
646
'''The total amount of system RAM in bytes.
752
"""The total amount of system RAM in bytes.
648
754
This is what is reported by the OS, and may be overcommitted when
649
755
there are multiple containers hosted on the same machine.
651
757
with open('/proc/meminfo', 'r') as f:
652
758
for line in f.readlines():