parent
c9f101ccb3
commit
09093237fa
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
||||
From 639732ddca765b2f147ef0c0a896968e3304ca49 Mon Sep 17 00:00:00 2001
|
||||
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
|
||||
Date: Mon, 23 Oct 2023 09:28:55 +0200
|
||||
Subject: [PATCH] fence_cisco_mds: undo metadata change, as it is an I/O agent
|
||||
|
||||
---
|
||||
agents/cisco_mds/fence_cisco_mds.py | 2 +-
|
||||
tests/data/metadata/fence_cisco_mds.xml | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/agents/cisco_mds/fence_cisco_mds.py b/agents/cisco_mds/fence_cisco_mds.py
|
||||
index 04cd1f842..fbb876a94 100644
|
||||
--- a/agents/cisco_mds/fence_cisco_mds.py
|
||||
+++ b/agents/cisco_mds/fence_cisco_mds.py
|
||||
@@ -77,7 +77,7 @@ def main():
|
||||
|
||||
docs = {}
|
||||
docs["shortdesc"] = "Fence agent for Cisco MDS"
|
||||
- docs["longdesc"] = "fence_cisco_mds is a Power Fencing agent \
|
||||
+ docs["longdesc"] = "fence_cisco_mds is an I/O Fencing agent \
|
||||
which can be used with any Cisco MDS 9000 series with SNMP enabled device."
|
||||
docs["vendorurl"] = "http://www.cisco.com"
|
||||
show_docs(options, docs)
|
||||
diff --git a/tests/data/metadata/fence_cisco_mds.xml b/tests/data/metadata/fence_cisco_mds.xml
|
||||
index 2105ecccc..829c9dcbe 100644
|
||||
--- a/tests/data/metadata/fence_cisco_mds.xml
|
||||
+++ b/tests/data/metadata/fence_cisco_mds.xml
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" ?>
|
||||
<resource-agent name="fence_cisco_mds" shortdesc="Fence agent for Cisco MDS" >
|
||||
-<longdesc>fence_cisco_mds is a Power Fencing agent which can be used with any Cisco MDS 9000 series with SNMP enabled device.</longdesc>
|
||||
+<longdesc>fence_cisco_mds is an I/O Fencing agent which can be used with any Cisco MDS 9000 series with SNMP enabled device.</longdesc>
|
||||
<vendor-url>http://www.cisco.com</vendor-url>
|
||||
<parameters>
|
||||
<parameter name="action" unique="0" required="1">
|
@ -0,0 +1,159 @@
|
||||
From dcb8ddd13c3dfad02e00c07f283251e0c2a60c46 Mon Sep 17 00:00:00 2001
|
||||
From: Reid Wahl <nrwahl@protonmail.com>
|
||||
Date: Mon, 16 Aug 2021 17:44:13 -0700
|
||||
Subject: [PATCH] fence_zvmip: Update longdesc to document all required
|
||||
functions
|
||||
|
||||
In RHBZ#1935641, IBM explained that the requesting user needs
|
||||
authorization for more functions than what is currently documented.
|
||||
|
||||
They said:
|
||||
"""
|
||||
What we found is that you need rights from three different NICKS:
|
||||
SERVER_MANAGEMENT, IMAGE_CHARACTERISTICS and IMAGE_OPERATIONS.
|
||||
You won't be able to give a user all three NICKS.
|
||||
Therefore, you have to create a new NICK with all capabilities from all
|
||||
three NICKS together and then assign the new NICK to the USER
|
||||
"ZCLUSTER".
|
||||
Even better is to just use the needed Subset with a new NICK.
|
||||
We found five commands which are used in the fencing code and on the
|
||||
z/VM Log which should be enough for fencing to work.
|
||||
|
||||
We suggest creating following files:
|
||||
|
||||
File VSMWORK1 NAMELIST:
|
||||
```
|
||||
:nick.ZVM_FENCE
|
||||
:list.
|
||||
IMAGE_ACTIVATE
|
||||
IMAGE_DEACTIVATE
|
||||
IMAGE_STATUS_QUERY
|
||||
CHECK_AUTHENTICATION
|
||||
IMAGE_NAME_QUERY_DM
|
||||
```
|
||||
|
||||
File VSMWORK1 AUTHLIST:
|
||||
```
|
||||
ZCLUSTER ALL ZVM_FENCE
|
||||
```
|
||||
|
||||
For details, we suggest adding a link to the current z/VM docu:
|
||||
- NAMELIST: https://www.ibm.com/support/knowledgecenter/de/SSB27U_7.2.0/com.ibm.zvm.v720.dmse6/namelst.htm
|
||||
- AUTHLIST: https://www.ibm.com/support/knowledgecenter/de/SSB27U_7.2.0/com.ibm.zvm.v720.dmse6/auf.htm
|
||||
"""
|
||||
|
||||
Resolves: RHBZ1935641
|
||||
|
||||
Signed-off-by: Reid Wahl <nrwahl@protonmail.com>
|
||||
---
|
||||
agents/zvm/fence_zvmip.py | 37 ++++++++++++++++++++++-------
|
||||
tests/data/metadata/fence_zvmip.xml | 37 ++++++++++++++++++++++-------
|
||||
2 files changed, 56 insertions(+), 18 deletions(-)
|
||||
|
||||
diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py
|
||||
index 4f538e10d..c37950a20 100644
|
||||
--- a/agents/zvm/fence_zvmip.py
|
||||
+++ b/agents/zvm/fence_zvmip.py
|
||||
@@ -199,21 +199,40 @@ def main():
|
||||
|
||||
docs = {}
|
||||
docs["shortdesc"] = "Fence agent for use with z/VM Virtual Machines"
|
||||
- docs["longdesc"] = """The fence_zvm agent is intended to be used with with z/VM SMAPI service via TCP/IP
|
||||
+ docs["longdesc"] = """The fence_zvmip agent is intended to be used with the
|
||||
+z/VM SMAPI service via TCP/IP.
|
||||
|
||||
-To use this agent the z/VM SMAPI service needs to be configured to allow the virtual machine running this agent to connect to it and issue
|
||||
-the image_recycle operation. This involves updating the VSMWORK1 AUTHLIST VMSYS:VSMWORK1. file. The entry should look something similar to
|
||||
-this:
|
||||
+The z/VM SMAPI service must be configured so that the virtual machine running
|
||||
+the agent can connect to the service, access the system's directory manager,
|
||||
+and shortly thereafter run image_deactivate and image_activate. This involves
|
||||
+updating the VSMWORK1 NAMELIST and VSMWORK1 AUTHLIST VMSYS:VSMWORK1 files.
|
||||
+
|
||||
+The NAMELIST entry assigns all the required functions to one nick and should
|
||||
+look similar to this:
|
||||
+
|
||||
+:nick.ZVM_FENCE
|
||||
+:list.
|
||||
+IMAGE_ACTIVATE
|
||||
+IMAGE_DEACTIVATE
|
||||
+IMAGE_STATUS_QUERY
|
||||
+CHECK_AUTHENTICATION
|
||||
+IMAGE_NAME_QUERY_DM
|
||||
+
|
||||
+
|
||||
+The AUTHLIST entry authorizes the user to perform all the functions associated
|
||||
+with the nick, and should look similar to this:
|
||||
|
||||
Column 1 Column 66 Column 131
|
||||
|
||||
- | | |
|
||||
- V V V
|
||||
+| | |
|
||||
+V V V
|
||||
+
|
||||
+XXXXXXXX ALL ZVM_FENCE
|
||||
|
||||
-XXXXXXXX ALL IMAGE_CHARACTERISTICS
|
||||
+where XXXXXXXX is the name of the user in the authuser field of the request.
|
||||
|
||||
-Where XXXXXXX is the name of the virtual machine used in the authuser field of the request. This virtual machine also has to be authorized
|
||||
-to access the system's directory manager.
|
||||
+Refer to the official z/VM documentation for complete instructions and
|
||||
+reference materials.
|
||||
"""
|
||||
docs["vendorurl"] = "http://www.ibm.com"
|
||||
show_docs(options, docs)
|
||||
diff --git a/tests/data/metadata/fence_zvmip.xml b/tests/data/metadata/fence_zvmip.xml
|
||||
index 6996ab736..96393bdfa 100644
|
||||
--- a/tests/data/metadata/fence_zvmip.xml
|
||||
+++ b/tests/data/metadata/fence_zvmip.xml
|
||||
@@ -1,20 +1,39 @@
|
||||
<?xml version="1.0" ?>
|
||||
<resource-agent name="fence_zvmip" shortdesc="Fence agent for use with z/VM Virtual Machines" >
|
||||
-<longdesc>The fence_zvm agent is intended to be used with with z/VM SMAPI service via TCP/IP
|
||||
+<longdesc>The fence_zvmip agent is intended to be used with the
|
||||
+z/VM SMAPI service via TCP/IP.
|
||||
|
||||
-To use this agent the z/VM SMAPI service needs to be configured to allow the virtual machine running this agent to connect to it and issue
|
||||
-the image_recycle operation. This involves updating the VSMWORK1 AUTHLIST VMSYS:VSMWORK1. file. The entry should look something similar to
|
||||
-this:
|
||||
+The z/VM SMAPI service must be configured so that the virtual machine running
|
||||
+the agent can connect to the service, access the system's directory manager,
|
||||
+and shortly thereafter run image_deactivate and image_activate. This involves
|
||||
+updating the VSMWORK1 NAMELIST and VSMWORK1 AUTHLIST VMSYS:VSMWORK1 files.
|
||||
+
|
||||
+The NAMELIST entry assigns all the required functions to one nick and should
|
||||
+look similar to this:
|
||||
+
|
||||
+:nick.ZVM_FENCE
|
||||
+:list.
|
||||
+IMAGE_ACTIVATE
|
||||
+IMAGE_DEACTIVATE
|
||||
+IMAGE_STATUS_QUERY
|
||||
+CHECK_AUTHENTICATION
|
||||
+IMAGE_NAME_QUERY_DM
|
||||
+
|
||||
+
|
||||
+The AUTHLIST entry authorizes the user to perform all the functions associated
|
||||
+with the nick, and should look similar to this:
|
||||
|
||||
Column 1 Column 66 Column 131
|
||||
|
||||
- | | |
|
||||
- V V V
|
||||
+| | |
|
||||
+V V V
|
||||
+
|
||||
+XXXXXXXX ALL ZVM_FENCE
|
||||
|
||||
-XXXXXXXX ALL IMAGE_CHARACTERISTICS
|
||||
+where XXXXXXXX is the name of the user in the authuser field of the request.
|
||||
|
||||
-Where XXXXXXX is the name of the virtual machine used in the authuser field of the request. This virtual machine also has to be authorized
|
||||
-to access the system's directory manager.
|
||||
+Refer to the official z/VM documentation for complete instructions and
|
||||
+reference materials.
|
||||
</longdesc>
|
||||
<vendor-url>http://www.ibm.com</vendor-url>
|
||||
<parameters>
|
@ -0,0 +1,41 @@
|
||||
From adac1d81c5758235b6df46d0a91f1e948655848a Mon Sep 17 00:00:00 2001
|
||||
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
|
||||
Date: Wed, 3 Jan 2024 10:17:50 +0100
|
||||
Subject: [PATCH] fence_zvmip: fix manpage formatting
|
||||
|
||||
---
|
||||
agents/zvm/fence_zvmip.py | 14 +++++++-------
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py
|
||||
index f1cea2652..bd8273c49 100644
|
||||
--- a/agents/zvm/fence_zvmip.py
|
||||
+++ b/agents/zvm/fence_zvmip.py
|
||||
@@ -210,12 +210,12 @@ def main():
|
||||
The NAMELIST entry assigns all the required functions to one nick and should
|
||||
look similar to this:
|
||||
|
||||
-:nick.ZVM_FENCE
|
||||
-:list.
|
||||
-IMAGE_ACTIVATE
|
||||
-IMAGE_DEACTIVATE
|
||||
-IMAGE_STATUS_QUERY
|
||||
-CHECK_AUTHENTICATION
|
||||
+:nick.ZVM_FENCE\n.br\n\
|
||||
+:list.\n.br\n\
|
||||
+IMAGE_ACTIVATE\n.br\n\
|
||||
+IMAGE_DEACTIVATE\n.br\n\
|
||||
+IMAGE_STATUS_QUERY\n.br\n\
|
||||
+CHECK_AUTHENTICATION\n.br\n\
|
||||
IMAGE_NAME_QUERY_DM
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ def main():
|
||||
|
||||
Column 1 Column 66 Column 131
|
||||
|
||||
-| | |
|
||||
+| | |\n.br\n\
|
||||
V V V
|
||||
|
||||
XXXXXXXX ALL ZVM_FENCE
|
@ -0,0 +1,380 @@
|
||||
From 55451b6fd007e6f9a6d6860e95304b7c5c27cc1b Mon Sep 17 00:00:00 2001
|
||||
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
|
||||
Date: Thu, 2 May 2024 15:10:16 +0200
|
||||
Subject: [PATCH 1/2] fencing: add support for docs["agent_name"] to use the
|
||||
main agent name when generating manpages
|
||||
|
||||
---
|
||||
lib/fencing.py.py | 12 +++++++++---
|
||||
tests/data/metadata/fence_eps.xml | 9 ++++++---
|
||||
2 files changed, 15 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/lib/fencing.py.py b/lib/fencing.py.py
|
||||
index 511eb2689..66e2ff156 100644
|
||||
--- a/lib/fencing.py.py
|
||||
+++ b/lib/fencing.py.py
|
||||
@@ -603,7 +603,7 @@ def usage(avail_opt):
|
||||
if len(value["help"]) != 0:
|
||||
print(" " + _join_wrap([value["help"]], first_indent=3))
|
||||
|
||||
-def metadata(options, avail_opt, docs):
|
||||
+def metadata(options, avail_opt, docs, agent_name=os.path.basename(sys.argv[0])):
|
||||
# avail_opt has to be unique, if there are duplicities then they should be removed
|
||||
sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt)) if "longopt" in all_opt[key]]
|
||||
# Find keys that are going to replace inconsistent names
|
||||
@@ -617,7 +617,7 @@ def metadata(options, avail_opt, docs):
|
||||
docs["longdesc"] = re.sub(r"\\f[BPIR]|\.P|\.TP|\.br\n", r"", docs["longdesc"])
|
||||
|
||||
print("<?xml version=\"1.0\" ?>")
|
||||
- print("<resource-agent name=\"" + os.path.basename(sys.argv[0]) + \
|
||||
+ print("<resource-agent name=\"" + agent_name + \
|
||||
"\" shortdesc=\"" + docs["shortdesc"] + "\" >")
|
||||
for (symlink, desc) in docs.get("symlink", []):
|
||||
print("<symlink name=\"" + symlink + "\" shortdesc=\"" + desc + "\"/>")
|
||||
@@ -928,9 +928,15 @@ def show_docs(options, docs=None):
|
||||
sys.exit(0)
|
||||
|
||||
if options.get("--action", "") in ["metadata", "manpage"]:
|
||||
+ if options["--action"] == "metadata" or "agent_name" not in docs:
|
||||
+ agent_name=os.path.basename(sys.argv[0])
|
||||
+ else:
|
||||
+ agent_name=docs["agent_name"]
|
||||
+
|
||||
+
|
||||
if "port_as_ip" in device_opt:
|
||||
device_opt.remove("separator")
|
||||
- metadata(options, device_opt, docs)
|
||||
+ metadata(options, device_opt, docs, agent_name)
|
||||
sys.exit(0)
|
||||
|
||||
if "--version" in options:
|
||||
diff --git a/tests/data/metadata/fence_eps.xml b/tests/data/metadata/fence_eps.xml
|
||||
index 3f9ebdc22..a3aeb1aea 100644
|
||||
--- a/tests/data/metadata/fence_eps.xml
|
||||
+++ b/tests/data/metadata/fence_eps.xml
|
||||
@@ -1,9 +1,12 @@
|
||||
<?xml version="1.0" ?>
|
||||
<resource-agent name="fence_eps" shortdesc="Fence agent for ePowerSwitch" >
|
||||
-<longdesc>fence_eps is a Power Fencing agent which can be used with the ePowerSwitch 8M+ power switch to fence connected machines. Fence agent works ONLY on 8M+ device, because this is only one, which has support for hidden page feature.
|
||||
+<symlink name="fence_epsr2" shortdesc="Fence agent for ePowerSwitch R2 and newer"/>
|
||||
+<longdesc>fence_eps is a Power Fencing agent which can be used with the ePowerSwitch 8M+ power switch to fence connected machines. It ONLY works on 8M+ devices, as they support the hidden page feature.
|
||||
|
||||
-Agent basically works by connecting to hidden page and pass appropriate arguments to GET request. This means, that hidden page feature must be enabled and properly configured.</longdesc>
|
||||
-<vendor-url>http://www.epowerswitch.com</vendor-url>
|
||||
+The agent works by connecting to the hidden page and pass the appropriate arguments to GET request. This means, that the hidden page feature must be enabled and properly configured.
|
||||
+
|
||||
+NOTE: In most cases you want to use fence_epsr2, as fence_eps only works with older hardware.</longdesc>
|
||||
+<vendor-url>https://www.neol.com</vendor-url>
|
||||
<parameters>
|
||||
<parameter name="action" unique="0" required="1">
|
||||
<getopt mixed="-o, --action=[action]" />
|
||||
|
||||
From 639f5293e0b2c0153ea01bf37534b74f436dd630 Mon Sep 17 00:00:00 2001
|
||||
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
|
||||
Date: Tue, 13 Feb 2024 11:11:25 +0100
|
||||
Subject: [PATCH 2/2] fence_eps: add fence_epsr2 for ePowerSwitch R2 and newer
|
||||
|
||||
---
|
||||
agents/eps/fence_eps.py | 46 ++++---
|
||||
fence-agents.spec.in | 4 +-
|
||||
tests/data/metadata/fence_epsr2.xml | 178 ++++++++++++++++++++++++++++
|
||||
3 files changed, 211 insertions(+), 17 deletions(-)
|
||||
create mode 100644 tests/data/metadata/fence_epsr2.xml
|
||||
|
||||
diff --git a/agents/eps/fence_eps.py b/agents/eps/fence_eps.py
|
||||
index 81e439533..1e6bda099 100644
|
||||
--- a/agents/eps/fence_eps.py
|
||||
+++ b/agents/eps/fence_eps.py
|
||||
@@ -3,8 +3,8 @@
|
||||
# The Following Agent Has Been Tested On:
|
||||
# ePowerSwitch 8M+ version 1.0.0.4
|
||||
|
||||
-import sys, re
|
||||
-import base64, string, socket
|
||||
+import sys, os, re
|
||||
+import base64, socket
|
||||
import logging
|
||||
import atexit
|
||||
sys.path.append("@FENCEAGENTSLIBDIR@")
|
||||
@@ -37,7 +37,7 @@ def eps_run_command(options, params):
|
||||
options["--password"] = "" # Default is empty password
|
||||
|
||||
# String for Authorization header
|
||||
- auth_str = 'Basic ' + string.strip(base64.encodestring(options["--username"]+':'+options["--password"]))
|
||||
+ auth_str = 'Basic ' + str(base64.encodebytes(bytes(options["--username"]+':'+options["--password"], "utf-8")).decode("utf-8").strip())
|
||||
logging.debug("Authorization: %s\n", auth_str)
|
||||
conn.putheader('Authorization', auth_str)
|
||||
|
||||
@@ -60,16 +60,22 @@ def eps_run_command(options, params):
|
||||
logging.error("Failed: {}".format(str(e)))
|
||||
fail(EC_LOGIN_DENIED)
|
||||
|
||||
- return result
|
||||
+ return result.decode("utf-8", "ignore")
|
||||
|
||||
def get_power_status(conn, options):
|
||||
del conn
|
||||
ret_val = eps_run_command(options, "")
|
||||
|
||||
result = {}
|
||||
- status = re.findall(r"p(\d{2})=(0|1)\s*\<br\>", ret_val.lower())
|
||||
+ if os.path.basename(sys.argv[0]) == "fence_eps":
|
||||
+ status = re.findall(r"p(\d{2})=(0|1)\s*\<br\>", ret_val.lower())
|
||||
+ elif os.path.basename(sys.argv[0]) == "fence_epsr2":
|
||||
+ status = re.findall(r"m0:o(\d)=(on|off)\s*", ret_val.lower())
|
||||
for out_num, out_stat in status:
|
||||
- result[out_num] = ("", (out_stat == "1" and "on" or "off"))
|
||||
+ if os.path.basename(sys.argv[0]) == "fence_eps":
|
||||
+ result[out_num] = ("", (out_stat == "1" and "on" or "off"))
|
||||
+ elif os.path.basename(sys.argv[0]) == "fence_epsr2":
|
||||
+ result[out_num] = ("", out_stat)
|
||||
|
||||
if not options["--action"] in ['monitor', 'list']:
|
||||
if not options["--plug"] in result:
|
||||
@@ -81,7 +87,12 @@ def get_power_status(conn, options):
|
||||
|
||||
def set_power_status(conn, options):
|
||||
del conn
|
||||
- eps_run_command(options, "P%s=%s"%(options["--plug"], (options["--action"] == "on" and "1" or "0")))
|
||||
+ if os.path.basename(sys.argv[0]) == "fence_eps":
|
||||
+ eps_run_command(options, "P%s=%s"%(options["--plug"], (options["--action"] == "on" and "1" or "0")))
|
||||
+ elif os.path.basename(sys.argv[0]) == "fence_epsr2":
|
||||
+ if options["--action"] == "reboot":
|
||||
+ options["--action"] = "off"
|
||||
+ eps_run_command(options, "M0:O%s=%s"%(options["--plug"], options["--action"]))
|
||||
|
||||
# Define new option
|
||||
def eps_define_new_opts():
|
||||
@@ -107,20 +118,25 @@ def main():
|
||||
options = check_input(device_opt, process_input(device_opt))
|
||||
|
||||
docs = {}
|
||||
+ docs["agent_name"] = "fence_eps"
|
||||
docs["shortdesc"] = "Fence agent for ePowerSwitch"
|
||||
- docs["longdesc"] = "fence_eps is a Power Fencing agent \
|
||||
+ docs["longdesc"] = os.path.basename(sys.argv[0]) + " is a Power Fencing agent \
|
||||
which can be used with the ePowerSwitch 8M+ power switch to fence \
|
||||
-connected machines. Fence agent works ONLY on 8M+ device, because \
|
||||
-this is only one, which has support for hidden page feature. \
|
||||
+connected machines. It ONLY works on 8M+ devices, as \
|
||||
+they support the hidden page feature. \
|
||||
\n.TP\n\
|
||||
-Agent basically works by connecting to hidden page and pass \
|
||||
-appropriate arguments to GET request. This means, that hidden \
|
||||
-page feature must be enabled and properly configured."
|
||||
- docs["vendorurl"] = "http://www.epowerswitch.com"
|
||||
+The agent works by connecting to the hidden page and pass \
|
||||
+the appropriate arguments to GET request. This means, that the hidden \
|
||||
+page feature must be enabled and properly configured. \
|
||||
+\n.TP\n\
|
||||
+NOTE: In most cases you want to use fence_epsr2, as fence_eps \
|
||||
+only works with older hardware."
|
||||
+ docs["vendorurl"] = "https://www.neol.com"
|
||||
+ docs["symlink"] = [("fence_epsr2", "Fence agent for ePowerSwitch R2 and newer")]
|
||||
show_docs(options, docs)
|
||||
|
||||
run_delay(options)
|
||||
- #Run fence action. Conn is None, beacause we always need open new http connection
|
||||
+ #Run fence action. Conn is None, because we always need open new http connection
|
||||
result = fence_action(None, options, set_power_status, get_power_status, get_power_status)
|
||||
|
||||
sys.exit(result)
|
||||
diff --git a/fence-agents.spec.in b/fence-agents.spec.in
|
||||
index e139e6da5..5b8066122 100644
|
||||
--- a/fence-agents.spec.in
|
||||
+++ b/fence-agents.spec.in
|
||||
@@ -597,8 +597,8 @@ BuildArch: noarch
|
||||
Fence agent for ePowerSwitch 8M+ power switches that are accessed
|
||||
via the HTTP(s) protocol.
|
||||
%files eps
|
||||
-%{_sbindir}/fence_eps
|
||||
-%{_mandir}/man8/fence_eps.8*
|
||||
+%{_sbindir}/fence_eps*
|
||||
+%{_mandir}/man8/fence_eps*.8*
|
||||
|
||||
%package gce
|
||||
License: GPL-2.0-or-later AND LGPL-2.0-or-later
|
||||
diff --git a/tests/data/metadata/fence_epsr2.xml b/tests/data/metadata/fence_epsr2.xml
|
||||
new file mode 100644
|
||||
index 000000000..37074e052
|
||||
--- /dev/null
|
||||
+++ b/tests/data/metadata/fence_epsr2.xml
|
||||
@@ -0,0 +1,178 @@
|
||||
+<?xml version="1.0" ?>
|
||||
+<resource-agent name="fence_epsr2" shortdesc="Fence agent for ePowerSwitch" >
|
||||
+<symlink name="fence_epsr2" shortdesc="Fence agent for ePowerSwitch R2 and newer"/>
|
||||
+<longdesc>fence_epsr2 is a Power Fencing agent which can be used with the ePowerSwitch 8M+ power switch to fence connected machines. It ONLY works on 8M+ devices, as they support the hidden page feature.
|
||||
+
|
||||
+The agent works by connecting to the hidden page and pass the appropriate arguments to GET request. This means, that the hidden page feature must be enabled and properly configured.
|
||||
+
|
||||
+NOTE: In most cases you want to use fence_epsr2, as fence_eps only works with older hardware.</longdesc>
|
||||
+<vendor-url>https://www.neol.com</vendor-url>
|
||||
+<parameters>
|
||||
+ <parameter name="action" unique="0" required="1">
|
||||
+ <getopt mixed="-o, --action=[action]" />
|
||||
+ <content type="string" default="reboot" />
|
||||
+ <shortdesc lang="en">Fencing action</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="hidden_page" unique="0" required="0" deprecated="1">
|
||||
+ <getopt mixed="-c, --page=[page]" />
|
||||
+ <content type="string" default="hidden.htm" />
|
||||
+ <shortdesc lang="en">Name of hidden page</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="ip" unique="0" required="1" obsoletes="ipaddr">
|
||||
+ <getopt mixed="-a, --ip=[ip]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">IP address or hostname of fencing device</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="ipaddr" unique="0" required="1" deprecated="1">
|
||||
+ <getopt mixed="-a, --ip=[ip]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">IP address or hostname of fencing device</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="ipport" unique="0" required="0">
|
||||
+ <getopt mixed="-u, --ipport=[port]" />
|
||||
+ <content type="integer" default="80" />
|
||||
+ <shortdesc lang="en">TCP/UDP port to use for connection with device</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="login" unique="0" required="0" deprecated="1">
|
||||
+ <getopt mixed="-l, --username=[name]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Login name</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="page" unique="0" required="0" obsoletes="hidden_page">
|
||||
+ <getopt mixed="-c, --page=[page]" />
|
||||
+ <content type="string" default="hidden.htm" />
|
||||
+ <shortdesc lang="en">Name of hidden page</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="passwd" unique="0" required="0" deprecated="1">
|
||||
+ <getopt mixed="-p, --password=[password]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Login password or passphrase</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="passwd_script" unique="0" required="0" deprecated="1">
|
||||
+ <getopt mixed="-S, --password-script=[script]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Script to run to retrieve password</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="password" unique="0" required="0" obsoletes="passwd">
|
||||
+ <getopt mixed="-p, --password=[password]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Login password or passphrase</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="password_script" unique="0" required="0" obsoletes="passwd_script">
|
||||
+ <getopt mixed="-S, --password-script=[script]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Script to run to retrieve password</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="plug" unique="0" required="1" obsoletes="port">
|
||||
+ <getopt mixed="-n, --plug=[id]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Physical plug number on device, UUID or identification of machine</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="port" unique="0" required="1" deprecated="1">
|
||||
+ <getopt mixed="-n, --plug=[id]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Physical plug number on device, UUID or identification of machine</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="username" unique="0" required="0" obsoletes="login">
|
||||
+ <getopt mixed="-l, --username=[name]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Login name</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="quiet" unique="0" required="0">
|
||||
+ <getopt mixed="-q, --quiet" />
|
||||
+ <content type="boolean" />
|
||||
+ <shortdesc lang="en">Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="verbose" unique="0" required="0">
|
||||
+ <getopt mixed="-v, --verbose" />
|
||||
+ <content type="boolean" />
|
||||
+ <shortdesc lang="en">Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity.</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="verbose_level" unique="0" required="0">
|
||||
+ <getopt mixed="--verbose-level" />
|
||||
+ <content type="integer" />
|
||||
+ <shortdesc lang="en">Level of debugging detail in output. Defaults to the number of --verbose flags specified on the command line, or to 1 if verbose=1 in a stonith device configuration (i.e., on stdin).</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="debug" unique="0" required="0" deprecated="1">
|
||||
+ <getopt mixed="-D, --debug-file=[debugfile]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Write debug information to given file</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="debug_file" unique="0" required="0" obsoletes="debug">
|
||||
+ <getopt mixed="-D, --debug-file=[debugfile]" />
|
||||
+ <shortdesc lang="en">Write debug information to given file</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="version" unique="0" required="0">
|
||||
+ <getopt mixed="-V, --version" />
|
||||
+ <content type="boolean" />
|
||||
+ <shortdesc lang="en">Display version information and exit</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="help" unique="0" required="0">
|
||||
+ <getopt mixed="-h, --help" />
|
||||
+ <content type="boolean" />
|
||||
+ <shortdesc lang="en">Display help and exit</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="plug_separator" unique="0" required="0">
|
||||
+ <getopt mixed="--plug-separator=[char]" />
|
||||
+ <content type="string" default="," />
|
||||
+ <shortdesc lang="en">Separator for plug parameter when specifying more than 1 plug</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="separator" unique="0" required="0">
|
||||
+ <getopt mixed="-C, --separator=[char]" />
|
||||
+ <content type="string" default="," />
|
||||
+ <shortdesc lang="en">Separator for CSV created by 'list' operation</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="delay" unique="0" required="0">
|
||||
+ <getopt mixed="--delay=[seconds]" />
|
||||
+ <content type="second" default="0" />
|
||||
+ <shortdesc lang="en">Wait X seconds before fencing is started</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="disable_timeout" unique="0" required="0">
|
||||
+ <getopt mixed="--disable-timeout=[true/false]" />
|
||||
+ <content type="string" />
|
||||
+ <shortdesc lang="en">Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="login_timeout" unique="0" required="0">
|
||||
+ <getopt mixed="--login-timeout=[seconds]" />
|
||||
+ <content type="second" default="5" />
|
||||
+ <shortdesc lang="en">Wait X seconds for cmd prompt after login</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="power_timeout" unique="0" required="0">
|
||||
+ <getopt mixed="--power-timeout=[seconds]" />
|
||||
+ <content type="second" default="20" />
|
||||
+ <shortdesc lang="en">Test X seconds for status change after ON/OFF</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="power_wait" unique="0" required="0">
|
||||
+ <getopt mixed="--power-wait=[seconds]" />
|
||||
+ <content type="second" default="0" />
|
||||
+ <shortdesc lang="en">Wait X seconds after issuing ON/OFF</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="shell_timeout" unique="0" required="0">
|
||||
+ <getopt mixed="--shell-timeout=[seconds]" />
|
||||
+ <content type="second" default="3" />
|
||||
+ <shortdesc lang="en">Wait X seconds for cmd prompt after issuing command</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="stonith_status_sleep" unique="0" required="0">
|
||||
+ <getopt mixed="--stonith-status-sleep=[seconds]" />
|
||||
+ <content type="second" default="1" />
|
||||
+ <shortdesc lang="en">Sleep X seconds between status calls during a STONITH action</shortdesc>
|
||||
+ </parameter>
|
||||
+ <parameter name="retry_on" unique="0" required="0">
|
||||
+ <getopt mixed="--retry-on=[attempts]" />
|
||||
+ <content type="integer" default="1" />
|
||||
+ <shortdesc lang="en">Count of attempts to retry power on</shortdesc>
|
||||
+ </parameter>
|
||||
+</parameters>
|
||||
+<actions>
|
||||
+ <action name="on" automatic="0"/>
|
||||
+ <action name="off" />
|
||||
+ <action name="reboot" />
|
||||
+ <action name="status" />
|
||||
+ <action name="list" />
|
||||
+ <action name="list-status" />
|
||||
+ <action name="monitor" />
|
||||
+ <action name="metadata" />
|
||||
+ <action name="manpage" />
|
||||
+ <action name="validate-all" />
|
||||
+</actions>
|
||||
+</resource-agent>
|
@ -0,0 +1,65 @@
|
||||
From d655030770081e2dfe46f90e27620472a502289d Mon Sep 17 00:00:00 2001
|
||||
From: David Lord <davidism@gmail.com>
|
||||
Date: Thu, 2 May 2024 09:14:00 -0700
|
||||
Subject: [PATCH] disallow invalid characters in keys to xmlattr filter
|
||||
|
||||
---
|
||||
CHANGES.rst | 6 ++++++
|
||||
src/jinja2/filters.py | 22 +++++++++++++++++-----
|
||||
tests/test_filters.py | 11 ++++++-----
|
||||
3 files changed, 29 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/kubevirt/jinja2/filters.py b/kubevirt/jinja2/filters.py
|
||||
index 4cf3c11fb..acd11976e 100644
|
||||
--- a/kubevirt/jinja2/filters.py
|
||||
+++ b/kubevirt/jinja2/filters.py
|
||||
@@ -250,7 +250,9 @@ def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K
|
||||
yield from value.items()
|
||||
|
||||
|
||||
-_space_re = re.compile(r"\s", flags=re.ASCII)
|
||||
+# Check for characters that would move the parser state from key to value.
|
||||
+# https://html.spec.whatwg.org/#attribute-name-state
|
||||
+_attr_key_re = re.compile(r"[\s/>=]", flags=re.ASCII)
|
||||
|
||||
|
||||
@pass_eval_context
|
||||
@@ -259,8 +261,14 @@ def do_xmlattr(
|
||||
) -> str:
|
||||
"""Create an SGML/XML attribute string based on the items in a dict.
|
||||
|
||||
- If any key contains a space, this fails with a ``ValueError``. Values that
|
||||
- are neither ``none`` nor ``undefined`` are automatically escaped.
|
||||
+ **Values** that are neither ``none`` nor ``undefined`` are automatically
|
||||
+ escaped, safely allowing untrusted user input.
|
||||
+
|
||||
+ User input should not be used as **keys** to this filter. If any key
|
||||
+ contains a space, ``/`` solidus, ``>`` greater-than sign, or ``=`` equals
|
||||
+ sign, this fails with a ``ValueError``. Regardless of this, user input
|
||||
+ should never be used as keys to this filter, or must be separately validated
|
||||
+ first.
|
||||
|
||||
.. sourcecode:: html+jinja
|
||||
|
||||
@@ -280,6 +288,10 @@ def do_xmlattr(
|
||||
As you can see it automatically prepends a space in front of the item
|
||||
if the filter returned something unless the second parameter is false.
|
||||
|
||||
+ .. versionchanged:: 3.1.4
|
||||
+ Keys with ``/`` solidus, ``>`` greater-than sign, or ``=`` equals sign
|
||||
+ are not allowed.
|
||||
+
|
||||
.. versionchanged:: 3.1.3
|
||||
Keys with spaces are not allowed.
|
||||
"""
|
||||
@@ -289,8 +301,8 @@ def do_xmlattr(
|
||||
if value is None or isinstance(value, Undefined):
|
||||
continue
|
||||
|
||||
- if _space_re.search(key) is not None:
|
||||
- raise ValueError(f"Spaces are not allowed in attributes: '{key}'")
|
||||
+ if _attr_key_re.search(key) is not None:
|
||||
+ raise ValueError(f"Invalid character in attribute name: {key!r}")
|
||||
|
||||
items.append(f'{escape(key)}="{escape(value)}"')
|
||||
|
@ -0,0 +1,68 @@
|
||||
From 9d0d0d013c7edae43a4ebc5f46bf2e7a4f127654 Mon Sep 17 00:00:00 2001
|
||||
From: "sreejit.mohanan" <sreejit.mohanan@nutanix.com>
|
||||
Date: Fri, 17 Feb 2023 18:04:03 -0800
|
||||
Subject: [PATCH] fence_scsi: fix registration handling if ISID conflicts ISID
|
||||
(Initiator Session ID) belonging to I_T Nexus changes for RHEL based on the
|
||||
session ID. This means that the connection to the device can be set up with
|
||||
different ISID on reconnects.
|
||||
|
||||
fence_scsi treats same key as a tip to ignore issuing registration
|
||||
to the device but if the device was registered using a different
|
||||
ISID, the key would be the same but the I_T Nexus (new ISID) would
|
||||
not have access to the device.
|
||||
|
||||
Fixing this by preempting the old key and replacing with the current
|
||||
one.
|
||||
---
|
||||
agents/scsi/fence_scsi.py | 35 ++++++++++++++++++++++++++++++++---
|
||||
1 file changed, 32 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/agents/scsi/fence_scsi.py b/agents/scsi/fence_scsi.py
|
||||
index f9e6823b2..85e4f29e6 100644
|
||||
--- a/agents/scsi/fence_scsi.py
|
||||
+++ b/agents/scsi/fence_scsi.py
|
||||
@@ -137,12 +137,41 @@ def register_dev(options, dev):
|
||||
for slave in get_mpath_slaves(dev):
|
||||
register_dev(options, slave)
|
||||
return True
|
||||
- if get_reservation_key(options, dev, False) == options["--key"]:
|
||||
- return True
|
||||
+
|
||||
+ # Check if any registration exists for the key already. We track this in
|
||||
+ # order to decide whether the existing registration needs to be cleared.
|
||||
+ # This is needed since the previous registration could be for a
|
||||
+ # different I_T nexus (different ISID).
|
||||
+ registration_key_exists = False
|
||||
+ if options["--key"] in get_registration_keys(options, dev):
|
||||
+ registration_key_exists = True
|
||||
+ if not register_helper(options, options["--key"], dev):
|
||||
+ return False
|
||||
+
|
||||
+ if registration_key_exists:
|
||||
+ # If key matches, make sure it matches with the connection that
|
||||
+ # exists right now. To do this, we can issue a preempt with same key
|
||||
+ # which should replace the old invalid entries from the target.
|
||||
+ if not preempt(options, options["--key"], dev):
|
||||
+ return False
|
||||
+
|
||||
+ # If there was no reservation, we need to issue another registration
|
||||
+ # since the previous preempt would clear registration made above.
|
||||
+ if get_reservation_key(options, dev, False) != options["--key"]:
|
||||
+ return register_helper(options, options["--key"], dev)
|
||||
+ return True
|
||||
+
|
||||
+# cancel registration without aborting tasks
|
||||
+def preempt(options, host, dev):
|
||||
+ reset_dev(options,dev)
|
||||
+ cmd = options["--sg_persist-path"] + " -n -o -P -T 5 -K " + host + " -S " + options["--key"] + " -d " + dev
|
||||
+ return not bool(run_cmd(options, cmd)["rc"])
|
||||
+
|
||||
+# helper function to send the register command
|
||||
+def register_helper(options, host, dev):
|
||||
reset_dev(options, dev)
|
||||
cmd = options["--sg_persist-path"] + " -n -o -I -S " + options["--key"] + " -d " + dev
|
||||
cmd += " -Z" if "--aptpl" in options else ""
|
||||
- #cmd return code != 0 but registration can be successful
|
||||
return not bool(run_cmd(options, cmd)["rc"])
|
||||
|
||||
|
@ -0,0 +1,103 @@
|
||||
From 34baef58db442148b8e067509d2cdd37b7a91ef4 Mon Sep 17 00:00:00 2001
|
||||
From: "sreejit.mohanan" <sreejit.mohanan@nutanix.com>
|
||||
Date: Thu, 7 Sep 2023 15:57:51 -0700
|
||||
Subject: [PATCH] fence_scsi: fix registration handling in device 'off'
|
||||
workflows
|
||||
|
||||
ISID (Initiator Session ID) belonging to I_T Nexus changes for
|
||||
RHEL based on the session ID. This means that the connection to
|
||||
the device can be set up with different ISID on reconnects.
|
||||
|
||||
When a device is powered off, fence_scsi assumes that the client
|
||||
has a registration to the device and sends a preempt-and-abort
|
||||
request which ends up failing due to reservation conflict.
|
||||
|
||||
Fixing this by registering the host key with the device and preempting
|
||||
the old registration (if it exists). This should make sure that the
|
||||
host is able to preempt the other key successfully.
|
||||
---
|
||||
agents/scsi/fence_scsi.py | 29 +++++++++++++++--------------
|
||||
1 file changed, 15 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/agents/scsi/fence_scsi.py b/agents/scsi/fence_scsi.py
|
||||
index 42530ceb5..519319bf5 100644
|
||||
--- a/agents/scsi/fence_scsi.py
|
||||
+++ b/agents/scsi/fence_scsi.py
|
||||
@@ -41,7 +41,7 @@ def set_status(conn, options):
|
||||
for dev in options["devices"]:
|
||||
is_block_device(dev)
|
||||
|
||||
- register_dev(options, dev)
|
||||
+ register_dev(options, dev, options["--key"])
|
||||
if options["--key"] not in get_registration_keys(options, dev):
|
||||
count += 1
|
||||
logging.debug("Failed to register key "\
|
||||
@@ -62,7 +62,7 @@ def set_status(conn, options):
|
||||
fail_usage("Failed: keys cannot be same. You can not fence yourself.")
|
||||
for dev in options["devices"]:
|
||||
is_block_device(dev)
|
||||
-
|
||||
+ register_dev(options, dev, host_key)
|
||||
if options["--key"] in get_registration_keys(options, dev):
|
||||
preempt_abort(options, host_key, dev)
|
||||
|
||||
@@ -131,11 +131,11 @@ def reset_dev(options, dev):
|
||||
return run_cmd(options, options["--sg_turs-path"] + " " + dev)["rc"]
|
||||
|
||||
|
||||
-def register_dev(options, dev):
|
||||
+def register_dev(options, dev, key):
|
||||
dev = os.path.realpath(dev)
|
||||
if re.search(r"^dm", dev[5:]):
|
||||
for slave in get_mpath_slaves(dev):
|
||||
- register_dev(options, slave)
|
||||
+ register_dev(options, slave, key)
|
||||
return True
|
||||
|
||||
# Check if any registration exists for the key already. We track this in
|
||||
@@ -143,34 +143,35 @@ def register_dev(options, dev):
|
||||
# This is needed since the previous registration could be for a
|
||||
# different I_T nexus (different ISID).
|
||||
registration_key_exists = False
|
||||
- if options["--key"] in get_registration_keys(options, dev):
|
||||
+ if key in get_registration_keys(options, dev):
|
||||
+ logging.debug("Registration key exists for device " + dev)
|
||||
registration_key_exists = True
|
||||
- if not register_helper(options, options["--key"], dev):
|
||||
+ if not register_helper(options, dev, key):
|
||||
return False
|
||||
|
||||
if registration_key_exists:
|
||||
# If key matches, make sure it matches with the connection that
|
||||
# exists right now. To do this, we can issue a preempt with same key
|
||||
# which should replace the old invalid entries from the target.
|
||||
- if not preempt(options, options["--key"], dev):
|
||||
+ if not preempt(options, key, dev, key):
|
||||
return False
|
||||
|
||||
# If there was no reservation, we need to issue another registration
|
||||
# since the previous preempt would clear registration made above.
|
||||
- if get_reservation_key(options, dev, False) != options["--key"]:
|
||||
- return register_helper(options, options["--key"], dev)
|
||||
+ if get_reservation_key(options, dev, False) != key:
|
||||
+ return register_helper(options, dev, key)
|
||||
return True
|
||||
|
||||
-# cancel registration without aborting tasks
|
||||
-def preempt(options, host, dev):
|
||||
+# helper function to preempt host with 'key' using 'host_key' without aborting tasks
|
||||
+def preempt(options, host_key, dev, key):
|
||||
reset_dev(options,dev)
|
||||
- cmd = options["--sg_persist-path"] + " -n -o -P -T 5 -K " + host + " -S " + options["--key"] + " -d " + dev
|
||||
+ cmd = options["--sg_persist-path"] + " -n -o -P -T 5 -K " + host_key + " -S " + key + " -d " + dev
|
||||
return not bool(run_cmd(options, cmd)["rc"])
|
||||
|
||||
# helper function to send the register command
|
||||
-def register_helper(options, host, dev):
|
||||
+def register_helper(options, dev, key):
|
||||
reset_dev(options, dev)
|
||||
- cmd = options["--sg_persist-path"] + " -n -o -I -S " + options["--key"] + " -d " + dev
|
||||
+ cmd = options["--sg_persist-path"] + " -n -o -I -S " + key + " -d " + dev
|
||||
cmd += " -Z" if "--aptpl" in options else ""
|
||||
return not bool(run_cmd(options, cmd)["rc"])
|
||||
|
Loading…
Reference in new issue