1
# Copyright 2014-2015 Canonical Limited.
3
# This file is part of charm-helpers.
5
# charm-helpers is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU Lesser General Public License version 3 as
7
# published by the Free Software Foundation.
9
# charm-helpers is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU Lesser General Public License for more details.
14
# You should have received a copy of the GNU Lesser General Public License
15
# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1
17
"""Tools for working with the host system"""
2
18
# Copyright 2012 Canonical Ltd.
54
71
def service_running(service):
55
72
"""Determine whether a system service is running"""
57
output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)
74
output = subprocess.check_output(
75
['service', service, 'status'],
76
stderr=subprocess.STDOUT).decode('UTF-8')
58
77
except subprocess.CalledProcessError:
67
86
def service_available(service_name):
68
87
"""Determine whether a system service is available"""
70
subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
71
except subprocess.CalledProcessError:
89
subprocess.check_output(
90
['service', service_name, 'status'],
91
stderr=subprocess.STDOUT).decode('UTF-8')
92
except subprocess.CalledProcessError as e:
93
return 'unrecognized service' not in e.output
120
def add_group(group_name, system_group=False):
121
"""Add a group to the system"""
123
group_info = grp.getgrnam(group_name)
124
log('group {0} already exists!'.format(group_name))
126
log('creating group {0}'.format(group_name))
129
cmd.append('--system')
134
cmd.append(group_name)
135
subprocess.check_call(cmd)
136
group_info = grp.getgrnam(group_name)
99
140
def add_user_to_group(username, group):
100
141
"""Add a user to a group"""
130
171
subprocess.check_call(cmd)
133
def mkdir(path, owner='root', group='root', perms=0555, force=False):
174
def mkdir(path, owner='root', group='root', perms=0o555, force=False):
134
175
"""Create a directory"""
135
176
log("Making dir {} {}:{} {:o}".format(path, owner, group,
137
178
uid = pwd.getpwnam(owner).pw_uid
138
179
gid = grp.getgrnam(group).gr_gid
139
180
realpath = os.path.abspath(path)
140
if os.path.exists(realpath):
141
if force and not os.path.isdir(realpath):
181
path_exists = os.path.exists(realpath)
182
if path_exists and force:
183
if not os.path.isdir(realpath):
142
184
log("Removing non-directory file {} prior to mkdir()".format(path))
143
185
os.unlink(realpath)
186
os.makedirs(realpath, perms)
187
elif not path_exists:
145
188
os.makedirs(realpath, perms)
146
189
os.chown(realpath, uid, gid)
149
def write_file(path, content, owner='root', group='root', perms=0444):
150
"""Create or overwrite a file with the contents of a string"""
190
os.chmod(realpath, perms)
193
def write_file(path, content, owner='root', group='root', perms=0o444):
194
"""Create or overwrite a file with the contents of a byte string."""
151
195
log("Writing file {} {}:{} {:o}".format(path, owner, group, perms))
152
196
uid = pwd.getpwnam(owner).pw_uid
153
197
gid = grp.getgrnam(group).gr_gid
154
with open(path, 'w') as target:
198
with open(path, 'wb') as target:
155
199
os.fchown(target.fileno(), uid, gid)
156
200
os.fchmod(target.fileno(), perms)
157
201
target.write(content)
177
221
cmd_args.extend([device, mountpoint])
179
223
subprocess.check_output(cmd_args)
180
except subprocess.CalledProcessError, e:
224
except subprocess.CalledProcessError as e:
181
225
log('Error mounting {} at {}\n{}'.format(device, mountpoint, e.output))
191
235
cmd_args = ['umount', mountpoint]
193
237
subprocess.check_output(cmd_args)
194
except subprocess.CalledProcessError, e:
238
except subprocess.CalledProcessError as e:
195
239
log('Error unmounting {}\n{}'.format(mountpoint, e.output))
209
253
return system_mounts
213
"""Generate a md5 hash of the contents of 'path' or None if not found """
256
def file_hash(path, hash_type='md5'):
258
Generate a hash checksum of the contents of 'path' or None if not found.
260
:param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
261
such as md5, sha1, sha256, sha512, etc.
214
263
if os.path.exists(path):
216
with open(path, 'r') as source:
217
h.update(source.read()) # IGNORE:E1101 - it does have update
264
h = getattr(hashlib, hash_type)()
265
with open(path, 'rb') as source:
266
h.update(source.read())
218
267
return h.hexdigest()
272
def check_hash(path, checksum, hash_type='md5'):
274
Validate a file using a cryptographic checksum.
276
:param str checksum: Value of the checksum used to validate the file.
277
:param str hash_type: Hash algorithm used to generate `checksum`.
278
Can be any hash alrgorithm supported by :mod:`hashlib`,
279
such as md5, sha1, sha256, sha512, etc.
280
:raises ChecksumError: If the file fails the checksum
283
actual_checksum = file_hash(path, hash_type)
284
if checksum != actual_checksum:
285
raise ChecksumError("'%s' != '%s'" % (checksum, actual_checksum))
288
class ChecksumError(ValueError):
223
292
def restart_on_change(restart_map, stopstart=False):
224
293
"""Restart services based on configuration files changing
270
339
def pwgen(length=None):
271
340
"""Generate a random pasword."""
272
341
if length is None:
342
# A random length is ok to use a weak PRNG
273
343
length = random.choice(range(35, 45))
274
344
alphanumeric_chars = [
275
l for l in (string.letters + string.digits)
345
l for l in (string.ascii_letters + string.digits)
276
346
if l not in 'l0QD1vAEIOUaeiou']
347
# Use a crypto-friendly PRNG (e.g. /dev/urandom) for making the
349
random_generator = random.SystemRandom()
278
random.choice(alphanumeric_chars) for _ in range(length)]
351
random_generator.choice(alphanumeric_chars) for _ in range(length)]
279
352
return(''.join(random_chars))
282
355
def list_nics(nic_type):
283
356
'''Return a list of nics of given type(s)'''
284
if isinstance(nic_type, basestring):
357
if isinstance(nic_type, six.string_types):
285
358
int_types = [nic_type]
287
360
int_types = nic_type
289
362
for int_type in int_types:
290
363
cmd = ['ip', 'addr', 'show', 'label', int_type + '*']
291
ip_output = subprocess.check_output(cmd).split('\n')
364
ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
292
365
ip_output = (line for line in ip_output if line)
293
366
for line in ip_output:
294
367
if line.split()[1].startswith(int_type):
295
interfaces.append(line.split()[1].replace(":", ""))
368
matched = re.search('.*: (' + int_type + r'[0-9]+\.[0-9]+)@.*', line)
370
interface = matched.groups()[0]
372
interface = line.split()[1].replace(":", "")
373
interfaces.append(interface)
296
375
return interfaces
316
395
def get_nic_hwaddr(nic):
317
396
cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
318
ip_output = subprocess.check_output(cmd)
397
ip_output = subprocess.check_output(cmd).decode('UTF-8')
320
399
words = ip_output.split()
321
400
if 'link/ether' in words:
330
409
* 0 => Installed revno is the same as supplied arg
331
410
* -1 => Installed revno is less than supplied arg
412
This function imports apt_cache function from charmhelpers.fetch if
413
the pkgcache argument is None. Be sure to add charmhelpers.fetch if
414
you call this function, or pass an apt_pkg.Cache() instance.
335
from charmhelpers.fetch import apt_cache
418
from charmhelpers.fetch import apt_cache
337
419
pkgcache = apt_cache()
338
420
pkg = pkgcache[package]
339
421
return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
351
def chownr(path, owner, group):
433
def chownr(path, owner, group, follow_links=True):
352
434
uid = pwd.getpwnam(owner).pw_uid
353
435
gid = grp.getgrnam(group).gr_gid
355
441
for root, dirs, files in os.walk(path):
356
442
for name in dirs + files:
357
443
full = os.path.join(root, name)
358
444
broken_symlink = os.path.lexists(full) and not os.path.exists(full)
359
445
if not broken_symlink:
360
os.chown(full, uid, gid)
446
chown(full, uid, gid)
449
def lchownr(path, owner, group):
450
chownr(path, owner, group, follow_links=False)