summaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:48:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:48:59 +0000
commitd835b2cae8abc71958b69362162e6a70c3d7ef63 (patch)
tree81052e3d2ce3e1bcda085f73d925e9d6257dec15 /utils
parentInitial commit. (diff)
downloadcrmsh-d835b2cae8abc71958b69362162e6a70c3d7ef63.tar.xz
crmsh-d835b2cae8abc71958b69362162e6a70c3d7ef63.zip
Adding upstream version 4.6.0.upstream/4.6.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utils')
-rwxr-xr-xutils/crm_clean.py46
-rw-r--r--utils/crm_init.py251
-rwxr-xr-xutils/crm_pkg.py342
-rwxr-xr-xutils/crm_rpmcheck.py72
-rw-r--r--utils/crm_script.py190
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))
+