Compare commits

...

No commits in common. "c8" and "c8-beta" have entirely different histories.
c8 ... c8-beta

18 changed files with 19 additions and 1924 deletions

View File

@ -1,62 +1,37 @@
3297473a9d57e93ff378eab173990c1b64673c01 SOURCES/Jinja2-3.0.2.tar.gz
e1b766b2b1601fde67b3b19ed2f13b9746bb1cca SOURCES/MarkupSafe-2.0.1.tar.gz
3880207fdf0db1eeadf976092c8fbf80a7335c24 SOURCES/PyJWT-2.4.0.tar.gz
a8c40a3ae9d4c159382a58db3153d83e5521c51e SOURCES/PyYAML-6.0.tar.gz
0c47ce98be5a519023c16e10027ed1268c489fcc SOURCES/adal-1.2.7.tar.gz
0a56f6d9ed2014a363486d33b63eca094379be06 SOURCES/aliyun-python-sdk-core-2.13.1.tar.gz
c2a98b9a1562d223a76514f05028488ca000c395 SOURCES/aliyun-python-sdk-ecs-4.9.3.tar.gz
f14647a4d37a9a254c4e711b95a7654fc418e41e SOURCES/aliyun-python-sdk-vpc-3.0.2.tar.gz
1f493a02d15374027ae2bcb2ea4daf5b907c528b SOURCES/azure-common-1.1.28.zip
acfa532a6e6acc7311d697b3835a6190796ab5b4 SOURCES/azure-core-1.24.2.zip
46d2d17d958ae305ced32fdd6aa847b0cdf31989 SOURCES/azure-identity-1.10.0.zip
2a2e61ec1805165b19c4652b316225ced9b1ca28 SOURCES/azure-mgmt-compute-27.2.0.zip
f73acf29dd33e65a37b13dc8048832cbdd9e8306 SOURCES/azure-mgmt-core-1.3.2.zip
d4672e130177a9b330cf40c5bded8ed3aa4b1143 SOURCES/azure-mgmt-network-20.0.0.zip
2512ff4ef016cad0b916006f6acf2a309f908c4d SOURCES/botocore-1.23.46.tar.gz
0d12f48faa727f0979e9ad5c4c80dfa32b73caff SOURCES/cachetools-4.2.4.tar.gz
ec7e8dd8ef95edfdb83a1ea040b8b88507b47615 SOURCES/certifi-2023.7.22.tar.gz
c42a46cd11f6153f299cf10e9c236e8b2a143c21 SOURCES/cffi-1.15.1.tar.gz
2384f6cfba4685d901262e073a4455d4cf76d102 SOURCES/chardet-4.0.0.tar.gz
865df92e66e5dc7b940144cbad8115c07dc8784f SOURCES/charset-normalizer-2.0.7.tar.gz
eb8be696115458f9368432525e9cae11d0f6bebf SOURCES/cryptography-3.3.2.tar.gz
e2561df8e7ff9113dab118a651371dd88dab0142 SOURCES/fence-agents-4.2.1.tar.gz
81c165cd2a388d5a6cd415308edaf11ee5bf42cd SOURCES/flit_core-3.10.1.tar.gz
f4e578dc0ed68d6667d7b36cdfc2647d55e9858f SOURCES/google-auth-2.3.0.tar.gz
74ec77d2e2ef6b2ef8503e6e398faa6f3ba298ae SOURCES/httplib2-0.19.1-py3-none-any.whl
08c0449533fc94462f78652dea209099754d9ee4 SOURCES/idna-3.3.tar.gz
ea36ce1c780dd44f01225dca7f9995a6685a60cc SOURCES/isodate-0.6.1.tar.gz
356c48dfea2214dd9e7e2b222a99dddfe9c0d05c SOURCES/jmespath-0.10.0.tar.gz
d06a9547b1a87e9c51b0a7c708189d993f2e3d89 SOURCES/kubernetes-12.0.1.tar.gz
373683b6adda29af3b1334a69bf149b85deb7899 SOURCES/msal-1.27.0.tar.gz
04e016bd1fa4ed6ddb852095a45d4f8c81a5b54a SOURCES/msal-extensions-1.0.0.tar.gz
00c5509205e59ebae09e5d3fe068ab61588e9b4a SOURCES/msrest-0.7.1.zip
bcedc87fc73e9738cbc8b0435513c01b6eb0b5c2 SOURCES/msrestazure-0.6.4.tar.gz
f6efa66f6106b069b5c0e0cf8cc677e4e96c91ca SOURCES/oauthlib-3.1.1.tar.gz
7e2f8f4cebf309ef6aaf740ee9073276d6937802 SOURCES/oauthlib-3.2.2.tar.gz
570d69d8c108ebb8aee562389d13b07dfb61ce25 SOURCES/openshift-0.12.1.tar.gz
bccbc1bf76a9db46998eb8e1ffa2f2a2baf9237a SOURCES/packaging-21.2-py3-none-any.whl
a243525070cf70f22a185447ebd3e1435dabb218 SOURCES/pip-21.3.1.tar.gz
67c8e65065f751c87cadf973a0a2d7b2cfad3bfa SOURCES/poetry-core-1.0.8.tar.gz
4f099f76d500f4ca500a17a3a647f84b7a796e54 SOURCES/portalocker-2.7.0.tar.gz
e0fa19f8fda46a1fa2253477499b116b33f67175 SOURCES/pyasn1-0.4.8.tar.gz
43b89feb6864fe359aae89120627165219de313b SOURCES/pyasn1-modules-0.2.8.tar.gz
0ae93d89b69fab48af3a407a2f8663bcea270c3d SOURCES/pycparser-2.20.tar.gz
c55d177e9484d974c95078d4ae945f89ba2c7251 SOURCES/pycryptodome-3.20.0.tar.gz
c8307f47e3b75a2d02af72982a2dfefa3f56e407 SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl
c2ba10c775b7a52a4b57cac4d4110a0c0f812a82 SOURCES/python-dateutil-2.8.2.tar.gz
1dc2fa004aa6517f1620e55d8a7b8e68a9cf2a47 SOURCES/python-string-utils-1.0.0.tar.gz
8c7a89d183d3e9b70bf91ba5b75eccf7111b9d8d SOURCES/requests-2.26.0.tar.gz
96c46fc47e75d06fa7ead4d7fb1ae7d58d68989f SOURCES/requests-2.27.1.tar.gz
f139aed770519b6a095b8fdc888d03955cbe9d8e SOURCES/requests-oauthlib-1.3.0.tar.gz
024bb67bc625557fd15b70e1c8e7d6abf5aa75dd SOURCES/requests-oauthlib-2.0.0.tar.gz
e8a53067e03fe1b6682fd99a40a7359396a06daa SOURCES/rsa-4.7.2.tar.gz
d1011ff44cd5a045de0460c1b79ec65592e86860 SOURCES/ruamel.yaml-0.17.16.tar.gz
27de97227bbbde5a9f571f9fad223578d7bdf7cc SOURCES/ruamel.yaml.clib-0.2.6.tar.gz
d5354718cb8c9330d3abc27445467ce8a5ed9d70 SOURCES/setuptools-58.3.0.tar.gz
0f34eba670121f9c41939ca8d805687de359f71c SOURCES/setuptools_scm-6.4.2.tar.gz
a4f02fddae697614e356cadfddb6241cc7737f38 SOURCES/setuptools_scm-6.3.2.tar.gz
06fa0bb50f2a4e2917fd14c21e9d2d5508ce0163 SOURCES/six-1.16.0.tar.gz
2a2b9fb5137976719423b1bb8241b83eb32bb7b0 SOURCES/tomli-1.1.0.tar.gz
0fab53e2ff16ae6370cfcf72e3e251535a5ebe74 SOURCES/typing_extensions-4.1.1.tar.gz
b42b7960047441db7dc021cc20e14279bd836f8d SOURCES/tomli-1.0.1.tar.gz
84e2852d8da1655373f7ce5e7d5d3e256b62b4e4 SOURCES/urllib3-1.26.18.tar.gz
540f083782c584989c1a0f69ffd69ba7aae07db6 SOURCES/websocket-client-1.2.1.tar.gz
f9f3d980579b88baaacdb6c4a3cc4466b9353030 SOURCES/wheel-0.37.1.tar.gz

29
.gitignore vendored
View File

