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 - [email protected] + +- 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 <[email protected]> 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 [email protected]. 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 <[email protected]> # 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():
