diff options
Diffstat (limited to '')
-rwxr-xr-x | utils/crm_clean.py | 46 | ||||
-rw-r--r-- | utils/crm_init.py | 251 | ||||
-rwxr-xr-x | utils/crm_pkg.py | 342 | ||||
-rwxr-xr-x | utils/crm_rpmcheck.py | 72 | ||||
-rw-r--r-- | utils/crm_script.py | 190 |
5 files changed, 901 insertions, 0 deletions
diff --git a/utils/crm_clean.py b/utils/crm_clean.py new file mode 100755 index 0000000..5bb79f0 --- /dev/null +++ b/utils/crm_clean.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 +import os +import sys +import shutil +errors = [] +mydir = os.path.dirname(os.path.abspath(sys.modules[__name__].__file__)) + + +def bad(path): + return ((not os.path.isabs(path)) or os.path.dirname(path) == '/' or + path.startswith('/var') or path.startswith('/usr') or + (not path.startswith(mydir))) + +for f in sys.argv[1:]: + if bad(f): + errors.append("cannot remove %s from %s" % (f, mydir)) + continue + try: + if os.path.isfile(f): + os.remove(f) + elif os.path.isdir(f): + if os.path.isfile(os.path.join(f, 'crm_script.debug')): + print(open(os.path.join(f, 'crm_script.debug')).read()) + + # to check whether this clean request came from health + # if it does, delete all except health-report + del_flag = 0 + for x in os.listdir(f): + if x.startswith("health-report"): + del_flag = 1 + + if del_flag == 1: + for x in os.listdir(f): + if x.startswith("health-report"): + continue + if os.path.isfile(x): + os.remove(x) + elif os.path.isdir(x): + shutil.rmtree(x) + else: + shutil.rmtree(f) + except OSError as e: + errors.append(e) +if errors: + print('\n'.join(errors), file=sys.stderr) + sys.exit(1) diff --git a/utils/crm_init.py b/utils/crm_init.py new file mode 100644 index 0000000..226ce83 --- /dev/null +++ b/utils/crm_init.py @@ -0,0 +1,251 @@ +import os +import pwd +import re +import platform +import socket +import crm_script + +PACKAGES = ['booth', 'cluster-glue', 'corosync', 'crmsh', 'csync2', 'drbd', + 'fence-agents', 'gfs2', 'gfs2-utils', 'hawk', 'ocfs2', + 'ocfs2-tools', 'pacemaker', 'pacemaker-mgmt', + 'resource-agents', 'sbd'] +SERVICES = ['sshd', 'ntp', 'corosync', 'pacemaker', 'hawk', 'SuSEfirewall2_init'] +SSH_KEY = os.path.expanduser('~/.ssh/id_rsa') +CSYNC2_KEY = '/etc/csync2/key_hagroup' +CSYNC2_CFG = '/etc/csync2/csync2.cfg' +COROSYNC_CONF = '/etc/corosync/corosync.conf' +SYSCONFIG_SBD = '/etc/sysconfig/sbd' +SYSCONFIG_FW = '/etc/sysconfig/SuSEfirewall2' +SYSCONFIG_FW_CLUSTER = '/etc/sysconfig/SuSEfirewall2.d/services/cluster' + + +def rpm_info(): + 'check installed packages' + return crm_script.rpmcheck(PACKAGES) + + +def service_info(service): + "Returns information about a given service" + active, enabled = 'unknown', 'unknown' + rc, out, err = crm_script.call(["/usr/bin/systemctl", "is-enabled", "%s.service" % (service)]) + if rc in (0, 1, 3) and out: + enabled = out.strip() + else: + return {'name': service, 'error': err.strip()} + rc, out, err = crm_script.call(["/usr/bin/systemctl", "is-active", "%s.service" % (service)]) + if rc in (0, 1, 3) and out: + active = out.strip() + else: + return {'name': service, 'error': err.strip()} + return {'name': service, 'active': active, 'enabled': enabled} + + +def services_info(): + 'check enabled/active services' + return [service_info(service) for service in SERVICES] + +def get_user(): + return pwd.getpwuid(os.getuid()).pw_name + +def sys_info(): + 'system information' + system, node, release, version, machine, processor = platform.uname() + hostname = platform.node().split('.')[0] + return {'system': system, + 'node': node, + 'release': release, + 'version': version, + 'machine': machine, + 'processor': processor, + 'user': get_user(), + 'hostname': hostname, + 'fqdn': socket.getfqdn()} + + +def net_info(): + ret = {} + interfaces = [] + ret['interfaces'] = interfaces + hostname = platform.node().split('.')[0] + try: + ip = socket.gethostbyname(hostname) + ret['hostname'] = {'name': hostname, 'ip': ip} + except Exception as e: + ret['hostname'] = {'error': str(e)} + return ret + + +def files_info(): + def check(fn): + if os.path.isfile(os.path.expanduser(fn)): + return os.path.expanduser(fn) + return '' + return {'ssh_key': check(SSH_KEY), + 'csync2_key': check(CSYNC2_KEY), + 'csync2_cfg': check(CSYNC2_CFG), + 'corosync_conf': check(COROSYNC_CONF), + 'sysconfig_sbd': check(SYSCONFIG_SBD), + 'sysconfig_fw': check(SYSCONFIG_FW), + 'sysconfig_fw_cluster': check(SYSCONFIG_FW_CLUSTER), + } + + +def logrotate_info(): + rc, _, _ = crm_script.call( + 'grep -r corosync.conf /etc/logrotate.d', + shell=True) + return {'corosync.conf': rc == 0} + + +def disk_info(): + rc, out, err = crm_script.call(['df'], shell=False) + if rc == 0: + disk_use = [] + for line in out.split('\n')[1:]: + line = line.strip() + if line: + data = line.split() + if len(data) >= 6: + disk_use.append((data[5], int(data[4][:-1]))) + return disk_use + return [] + + +def info(): + return {'rpm': rpm_info(), + 'services': services_info(), + 'system': sys_info(), + 'net': net_info(), + 'files': files_info(), + 'logrotate': logrotate_info(), + 'disk': disk_info()} + + +def verify(data): + """ + Given output from info(), verifies + as much as possible before init/add. + """ + def check_diskspace(): + for host, info in data.items(): + for mount, percent in info['disk']: + interesting = (mount == '/' or + mount.startswith('/var/log') or + mount.startswith('/tmp')) + if interesting and percent > 90: + crm_script.exit_fail("Not enough space on %s:%s" % (host, mount)) + + def check_services(): + for host, info in data.items(): + for svc in info['services']: + if svc['name'] == 'pacemaker' and svc['active'] == 'active': + crm_script.exit_fail("%s already running pacemaker" % (host)) + if svc['name'] == 'corosync' and svc['active'] == 'active': + crm_script.exit_fail("%s already running corosync" % (host)) + + def verify_host(host, info): + if host != info['system']['hostname']: + crm_script.exit_fail("Hostname mismatch: %s is not %s" % + (host, info['system']['hostname'])) + + def compare_system(systems): + def check(value, msg): + vals = set([system[value] for host, system in systems]) + if len(vals) > 1: + info = ', '.join('%s: %s' % (h, system[value]) for h, system in systems) + crm_script.exit_fail("%s: %s" % (msg, info)) + + check('machine', 'Architecture differs') + #check('release', 'Kernel release differs') + #check('distname', 'Distribution differs') + #check('distver', 'Distribution version differs') + #check('version', 'Kernel version differs') + + for host, info in data.items(): + verify_host(host, info) + + compare_system((h, info['system']) for h, info in data.items()) + + check_diskspace() + check_services() + + +# common functions to initialize a cluster node + + +def is_service_enabled(name): + info = service_info(name) + if info.get('name') == name and info.get('enabled') == 'enabled': + return True + return False + + +def is_service_active(name): + info = service_info(name) + if info.get('name') == name and info.get('active') == 'active': + return True + return False + + +def install_packages(packages): + for pkg in packages: + try: + crm_script.package(pkg, 'latest') + except Exception as e: + crm_script.exit_fail("Failed to install %s: %s" % (pkg, e)) + + +def configure_firewall(): + _SUSE_FW_TEMPLATE = """## Name: HAE cluster ports +## Description: opens ports for HAE cluster services +TCP="%(tcp)s" +UDP="%(udp)s" +""" + corosync_mcastport = crm_script.param('mcastport') + if not corosync_mcastport: + rc, out, err = crm_script.call(['crm', 'corosync', 'get', 'totem.interface.mcastport']) + if rc == 0: + corosync_mcastport = out.strip() + FW = '/etc/sysconfig/SuSEfirewall2' + FW_CLUSTER = '/etc/sysconfig/SuSEfirewall2.d/services/cluster' + + tcp_ports = '30865 5560 7630 21064' + udp_ports = '%s %s' % (corosync_mcastport, int(corosync_mcastport) - 1) + + if is_service_enabled('SuSEfirewall2'): + if os.path.isfile(FW_CLUSTER): + tmpl = open(FW_CLUSTER).read() + tmpl = re.sub(r'^TCP="(.*)"', 'TCP="%s"' % (tcp_ports), tmpl, flags=re.M) + tmpl = re.sub(r'^UDP="(.*)"', 'UDP="%s"' % (udp_ports), tmpl, flags=re.M) + with open(FW_CLUSTER, 'w') as f: + f.write(tmpl) + elif os.path.isdir(os.path.dirname(FW_CLUSTER)): + with open(FW_CLUSTER, 'w') as fwc: + fwc.write(_SUSE_FW_TEMPLATE % {'tcp': tcp_ports, + 'udp': udp_ports}) + else: + # neither the cluster file nor the services + # directory exists + crm_script.exit_fail("SUSE firewall is configured but %s does not exist" % + os.path.dirname(FW_CLUSTER)) + + # add cluster to FW_CONFIGURATIONS_EXT + if os.path.isfile(FW): + txt = open(FW).read() + m = re.search(r'^FW_CONFIGURATIONS_EXT="(.*)"', txt, re.M) + if m: + services = m.group(1).split() + if 'cluster' not in services: + services.append('cluster') + txt = re.sub(r'^FW_CONFIGURATIONS_EXT="(.*)"', + r'FW_CONFIGURATIONS_EXT="%s"' % (' '.join(services)), + txt, + flags=re.M) + else: + txt += '\nFW_CONFIGURATIONS_EXT="cluster"' + with open(FW, 'w') as fw: + fw.write(txt) + if is_service_active('SuSEfirewall2'): + crm_script.service('SuSEfirewall2', 'restart') + + # TODO: other platforms diff --git a/utils/crm_pkg.py b/utils/crm_pkg.py new file mode 100755 index 0000000..b775004 --- /dev/null +++ b/utils/crm_pkg.py @@ -0,0 +1,342 @@ +#!/usr/bin/python3 +# Copyright (C) 2013 Kristoffer Gronlund <kgronlund@suse.com> +# See COPYING for license information. + +import os +import sys +import subprocess +import json + + +DRY_RUN = False + + +def get_platform(): + return os.uname()[0] + + +def fail(msg): + print(msg, file=sys.stderr) + sys.exit(1) + + +def run(cmd): + proc = subprocess.Popen(cmd, + shell=False, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate(None) + return proc.returncode, out, err + + +def is_program(prog): + """Is this program available?""" + for p in os.getenv("PATH").split(os.pathsep): + filename = os.path.join(p, prog) + if os.path.isfile(filename) and os.access(filename, os.X_OK): + return filename + return None + + +class PackageManager(object): + def dispatch(self, name, state): + if state in ('installed', 'present'): + return self.present(name) + elif state in ('absent', 'removed'): + return self.absent(name) + elif state == 'latest': + return self.latest(name) + fail(msg="Unknown state: " + state) + + def present(self, name): + raise NotImplementedError + + def latest(self, name): + raise NotImplementedError + + def absent(self, name): + raise NotImplementedError + + +class Zypper(PackageManager): + def __init__(self): + self._rpm = is_program('rpm') + self._zyp = is_program('zypper') + if self._rpm is None or self._zyp is None: + raise OSError("Missing tools: %s, %s" % (self._rpm, self._zyp)) + + def get_version(self, name): + cmd = [self._rpm, '-q', name] + rc, stdout, stderr = run(cmd) + if rc == 0: + for line in stdout.splitlines(): + if name in line.decode('utf-8'): + return line.strip() + return None + + def is_installed(self, name): + if not isinstance(self._rpm, str): + raise IOError(str(self._rpm)) + if not isinstance(name, str): + raise IOError(str(name)) + cmd = [self._rpm, '--query', '--info', name] + rc, stdout, stderr = run(cmd) + return rc == 0 + + def present(self, name): + if self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._zyp, + '--non-interactive', + '--no-refresh', + 'install', + '--auto-agree-with-licenses', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + def latest(self, name): + if not self.is_installed(name): + return self.present(name) + + if DRY_RUN: + return (0, b'', b'', True) + + pre_version = self.get_version(name) + cmd = [self._zyp, + '--non-interactive', + '--no-refresh', + 'update', + '--auto-agree-with-licenses', + name] + rc, stdout, stderr = run(cmd) + post_version = self.get_version(name) + changed = pre_version != post_version + return (rc, stdout, stderr, changed) + + def absent(self, name): + if not self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._zyp, + '--non-interactive', + 'remove', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + +class Yum(PackageManager): + def __init__(self): + self._rpm = is_program('rpm') + self._yum = is_program('yum') + + def get_version(self, name): + cmd = [self._rpm, '-q', name] + rc, stdout, stderr = run(cmd) + if rc == 0: + for line in stdout.splitlines(): + if name in line: + return line.strip() + return None + + def is_installed(self, name): + cmd = [self._rpm, '--query', '--info', name] + rc, stdout, stderr = run(cmd) + return rc == 0 + + def present(self, name): + if self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._yum, + '--assumeyes', + '-d', '2', + 'install', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + def latest(self, name): + if not self.is_installed(name): + return self.present(name) + + if DRY_RUN: + return (0, b'', b'', True) + + pre_version = self.get_version(name) + cmd = [self._yum, + '--assumeyes', + '-d', '2', + 'update', + name] + rc, stdout, stderr = run(cmd) + post_version = self.get_version(name) + changed = pre_version != post_version + return (rc, stdout, stderr, changed) + + def absent(self, name): + if not self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._yum, + '--assumeyes', + '-d', '2', + 'erase', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + +class Apt(PackageManager): + def __init__(self): + self._apt = is_program('apt-get') + self._dpkg = is_program('dpkg') + if self._apt is None or self._dpkg is None: + raise OSError("Missing tools: %s, %s" % (self._apt, self._dpkg)) + + def get_version(self, name): + cmd = [self._dpkg, '--status', name] + rc, stdout, stderr = run(cmd) + if rc == 0: + for line in stdout.splitlines(): + info = line.decode('utf-8').split(':', 1) + if len(info) == 2 and info[0] == 'Version': + return info[1].strip() + return None + + def is_installed(self, name): + cmd = [self._dpkg, '--status', name] + rc, stdout, stderr = run(cmd) + if rc == 0: + for line in stdout.splitlines(): + info = line.decode('utf-8').split(':', 1) + if len(info) == 2 and info[0] == 'Status': + return info[1].strip().endswith('installed') + return False + + def present(self, name): + if self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._apt, + '--assume-yes', + '--quiet', + 'install', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + def latest(self, name): + if not self.is_installed(name): + return self.present(name) + + if DRY_RUN: + return (0, b'', b'', True) + + pre_version = self.get_version(name) + cmd = [self._apt, + '--assume-yes', + '--quiet', + '--only-upgrade', + 'install', + name] + rc, stdout, stderr = run(cmd) + post_version = self.get_version(name) + changed = pre_version != post_version + return (rc, stdout, stderr, changed) + + def absent(self, name): + if not self.is_installed(name): + return (0, b'', b'', False) + + if DRY_RUN: + return (0, b'', b'', True) + + cmd = [self._apt, + '--assume-yes', + '--quiet', + 'purge', + name] + rc, stdout, stderr = run(cmd) + changed = rc == 0 + return (rc, stdout, stderr, changed) + + +class Pacman(PackageManager): + pass + + +def manage_package(pkg, state): + """ + Gathers version and release information about a package. + """ + if pkg is None: + raise IOError("PKG IS NONE") + pf = get_platform() + if pf != 'Linux': + fail(msg="Unsupported platform: " + pf) + managers = { + 'zypper': Zypper, + 'yum': Yum, + 'apt-get': Apt, + #'pacman': Pacman + } + for name, mgr in managers.items(): + exe = is_program(name) + if exe: + rc, stdout, stderr, changed = mgr().dispatch(pkg, state) + return {'rc': rc, + 'stdout': stdout.decode('utf-8'), + 'stderr': stderr.decode('utf-8'), + 'changed': changed + } + fail(msg="No supported package manager found") + + +def main(): + import argparse + parser = argparse.ArgumentParser( + description="(Semi)-Universal package installer", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument('-d', '--dry-run', dest='dry_run', action='store_true', + help="Only check if changes would be made") + + parser.add_argument('-n', '--name', metavar='name', type=str, + help="Name of package") + + parser.add_argument('-s', '--state', metavar='state', type=str, + help="Desired state (present|latest|removed)", default="present") + + args = parser.parse_args() + global DRY_RUN + DRY_RUN = args.dry_run + if not args.name or not args.state: + raise IOError("Bad arguments: %s" % (sys.argv)) + data = manage_package(args.name, args.state) + print(json.dumps(str(data))) + +main() diff --git a/utils/crm_rpmcheck.py b/utils/crm_rpmcheck.py new file mode 100755 index 0000000..4901106 --- /dev/null +++ b/utils/crm_rpmcheck.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +# Copyright (C) 2013 Kristoffer Gronlund <kgronlund@suse.com> +# See COPYING for license information. + +import os +import sys +import json +import subprocess + + +def run(cmd): + proc = subprocess.Popen(cmd, + shell=False, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, err = proc.communicate(None) + proc.wait() + return proc.returncode, out.decode('utf-8'), err.decode('utf-8') + + +def package_data(pkg): + """ + Gathers version and release information about a package. + """ + if os.path.isfile('/bin/rpm'): + return rpm_package_data(pkg) + + if os.path.isfile('/usr/bin/dpkg'): + return dpkg_package_data(pkg) + + return {'name': pkg, 'error': "unknown package manager"} + + +def rpm_package_data(pkg): + """ + Gathers version and release information about an RPM package. + """ + _qfmt = 'version: %{VERSION}\nrelease: %{RELEASE}\n' + rc, out, err = run(['/bin/rpm', '-q', '--queryformat=' + _qfmt, pkg]) + if rc == 0: + data = {'name': pkg} + for line in out.split('\n'): + info = line.split(':', 1) + if len(info) == 2: + data[info[0].strip()] = info[1].strip() + return data + else: + return {'name': pkg, 'error': "package not installed"} + + +def dpkg_package_data(pkg): + """ + Gathers version and release information about a DPKG package. + """ + rc, out, err = run(['/usr/bin/dpkg', '--status', pkg]) + if rc == 0: + data = {'name': pkg} + for line in out.split('\n'): + info = line.split(':', 1) + if len(info) == 2: + data[info[0].strip().lower()] = info[1].strip() + return data + else: + return {'name': pkg, 'error': "package not installed"} + + +def main(): + data = [package_data(pkg) for pkg in sys.argv[1:]] + print(json.dumps(data)) + +main() diff --git a/utils/crm_script.py b/utils/crm_script.py new file mode 100644 index 0000000..2341539 --- /dev/null +++ b/utils/crm_script.py @@ -0,0 +1,190 @@ +import os +import sys +import getpass +import select +import subprocess as proc +try: + import json +except ImportError: + import simplejson as json + +_input = None + +# read stdin, if there's anything to read +_stdin_data = {} +while sys.stdin in select.select([sys.stdin], [], [], 0)[0]: + line = sys.stdin.readline() + if line: + d = line.split(':', 1) + if len(d) == 2: + _stdin_data[d[0].strip()] = d[1].strip() + else: + break + + +def decode_utf8(s): + """ + Convert the given byte sequence to a + utf8 string. + """ + if s is None or isinstance(s, str): + return s + return s.decode('utf-8', 'ignore') + + +def host(): + return os.uname()[1] + + +def get_input(): + global _input + if _input is None: + _input = json.load(open('./script.input')) + return _input + + +def parameters(): + return get_input()[0] + + +def param(name): + return parameters().get(name) + + +def output(step_idx): + if step_idx < len(get_input()): + return get_input()[step_idx] + return {} + + +def exit_fail(msg): + print(msg, file=sys.stderr) + sys.exit(1) + + +def exit_ok(data): + print(json.dumps(data)) + sys.exit(0) + + +def is_true(s): + if s in (True, False): + return s + return isinstance(s, str) and s.lower() in ('yes', 'true', '1', 'on') + + +_debug_enabled = None + + +def debug_enabled(): + global _debug_enabled + if _debug_enabled is None: + _debug_enabled = is_true(param('debug')) + return _debug_enabled + + +def info(msg): + "writes msg to log" + with open('./crm_script.debug', 'a') as dbglog: + dbglog.write('%s' % (msg)) + + +def debug(msg): + "writes msg to log and syslog if debug is enabled" + if debug_enabled(): + try: + with open('./crm_script.debug', 'a') as dbglog: + dbglog.write('%s\n' % (msg)) + import syslog + syslog.openlog("crmsh", 0, syslog.LOG_USER) + syslog.syslog(syslog.LOG_NOTICE, str(msg).encode('utf8')) + except: + pass + + +def call(cmd, shell=False): + debug("crm_script(call): %s" % (cmd)) + p = proc.Popen(cmd, shell=shell, stdin=None, stdout=proc.PIPE, stderr=proc.PIPE) + out, err = p.communicate() + return p.returncode, decode_utf8(out).strip(), decode_utf8(err).strip() + + +def use_sudo(): + return getpass.getuser() != 'root' and is_true(param('sudo')) and _stdin_data.get('sudo') + + +def sudo_call(cmd, shell=False): + if not use_sudo(): + return call(cmd, shell=shell) + debug("crm_script(sudo_call): %s" % (cmd)) + os.unsetenv('SSH_ASKPASS') + call(['sudo', '-k'], shell=False) + sudo_prompt = 'crm_script_sudo_prompt' + if isinstance(cmd, str): + cmd = "sudo -H -S -p '%s' %s" % (sudo_prompt, cmd) + else: + cmd = ['sudo', '-H', '-S', '-p', sudo_prompt] + cmd + p = proc.Popen(cmd, shell=shell, stdin=proc.PIPE, stdout=proc.PIPE, stderr=proc.PIPE) + sudo_pass = "%s\n" % (_stdin_data.get('sudo', 'linux')) + debug("CMD(SUDO): %s" % (str(cmd))) + out, err = p.communicate(input=sudo_pass) + return p.returncode, out.strip(), err.strip() + + +def service(name, action): + if action.startswith('is-'): + return call(['/usr/bin/systemctl', action, name + '.service']) + return sudo_call(['/usr/bin/systemctl', action, name + '.service']) + + +def package(name, state): + rc, out, err = sudo_call(['./crm_pkg.py', '-n', name, '-s', state]) + if rc != 0: + raise IOError("%s / %s" % (out, err)) + outp = json.loads(decode_utf8(out)) + if isinstance(outp, dict) and 'rc' in outp: + rc = int(outp['rc']) + if rc != 0: + raise IOError("(rc=%s) %s%s" % (rc, outp.get('stdout', ''), outp.get('stderr', ''))) + return outp + + +def check_package(name, state): + rc, out, err = call(['./crm_pkg.py', '--dry-run', '-n', name, '-s', state]) + if rc != 0: + raise IOError(err) + outp = json.loads(out) + if isinstance(outp, dict) and 'rc' in outp: + rc = int(outp['rc']) + if rc != 0: + raise IOError("(rc=%s) %s%s" % (rc, outp.get('stdout', ''), outp.get('stderr', ''))) + return outp + + +def rpmcheck(names): + rc, out, err = call(['./crm_rpmcheck.py'] + names) + if rc != 0: + raise IOError(err) + return json.loads(out) + + +def save_template(template, dest, **kwargs): + ''' + 1. Reads a template from <template>, + 2. Replaces all template variables with those in <kwargs> and + 3. writes the resulting file to <dest> + ''' + import re + tmpl = open(template).read() + keys = re.findall(r'%\((\w+)\)s', tmpl, re.MULTILINE) + missing_keys = set(keys) - set(kwargs.keys()) + if missing_keys: + raise ValueError("Missing template arguments: %s" % ', '.join(missing_keys)) + tmpl = tmpl % kwargs + try: + with open(dest, 'w') as f: + f.write(tmpl) + except Exception as e: + raise IOError("Failed to write %s from template %s: %s" % (dest, template, e)) + debug("crm_script(save_template): wrote %s" % (dest)) + |