@ -1,62 +1,37 @@
SOURCES/Jinja2-3.0.2.tar.gz
SOURCES/MarkupSafe-2.0.1.tar.gz
SOURCES/PyJWT-2.4.0.tar.gz
SOURCES/PyYAML-6.0.tar.gz
SOURCES/adal-1.2.7.tar.gz
SOURCES/aliyun-python-sdk-core-2.13.1.tar.gz
SOURCES/aliyun-python-sdk-ecs-4.9.3.tar.gz
SOURCES/aliyun-python-sdk-vpc-3.0.2.tar.gz
SOURCES/azure-common-1.1.28.zip
SOURCES/azure-core-1.24.2.zip
SOURCES/azure-identity-1.10.0.zip
SOURCES/azure-mgmt-compute-27.2.0.zip
SOURCES/azure-mgmt-core-1.3.2.zip
SOURCES/azure-mgmt-network-20.0.0.zip
SOURCES/botocore-1.23.46.tar.gz
SOURCES/cachetools-4.2.4.tar.gz
SOURCES/certifi-2023.7.22.tar.gz
SOURCES/cffi-1.15.1.tar.gz
SOURCES/chardet-4.0.0.tar.gz
SOURCES/charset-normalizer-2.0.7.tar.gz
SOURCES/cryptography-3.3.2.tar.gz
SOURCES/fence-agents-4.2.1.tar.gz
SOURCES/flit_core-3.10.1.tar.gz
SOURCES/google-auth-2.3.0.tar.gz
SOURCES/httplib2-0.19.1-py3-none-any.whl
SOURCES/idna-3.3.tar.gz
SOURCES/isodate-0.6.1.tar.gz
SOURCES/jmespath-0.10.0.tar.gz
SOURCES/kubernetes-12.0.1.tar.gz
SOURCES/msal-1.27.0.tar.gz
SOURCES/msal-extensions-1.0.0.tar.gz
SOURCES/msrest-0.7.1.zip
SOURCES/msrestazure-0.6.4.tar.gz
SOURCES/oauthlib-3.1.1.tar.gz
SOURCES/oauthlib-3.2.2.tar.gz
SOURCES/openshift-0.12.1.tar.gz
SOURCES/packaging-21.2-py3-none-any.whl
SOURCES/pip-21.3.1.tar.gz
SOURCES/poetry-core-1.0.8.tar.gz
SOURCES/portalocker-2.7.0.tar.gz
SOURCES/pyasn1-0.4.8.tar.gz
SOURCES/pyasn1-modules-0.2.8.tar.gz
SOURCES/pycparser-2.20.tar.gz
SOURCES/pycryptodome-3.20.0.tar.gz
SOURCES/pyparsing-2.4.7-py2.py3-none-any.whl
SOURCES/python-dateutil-2.8.2.tar.gz
SOURCES/python-string-utils-1.0.0.tar.gz
SOURCES/requests-2.26.0.tar.gz
SOURCES/requests-2.27.1.tar.gz
SOURCES/requests-oauthlib-1.3.0.tar.gz
SOURCES/requests-oauthlib-2.0.0.tar.gz
SOURCES/rsa-4.7.2.tar.gz
SOURCES/ruamel.yaml-0.17.16.tar.gz
SOURCES/ruamel.yaml.clib-0.2.6.tar.gz
SOURCES/setuptools-58.3.0.tar.gz
SOURCES/setuptools_scm-6.4.2.tar.gz
SOURCES/setuptools_scm-6.3.2.tar.gz
SOURCES/six-1.16.0.tar.gz
SOURCES/tomli-1.1.0.tar.gz
SOURCES/typing_extensions-4.1.1.tar.gz
SOURCES/tomli-1.0.1.tar.gz
SOURCES/urllib3-1.26.18.tar.gz
SOURCES/websocket-client-1.2.1.tar.gz
SOURCES/wheel-0.37.1.tar.gz

View File

@ -1,28 +0,0 @@
From 57acb7c26d809cf864ec439b8bcd6364702022d5 Mon Sep 17 00:00:00 2001
From: Nate Prewitt <nate.prewitt@gmail.com>
Date: Wed, 25 Sep 2024 08:03:20 -0700
Subject: [PATCH] Only use hostname to do netrc lookup instead of netloc
---
src/requests/utils.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/kubevirt/requests/utils.py b/kubevirt/requests/utils.py
index 699683e5d9..8a307ca8a0 100644
--- a/kubevirt/requests/utils.py
+++ b/kubevirt/requests/utils.py
@@ -236,13 +236,7 @@ def get_netrc_auth(url, raise_errors=False):
return
ri = urlparse(url)
-
- # Strip port numbers from netloc. This weird `if...encode`` dance is
- # used for Python 3.2, which doesn't support unicode literals.
- splitstr = b':'
- if isinstance(url, str):
- splitstr = splitstr.decode('ascii')
- host = ri.netloc.split(splitstr)[0]
+ host = ri.hostname
try:
_netrc = netrc(netrc_path).authenticators(host)

View File

@ -1,66 +0,0 @@
From 57acb7c26d809cf864ec439b8bcd6364702022d5 Mon Sep 17 00:00:00 2001
From: Nate Prewitt <nate.prewitt@gmail.com>
Date: Wed, 25 Sep 2024 08:03:20 -0700
Subject: [PATCH] Only use hostname to do netrc lookup instead of netloc
---
src/requests/utils.py | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/aliyun/aliyunsdkcore/vendored/requests/utils.py b/aliyun/aliyunsdkcore/vendored/requests/utils.py
index 699683e5d9..8a307ca8a0 100644
--- a/aliyun/aliyunsdkcore/vendored/requests/utils.py
+++ b/aliyun/aliyunsdkcore/vendored/requests/utils.py
@@ -182,13 +182,7 @@
return
ri = urlparse(url)
-
- # Strip port numbers from netloc. This weird `if...encode`` dance is
- # used for Python 3.2, which doesn't support unicode literals.
- splitstr = b':'
- if isinstance(url, str):
- splitstr = splitstr.decode('ascii')
- host = ri.netloc.split(splitstr)[0]
+ host = ri.hostname
try:
_netrc = netrc(netrc_path).authenticators(host)
diff --git a/aws/requests/utils.py b/aws/requests/utils.py
index 699683e5d9..8a307ca8a0 100644
--- a/aws/requests/utils.py
+++ b/aws/requests/utils.py
@@ -236,13 +236,7 @@ def get_netrc_auth(url, raise_errors=False):
return
ri = urlparse(url)
-
- # Strip port numbers from netloc. This weird `if...encode`` dance is
- # used for Python 3.2, which doesn't support unicode literals.
- splitstr = b':'
- if isinstance(url, str):
- splitstr = splitstr.decode('ascii')
- host = ri.netloc.split(splitstr)[0]
+ host = ri.hostname
try:
_netrc = netrc(netrc_path).authenticators(host)
diff --git a/azure/requests/utils.py b/azure/requests/utils.py
index 699683e5d9..8a307ca8a0 100644
--- a/azure/requests/utils.py
+++ b/azure/requests/utils.py
@@ -236,13 +236,7 @@ def get_netrc_auth(url, raise_errors=False):
return
ri = urlparse(url)
-
- # Strip port numbers from netloc. This weird `if...encode`` dance is
- # used for Python 3.2, which doesn't support unicode literals.
- splitstr = b':'
- if isinstance(url, str):
- splitstr = splitstr.decode('ascii')
- host = ri.netloc.split(splitstr)[0]
+ host = ri.hostname
try:
_netrc = netrc(netrc_path).authenticators(host)

View File

