Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fence-agents for openSUSE:Factory checked in at 2025-12-03 14:12:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fence-agents (Old) and /work/SRC/openSUSE:Factory/.fence-agents.new.14147 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fence-agents" Wed Dec 3 14:12:40 2025 rev:89 rq:1320851 version:4.16.0+git.1763372689.07285aac Changes: -------- --- /work/SRC/openSUSE:Factory/fence-agents/fence-agents.changes 2025-09-24 15:26:51.362853941 +0200 +++ /work/SRC/openSUSE:Factory/.fence-agents.new.14147/fence-agents.changes 2025-12-03 14:14:00.302307202 +0100 @@ -1,0 +2,8 @@ +Tue Nov 25 13:21:25 UTC 2025 - Peter Varkoly <[email protected]> + +- Update to version 4.16.0+git.1763372689.07285aac: + * fence_gce: fix node list limit (#640) + * fence_hetzner_cloud: add new fence agent for Hetzner Cloud + * fence_kubevirt: add fallback to kubernetes.client.rest to avoid errors with some versions of the kubernetes library + +------------------------------------------------------------------- Old: ---- fence-agents-4.16.0+git.1758188595.5a0c09a2.tar.xz New: ---- fence-agents-4.16.0+git.1763372689.07285aac.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fence-agents.spec ++++++ --- /var/tmp/diff_new_pack.6ty36R/_old 2025-12-03 14:14:00.882331679 +0100 +++ /var/tmp/diff_new_pack.6ty36R/_new 2025-12-03 14:14:00.886331848 +0100 @@ -19,7 +19,7 @@ %define agent_list aliyun alom apc apc_snmp aws azure_arm bladecenter brocade cisco_mds cisco_ucs drac5 dummy eaton_snmp eaton_ssh emerson eps evacuate gce hds_cb hpblade ibmblade ibmz ibm_powervs ibm_vpc ifmib ilo ilo_moonshot ilo_mp ilo_ssh intelmodular ipdu ipmilan ironic kdump lpar mpath netio nutanix_ahv powerman pve raritan rcd_serial redfish rsa rsb sanbox2 sbd scsi vbox virsh vmware vmware_rest wti zvm Name: fence-agents Summary: Set of unified programs capable of host isolation ("fencing") -Version: 4.16.0+git.1758188595.5a0c09a2 +Version: 4.16.0+git.1763372689.07285aac Release: 0 License: GPL-2.0-or-later AND LGPL-2.0-or-later Group: Productivity/Clustering/HA ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.6ty36R/_old 2025-12-03 14:14:00.950334549 +0100 +++ /var/tmp/diff_new_pack.6ty36R/_new 2025-12-03 14:14:00.954334717 +0100 @@ -3,6 +3,6 @@ <param name="url">git://github.com/ClusterLabs/fence-agents.git</param> <param name="changesrevision">8d746be92f191aa289f13a3703031c122a5e6cf3</param></service><service name="tar_scm"> <param name="url">https://github.com/ClusterLabs/fence-agents</param> - <param name="changesrevision">5a0c09a23986965507f37a350f3bba20df18c79b</param></service></servicedata> + <param name="changesrevision">07285aacbc2194c6a2dfc7dd62dda9a3308bc243</param></service></servicedata> (No newline at EOF) ++++++ fence-agents-4.16.0+git.1758188595.5a0c09a2.tar.xz -> fence-agents-4.16.0+git.1763372689.07285aac.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/gce/fence_gce.py new/fence-agents-4.16.0+git.1763372689.07285aac/agents/gce/fence_gce.py --- old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/gce/fence_gce.py 2025-09-18 11:43:15.000000000 +0200 +++ new/fence-agents-4.16.0+git.1763372689.07285aac/agents/gce/fence_gce.py 2025-11-17 10:44:49.000000000 +0100 @@ -150,15 +150,24 @@ result = {} plug = options["--plug"] if "--plug" in options else "" zones = options["--zone"] if "--zone" in options else "" + filter = "name="+plug if plug != "" else "" + max_results = 1 if options.get("--action") == "monitor" else 500 if not zones: zones = get_zone(conn, options, plug) if "--plugzonemap" not in options else options["--plugzonemap"][plug] try: for zone in zones.split(","): - instanceList = retry_api_execute(options, conn.instances().list( + request = conn.instances().list( project=options["--project"], - zone=zone)) + zone=zone, + filter=filter, + maxResults=max_results) + while request is not None: + instanceList = retry_api_execute(options, request) + if "items" not in instanceList: + break for instance in instanceList["items"]: result[instance["id"]] = (instance["name"], translate_status(instance["status"])) + request = conn.instances().list_next(previous_request=request, previous_response=instanceList) except Exception as err: fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/hetzner_cloud/fence_hetzner_cloud.py new/fence-agents-4.16.0+git.1763372689.07285aac/agents/hetzner_cloud/fence_hetzner_cloud.py --- old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/hetzner_cloud/fence_hetzner_cloud.py 1970-01-01 01:00:00.000000000 +0100 +++ new/fence-agents-4.16.0+git.1763372689.07285aac/agents/hetzner_cloud/fence_hetzner_cloud.py 2025-11-17 10:44:49.000000000 +0100 @@ -0,0 +1,218 @@ +#!@PYTHON@ -tt + +import sys +import pycurl, io, json +import logging +import atexit + +sys.path.append("@FENCEAGENTSLIBDIR@") +from fencing import * +from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS + +if sys.version_info[0] > 2: + import urllib.parse as urllib +else: + import urllib + +FENCE_STATUS_MAP = {True: "on", False: "off"} + +HETZNER_SERVER_ACTIONS = {"on": "poweron", "off": "poweroff"} + +HETZNER_STATUSES_OFF = ["off"] + + +def connect(opt): + conn = pycurl.Curl() + conn.base_url = opt["--api-url"] + conn.setopt( + pycurl.HTTPHEADER, ["Authorization: Bearer {}".format(opt["--api-token"])] + ) + conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) + try: + send_command( + conn, "/server_types", "GET", query_string={"per_page": 1}, pages_max=1 + ) + except Exception as e: + logging.error(e) + fail(EC_LOGIN_DENIED) + return conn + + +def disconnect(conn): + conn.close() + + +def send_command(conn, path, method, query_string={}, pages_max=None): + results = [] + result = {"meta": {"pagination": {"next_page": 1}}} + while result["meta"]["pagination"]["next_page"] and ( + not pages_max or len(results) < pages_max + ): + query_string["page"] = result["meta"]["pagination"]["next_page"] + result = send_request(conn, path, method, query_string) + results.append(result) + if method != "GET": + break + return results + + +def send_request(conn, path, method, query_string={}): + url = conn.base_url + path + if query_string: + url += "?" + urllib.urlencode(query_string) + logging.debug("REQUEST {} {}".format(method, url)) + conn.setopt(pycurl.URL, url.encode("ascii")) + web_buffer = io.BytesIO() + if method == "GET": + conn.setopt(pycurl.POST, 0) + elif method == "POST": + conn.setopt(pycurl.POSTFIELDS, "") + conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) + try: + conn.perform() + except Exception as e: + raise (e) + result_status = conn.getinfo(pycurl.HTTP_CODE) + result_body = web_buffer.getvalue().decode("UTF-8") + web_buffer.close() + if len(result_body) > 0: + result_body = json.loads(result_body) + if 200 <= result_status < 300: + logging.debug("RESULT {} {}".format(result_status, result_body)) + else: + raise Exception("RESULT {} {}".format(result_status, result_body)) + return result_body + + +def get_server(conn, options): + try: + results = send_command( + conn, + "/servers", + "GET", + query_string={"name": options["--plug"]}, + pages_max=1, + ) + except Exception as e: + logging.error(e) + fail(EC_STATUS) + if len(results) != 1: + fail(EC_STATUS) + if len(results[0]["servers"]) == 1: + return results[0]["servers"][0] + else: + logging.warning( + "SCANNING over all servers! For better performance, set Hetzner Cloud server name to Fencing Agent plug (i.e. Pacemaker node name)." + ) + try: + results = send_command(conn, "/servers", "GET") + except Exception as e: + logging.error(e) + fail(EC_STATUS) + for result in results: + if "servers" not in result: + fail(EC_STATUS) + for server in result["servers"]: + ips = [ + server["public_net"]["ipv4"]["ip"], + server["public_net"]["ipv6"]["ip"], + ] + list(map(lambda e: e["ip"], server["private_net"])) + if options["--plug"] in ips: + return server + fail(EC_STATUS) + + +def get_power_status(conn, options): + server = get_server(conn, options) + status = FENCE_STATUS_MAP[server["status"] not in HETZNER_STATUSES_OFF] + logging.debug("{} {} -> {}".format(server["id"], server["status"], status)) + return status + + +def set_power_status(conn, options): + server = get_server(conn, options) + logging.debug(server) + logging.debug(options) + path = "/servers/{}/actions/{}".format( + server["id"], HETZNER_SERVER_ACTIONS[options["--action"]] + ) + try: + send_command(conn, path, "POST") + except Exception as e: + logging.error(e) + fail(EC_STATUS) + + +def get_list(conn, options): + outlets = {} + try: + results = send_command(conn, "/servers", "GET") + except Exception as e: + logging.error(e) + fail(EC_STATUS) + for result in results: + if "servers" not in result: + fail(EC_STATUS) + for server in result["servers"]: + status = FENCE_STATUS_MAP[server["status"] not in HETZNER_STATUSES_OFF] + logging.debug("{} {} -> {}".format(server["id"], server["status"], status)) + outlets[server["name"]] = ("", status) + return outlets + + +def define_new_opts(): + all_opt["api_url"] = { + "getopt": ":", + "longopt": "api-url", + "help": "--api-url=[url] API URL", + "default": "https://api.hetzner.cloud/v1", + "required": "0", + "shortdesc": "API URL", + "order": 0, + } + all_opt["api_token"] = { + "getopt": ":", + "longopt": "api-token", + "help": "--api-token=[token] API token", + "required": "1", + "shortdesc": "API token", + "order": 0, + } + + +def main(): + device_opt = [ + "api_url", + "api_token", + "no_password", + "port", + "web", + ] + + atexit.register(atexit_handler) + define_new_opts() + + all_opt["shell_timeout"]["default"] = "5" + all_opt["power_wait"]["default"] = "5" + + options = check_input(device_opt, process_input(device_opt)) + + docs = {} + docs["shortdesc"] = "Fence agent for Hetzner Cloud" + docs["longdesc"] = """fence_hetzner_cloud is a Power Fencing agent which can be \ +used with Hetzner Cloud via its API to fence cloud servers.""" + docs["vendorurl"] = "https://www.hetzner.com" + show_docs(options, docs) + + run_delay(options) + + conn = connect(options) + atexit.register(disconnect, conn) + + result = fence_action(conn, options, set_power_status, get_power_status, get_list) + + sys.exit(result) + + +if __name__ == "__main__": + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/kubevirt/fence_kubevirt.py new/fence-agents-4.16.0+git.1763372689.07285aac/agents/kubevirt/fence_kubevirt.py --- old/fence-agents-4.16.0+git.1758188595.5a0c09a2/agents/kubevirt/fence_kubevirt.py 2025-09-18 11:43:15.000000000 +0200 +++ new/fence-agents-4.16.0+git.1763372689.07285aac/agents/kubevirt/fence_kubevirt.py 2025-11-17 10:44:49.000000000 +0100 @@ -10,7 +10,10 @@ try: from kubernetes.client.exceptions import ApiException except ImportError: - logging.error("Couldn\'t import kubernetes.client.exceptions.ApiException - not found or not accessible") + try: + from kubernetes.client.rest import ApiException + except ImportError: + logging.error("Couldn\'t import kubernetes.client.exceptions.ApiException or kubernetes.client.rest.ApiException - not found or not accessible") def _get_namespace(options): from kubernetes import config diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1758188595.5a0c09a2/fence-agents.spec.in new/fence-agents-4.16.0+git.1763372689.07285aac/fence-agents.spec.in --- old/fence-agents-4.16.0+git.1758188595.5a0c09a2/fence-agents.spec.in 2025-09-18 11:43:15.000000000 +0200 +++ new/fence-agents-4.16.0+git.1763372689.07285aac/fence-agents.spec.in 2025-11-17 10:44:49.000000000 +0100 @@ -51,6 +51,7 @@ fence-agents-eps \\ fence-agents-gce \\ fence-agents-hds-cb \\ +fence-agents-hetzner-cloud \\ fence-agents-heuristics-ping \\ fence-agents-hpblade \\ fence-agents-ibmblade \\ @@ -645,6 +646,18 @@ %{_sbindir}/fence_hds_cb %{_mandir}/man8/fence_hds_cb.8* +%package hetzner-cloud +License: GPL-2.0-or-later AND LGPL-2.0-or-later +Summary: Fence agent for Hetzner Cloud +Requires: fence-agents-common = %{version}-%{release} +BuildArch: noarch +Obsoletes: fence-agents < 3.1.13 +%description hetzner-cloud +Fence agent for Hetzner Cloud. +%files hetzner-cloud +%{_sbindir}/fence_hetzner_cloud +%{_mandir}/man8/fence_hetzner_cloud.8* + %package heuristics-ping License: GPL-2.0-or-later AND LGPL-2.0-or-later Summary: Pseudo fence agent to affect other agents based on ping-heuristics diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fence-agents-4.16.0+git.1758188595.5a0c09a2/tests/data/metadata/fence_hetzner_cloud.xml new/fence-agents-4.16.0+git.1763372689.07285aac/tests/data/metadata/fence_hetzner_cloud.xml --- old/fence-agents-4.16.0+git.1758188595.5a0c09a2/tests/data/metadata/fence_hetzner_cloud.xml 1970-01-01 01:00:00.000000000 +0100 +++ new/fence-agents-4.16.0+git.1763372689.07285aac/tests/data/metadata/fence_hetzner_cloud.xml 2025-11-17 10:44:49.000000000 +0100 @@ -0,0 +1,128 @@ +<?xml version="1.0" ?> +<resource-agent name="fence_hetzner_cloud" shortdesc="Fence agent for Hetzner Cloud" > +<longdesc>fence_hetzner_cloud is a Power Fencing agent which can be used with Hetzner Cloud via its API to fence cloud servers.</longdesc> +<vendor-url>https://www.hetzner.com</vendor-url> +<parameters> + <parameter name="api_token" unique="0" required="1"> + <getopt mixed="--api-token=[token]" /> + <content type="string" /> + <shortdesc lang="en">API token</shortdesc> + </parameter> + <parameter name="api_url" unique="0" required="0"> + <getopt mixed="--api-url=[url]" /> + <content type="string" default="https://api.hetzner.cloud/v1" /> + <shortdesc lang="en">API URL</shortdesc> + </parameter> + <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="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="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="5" /> + <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="5" /> + <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>
