Hello community,

here is the log from the commit of package crmsh for openSUSE:Factory checked 
in at 2019-03-26 15:45:19
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/crmsh (Old)
 and      /work/SRC/openSUSE:Factory/.crmsh.new.25356 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "crmsh"

Tue Mar 26 15:45:19 2019 rev:155 rq:688380 version:4.0.0+git.1552985860.56f2db3a

Changes:
--------
--- /work/SRC/openSUSE:Factory/crmsh/crmsh.changes      2019-02-08 
12:11:15.585517474 +0100
+++ /work/SRC/openSUSE:Factory/.crmsh.new.25356/crmsh.changes   2019-03-26 
15:45:25.168082051 +0100
@@ -1,0 +2,15 @@
+Thu Mar 21 08:17:24 UTC 2019 - xli...@suse.com
+
+- Update to version 4.0.0+git.1552985860.56f2db3a:
+  * Fix: bsc#1129719: check command and related files exist
+  * High: constants: add "promotable", "promoted-max" and "promoted-node-max" 
in clone meta attributes
+  * Fix: cibconfig: #425 The ID attribute is not required for select and 
select_attributes
+  * medium: scripts: Set kind for order constraints, not score (bsc#1123187)
+  * low: utils: add support for dpkg
+  * low: utils: add support for apt-get
+  * low: utils: convert string contstants to bytes
+  * Fix: bsc#1120857,1120856 bootstrap warning messages should better start 
with like "WARNING:" instead of "!"
+  * Fix: bsc#1120554, bsc#1120555 crmsh crashed when using 
configure>template>apply
+  * High: hbreport: fix UnicodeEncodeError while print(bsc#1093564)
+
+-------------------------------------------------------------------

Old:
----
  crmsh-4.0.0+git.1543873923.0f9166fd.tar.bz2

New:
----
  crmsh-4.0.0+git.1552985860.56f2db3a.tar.bz2

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ crmsh.spec ++++++
--- /var/tmp/diff_new_pack.JAvvMJ/_old  2019-03-26 15:45:26.264081524 +0100
+++ /var/tmp/diff_new_pack.JAvvMJ/_new  2019-03-26 15:45:26.268081522 +0100
@@ -36,7 +36,7 @@
 Summary:        High Availability cluster command-line interface
 License:        GPL-2.0-or-later
 Group:          %{pkg_group}
-Version:        4.0.0+git.1543873923.0f9166fd
+Version:        4.0.0+git.1552985860.56f2db3a
 Release:        0
 Url:            http://crmsh.github.io
 Source0:        %{name}-%{version}.tar.bz2

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.JAvvMJ/_old  2019-03-26 15:45:26.304081505 +0100
+++ /var/tmp/diff_new_pack.JAvvMJ/_new  2019-03-26 15:45:26.304081505 +0100
@@ -1,4 +1,4 @@
 <servicedata>
 <service name="tar_scm">
             <param name="url">git://github.com/ClusterLabs/crmsh.git</param>
-          <param 
name="changesrevision">6de02b2da5c35d14752fdc5d13ab35894b799582</param></service></servicedata>
\ No newline at end of file
+          <param 
name="changesrevision">56f2db3ab753c7df0554158b718faa33ecefb081</param></service></servicedata>
\ No newline at end of file

++++++ crmsh-4.0.0+git.1543873923.0f9166fd.tar.bz2 -> 
crmsh-4.0.0+git.1552985860.56f2db3a.tar.bz2 ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.0.0+git.1543873923.0f9166fd/.travis.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/.travis.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/.travis.yml 2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/.travis.yml 2019-03-19 
09:57:40.000000000 +0100
@@ -4,6 +4,15 @@
 services:
   - docker
 
+env:
+  global:
+    - 
CC_TEST_REPORTER_ID=a2579335b631ec35473874d7bb4fe983025c0287cea89c9dc34c35f98ee3963d
+
+before_script:
+  - curl -L 
https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64
 > ./cc-test-reporter
+  - chmod +x ./cc-test-reporter
+  - ./cc-test-reporter before-build
+
 before_install:
   - docker pull krig/crmsh:latest
 
@@ -12,3 +21,6 @@
 
 after_failure:
   - sudo cat $TRAVIS_BUILD_DIR/crmtestout/regression.out 
$TRAVIS_BUILD_DIR/crmtestout/crm.*
+
+after_script:
+  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/bootstrap.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/bootstrap.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/bootstrap.py  2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/bootstrap.py  2019-03-19 
09:57:40.000000000 +0100
@@ -96,7 +96,7 @@
     Log and display a warning message.
     """
     log("WARNING: {}".format(" ".join(str(arg) for arg in args)))
-    print(term.render(clidisplay.warn("! {}".format(" ".join(str(arg) for arg 
in args)))))
+    print(term.render(clidisplay.warn("WARNING: {}".format(" ".join(str(arg) 
for arg in args)))))
 
 
 @utils.memoize
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/cibconfig.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/cibconfig.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/cibconfig.py  2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/cibconfig.py  2019-03-19 
09:57:40.000000000 +0100
@@ -686,7 +686,7 @@
         'alerts': 'alert',
         }
 
-    idless = set(['operations', 'fencing-topology', 'network', 'docker', 
'rkt', 'storage'])
+    idless = set(['operations', 'fencing-topology', 'network', 'docker', 
'rkt', 'storage', 'select', 'select_attributes'])
     isref = set(['resource_ref', 'obj_ref', 'crmsh-ref'])
 
     def needs_id(node):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/constants.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/constants.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/constants.py  2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/constants.py  2019-03-19 
09:57:40.000000000 +0100
@@ -219,7 +219,7 @@
 clone_meta_attributes = common_meta_attributes + (
     "ordered", "notify", "interleave", "globally-unique",
     "clone-max", "clone-node-max", "clone-state", "description",
-    "clone-min",
+    "clone-min", "promotable", "promoted-max", "promoted-node-max",
 )
 ms_meta_attributes = common_meta_attributes + (
     "clone-max", "clone-node-max", "notify", "globally-unique", "ordered", 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/log_patterns.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/log_patterns.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/log_patterns.py       
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/log_patterns.py       
2019-03-19 09:57:40.000000000 +0100
@@ -33,7 +33,7 @@
             "lrmd.*WARN: .* %% .*timed out$",
             "crmd.*LRM operation 
%%_(?:start|stop|promote|demote|migrate)_.*confirmed=true",
             "crmd.*LRM operation %%_.*Timed Out",
-            "[(]%%[)][[]",
+            "[(]%%[)]\[",
         ),
         (  # detail 1
             "lrmd.*%% (?:probe|notify)",
@@ -91,7 +91,7 @@
 
             "crmd.*LRM operation 
%%_(?:start|stop|promote|demote|migrate)_.*confirmed=true",
             "crmd.*LRM operation %%_.*Timed Out",
-            "[(]%%[)][[]",
+            "[(]%%[)]\[",
         ),
         (  # detail 1
             "crmd.*Initiating.*%%_(?:monitor_0|notify)",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/options.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/options.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/options.py    2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/options.py    2019-03-19 
09:57:40.000000000 +0100
@@ -16,3 +16,77 @@
 scriptdir = ""
 # set to true when completing non-interactively
 shell_completion = False
+
+
+arg_parameter_key = ["action", "dest", "metavar", "default", "type", "help"]
+
+cluster_init_base_options_value = [
+    ("-h", "--help", "store_true", "help", "", None, None, "Show this help 
message"),
+
+    ("-q", "--quiet", "store_true", "quiet", "", None, None,
+     "Be quiet (don't describe what's happening, just do it)"),
+
+    ("-y", "--yes", "store_true", "yes_to_all", "", None, None,
+     'Answer "yes" to all prompts (use with caution, this is destructive, 
especially during the "storage" stage)'),
+
+    ("-t", "--template", "store", "template", "", None, None,
+     'Optionally configure cluster with template "name" (currently only 
"ocfs2" is valid here)'),
+
+    ("-n", "--name", "store", "name", "NAME", "hacluster", None,
+     'Set the name of the configured cluster.'),
+
+    ("-N", "--nodes", "store", "nodes", "NODES", None, None,
+     'Additional nodes to add to the created cluster. ' +
+     'May include the current node, which will always be the initial cluster 
node.'),
+
+    ("-S", "--enable-sbd", "store_true", "diskless_sbd", "", None, None,
+     "Enable SBD even if no SBD device is configured (diskless mode)"),
+
+    ("-w", "--watchdog", "store", "watchdog", "WATCHDOG", None, None, "Use the 
given watchdog device")
+]
+
+cluster_init_net_options_value = [
+    ("-i", "--interface", "store", "nic", "IF", None, str, "Bind to IP address 
on interface IF"),
+
+    ("-u", "--unicast", "store_true", "unicast", "", None, None,
+     "Configure corosync to communicate over unicast (UDP), and not multicast. 
" +
+     "Default is multicast unless an environment where multicast cannot be 
used is detected."),
+
+    ("-A", "--admin-ip", "store", "admin_ip", "IP", None, str,
+     "Configure IP address as an administration virtual IP"),
+
+    ("-M", "--multi-heartbeats", "store_true", "second_hb", "", None, None,
+     "Configure corosync with second heartbeat line"),
+
+    ("-I", "--ipv6", "store_true", "ipv6", "", None, None, "Configure corosync 
use IPv6"),
+
+    ("--qdevice", "store", "qdevice", "QDEVICE", None, str, "QDevice IP"),
+
+    ("--qdevice-port", "store", "qdevice_port", "QDEVICE_PORT", 5403, int, 
"QDevice port"),
+
+    ("--qdevice-algo", "store", "qdevice_algo", "QDEVICE_ALGO", "ffsplit", 
str, "QDevice algorithm"),
+
+    ("--qdevice-tie-breaker", "store", "qdevice_tie_breaker", 
"QDEVICE_TIE_BREAKER", "lowest", str,
+     "QDevice tie breaker")
+]
+
+cluster_init_storage_options_value = [
+    ("-p", "--partition-device", "store", "shared_device", "DEVICE", None, str,
+     'Partition this shared storage device (only used in "storage" stage)'),
+
+    ("-s", "--sbd-device", "store", "sbd_device", "DEVICE", None, str,
+     "Block device to use for SBD fencing"),
+
+    ("-o", "--ocfs2-device", "store", "ocfs2_device", "DEVICE", None, str,
+     'Block device to use for OCFS2 (only used in "vgfs" stage)')
+]
+
+CLUSTER_INIT_BASE_OPTIONS = []
+CLUSTER_INIT_NET_OPTIONS = []
+CLUSTER_INIT_STORAGE_OPTIONS = []
+for value_tuple in cluster_init_base_options_value:
+    CLUSTER_INIT_BASE_OPTIONS.append((value_tuple[:-6], 
dict(zip(arg_parameter_key, value_tuple[-6:]))))
+for value_tuple in cluster_init_net_options_value:
+    CLUSTER_INIT_NET_OPTIONS.append((value_tuple[:-6], 
dict(zip(arg_parameter_key, value_tuple[-6:]))))
+for value_tuple in cluster_init_storage_options_value:
+    CLUSTER_INIT_STORAGE_OPTIONS.append((value_tuple[:-6], 
dict(zip(arg_parameter_key, value_tuple[-6:]))))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/ui_cluster.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/ui_cluster.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/ui_cluster.py 2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/ui_cluster.py 2019-03-19 
09:57:40.000000000 +0100
@@ -4,6 +4,7 @@
 
 import optparse
 import re
+from . import options as cmdoptions
 from . import command
 from . import utils
 from .msg import err_buf
@@ -183,58 +184,19 @@
     will be skipped.
 """, add_help_option=False)
 
-        parser.add_option("-h", "--help", action="store_true", dest="help", 
help="Show this help message")
-        parser.add_option("-q", "--quiet", action="store_true", dest="quiet",
-                          help="Be quiet (don't describe what's happening, 
just do it)")
-        parser.add_option("-y", "--yes", action="store_true", 
dest="yes_to_all",
-                          help='Answer "yes" to all prompts (use with caution, 
this is destructive, especially during the "storage" stage)')
-        parser.add_option("-t", "--template", dest="template",
-                          help='Optionally configure cluster with template 
"name" (currently only "ocfs2" is valid here)')
-        parser.add_option("-n", "--name", metavar="NAME", dest="name", 
default="hacluster",
-                          help='Set the name of the configured cluster.')
-        parser.add_option("-N", "--nodes", metavar="NODES", dest="nodes",
-                          help='Additional nodes to add to the created 
cluster. ' +
-                          'May include the current node, which will always be 
the initial cluster node.')
-        # parser.add_option("--quick-start", dest="quickstart", 
action="store_true", help="Perform basic system configuration (NTP, watchdog, 
/etc/hosts)")
-        parser.add_option("-S", "--enable-sbd", dest="diskless_sbd", 
action="store_true",
-                          help="Enable SBD even if no SBD device is configured 
(diskless mode)")
-        parser.add_option("-w", "--watchdog", dest="watchdog", 
metavar="WATCHDOG",
-                          help="Use the given watchdog device")
+        for _options in cmdoptions.CLUSTER_INIT_BASE_OPTIONS:
+            parser.add_option(*_options[0], **_options[-1])
 
         network_group = optparse.OptionGroup(parser, "Network configuration", 
"Options for configuring the network and messaging layer.")
-        network_group.add_option("-i", "--interface", dest="nic", metavar="IF",
-                                 help="Bind to IP address on interface IF")
-        network_group.add_option("-u", "--unicast", action="store_true", 
dest="unicast",
-                                 help="Configure corosync to communicate over 
unicast (UDP), and not multicast. " +
-                                 "Default is multicast unless an environment 
where multicast cannot be used is detected.")
-        network_group.add_option("-A", "--admin-ip", dest="admin_ip", 
metavar="IP",
-                                 help="Configure IP address as an 
administration virtual IP")
-        network_group.add_option("-M", "--multi-heartbeats", 
action="store_true", dest="second_hb",
-                                 help="Configure corosync with second 
heartbeat line")
-        network_group.add_option("-I", "--ipv6", action="store_true", 
dest="ipv6",
-                                 help="Configure corosync use IPv6")
-        network_group.add_option("--qdevice",
-                                 dest="qdevice", metavar="QDEVICE",
-                                 help="QDevice IP")
-        network_group.add_option("--qdevice-port",
-                                 dest="qdevice_port", metavar="QDEVICE_PORT", 
type="int", default=5403,
-                                 help="QDevice port")
-        network_group.add_option("--qdevice-algo",
-                                 dest="qdevice_algo", metavar="QDEVICE_ALGO", 
default="ffsplit",
-                                 help="QDevice algorithm")
-        network_group.add_option("--qdevice-tie-breaker",
-                                 dest="qdevice_tie_breaker", 
metavar="QDEVICE_TIE_BREAKER", default="lowest",
-                                 help="QDevice algorithm")
+        for _options in cmdoptions.CLUSTER_INIT_NET_OPTIONS:
+            network_group.add_option(*_options[0], **_options[-1])
         parser.add_option_group(network_group)
 
         storage_group = optparse.OptionGroup(parser, "Storage configuration", 
"Options for configuring shared storage.")
-        storage_group.add_option("-p", "--partition-device", 
dest="shared_device", metavar="DEVICE",
-                                 help='Partition this shared storage device 
(only used in "storage" stage)')
-        storage_group.add_option("-s", "--sbd-device", dest="sbd_device", 
metavar="DEVICE",
-                                 help="Block device to use for SBD fencing")
-        storage_group.add_option("-o", "--ocfs2-device", dest="ocfs2_device", 
metavar="DEVICE",
-                                 help='Block device to use for OCFS2 (only 
used in "vgfs" stage)')
+        for _options in cmdoptions.CLUSTER_INIT_STORAGE_OPTIONS:
+            storage_group.add_option(*_options[0], **_options[-1])
         parser.add_option_group(storage_group)
+
         try:
             options, args = parser.parse_args(list(args))
         except:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/ui_template.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/ui_template.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/crmsh/ui_template.py        
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/crmsh/ui_template.py        
2019-03-19 09:57:40.000000000 +0100
@@ -296,11 +296,10 @@
         err_buf.start_tmp_lineno()
         rc = True
         for inp in f:
+            inp = utils.to_ascii(inp)
             err_buf.incr_lineno()
             if inp.startswith('#'):
                 continue
-            if isinstance(inp, str):
-                inp = inp.encode('ascii')
             inp = inp.strip()
             try:
                 s = shlex.split(inp)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.0.0+git.1543873923.0f9166fd/doc/crm.8.adoc 
new/crmsh-4.0.0+git.1552985860.56f2db3a/doc/crm.8.adoc
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/doc/crm.8.adoc      2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/doc/crm.8.adoc      2019-03-19 
09:57:40.000000000 +0100
@@ -3082,6 +3082,9 @@
 ...............
 clone cl_fence apc_1 \
   meta clone-node-max=1 globally-unique=false
+
+clone disk1 drbd1 \
+  meta promotable=true notify=true globally-unique=false
 ...............
 
 [[cmdhelp_configure_colocation,colocate resources]]
@@ -3604,6 +3607,18 @@
   meta notify=true globally-unique=false
 ...............
 
+.Note on `ms` deprecated
+****************************
+From Pacemaker-2.0, the resource type referred to as "master/slave",
+"stateful", or "multi-state" is no longer a separate resource type,
+but a variation of clone now referred to as a "promotable clone".
+For backward compatibility, above configurations are also accepted.
+...............
+clone disk1 drbd1 \
+  meta promotable=true notify=true globally-unique=false
+...............
+****************************
+
 .Note on `id-ref` usage
 ****************************
 Instance or meta attributes (`params` and `meta`) may contain
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/doc/releasing-a-new-version.md 
new/crmsh-4.0.0+git.1552985860.56f2db3a/doc/releasing-a-new-version.md
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/doc/releasing-a-new-version.md      
1970-01-01 01:00:00.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/doc/releasing-a-new-version.md      
2019-03-19 09:57:40.000000000 +0100
@@ -0,0 +1,199 @@
+# Releasing a new version
+
+A guide to releasing new versions of crmsh.
+
+## Version scheme
+
+We follow a somewhat loose version of Semantic Versioning, with a
+three-part version number:
+
+    <major>.<minor>.<patch>
+
+The major version number is increased rarely, arbitrarily and
+indicates big changes to the shell. Moving from Python 2 to Python 3
+was such a change, for example. It does not indicate breaking
+changes: We try not to make breaking changes at all. If there is a
+breaking change, it is hopefully a mistake that would be fixed with a
+patch. If not, it should be noted very clearly and probably only
+released with a major version number change.
+
+The minor version number indicates new features and bugfixes, but
+hopefully no breaking changes.
+
+The patch version number indicates bugfixes only, and no breaking
+changes.
+
+## Steps
+
+1. Updating the changelog
+
+In `/ChangeLog`, there is a curated list of changes included in this
+release. This log should be updated based on the git history. Remove
+any updates that are tagged `dev:` or `test:` since these are internal
+changes, and clean up the changelog in any other way you might want.
+
+To get the list of changes since the last release, you can use `git
+log` with a custom format. This example gets the changes between 3.0
+and 3.0.1, filtering out any changes tagged `dev:` or `test:`:
+
+    PAGER=cat git log --format="- %s" 3.0.0..3.0.1 | \
+        grep -Ev -- '-[ ](dev|test):.*'
+
+2. Tagging the release
+
+Using `git tag` you can see the list of existing tags. Depending
+on the version being released, you will want to tag the current commit
+with that release. Make it a signed tag:
+
+    git tag -s -u <u...@example.com> 4.1.0
+
+In the tag message I usually just put
+
+    Release 4.1.0
+
+(of course, change `4.1.0` to whatever release it is you are tagging.
+
+Remember to push the tag to the Github repository. Assuming that the
+github repository is `origin`, this command should do the trick:
+
+    git push --tags origin
+
+
+3. Release email
+
+Send a release email to us...@clusterlabs.org. Here is the template
+that I usually follow with release emails:
+
+```
+Hello everyone!
+
+I'm happy to announce the release of crmsh version <VERSION>.
+
+<DESCRIPTION: some notes about the release>
+
+There are some other changes in this release as well, see the
+ChangeLog for the complete list of changes:
+
+* https://github.com/ClusterLabs/crmsh/blob/<VERSION>/ChangeLog
+
+The source code can be downloaded from Github:
+
+* https://github.com/ClusterLabs/crmsh/releases/tag/<VERSION>
+
+Packaged versions of crmsh should be available shortly from your
+distribution of choice. Development packages for openSUSE Tumbleweed
+are available from the Open Build System, here:
+
+* http://download.opensuse.org/repositories/network:/ha-clustering:/Factory/
+
+Archives of the tagged release:
+
+* https://github.com/ClusterLabs/crmsh/archive/<VERSION>.tar.gz
+* https://github.com/ClusterLabs/crmsh/archive/<VERSION>.zip
+
+As usual, a huge thank you to all contributors and users of crmsh!
+
+```
+
+4. Website update
+
+The crmsh website is hosted on Github as a github page. The URL to the
+website repository is
+
+    https://github.com/crmsh/crmsh.github.io
+
+The website contents themselves (the sources) are found in the regular
+crmsh repository, in the `/doc/website-v1` folder. There is a
+`Makefile` in this folder which can be used to regenerate the
+website.
+
+Doing this requires `asciidoc` and `Pygments` to be installed, as well
+as the custom Pygments filter `ansiclr`.
+
+`ansiclr` is found in the `/contrib/pygments_crmsh_lexers` folder, and
+can be installed by running `python setup.py install` in the
+`/contrib` folder.
+
+**Note: ansiclr seems to be broken at the moment. Just ignore
+it. Everything should still work except some highlighting.**
+
+To create the news update, copy a previous update (found in
+`/doc/website-v1/news`), rename it to an appropriate name based on the
+current date, and replace the contents based on the announcement
+email.
+
+Remember to update the title, author and date information at the top
+of the news entry, to ensure that it appears correctly on the site.
+
+To generate the site including the new entry, run
+
+    make
+
+The new site should now sit in `/doc/website-v1/gen`. To update the
+site, using rsync should work:
+
+    rsync -Pavz doc/website-v1/gen/ <path-to-website-checkout>/crmsh.github.io/
+
+5. Update `network:ha-clustering:Factory`
+
+On the Open Build Service, the project
+`network:ha-clustering:Factory/crmsh` is used as the development
+project for openSUSE Tumbleweed. This project mirrors the state of the
+`master` branch in crmsh, but for policy reasons it is not
+automatically updated.
+
+The following steps assumes that you are a maintainer of
+`network:ha-clustering:Factory`. If not, you can still make the update
+but you will have to branch the `crmsh` package, make the update there
+and then submit an update request using `osc submit`. Then a
+maintainer will have to review your submission.
+
+To update the package and submit to `openSUSE:Factory`, the following
+steps will do the trick. First, check out a local copy of the crmsh
+project:
+
+    osc co network:ha-clustering:Factory crmsh
+    cd network:ha-clustering:Factory/crmsh
+
+If you already have a copy, make sure it is up to date:
+
+    osc update
+
+Update the `_service` file so that the version number reflects the
+latest version of the `master` branch in git.
+
+Pull in the latest changes from git:
+
+    osc service dr
+
+This will update the spec file and the changes file. Clean up the
+changes file if you want (not strictly neccessary), and remove any old
+tarball still in the package directory, and then add/remove the
+changes to osc:
+
+    osc ar
+
+Check that everything looks good, and then commit:
+
+    osc diff
+    osc commit
+
+Once the package has built successfully on OBS, you can submit to
+`openSUSE:Factory`:
+
+    osc submit
+
+6. Update `network:ha-clustering:Stable`
+
+If this is a minor release for the latest major release, or a new
+major release, we should update the version of crmsh hosted at
+`network:ha-clustering:Stable` on OBS.
+
+Since `master` is probably up to date with this latest version, doing
+so should be as simple as submitting crmsh from
+`network:ha-clustering:Factory` to `network:ha-clustering:Stable`.
+
+Once the release is tagged, the announcement email is sent, the
+website updated and the packages updated, the release is done.
+
+Congratulations!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/hb_report/hb_report.in 
new/crmsh-4.0.0+git.1552985860.56f2db3a/hb_report/hb_report.in
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/hb_report/hb_report.in      
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/hb_report/hb_report.in      
2019-03-19 09:57:40.000000000 +0100
@@ -282,6 +282,9 @@
     #     problem description template, and prints final notes
     #
     if is_collector():
+        import io
+        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
+
         utillib.collect_info()
         cmd = r"cd %s/.. && tar -h -cf - %s" % (constants.WORKDIR, 
constants.WE)
         code, out, err = crmutils.get_stdout_stderr(cmd)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/hb_report/utillib.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/hb_report/utillib.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/hb_report/utillib.py        
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/hb_report/utillib.py        
2019-03-19 09:57:40.000000000 +0100
@@ -1642,6 +1642,10 @@
                 "cat /sys/fs/ocfs2/cluster_stack"
                 ]
         for cmd in cmds:
+            cmd_name = cmd.split()[0]
+            if not which(cmd_name) or \
+               cmd_name == "cat" and not os.path.exists(cmd.split()[1]):
+                continue
             _, out = crmutils.get_stdout(cmd)
             f.write("\n\n#=====[ Command ] ==========================#\n")
             f.write("# %s\n"%(cmd))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/db2-hadr/main.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/db2-hadr/main.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/db2-hadr/main.yml   
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/db2-hadr/main.yml   
2019-03-19 09:57:40.000000000 +0100
@@ -40,4 +40,4 @@
       ms ms-{{db2:id}} {{db2:id}}
         meta target-role=Stopped notify=true
       colocation {{virtual-ip:id}}-with-master inf: {{virtual-ip:id}}:Started 
ms-{{db2:id}}:Master
-      order {{virtual-ip:id}}-after-master inf: ms-{{db2:id}}:promote 
{{virtual-ip:id}}:start
+      order {{virtual-ip:id}}-after-master Mandatory: ms-{{db2:id}}:promote 
{{virtual-ip:id}}:start
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/lvm-drbd/main.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/lvm-drbd/main.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/lvm-drbd/main.yml   
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/lvm-drbd/main.yml   
2019-03-19 09:57:40.000000000 +0100
@@ -58,5 +58,5 @@
   - shortdesc: Configure LVM and File System Group and Constraints
     cib: |
       group {{group_id}} {{lvm:id}} 
{{#example_fs:id}}{{example_fs:id}}{{/example_fs:id}}
-      order o-drbd_before_{{group_id}} inf: ms-{{drbd:id}}:promote 
{{group_id}}:start
+      order o-drbd_before_{{group_id}} Mandatory: ms-{{drbd:id}}:promote 
{{group_id}}:start
       colocation c-{{group_id}}_on_drbd inf: {{group_id}} ms-{{drbd:id}}:Master
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/nfsserver/main.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/nfsserver/main.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/nfsserver/main.yml  
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/nfsserver/main.yml  
2019-03-19 09:57:40.000000000 +0100
@@ -61,11 +61,11 @@
   - include: virtual-ip
   - cib: |
       group g-nfs {{exportfs:id}} {{virtual-ip:id}}
-      order base-then-nfs inf: {{base-id}} g-nfs
+      order base-then-nfs Mandatory: {{base-id}} g-nfs
       colocation nfs-with-base inf: g-nfs {{base-id}}
       {{#rootfs}}
       clone c-{{rootfs:id}} {{rootfs:id}}
-      order rootfs-before-nfs inf: c-{{rootfs:id}} g-nfs:start
+      order rootfs-before-nfs Mandatory: c-{{rootfs:id}} g-nfs:start
       colocation nfs-with-rootfs inf: g-nfs c-{{rootfs:id}}
       {{/rootfs}}
   - call: /usr/sbin/exportfs -v
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/nfsserver-lvm-drbd/main.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/nfsserver-lvm-drbd/main.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/nfsserver-lvm-drbd/main.yml 
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/nfsserver-lvm-drbd/main.yml 
2019-03-19 09:57:40.000000000 +0100
@@ -112,7 +112,7 @@
   - shortdesc: Configure LVM and File System Group and Constraints
     cib: |
       group g-nfs {{lvm:id}} 
{{#example_fs:id}}{{example_fs:id}}{{/example_fs:id}}
-      order o-drbd_before_nfs inf: ms-{{drbd:id}}:promote g-nfs:start
+      order o-drbd_before_nfs Mandatory: ms-{{drbd:id}}:promote g-nfs:start
       colocation c-nfs_on_drbd inf: g-nfs ms-{{drbd:id}}:Master
 
   - include: rootfs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/oracle/main.yml 
new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/oracle/main.yml
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/scripts/oracle/main.yml     
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/scripts/oracle/main.yml     
2019-03-19 09:57:40.000000000 +0100
@@ -47,5 +47,5 @@
         op monitor interval="120s"
 
       colocation lsn-with-{{id}} inf: {{id}} lsn-{{id}}
-      order lsn-before-{{id}} inf: lsn-{{id}} {{id}}
+      order lsn-before-{{id}} Mandatory: lsn-{{id}} {{id}}
     
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/bugs 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/bugs
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/bugs 2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/bugs 2019-03-19 
09:57:40.000000000 +0100
@@ -60,3 +60,20 @@
 _test
 verify
 .
+session template
+configure
+erase
+node node1
+primitive st stonith:null \
+       params hostlist='node1' \
+       meta description="some description here" requires=nothing \
+       op monitor interval=60m
+template
+new vip virtual-ip params ip=10.10.10.123
+load vip
+apply update
+up
+commit
+_test
+verify
+.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/bugs.exp 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/bugs.exp
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/bugs.exp     
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/bugs.exp     
2019-03-19 09:57:40.000000000 +0100
@@ -126,3 +126,21 @@
 .INP: commit
 .INP: _test
 .INP: verify
+.TRY template
+.INP: configure
+.INP: erase
+.INP: node node1
+.INP: primitive st stonith:null        params hostlist='node1'         meta 
description="some description here" requires=nothing       op monitor 
interval=60m
+.EXT crm_resource --show-metadata stonith:null
+.EXT stonithd metadata
+.INP: template
+.INP: new vip virtual-ip params ip=10.10.10.123
+INFO: 6: pulling in template virtual-ip
+.INP: load vip
+.INP: apply update
+.EXT crm_resource --show-metadata ocf:heartbeat:IPaddr
+.EXT crm_resource --list-ocf-alternatives IPaddr
+.INP: up
+.INP: commit
+.INP: _test
+.INP: verify
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/confbasic 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/confbasic
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/confbasic    
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/confbasic    
2019-03-19 09:57:40.000000000 +0100
@@ -43,6 +43,11 @@
        op_params 1: op_param=smart \
        op_meta 2: rule #ra-version version:gt 1.0 start-delay=120m \
        op_meta 1: start-delay=60m
+primitive d8 ocf:pacemaker:Dummy
+clone m7 d8 \
+       meta promotable=true \
+       meta promoted-max=1 \
+       meta promoted-node-max=1
 location l1 g1 100: node1
 location l2 c \
        rule $id=l2-rule1 100: #uname eq node1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/confbasic.exp 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/confbasic.exp
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/confbasic.exp        
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/confbasic.exp        
2019-03-19 09:57:40.000000000 +0100
@@ -31,6 +31,8 @@
 .INP: ms m6 s6
 .INP: primitive d7 Dummy       params rule inf: #uname eq node1 fake=1         
params rule inf: #uname eq node2 fake=2         op start interval=0 timeout=60s 
        op_params 2: rule #uname eq node1 op_param=dummy         op_params 1: 
op_param=smart         op_meta 2: rule #ra-version version:gt 1.0 
start-delay=120m         op_meta 1: start-delay=60m
 .EXT crm_resource --show-metadata ocf:heartbeat:Dummy
+.INP: primitive d8 ocf:pacemaker:Dummy
+.INP: clone m7 d8      meta promotable=true    meta promoted-max=1     meta 
promoted-node-max=1
 .INP: location l1 g1 100: node1
 .INP: location l2 c    rule $id=l2-rule1 100: #uname eq node1
 .INP: location l3 m5   rule inf: #uname eq node1 and pingd gt 0
@@ -56,7 +58,7 @@
 .INP: set d2.mondelay 45
 .INP: _test
 .INP: verify
-WARNING: 51: c2: resource d1 is grouped, constraints should apply to the group
+WARNING: 53: c2: resource d1 is grouped, constraints should apply to the group
 .EXT crmd metadata
 .EXT pengine metadata
 .EXT cib metadata
@@ -86,6 +88,7 @@
        op_params 1: op_param=smart \
        op_meta 2: rule #ra-version version:gt 1.0 start-delay=120m \
        op_meta 1: start-delay=60m
+primitive d8 ocf:pacemaker:Dummy
 primitive s5 ocf:pacemaker:Stateful \
        operations  $id-ref=d1-ops
 primitive s6 ocf:pacemaker:Stateful \
@@ -103,6 +106,10 @@
 ms m6 s6
 clone c d3 \
        meta clone-max=1
+clone m7 d8 \
+       meta promotable=true \
+       meta promoted-max=1 \
+       meta promoted-node-max=1
 tag t1 m5 m6
 colocation c1 inf: m6 m5
 colocation c2 inf: m5:Master d1:Started
@@ -139,4 +146,4 @@
        rule 100: #uname eq node1 \
        record-pending=true
 .INP: commit
-WARNING: 53: c2: resource d1 is grouped, constraints should apply to the group
+WARNING: 55: c2: resource d1 is grouped, constraints should apply to the group
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/newfeatures 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/newfeatures
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/newfeatures  
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/newfeatures  
2019-03-19 09:57:40.000000000 +0100
@@ -27,6 +27,11 @@
         trap_node_states="non-trap" \
         trap_resource_tasks="start,stop,monitor,promote,demote" \
         to "192.168.40.9"
+alert notify_10 /usr/share/pacemaker/alerts/alert_snmp.sh \
+        attributes \
+        trap_add_hires_timestamp_oid="false" \
+        select attributes { master-prmStateful test1 } \
+        to 192.168.28.188
 show tag:ones and type:location
 show tag:ones and p1
 show
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/newfeatures.exp 
new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/newfeatures.exp
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/test/testcases/newfeatures.exp      
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/test/testcases/newfeatures.exp      
2019-03-19 09:57:40.000000000 +0100
@@ -19,6 +19,7 @@
 .INP: primitive node1 Dummy
 .INP: tag ones l1 p1
 .INP: alert notify_9 /usr/share/pacemaker/alerts/alert_snmp.sh         
attributes         trap_add_hires_timestamp_oid="false"         
trap_node_states="non-trap"         
trap_resource_tasks="start,stop,monitor,promote,demote"         to 
"192.168.40.9"
+.INP: alert notify_10 /usr/share/pacemaker/alerts/alert_snmp.sh         
attributes         trap_add_hires_timestamp_oid="false"         select 
attributes { master-prmStateful test1 }         to 192.168.28.188
 .INP: show tag:ones and type:location
 location l1 { p0 p1 p2 } inf: node1
 .INP: show tag:ones and p1
@@ -45,6 +46,10 @@
 property cib-bootstrap-options: \
        rule #uname eq node1 \
        stonith-enabled=no
+alert notify_10 "/usr/share/pacemaker/alerts/alert_snmp.sh" \
+       attributes trap_add_hires_timestamp_oid=false \
+        select attributes { master-prmStateful test1 } \
+       to 192.168.28.188
 alert notify_9 "/usr/share/pacemaker/alerts/alert_snmp.sh" \
        attributes trap_add_hires_timestamp_oid=false trap_node_states=non-trap 
trap_resource_tasks="start,stop,monitor,promote,demote" \
        to 192.168.40.9
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/crmsh-4.0.0+git.1543873923.0f9166fd/utils/crm_pkg.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/utils/crm_pkg.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/utils/crm_pkg.py    2018-12-03 
22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/utils/crm_pkg.py    2019-03-19 
09:57:40.000000000 +0100
@@ -86,10 +86,10 @@
 
     def present(self, name):
         if self.is_installed(name):
-            return (0, '', '', False)
+            return (0, b'', b'', False)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         cmd = [self._zyp,
                '--non-interactive',
@@ -106,7 +106,7 @@
             return self.present(name)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         pre_version = self.get_version(name)
         cmd = [self._zyp,
@@ -122,10 +122,10 @@
 
     def absent(self, name):
         if not self.is_installed(name):
-            return (0, '', '', False)
+            return (0, b'', b'', False)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         cmd = [self._zyp,
                '--non-interactive',
@@ -157,10 +157,10 @@
 
     def present(self, name):
         if self.is_installed(name):
-            return (0, '', '', False)
+            return (0, b'', b'', False)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         cmd = [self._yum,
                '--assumeyes',
@@ -176,7 +176,7 @@
             return self.present(name)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         pre_version = self.get_version(name)
         cmd = [self._yum,
@@ -191,10 +191,10 @@
 
     def absent(self, name):
         if not self.is_installed(name):
-            return (0, '', '', False)
+            return (0, b'', b'', False)
 
         if DRY_RUN:
-            return (0, '', '', True)
+            return (0, b'', b'', True)
 
         cmd = [self._yum,
                '--assumeyes',
@@ -207,7 +207,82 @@
 
 
 class Apt(PackageManager):
-    pass
+    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):
@@ -226,7 +301,7 @@
     managers = {
         'zypper': Zypper,
         'yum': Yum,
-        #'apt-get': Apt,
+        'apt-get': Apt,
         #'pacman': Pacman
     }
     for name, mgr in managers.items():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/crmsh-4.0.0+git.1543873923.0f9166fd/utils/crm_rpmcheck.py 
new/crmsh-4.0.0+git.1552985860.56f2db3a/utils/crm_rpmcheck.py
--- old/crmsh-4.0.0+git.1543873923.0f9166fd/utils/crm_rpmcheck.py       
2018-12-03 22:52:03.000000000 +0100
+++ new/crmsh-4.0.0+git.1552985860.56f2db3a/utils/crm_rpmcheck.py       
2019-03-19 09:57:40.000000000 +0100
@@ -2,6 +2,7 @@
 # Copyright (C) 2013 Kristoffer Gronlund <kgronl...@suse.com>
 # See COPYING for license information.
 
+import os
 import sys
 import json
 import subprocess
@@ -22,6 +23,19 @@
     """
     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:
@@ -33,6 +47,22 @@
         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():


Reply via email to