@ -1,50 +0,0 @@
From 9460728b18c648e390390d888ac856628366a521 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Thu, 7 Aug 2025 14:53:30 +0200
Subject: [PATCH] fence_ibm_vpc: add apikey file support
---
agents/ibm_vpc/fence_ibm_vpc.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/agents/ibm_vpc/fence_ibm_vpc.py b/agents/ibm_vpc/fence_ibm_vpc.py
index efda5eed7..a87e9e6dc 100755
--- a/agents/ibm_vpc/fence_ibm_vpc.py
+++ b/agents/ibm_vpc/fence_ibm_vpc.py
@@ -7,7 +7,7 @@
import hashlib
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
-from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS, EC_GENERIC_ERROR
+from fencing import fail, run_delay, EC_BAD_ARGS, EC_LOGIN_DENIED, EC_STATUS, EC_GENERIC_ERROR
state = {
"running": "on",
@@ -315,6 +315,27 @@ def main():
####
run_delay(options)
+ if options["--apikey"][0] == '@':
+ key_file = options["--apikey"][1:]
+ try:
+ # read the API key from a file
+ with open(key_file, "r") as f:
+ try:
+ keys = json.loads(f.read())
+ # data seems to be in json format
+ # return the value of the item with the key 'Apikey'
+ options["--apikey"] = keys.get("Apikey", "")
+ if not options["--apikey"]:
+ # backward compatibility: former key name was 'apikey'
+ options["--apikey"] = keys.get("apikey", "")
+ # data is text, return as is
+ except ValueError:
+ f.seek(0)
+ options["--apikey"] = f.read().strip()
+ except FileNotFoundError:
+ logging.error("Failed: Cannot open file {}".format(key_file))
+ sys.exit(EC_BAD_ARGS)
+
conn = connect(options)
atexit.register(disconnect, conn)

View File

@ -1,92 +0,0 @@
From 5cf006ffa3a948ccded3a55c15669f1d5efef5f5 Mon Sep 17 00:00:00 2001
From: gguifelixamz <45173771+gguifelixamz@users.noreply.github.com>
Date: Tue, 19 Aug 2025 02:04:53 -0700
Subject: [PATCH] fence_aws: Add new skip_os_shutdown flag (#632)
---
agents/aws/fence_aws.py | 29 ++++++++++++++++++++++++++---
tests/data/metadata/fence_aws.xml | 5 +++++
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/agents/aws/fence_aws.py b/agents/aws/fence_aws.py
index 5459a06c4..cddca4580 100644
--- a/agents/aws/fence_aws.py
+++ b/agents/aws/fence_aws.py
@@ -12,7 +12,7 @@
import requests
import boto3
from requests import HTTPError
-from botocore.exceptions import ConnectionError, ClientError, EndpointConnectionError, NoRegionError
+from botocore.exceptions import ConnectionError, ClientError, EndpointConnectionError, NoRegionError, ParamValidationError
logger = logging.getLogger()
logger.propagate = False
@@ -120,14 +120,28 @@ def get_self_power_status(conn, instance_id):
def set_power_status(conn, options):
my_instance = get_instance_id(options)
try:
+ if options.get("--skip-os-shutdown", "false").lower() in ["1", "yes", "on", "true"]:
+ shutdown_option = {
+ "SkipOsShutdown": True,
+ "Force": True
+ }
+ else:
+ shutdown_option = {
+ "SkipOsShutdown": False,
+ "Force": True
+ }
if (options["--action"]=="off"):
if "--skip-race-check" in options or get_self_power_status(conn,my_instance) == "ok":
- conn.instances.filter(InstanceIds=[options["--plug"]]).stop(Force=True)
+ conn.instances.filter(InstanceIds=[options["--plug"]]).stop(**shutdown_option)
logger.debug("Called StopInstance API call for %s", options["--plug"])
else:
logger.debug("Skipping fencing as instance is not in running status")
elif (options["--action"]=="on"):
conn.instances.filter(InstanceIds=[options["--plug"]]).start()
+ except ParamValidationError:
+ if (options["--action"] == "off"):
+ logger.warning(f"SkipOsShutdown not supported with the current boto3 version {boto3.__version__} - falling back to graceful shutdown")
+ conn.instances.filter(InstanceIds=[options["--plug"]]).stop(Force=True)
except Exception as e:
logger.debug("Failed to power %s %s: %s", \
options["--action"], options["--plug"], e)
@@ -183,12 +197,21 @@ def define_new_opts():
"required": "0",
"order": 7
}
+ all_opt["skip_os_shutdown"] = {
+ "getopt" : ":",
+ "longopt" : "skip-os-shutdown",
+ "help" : "--skip-os-shutdown=[true|false] Uses SkipOsShutdown flag",
+ "shortdesc" : "Use SkipOsShutdown flag to stop the EC2 instance",
+ "required" : "0",
+ "default" : "false",
+ "order" : 8
+ }
# Main agent method
def main():
conn = None
- device_opt = ["port", "no_password", "region", "access_key", "secret_key", "filter", "boto3_debug", "skip_race_check"]
+ device_opt = ["port", "no_password", "region", "access_key", "secret_key", "filter", "boto3_debug", "skip_race_check", "skip_os_shutdown"]
atexit.register(atexit_handler)
diff --git a/tests/data/metadata/fence_aws.xml b/tests/data/metadata/fence_aws.xml
index ad471c797..c53873bbe 100644
--- a/tests/data/metadata/fence_aws.xml
+++ b/tests/data/metadata/fence_aws.xml
@@ -51,6 +51,11 @@ For instructions see: https://boto3.readthedocs.io/en/latest/guide/quickstart.ht
<content type="boolean" />
<shortdesc lang="en">Skip race condition check</shortdesc>
</parameter>
+ <parameter name="skip_os_shutdown" unique="0" required="0">
+ <getopt mixed="--skip-os-shutdown=[true|false]" />
+ <content type="string" default="false" />
+ <shortdesc lang="en">Use SkipOsShutdown flag to stop the EC2 instance</shortdesc>
+ </parameter>
<parameter name="quiet" unique="0" required="0">
<getopt mixed="-q, --quiet" />
<content type="boolean" />

View File

@ -1,31 +0,0 @@
diff -uNr a/aws/botocore/data/ec2/2016-11-15/service-2.json b/aws/botocore/data/ec2/2016-11-15/service-2.json
--- a/aws/botocore/data/ec2/2016-11-15/service-2.json 2025-08-19 11:21:50.328630448 +0200
+++ b/aws/botocore/data/ec2/2016-11-15/service-2.json 2025-08-19 11:25:37.767261040 +0200
@@ -45844,7 +45844,11 @@
},
"Hibernate":{
"shape":"Boolean",
- "documentation":"<p>Hibernates the instance if the instance was enabled for hibernation at launch. If the instance cannot hibernate successfully, a normal shutdown occurs. For more information, see <a href=\"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html\">Hibernate your instance</a> in the <i>Amazon EC2 User Guide</i>.</p> <p> Default: <code>false</code> </p>"
+ "documentation":"<p>Hibernates the instance if the instance was enabled for hibernation at launch. If the instance cannot hibernate successfully, a normal shutdown occurs. For more information, see <a href=\"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Hibernate.html\">Hibernate your Amazon EC2 instance</a> in the <i>Amazon EC2 User Guide</i>.</p> <p> Default: <code>false</code> </p>"
+ },
+ "SkipOsShutdown":{
+ "shape":"Boolean",
+ "documentation":"<p>Specifies whether to bypass the graceful OS shutdown process when the instance is stopped.</p> <important> <p>Bypassing the graceful OS shutdown might result in data loss or corruption (for example, memory contents not flushed to disk or loss of in-flight IOs) or skipped shutdown scripts.</p> </important> <p>Default: <code>false</code> </p>"
},
"DryRun":{
"shape":"Boolean",
@@ -46648,6 +46652,14 @@
"documentation":"<p>The IDs of the instances.</p> <p>Constraints: Up to 1000 instance IDs. We recommend breaking up this request into smaller batches.</p>",
"locationName":"InstanceId"
},
+ "Force":{
+ "shape":"Boolean",
+ "documentation":"<p>Forces the instances to terminate. The instance will first attempt a graceful shutdown, which includes flushing file system caches and metadata. If the graceful shutdown fails to complete within the timeout period, the instance shuts down forcibly without flushing the file system caches and metadata.</p>"
+ },
+ "SkipOsShutdown":{
+ "shape":"Boolean",
+ "documentation":"<p>Specifies whether to bypass the graceful OS shutdown process when the instance is terminated.</p> <p>Default: <code>false</code> </p>"
+ },
"DryRun":{
"shape":"Boolean",
"documentation":"<p>Checks whether you have the required permissions for the action, without actually making the request, and provides an error response. If you have the required permissions, the error response is <code>DryRunOperation</code>. Otherwise, it is <code>UnauthorizedOperation</code>.</p>",

View File

@ -1,65 +0,0 @@
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)}"')

View File

@ -1,32 +0,0 @@
From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001
From: Quentin Pradet <quentin.pradet@gmail.com>
Date: Mon, 17 Jun 2024 11:09:06 +0400
Subject: [PATCH] Merge pull request from GHSA-34jh-p97f-mpxf
* Strip Proxy-Authorization header on redirects
* Fix test_retry_default_remove_headers_on_redirect
* Set release date
---
CHANGES.rst | 5 +++++
src/urllib3/util/retry.py | 4 +++-
test/test_retry.py | 6 ++++-
test/with_dummyserver/test_poolmanager.py | 27 ++++++++++++++++++++---
4 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/kubevirt/urllib3/util/retry.py b/kubevirt/urllib3/util/retry.py
index 7a76a4a6ad..0456cceba4 100644
--- a/kubevirt/urllib3/util/retry.py
+++ b/kubevirt/urllib3/util/retry.py
@@ -189,7 +189,9 @@ class Retry:
RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503])
#: Default headers to be used for ``remove_headers_on_redirect``
- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"])
+ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(
+ ["Cookie", "Authorization", "Proxy-Authorization"]
+ )
#: Default maximum backoff time.
DEFAULT_BACKOFF_MAX = 120

View File

@ -1,32 +0,0 @@
From accff72ecc2f6cf5a76d9570198a93ac7c90270e Mon Sep 17 00:00:00 2001
From: Quentin Pradet <quentin.pradet@gmail.com>
Date: Mon, 17 Jun 2024 11:09:06 +0400
Subject: [PATCH] Merge pull request from GHSA-34jh-p97f-mpxf
* Strip Proxy-Authorization header on redirects
* Fix test_retry_default_remove_headers_on_redirect
* Set release date
---
CHANGES.rst | 5 +++++
src/urllib3/util/retry.py | 4 +++-
test/test_retry.py | 6 ++++-
test/with_dummyserver/test_poolmanager.py | 27 ++++++++++++++++++++---
4 files changed, 37 insertions(+), 5 deletions(-)
diff --git a/aws/urllib3/util/retry.py b/aws/urllib3/util/retry.py
index 7a76a4a6ad..0456cceba4 100644
--- a/aws/urllib3/util/retry.py
+++ b/aws/urllib3/util/retry.py
@@ -189,7 +189,9 @@ class Retry:
RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503])
#: Default headers to be used for ``remove_headers_on_redirect``
- DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(["Cookie", "Authorization"])
+ DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset(
+ ["Cookie", "Authorization", "Proxy-Authorization"]
+ )
#: Default maximum backoff time.
DEFAULT_BACKOFF_MAX = 120

View File

@ -1,203 +0,0 @@
diff --color -uNr a/kubevirt/setuptools/package_index.py b/kubevirt/setuptools/package_index.py
--- a/kubevirt/setuptools/package_index.py 2021-10-22 22:55:51.000000000 +0200
+++ b/kubevirt/setuptools/package_index.py 2024-07-24 14:06:14.833852463 +0200
@@ -1,5 +1,6 @@
"""PyPI and direct package downloading"""
import sys
+import subprocess
import os
import re
import io
@@ -558,7 +559,7 @@
scheme = URL_SCHEME(spec)
if scheme:
# It's a url, download it to tmpdir
- found = self._download_url(scheme.group(1), spec, tmpdir)
+ found = self._download_url(spec, tmpdir)
base, fragment = egg_info_for_url(spec)
if base.endswith('.py'):
found = self.gen_setup(found, fragment, tmpdir)
@@ -777,7 +778,7 @@
raise DistutilsError("Download error for %s: %s"
% (url, v)) from v
- def _download_url(self, scheme, url, tmpdir):
+ def _download_url(self, url, tmpdir):
# Determine download filename
#
name, fragment = egg_info_for_url(url)
@@ -792,19 +793,59 @@
filename = os.path.join(tmpdir, name)
- # Download the file
- #
- if scheme == 'svn' or scheme.startswith('svn+'):
- return self._download_svn(url, filename)
- elif scheme == 'git' or scheme.startswith('git+'):
- return self._download_git(url, filename)
- elif scheme.startswith('hg+'):
- return self._download_hg(url, filename)
- elif scheme == 'file':
- return urllib.request.url2pathname(urllib.parse.urlparse(url)[2])
- else:
- self.url_ok(url, True) # raises error if not allowed
- return self._attempt_download(url, filename)
+ return self._download_vcs(url, filename) or self._download_other(url, filename)
+
+ @staticmethod
+ def _resolve_vcs(url):
+ """
+ >>> rvcs = PackageIndex._resolve_vcs
+ >>> rvcs('git+http://foo/bar')
+ 'git'
+ >>> rvcs('hg+https://foo/bar')
+ 'hg'
+ >>> rvcs('git:myhost')
+ 'git'
+ >>> rvcs('hg:myhost')
+ >>> rvcs('http://foo/bar')
+ """
+ scheme = urllib.parse.urlsplit(url).scheme
+ pre, sep, post = scheme.partition('+')
+ # svn and git have their own protocol; hg does not
+ allowed = set(['svn', 'git'] + ['hg'] * bool(sep))
+ return next(iter({pre} & allowed), None)
+
+ def _download_vcs(self, url, spec_filename):
+ vcs = self._resolve_vcs(url)
+ if not vcs:
+ return
+ if vcs == 'svn':
+ raise DistutilsError(
+ f"Invalid config, SVN download is not supported: {url}"
+ )
+
+ filename, _, _ = spec_filename.partition('#')
+ url, rev = self._vcs_split_rev_from_url(url)
+
+ self.info(f"Doing {vcs} clone from {url} to {filename}")
+ subprocess.check_call([vcs, 'clone', '--quiet', url, filename])
+
+ co_commands = dict(
+ git=[vcs, '-C', filename, 'checkout', '--quiet', rev],
+ hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'],
+ )
+ if rev is not None:
+ self.info(f"Checking out {rev}")
+ subprocess.check_call(co_commands[vcs])
+
+ return filename
+
+ def _download_other(self, url, filename):
+ scheme = urllib.parse.urlsplit(url).scheme
+ if scheme == 'file': # pragma: no cover
+ return urllib.request.url2pathname(urllib.parse.urlparse(url).path)
+ # raise error if not allowed
+ self.url_ok(url, True)
+ return self._attempt_download(url, filename)
def scan_url(self, url):
self.process_url(url, True)
@@ -831,77 +872,37 @@
os.unlink(filename)
raise DistutilsError("Unexpected HTML page found at " + url)
- def _download_svn(self, url, filename):
- warnings.warn("SVN download support is deprecated", UserWarning)
- url = url.split('#', 1)[0] # remove any fragment for svn's sake
- creds = ''
- if url.lower().startswith('svn:') and '@' in url:
- scheme, netloc, path, p, q, f = urllib.parse.urlparse(url)
- if not netloc and path.startswith('//') and '/' in path[2:]:
- netloc, path = path[2:].split('/', 1)
- auth, host = _splituser(netloc)
- if auth:
- if ':' in auth:
- user, pw = auth.split(':', 1)
- creds = " --username=%s --password=%s" % (user, pw)
- else:
- creds = " --username=" + auth
- netloc = host
- parts = scheme, netloc, url, p, q, f
- url = urllib.parse.urlunparse(parts)
- self.info("Doing subversion checkout from %s to %s", url, filename)
- os.system("svn checkout%s -q %s %s" % (creds, url, filename))
- return filename
-
@staticmethod
- def _vcs_split_rev_from_url(url, pop_prefix=False):
- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url)
-
- scheme = scheme.split('+', 1)[-1]
-
- # Some fragment identification fails
- path = path.split('#', 1)[0]
-
- rev = None
- if '@' in path:
- path, rev = path.rsplit('@', 1)
-
- # Also, discard fragment
- url = urllib.parse.urlunsplit((scheme, netloc, path, query, ''))
-
- return url, rev
-
- def _download_git(self, url, filename):
- filename = filename.split('#', 1)[0]
- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
-
- self.info("Doing git clone from %s to %s", url, filename)
- os.system("git clone --quiet %s %s" % (url, filename))
+ def _vcs_split_rev_from_url(url):
+ """
+ Given a possible VCS URL, return a clean URL and resolved revision if any.
- if rev is not None:
- self.info("Checking out %s", rev)
- os.system("git -C %s checkout --quiet %s" % (
- filename,
- rev,
- ))
+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url
+ >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools')
+ ('https://github.com/pypa/setuptools', 'v69.0.0')
+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools')
+ ('https://github.com/pypa/setuptools', None)
+ >>> vsrfu('http://foo/bar')
+ ('http://foo/bar', None)
+ """
+ parts = urllib.parse.urlsplit(url)
- return filename
+ clean_scheme = parts.scheme.split('+', 1)[-1]
- def _download_hg(self, url, filename):
- filename = filename.split('#', 1)[0]
- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True)
+ # Some fragment identification fails
+ no_fragment_path, _, _ = parts.path.partition('#')
- self.info("Doing hg clone from %s to %s", url, filename)
- os.system("hg clone --quiet %s %s" % (url, filename))
+ pre, sep, post = no_fragment_path.rpartition('@')
+ clean_path, rev = (pre, post) if sep else (post, None)
- if rev is not None:
- self.info("Updating to %s", rev)
- os.system("hg --cwd %s up -C -r %s -q" % (
- filename,
- rev,
- ))
+ resolved = parts._replace(
+ scheme=clean_scheme,
+ path=clean_path,
+ # discard the fragment
+ fragment='',
+ ).geturl()
- return filename
+ return resolved, rev
def debug(self, msg, *args):
log.debug(msg, *args)

View File

@ -1,40 +0,0 @@
From cb57f1c2ee734a40d01249305965ea4ecdf02039 Mon Sep 17 00:00:00 2001
From: Oyvind Albrigtsen <oalbrigt@redhat.com>
Date: Thu, 5 Sep 2024 09:06:34 +0200
Subject: [PATCH] fence_scsi: preempt clears all devices on the mpath device,
so only run it for the first device
---
agents/scsi/fence_scsi.py | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/agents/scsi/fence_scsi.py b/agents/scsi/fence_scsi.py
index a1598411c..12f7fb49b 100644
--- a/agents/scsi/fence_scsi.py
+++ b/agents/scsi/fence_scsi.py
@@ -131,11 +131,13 @@ def reset_dev(options, dev):
return run_cmd(options, options["--sg_turs-path"] + " " + dev)["rc"]
-def register_dev(options, dev, key):
+def register_dev(options, dev, key, do_preempt=True):
dev = os.path.realpath(dev)
if re.search(r"^dm", dev[5:]):
- for slave in get_mpath_slaves(dev):
- register_dev(options, slave, key)
+ devices = get_mpath_slaves(dev)
+ register_dev(options, devices[0], key)
+ for device in devices[1:]:
+ register_dev(options, device, key, False)
return True
# Check if any registration exists for the key already. We track this in
@@ -153,7 +155,7 @@ def register_dev(options, dev, key):
# 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, key, dev, key):
+ if do_preempt and not preempt(options, key, dev, key):
return False
# If there was no reservation, we need to issue another registration

View File

@ -1,120 +0,0 @@
--- a/agents/ibm_powervs/fence_ibm_powervs.py 2024-10-18 10:30:40.651200620 +0200
+++ b/agents/ibm_powervs/fence_ibm_powervs.py 2024-10-18 10:30:35.157070713 +0200
@@ -1,13 +1,14 @@
#!@PYTHON@ -tt
import sys
-import pycurl, io, json
+import pycurl
+import io
+import json
import logging
import atexit
-import time
+
sys.path.append("@FENCEAGENTSLIBDIR@")
-from fencing import *
-from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS
+from fencing import all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, fail, run_delay, EC_STATUS
state = {
"ACTIVE": "on",
@@ -18,15 +19,35 @@
}
def get_token(conn, options):
- try:
- command = "identity/token"
- action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(options["--token"])
- res = send_command(conn, command, "POST", action, printResult=False)
- except Exception as e:
- logging.debug("Failed: {}".format(e))
- return "TOKEN_IS_MISSING_OR_WRONG"
-
- return res["access_token"]
+ try:
+ if options["--token"][0] == '@':
+ key_file = options["--token"][1:]
+ try:
+ # read the API key from a file
+ with open(key_file, "r") as f:
+ try:
+ keys = json.loads(f.read())
+ # data seems to be in json format
+ # return the value of the item with the key 'Apikey'
+ api_key = keys.get("Apikey", "")
+ if not api_key:
+ # backward compatibility: former key name was 'apikey'
+ api_key = keys.get("apikey", "")
+ # data is text, return as is
+ except ValueError:
+ api_key = f.read().strip()
+ except FileNotFoundError:
+ logging.debug("Failed: Cannot open file {}".format(key_file))
+ return "TOKEN_IS_MISSING_OR_WRONG"
+ else:
+ api_key = options["--token"]
+ command = "identity/token"
+ action = "grant_type=urn%3Aibm%3Aparams%3Aoauth%3Agrant-type%3Aapikey&apikey={}".format(api_key)
+ res = send_command(conn, command, "POST", action, printResult=False)
+ except Exception as e:
+ logging.debug("Failed: {}".format(e))
+ return "TOKEN_IS_MISSING_OR_WRONG"
+ return res["access_token"]
def get_list(conn, options):
outlets = {}
@@ -41,7 +62,7 @@
for r in res["pvmInstances"]:
if options["--verbose-level"] > 1:
logging.debug(json.dumps(r, indent=2))
- outlets[r["pvmInstanceID"]] = (r["serverName"], state[r["status"]])
+ outlets[r["pvmInstanceID"]] = (r["serverName"], state.get(r["status"], "unknown"))
return outlets
@@ -97,7 +118,7 @@
else:
logging.debug("Failed: Unable to cycle with {} for {}".format(options["--action"], e))
fail(EC_STATUS)
- return True
+ return True
def connect(opt, token):
conn = pycurl.Curl()
@@ -130,7 +151,10 @@
conn = pycurl.Curl()
# setup correct URL
- conn.base_url = "https://iam.cloud.ibm.com/"
+ if opt["--api-type"] == "private":
+ conn.base_url = "https://private.iam.cloud.ibm.com/"
+ else:
+ conn.base_url = "https://iam.cloud.ibm.com/"
if opt["--verbose-level"] > 1:
conn.setopt(pycurl.VERBOSE, 1)
@@ -265,9 +289,9 @@
define_new_opts()
all_opt["shell_timeout"]["default"] = "500"
- all_opt["power_timeout"]["default"] = "30"
- all_opt["power_wait"]["default"] = "1"
- all_opt["stonith_status_sleep"]["default"] = "2"
+ all_opt["power_timeout"]["default"] = "120"
+ all_opt["power_wait"]["default"] = "15"
+ all_opt["stonith_status_sleep"]["default"] = "10"
all_opt["api-type"]["default"] = "private"
all_opt["proxy"]["default"] = ""
@@ -275,8 +299,8 @@
docs = {}
docs["shortdesc"] = "Fence agent for IBM PowerVS"
- docs["longdesc"] = """fence_ibm_powervs is a Power Fencing agent which can be \
-used with IBM PowerVS to fence virtual machines."""
+ docs["longdesc"] = """fence_ibm_powervs is a power fencing agent for \
+IBM Power Virtual Server (IBM PowerVS) to fence virtual server instances."""
docs["vendorurl"] = "https://www.ibm.com"
show_docs(options, docs)

View File

@ -1,381 +0,0 @@
--- a/lib/azure_fence.py.py 2025-01-30 14:47:16.047999700 +0100
+++ b/lib/azure_fence.py.py 2025-01-30 12:06:10.847889534 +0100
@@ -14,6 +14,9 @@
IP_TYPE_DYNAMIC = "Dynamic"
MAX_RETRY = 10
RETRY_WAIT = 5
+NETWORK_MGMT_CLIENT_API_VERSION = "2021-05-01"
+AZURE_RHEL8_COMPUTE_VERSION = "27.2.0"
+AZURE_COMPUTE_VERSION_5 = "5.0.0"
class AzureSubResource:
Type = None
@@ -49,7 +52,7 @@
return None
def get_azure_resource(id):
- match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
+ match = re.match(r'(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
if not match:
fail_usage("{get_azure_resource} cannot parse resource id %s" % id)
@@ -86,6 +89,59 @@
return resource
+def azure_dep_versions(v):
+ return tuple(map(int, (v.split("."))))
+
+# Do azure API call to list all virtual machines in a resource group
+def get_vm_list(compute_client,rgName):
+ return compute_client.virtual_machines.list(rgName)
+
+# Do azue API call to shutdown a virtual machine
+def do_vm_power_off(compute_client,rgName,vmName, skipShutdown):
+ try:
+ # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0
+ from azure.mgmt.compute import __version__
+ except ImportError:
+ __version__ = "0.0.0"
+
+ # use different implementation call based on used version
+ if (azure_dep_versions(__version__) == azure_dep_versions(AZURE_COMPUTE_VERSION_5)):
+ logging.debug("{do_vm_power_off} azure.mgtm.compute version is to old to use 'begin_power_off' use 'power_off' function")
+ compute_client.virtual_machines.power_off(rgName, vmName, skip_shutdown=skipShutdown)
+ return
+
+ compute_client.virtual_machines.begin_power_off(rgName, vmName, skip_shutdown=skipShutdown)
+
+# Do azure API call to start a virtual machine
+def do_vm_start(compute_client,rgName,vmName):
+ try:
+ # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0
+ from azure.mgmt.compute import __version__
+ except ImportError:
+ __version__ = "0.0.0"
+
+ # use different implementation call based on used version
+ if (azure_dep_versions(__version__) == azure_dep_versions(AZURE_COMPUTE_VERSION_5)):
+ logging.debug("{do_vm_start} azure.mgtm.compute version is to old to use 'begin_start' use 'start' function")
+ compute_client.virtual_machines.start(rgName, vmName)
+ return
+
+ compute_client.virtual_machines.begin_start(rgName, vmName)
+
+def get_vm_resource(compute_client, rgName, vmName):
+ try:
+ # Version is not available in azure-mgmt-compute version 14.0.0 until 27.2.0
+ from azure.mgmt.compute import __version__
+ except ImportError:
+ __version__ = "0.0.0"
+
+ # use different implementation call based on used version
+ if (azure_dep_versions(__version__) <= azure_dep_versions(AZURE_RHEL8_COMPUTE_VERSION)):
+ return compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+
+ return compute_client.virtual_machines.get(resource_group_name=rgName, vm_name=vmName,expand="instanceView")
+
+
def get_fence_subnet_for_config(ipConfig, network_client):
subnetResource = get_azure_resource(ipConfig.subnet.id)
logging.debug("{get_fence_subnet_for_config} testing virtual network %s in resource group %s for a fence subnet" %(subnetResource.ResourceName, subnetResource.ResourceGroupName))
@@ -152,7 +208,7 @@
result = FENCE_STATE_ON
try:
- vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+ vm = get_vm_resource(compute_client, rgName, vmName)
allNICOK = True
for nicRef in vm.network_profile.network_interfaces:
@@ -179,7 +235,7 @@
import msrestazure.azure_exceptions
logging.info("{set_network_state} Setting state %s for %s in resource group %s" % (operation, vmName, rgName))
- vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+ vm = get_vm_resource(compute_client,rgName, vmName)
operations = []
for nicRef in vm.network_profile.network_interfaces:
@@ -268,10 +324,72 @@
return config
+# Function to fetch endpoints from metadata endpoint for azure_stack
+def get_cloud_from_arm_metadata_endpoint(arm_endpoint):
+ try:
+ import requests
+ session = requests.Session()
+ metadata_endpoint = arm_endpoint + "/metadata/endpoints?api-version=2015-01-01"
+ response = session.get(metadata_endpoint)
+ if response.status_code == 200:
+ metadata = response.json()
+ return {
+ "resource_manager": arm_endpoint,
+ "credential_scopes": [metadata.get("graphEndpoint") + "/.default"],
+ "authority_hosts": metadata['authentication'].get('loginEndpoint').replace("https://","")
+ }
+ else:
+ fail_usage("Failed to get cloud from metadata endpoint: %s - %s" % arm_endpoint, e)
+ except Exception as e:
+ fail_usage("Failed to get cloud from metadata endpoint: %s - %s" % arm_endpoint, e)
+
+def get_azure_arm_endpoints(cloudName, authority):
+ cloudEnvironment = {
+ "authority_hosts": authority
+ }
+
+ if cloudName == "AZURE_CHINA_CLOUD":
+ cloudEnvironment["resource_manager"] = "https://management.chinacloudapi.cn/"
+ cloudEnvironment["credential_scopes"] = ["https://management.chinacloudapi.cn/.default"]
+ return cloudEnvironment
+
+ if cloudName == "AZURE_US_GOV_CLOUD":
+ cloudEnvironment["resource_manager"] = "https://management.usgovcloudapi.net/"
+ cloudEnvironment["credential_scopes"] = ["https://management.core.usgovcloudapi.net/.default"]
+ return cloudEnvironment
+
+ if cloudName == "AZURE_PUBLIC_CLOUD":
+ cloudEnvironment["resource_manager"] = "https://management.azure.com/"
+ cloudEnvironment["credential_scopes"] = ["https://management.azure.com/.default"]
+ return cloudEnvironment
+
+
def get_azure_cloud_environment(config):
- cloud_environment = None
- if config.Cloud:
+ if (config.Cloud is None):
+ config.Cloud = "public"
+
+ try:
+ from azure.identity import AzureAuthorityHosts
+
+ azureCloudName = "AZURE_PUBLIC_CLOUD"
+ authorityHosts = AzureAuthorityHosts.AZURE_PUBLIC_CLOUD
if (config.Cloud.lower() == "china"):
+ azureCloudName = "AZURE_CHINA_CLOUD"
+ authorityHosts = AzureAuthorityHosts.AZURE_CHINA
+ elif (config.Cloud.lower() == "usgov"):
+ azureCloudName = "AZURE_US_GOV_CLOUD"
+ authorityHosts = AzureAuthorityHosts.AZURE_GOVERNMENT
+ elif (config.Cloud.lower() == "stack"):
+ # use custom function to call the azuer stack metadata endpoint to get required configuration.
+ return get_cloud_from_arm_metadata_endpoint(config.MetadataEndpoint)
+
+ return get_azure_arm_endpoints(azureCloudName, authorityHosts)
+
+ except ImportError:
+ if (config.Cloud.lower() == "public"):
+ from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD
+ cloud_environment = AZURE_PUBLIC_CLOUD
+ elif (config.Cloud.lower() == "china"):
from msrestazure.azure_cloud import AZURE_CHINA_CLOUD
cloud_environment = AZURE_CHINA_CLOUD
elif (config.Cloud.lower() == "germany"):
@@ -284,31 +402,43 @@
from msrestazure.azure_cloud import get_cloud_from_metadata_endpoint
cloud_environment = get_cloud_from_metadata_endpoint(config.MetadataEndpoint)
- return cloud_environment
+ authority_hosts = cloud_environment.endpoints.active_directory.replace("http://","")
+ return {
+ "resource_manager": cloud_environment.endpoints.resource_manager,
+ "credential_scopes": [cloud_environment.endpoints.active_directory_resource_id + "/.default"],
+ "authority_hosts": authority_hosts,
+ "cloud_environment": cloud_environment,
+ }
def get_azure_credentials(config):
credentials = None
cloud_environment = get_azure_cloud_environment(config)
- if config.UseMSI and cloud_environment:
- from msrestazure.azure_active_directory import MSIAuthentication
- credentials = MSIAuthentication(cloud_environment=cloud_environment)
- elif config.UseMSI:
- from msrestazure.azure_active_directory import MSIAuthentication
- credentials = MSIAuthentication()
- elif cloud_environment:
- from azure.common.credentials import ServicePrincipalCredentials
- credentials = ServicePrincipalCredentials(
+ if config.UseMSI:
+ try:
+ from azure.identity import ManagedIdentityCredential
+ credentials = ManagedIdentityCredential(authority=cloud_environment["authority_hosts"])
+ except ImportError:
+ from msrestazure.azure_active_directory import MSIAuthentication
+ credentials = MSIAuthentication(cloud_environment=cloud_environment["cloud_environment"])
+ return credentials
+
+ try:
+ # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core)
+ from azure.identity import ClientSecretCredential
+ credentials = ClientSecretCredential(
client_id = config.ApplicationId,
- secret = config.ApplicationKey,
- tenant = config.Tenantid,
- cloud_environment=cloud_environment
+ client_secret = config.ApplicationKey,
+ tenant_id = config.Tenantid,
+ authority=cloud_environment["authority_hosts"]
)
- else:
+ except ImportError:
+ # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available
from azure.common.credentials import ServicePrincipalCredentials
credentials = ServicePrincipalCredentials(
client_id = config.ApplicationId,
secret = config.ApplicationKey,
- tenant = config.Tenantid
+ tenant = config.Tenantid,
+ cloud_environment=cloud_environment["cloud_environment"]
)
return credentials
@@ -317,40 +447,75 @@
from azure.mgmt.compute import ComputeManagementClient
cloud_environment = get_azure_cloud_environment(config)
- if cloud_environment and config.Cloud.lower() == "stack" and not config.MetadataEndpoint:
- fail_usage("metadata-endpoint not specified")
credentials = get_azure_credentials(config)
- if cloud_environment:
+ # Try to read the default used api version from the installed package.
+ try:
+ compute_api_version = ComputeManagementClient.LATEST_PROFILE.get_profile_dict()["azure.mgmt.compute.ComputeManagementClient"]["virtual_machines"]
+ except Exception as e:
+ compute_api_version = ComputeManagementClient.DEFAULT_API_VERSION
+ logging.debug("{get_azure_compute_client} Failed to get the latest profile: %s using the default api version %s" % (e, compute_api_version))
+
+ logging.debug("{get_azure_compute_client} use virtual_machine api version: %s" %(compute_api_version))
+
+ if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint:
+ fail_usage("metadata-endpoint not specified")
+
+ try:
+ from azure.profiles import KnownProfiles
+ if (config.Cloud.lower() == "stack"):
+ client_profile = KnownProfiles.v2020_09_01_hybrid
+ else:
+ client_profile = KnownProfiles.default
compute_client = ComputeManagementClient(
credentials,
config.SubscriptionId,
- base_url=cloud_environment.endpoints.resource_manager
+ base_url=cloud_environment["resource_manager"],
+ profile=client_profile,
+ credential_scopes=cloud_environment["credential_scopes"],
+ api_version=compute_api_version
)
- else:
+ except TypeError:
compute_client = ComputeManagementClient(
credentials,
- config.SubscriptionId
+ config.SubscriptionId,
+ base_url=cloud_environment["resource_manager"],
+ api_version=compute_api_version
)
+
return compute_client
def get_azure_network_client(config):
from azure.mgmt.network import NetworkManagementClient
cloud_environment = get_azure_cloud_environment(config)
- if cloud_environment and config.Cloud.lower() == "stack" and not config.MetadataEndpoint:
- fail_usage("metadata-endpoint not specified")
credentials = get_azure_credentials(config)
- if cloud_environment:
+ if (config.Cloud.lower() == "stack") and not config.MetadataEndpoint:
+ fail_usage("metadata-endpoint not specified")
+
+
+ from azure.profiles import KnownProfiles
+
+ if (config.Cloud.lower() == "stack"):
+ client_profile = KnownProfiles.v2020_09_01_hybrid
+ else:
+ client_profile = KnownProfiles.default
+
+ try:
network_client = NetworkManagementClient(
credentials,
config.SubscriptionId,
- base_url=cloud_environment.endpoints.resource_manager
+ base_url=cloud_environment["resource_manager"],
+ profile=client_profile,
+ credential_scopes=cloud_environment["credential_scopes"],
+ api_version=NETWORK_MGMT_CLIENT_API_VERSION
)
- else:
+ except TypeError:
network_client = NetworkManagementClient(
credentials,
- config.SubscriptionId
+ config.SubscriptionId,
+ base_url=cloud_environment["resource_manager"],
+ api_version=NETWORK_MGMT_CLIENT_API_VERSION
)
return network_client
--- a/agents/azure_arm/fence_azure_arm.py 2025-01-30 15:28:35.889163377 +0100
+++ b/agents/azure_arm/fence_azure_arm.py 2025-01-30 15:28:52.190553135 +0100
@@ -7,7 +7,6 @@
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
from fencing import fail_usage, run_command, run_delay
-
sys.path.insert(0, '/usr/lib/fence-agents/bundled/azure')
import azure_fence
@@ -17,7 +16,7 @@
if clients:
compute_client = clients[0]
rgName = options["--resourceGroup"]
- vms = compute_client.virtual_machines.list(rgName)
+ vms = azure_fence.get_vm_list(compute_client,rgName)
try:
for vm in vms:
result[vm.name] = ("", None)
@@ -33,7 +32,7 @@
rgName = options["--resourceGroup"]
try:
- vms = compute_client.virtual_machines.list(rgName)
+ vms = azure_fence.get_vm_list(compute_client,rgName)
except Exception as e:
fail_usage("Failed: %s" % e)
@@ -74,7 +73,7 @@
powerState = "unknown"
try:
- vmStatus = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+ vmStatus = azure_fence.get_vm_resource(compute_client, rgName, vmName)
except Exception as e:
fail_usage("Failed: %s" % e)
@@ -117,11 +116,10 @@
if (options["--action"]=="off"):
logging.info("Poweroff " + vmName + " in resource group " + rgName)
- compute_client.virtual_machines.power_off(rgName, vmName, skip_shutdown=True)
+ azure_fence.do_vm_power_off(compute_client, rgName, vmName, True)
elif (options["--action"]=="on"):
logging.info("Starting " + vmName + " in resource group " + rgName)
- compute_client.virtual_machines.start(rgName, vmName)
-
+ azure_fence.do_vm_start(compute_client, rgName, vmName)
def define_new_opts():
all_opt["resourceGroup"] = {
@@ -241,7 +239,7 @@
except ImportError:
fail_usage("Azure Resource Manager Python SDK not found or not accessible")
except Exception as e:
- fail_usage("Failed: %s" % re.sub("^, ", "", str(e)))
+ fail_usage("Failed: %s" % re.sub(r"^, ", r"", str(e)))
if "--network-fencing" in options:
# use off-action to quickly return off once network is fenced instead of

View File

@ -1,365 +0,0 @@
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/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>

View File

@ -1,209 +0,0 @@
--- a/agents/aliyun/fence_aliyun.py 2025-06-23 12:03:30.176181831 +0200
+++ b/agents/aliyun/fence_aliyun.py 2025-06-23 12:04:38.243037392 +0200
@@ -1,53 +1,67 @@
#!@PYTHON@ -tt
-import sys, re
+import sys
import logging
import atexit
import json
+
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
-from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay
+from fencing import fail_usage, run_delay
+
try:
sys.path.insert(0, '/usr/lib/fence-agents/bundled/aliyun')
from aliyunsdkcore import client
from aliyunsdkcore.auth.credentials import EcsRamRoleCredential
+ from aliyunsdkcore.profile import region_provider
+except ImportError as e:
+ logging.warning("The 'aliyunsdkcore' module has been not installed or is unavailable, try to execute the command 'pip install aliyun-python-sdk-core --upgrade' to solve. error: %s" % e)
+
+
+try:
from aliyunsdkecs.request.v20140526.DescribeInstancesRequest import DescribeInstancesRequest
from aliyunsdkecs.request.v20140526.StartInstanceRequest import StartInstanceRequest
from aliyunsdkecs.request.v20140526.StopInstanceRequest import StopInstanceRequest
from aliyunsdkecs.request.v20140526.RebootInstanceRequest import RebootInstanceRequest
- from aliyunsdkcore.profile import region_provider
-except ImportError:
- pass
+except ImportError as e:
+ logging.warning("The 'aliyunsdkecs' module has been not installed or is unavailable, try to execute the command 'pip install aliyun-python-sdk-ecs --upgrade' to solve. error: %s" % e)
+
def _send_request(conn, request):
+ logging.debug("send request action: %s" % request.get_action_name())
request.set_accept_format('json')
try:
response_str = conn.do_action_with_exception(request)
- response_detail = json.loads(response_str)
- logging.debug("_send_request reponse: %s" % response_detail)
- return response_detail
except Exception as e:
- fail_usage("Failed: _send_request failed: %s" % e)
+ fail_usage("Failed: send request failed: Error: %s" % e)
+
+ response_detail = json.loads(response_str)
+ logging.debug("reponse: %s" % response_detail)
+ return response_detail
def start_instance(conn, instance_id):
+ logging.debug("start instance %s" % instance_id)
request = StartInstanceRequest()
request.set_InstanceId(instance_id)
_send_request(conn, request)
def stop_instance(conn, instance_id):
+ logging.debug("stop instance %s" % instance_id)
request = StopInstanceRequest()
request.set_InstanceId(instance_id)
request.set_ForceStop('true')
_send_request(conn, request)
def reboot_instance(conn, instance_id):
+ logging.debug("reboot instance %s" % instance_id)
request = RebootInstanceRequest()
request.set_InstanceId(instance_id)
request.set_ForceStop('true')
_send_request(conn, request)
def get_status(conn, instance_id):
+ logging.debug("get instance %s status" % instance_id)
request = DescribeInstancesRequest()
request.set_InstanceIds(json.dumps([instance_id]))
response = _send_request(conn, request)
@@ -59,20 +73,30 @@
return instance_status
def get_nodes_list(conn, options):
+ logging.debug("start to get nodes list")
result = {}
request = DescribeInstancesRequest()
request.set_PageSize(100)
+
+ if "--filter" in options:
+ filter_key = options["--filter"].split("=")[0].strip()
+ filter_value = options["--filter"].split("=")[1].strip()
+ params = request.get_query_params()
+ params[filter_key] = filter_value
+ request.set_query_params(params)
+
response = _send_request(conn, request)
- instance_status = None
if response is not None:
instance_list = response.get('Instances').get('Instance')
for item in instance_list:
instance_id = item.get('InstanceId')
instance_name = item.get('InstanceName')
result[instance_id] = (instance_name, None)
+ logging.debug("get nodes list: %s" % result)
return result
def get_power_status(conn, options):
+ logging.debug("start to get power(%s) status" % options["--plug"])
state = get_status(conn, options["--plug"])
if state == "Running":
@@ -81,14 +105,11 @@
status = "off"
else:
status = "unknown"
-
- logging.info("get_power_status: %s" % status)
-
+ logging.debug("the power(%s) status is %s" % (options["--plug"], status))
return status
-
def set_power_status(conn, options):
- logging.info("set_power_status: %s" % options["--action"])
+ logging.info("start to set power(%s) status to %s" % (options["--plug"], options["--action"]))
if (options["--action"]=="off"):
stop_instance(conn, options["--plug"])
@@ -97,7 +118,6 @@
elif (options["--action"]=="reboot"):
reboot_instance(conn, options["--plug"])
-
def define_new_opts():
all_opt["region"] = {
"getopt" : "r:",
@@ -126,17 +146,42 @@
all_opt["ram_role"] = {
"getopt": ":",
"longopt": "ram-role",
- "help": "--ram-role=[name] Ram Role",
+ "help": "--ram-role=[name] Ram Role",
"shortdesc": "Ram Role.",
"required": "0",
"order": 5
}
+ all_opt["credentials_file"] = {
+ "getopt": ":",
+ "longopt": "credentials-file",
+ "help": "--credentials-file=[path] Path to aliyun-cli credentials file",
+ "shortdesc": "Path to credentials file",
+ "required": "0",
+ "order": 6
+ }
+ all_opt["credentials_file_profile"] = {
+ "getopt": ":",
+ "longopt": "credentials-file-profile",
+ "help": "--credentials-file-profile=[profile] Credentials file profile",
+ "shortdesc": "Credentials file profile",
+ "required": "0",
+ "default": "default",
+ "order": 7
+ }
+ all_opt["filter"] = {
+ "getopt": ":",
+ "longopt": "filter",
+ "help": "--filter=[key=value] Filter (e.g. InstanceIds=[\"i-XXYYZZAA1\",\"i-XXYYZZAA2\"]",
+ "shortdesc": "Filter for list-action.",
+ "required": "0",
+ "order": 8
+ }
# Main agent method
def main():
conn = None
- device_opt = ["port", "no_password", "region", "access_key", "secret_key", "ram_role"]
+ device_opt = ["port", "no_password", "region", "access_key", "secret_key", "ram_role", "credentials_file", "credentials_file_profile", "filter"]
atexit.register(atexit_handler)
@@ -164,8 +209,25 @@
ram_role = options["--ram-role"]
role = EcsRamRoleCredential(ram_role)
conn = client.AcsClient(region_id=region, credential=role)
- region_provider.modify_point('Ecs', region, 'ecs.%s.aliyuncs.com' % region)
-
+ elif "--credentials-file" in options and "--credentials-file-profile" in options:
+ import os, configparser
+ try:
+ config = configparser.ConfigParser()
+ config.read(os.path.expanduser(options["--credentials-file"]))
+ access_key = config.get(options["--credentials-file-profile"], "aliyun_access_key_id")
+ secret_key = config.get(options["--credentials-file-profile"], "aliyun_access_key_secret")
+ conn = client.AcsClient(access_key, secret_key, region)
+ except Exception as e:
+ fail_usage("Failed: failed to read credentials file: %s" % e)
+ else:
+ fail_usage("Failed: User credentials are not set. Please set the Access Key and the Secret Key, or configure the RAM role.")
+
+ # Use intranet endpoint to access ECS service
+ try:
+ region_provider.modify_point('Ecs', region, 'ecs.%s.aliyuncs.com' % region)
+ except Exception as e:
+ logging.warning("Failed: failed to modify endpoint to 'ecs.%s.aliyuncs.com': %s" % (region, e))
+
# Operate the fencing device
result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list)
sys.exit(result)

View File

@ -1,4 +0,0 @@
azure-mgmt-compute
azure-mgmt-network
azure-identity
msrestazure

View File

@ -87,7 +87,7 @@
Name: fence-agents
Summary: Set of unified programs capable of host isolation ("fencing")
Version: 4.2.1
Release: 129%{?alphatag:.%{alphatag}}%{?dist}.14
Release: 129%{?alphatag:.%{alphatag}}%{?dist}
License: GPLv2+ and LGPLv2+
Group: System Environment/Base
URL: https://github.com/ClusterLabs/fence-agents
@ -133,40 +133,10 @@ Source30: %{reqstsoauthlib}-%{reqstsoauthlib_version}.tar.gz
Source31: %{oauthlib}-%{oauthlib_version}.tar.gz
Source32: %{ruamelyaml}-%{ruamelyaml_version}.tar.gz
Source33: %{setuptools}-%{setuptools_version}.tar.gz
# azure
Source34: requirements-azure.txt
Source35: azure-common-1.1.28.zip
Source36: azure-core-1.24.2.zip
Source37: azure-mgmt-compute-27.2.0.zip
Source38: azure-mgmt-core-1.3.2.zip
Source39: azure-mgmt-network-20.0.0.zip
Source40: azure-identity-1.10.0.zip
Source41: isodate-0.6.1.tar.gz
Source42: msrest-0.7.1.zip
Source43: oauthlib-3.2.2.tar.gz
Source44: PyJWT-2.4.0.tar.gz
Source45: requests-2.27.1.tar.gz
Source46: requests-oauthlib-2.0.0.tar.gz
Source47: msal-1.27.0.tar.gz
Source48: msal-extensions-1.0.0.tar.gz
Source49: portalocker-2.7.0.tar.gz
Source50: cryptography-3.3.2.tar.gz
Source51: cffi-1.15.1.tar.gz
Source52: typing_extensions-4.1.1.tar.gz
## msrestazure specific
Source54: msrestazure-0.6.4.tar.gz
Source55: adal-1.2.7.tar.gz
## required for installation
Source100: setuptools_scm-6.4.2.tar.gz
Source101: packaging-21.2-py3-none-any.whl
Source102: tomli-1.1.0.tar.gz
Source103: pycparser-2.20.tar.gz
Source104: wheel-0.37.1.tar.gz
Source105: pip-21.3.1.tar.gz
## azure
Source106: flit_core-3.10.1.tar.gz
## msrestazure
Source107: poetry-core-1.0.8.tar.gz
Source34: setuptools_scm-6.3.2.tar.gz
Source35: packaging-21.2-py3-none-any.whl
Source36: tomli-1.0.1.tar.gz
### END
Patch0: fence_impilan-fence_ilo_ssh-add-ilo5-support.patch
@ -312,27 +282,13 @@ Patch139: RHEL-5397-fence_scsi-2-fix-ISID-reg-handling-off.patch
Patch140: RHEL-5397-fence_scsi-3-fix-run_cmd.patch
Patch141: RHEL-5397-4-fence_scsi-log-err.patch
Patch142: RHEL-14343-fence_zvmip-2-fix-manpage-formatting.patch
Patch143: RHEL-7734-fence_eps-add-fence_epsr2-for-ePowerSwitch-R2-and-newer.patch
Patch144: RHEL-56840-fence_scsi-only-preempt-once-for-mpath-devices.patch
Patch145: RHEL-76492-fence_azure_arm-use-azure-identity.patch
Patch146: RHEL-65025-fence_ibm_powervs-add-private-endpoint-and-token-file-support.patch
Patch147: RHEL-99338-fence_aliyun-update.patch
Patch148: RHEL-107506-fence_ibm_vpc-add-apikey-file-support.patch
Patch149: RHEL-109814-1-fence_aws-add-skipshutdown-parameter.patch
### HA support libs/utils ###
# all archs
Patch1000: bz2218234-1-kubevirt-fix-bundled-dateutil-CVE-2007-4559.patch
Patch1001: RHEL-22174-kubevirt-fix-bundled-jinja2-CVE-2024-22195.patch
Patch1002: RHEL-35655-kubevirt-fix-bundled-jinja2-CVE-2024-34064.patch
Patch1003: RHEL-43568-1-kubevirt-fix-bundled-urllib3-CVE-2024-37891.patch
Patch1004: RHEL-50223-setuptools-fix-CVE-2024-6345.patch
Patch1005: RHEL-104741-1-kubevirt-fix-bundled-requests-CVE-2024-47081.patch
# cloud (x86_64 only)
Patch2000: bz2218234-2-aws-fix-bundled-dateutil-CVE-2007-4559.patch
Patch2001: RHEL-43568-2-aws-fix-bundled-urllib3-CVE-2024-37891.patch
Patch2002: RHEL-104741-2-aliyun-aws-azure-fix-bundled-requests-CVE-2024-47081.patch
Patch2003: RHEL-109814-2-botocore-add-SkipOsShutdown.patch
%if 0%{?fedora} || 0%{?rhel} > 7
%global supportedagents amt_ws apc apc_snmp bladecenter brocade cisco_mds cisco_ucs compute drac5 eaton_snmp emerson eps evacuate hds_cb hpblade ibmblade ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan kdump kubevirt lpar mpath redfish rhevm rsa rsb sbd scsi vmware_rest vmware_soap wti
@ -398,7 +354,7 @@ BuildRequires: libxslt
## establishing proper paths to particular programs
BuildRequires: gnutls-utils
## Python dependencies
BuildRequires: python3-devel python3-pycparser libffi-devel openssl-devel
BuildRequires: python3-devel
BuildRequires: python3-pexpect python3-pycurl python3-requests
BuildRequires: python3-suds openwsman-python3 python3-boto3
BuildRequires: python3-google-api-client python3-pip python3-wheel python3-jinja2
@ -553,13 +509,6 @@ BuildRequires: python3-google-api-client python3-pip python3-wheel python3-jinja
%patch -p1 -P 140
%patch -p1 -P 141
%patch -p1 -P 142
%patch -p1 -P 143 -F1
%patch -p1 -P 144
%patch -p1 -P 145
%patch -p1 -P 146
%patch -p1 -P 147
%patch -p1 -P 148
%patch -p1 -P 149
# prevent compilation of something that won't get used anyway
sed -i.orig 's|FENCE_ZVM=1|FENCE_ZVM=0|' configure.ac
@ -659,36 +608,24 @@ popd
# aws/kubevirt
%{__python3} -m pip install --user --no-index --find-links %{_sourcedir} setuptools-scm
# kubevirt
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt --no-index --find-links %{_sourcedir} openshift
rm -rf %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt/rsa*
%ifarch x86_64
# aws
%ifarch x86_64
%{__python3} -m pip install --user --no-index --find-links %{_sourcedir} jmespath
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/aws --no-index --find-links %{_sourcedir} botocore
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/aws --no-index --find-links %{_sourcedir} requests
# azure
%{__python3} -m pip install --user --upgrade --no-index --find-links %{_sourcedir} pip setuptools
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/azure --no-index --find-links %{_sourcedir} -r %{_sourcedir}/requirements-azure.txt
%endif
# kubevirt
%{__python3} -m pip install --target %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt --no-index --find-links %{_sourcedir} openshift
rm -rf %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}/kubevirt/rsa*
# regular patch doesnt work in build-section
pushd %{buildroot}/usr/lib/fence-agents/%{bundled_lib_dir}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1000}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=1 < %{PATCH1001}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=1 < %{PATCH1002}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH1003}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1004}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH1005}
%ifarch x86_64
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2000}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH2001}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=2 < %{PATCH2002}
/usr/bin/patch --no-backup-if-mismatch -p1 --fuzz=0 < %{PATCH2003}
%endif
popd
@ -873,41 +810,13 @@ Fence agent for Amazon AWS instances.
%ifarch x86_64
%package azure-arm
License: GPLv2+ and LGPLv2+ and MIT and MPL-2.0 and Apache-2.0 and BSD and PSF-2.0 and BSD-3-Clause and ISC
License: GPLv2+ and LGPLv2+
Group: System Environment/Base
Summary: Fence agent for Azure Resource Manager
Requires: fence-agents-common >= %{version}-%{release}
# azure
Provides: bundled(python3-adal) = 1.2.7
Provides: bundled(python3-azure-common) = 1.1.28
Provides: bundled(python3-azure-core) = 1.24.2
Provides: bundled(python3-azure-identity) = 1.10.0
Provides: bundled(python3-azure-mgmt-compute) = 27.2.0
Provides: bundled(python3-azure-mgmt-core) = 1.3.2
Provides: bundled(python3-azure-mgmt-network) = 20.0.0
Provides: bundled(python3-certifi) = 2023.7.22
Provides: bundled(python3-cffi) = 1.15.1
Provides: bundled(python3-charset-normalizer) = 2.0.7
Provides: bundled(python3-cryptography) = 3.3.2
Provides: bundled(python3-%{dateutil}) = %{dateutil_version}
Provides: bundled(python3-%{idna}) = %{idna_version}
Provides: bundled(python3-isodate) = 0.6.1
Provides: bundled(python3-msal) = 1.27.0
Provides: bundled(python3-msal-extensions) = 1.0.0
Provides: bundled(python3-msrest) = 0.7.1
Provides: bundled(python3-msrestazure) = 0.6.4
Provides: bundled(python3-oauthlib) = 3.2.2
Provides: bundled(python3-portalocker) = 2.7.0
Provides: bundled(python3-pycparser) = 2.20
Provides: bundled(python3-PyJWT) = 2.4.0
Provides: bundled(python3-requests) = 2.27.1
Provides: bundled(python3-requests-oauthlib) = 2.0.0
Provides: bundled(python3-six) = 1.16.0
Provides: bundled(python3-typing-extensions) = 4.1.1
Provides: bundled(python3-urllib3) = 1.26.18
Requires: python3-azure-sdk >= 4.0.0-9
Obsoletes: %{name} < %{version}-%{release}
Provides: python3-azure-sdk = 4.0.0-10
Obsoletes: python3-azure-sdk < 4.0.0-10
BuildArch: noarch
%description azure-arm
Fence agent for Azure Resource Manager instances.
%files azure-arm
@ -915,8 +824,6 @@ Fence agent for Azure Resource Manager instances.
%{_datadir}/fence/azure_fence.py*
%{_datadir}/fence/__pycache__/azure_fence.*
%{_mandir}/man8/fence_azure_arm.8*
# bundled libraries
/usr/lib/fence-agents/%{bundled_lib_dir}/azure
%endif
%package bladecenter
@ -1070,8 +977,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*
%ifarch x86_64
%package gce
@ -1609,50 +1516,6 @@ Fence agent for IBM z/VM over IP.
%endif
%changelog
* Thu Aug 21 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.14
- fence_aws: add skip_os_shutdown parameter
Resolves: RHEL-109814
* Fri Aug 15 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.13
- bundled requests: fix CVE-2024-47081
Resolves: RHEL-104741
* Tue Aug 12 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.12
- fence_ibm_vpc: add apikey file support
Resolves: RHEL-107506
* Tue Aug 5 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.11
- fence_aliyun: add credentials file support
Resolves: RHEL-99338
* Fri Apr 25 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.8
- fence_ibm_powervs: add private endpoint and token file support
Resolves: RHEL-65025
* Thu Jan 30 2025 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.7
- fence_azure_arm: use azure-identity instead of msrestazure, which has
been deprecated
Resolves: RHEL-76492
* Tue Sep 24 2024 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.5
- fence_scsi: preempt clears all devices on the mpath device, so only
run it for the first device
Resolves: RHEL-56840
* Wed Jul 24 2024 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.4
- bundled setuptools: fix CVE-2024-6345
Resolves: RHEL-50223
* Tue Jun 25 2024 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.3
- bundled urllib3: fix CVE-2024-37891
Resolves: RHEL-43568
* Thu May 30 2024 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129.2
- fence_eps: add fence_epsr2 for ePowerSwitch R2 and newer
Resolves: RHEL-7734
- bundled jinja2: fix CVE-2024-34064
Resolves: RHEL-35655
* Fri Jan 19 2024 Oyvind Albrigtsen <oalbrigt@redhat.com> - 4.2.1-129
- bundled urllib3: fix CVE-2023-45803
Resolves: RHEL-18132